diff --git a/packages/global/common/system/config/constants.ts b/packages/global/common/system/config/constants.ts index a47f11040..f679e3c9c 100644 --- a/packages/global/common/system/config/constants.ts +++ b/packages/global/common/system/config/constants.ts @@ -3,7 +3,8 @@ export enum SystemConfigsTypeEnum { fastgptPro = 'fastgptPro', systemMsgModal = 'systemMsgModal', license = 'license', - operationalAd = 'operationalAd' + operationalAd = 'operationalAd', + activityAd = 'activityAd' } export const SystemConfigsTypeMap = { @@ -21,5 +22,8 @@ export const SystemConfigsTypeMap = { }, [SystemConfigsTypeEnum.operationalAd]: { label: 'operationalAd' + }, + [SystemConfigsTypeEnum.activityAd]: { + label: 'activityAd' } }; diff --git a/packages/global/openapi/admin.ts b/packages/global/openapi/admin.ts index a24436edb..a2d35ac8e 100644 --- a/packages/global/openapi/admin.ts +++ b/packages/global/openapi/admin.ts @@ -1,6 +1,7 @@ import { createDocument } from 'zod-openapi'; import { DashboardPath } from './admin/core/dashboard'; import { TagsMap } from './tag'; +import { AdminSupportPath } from './admin/support'; export const adminOpenAPIDocument = createDocument({ openapi: '3.1.0', @@ -10,13 +11,18 @@ export const adminOpenAPIDocument = createDocument({ description: 'FastGPT Admin API 文档' }, paths: { - ...DashboardPath + ...DashboardPath, + ...AdminSupportPath }, servers: [{ url: '/api' }], 'x-tagGroups': [ { name: '仪表盘', tags: [TagsMap.adminDashboard] + }, + { + name: '系统配置', + tags: [TagsMap.adminInform] } ] }); diff --git a/packages/global/openapi/admin/core/dashboard/index.ts b/packages/global/openapi/admin/core/dashboard/index.ts index cd6061e92..b86ac7ddd 100644 --- a/packages/global/openapi/admin/core/dashboard/index.ts +++ b/packages/global/openapi/admin/core/dashboard/index.ts @@ -13,8 +13,6 @@ import { } from './api'; import { TagsMap } from '../../../tag'; -export * from './api'; - export const DashboardPath: OpenAPIPath = { '/admin/core/dashboard/getUserStats': { get: { diff --git a/packages/global/openapi/admin/support/index.ts b/packages/global/openapi/admin/support/index.ts new file mode 100644 index 000000000..d538ea771 --- /dev/null +++ b/packages/global/openapi/admin/support/index.ts @@ -0,0 +1,6 @@ +import { AdminUserPath } from './user'; +import type { OpenAPIPath } from '../../type'; + +export const AdminSupportPath: OpenAPIPath = { + ...AdminUserPath +}; diff --git a/packages/global/openapi/admin/support/user/index.ts b/packages/global/openapi/admin/support/user/index.ts new file mode 100644 index 000000000..f50c00bff --- /dev/null +++ b/packages/global/openapi/admin/support/user/index.ts @@ -0,0 +1,6 @@ +import { AdminInformPath } from './inform'; +import type { OpenAPIPath } from '../../../type'; + +export const AdminUserPath: OpenAPIPath = { + ...AdminInformPath +}; diff --git a/packages/global/openapi/admin/support/user/inform/api.ts b/packages/global/openapi/admin/support/user/inform/api.ts new file mode 100644 index 000000000..5b080a778 --- /dev/null +++ b/packages/global/openapi/admin/support/user/inform/api.ts @@ -0,0 +1,57 @@ +import { z } from 'zod'; +import { InformLevelEnum } from '../../../../../support/user/inform/constants'; + +// Send system inform +export const SendSystemInformBodySchema = z.object({ + title: z.string().meta({ description: '通知标题' }), + content: z.string().meta({ description: '通知内容' }), + level: z.enum(InformLevelEnum).meta({ description: '通知等级' }) +}); +export type SendSystemInformBodyType = z.infer; + +// Update system modal +export const UpdateSystemModalBodySchema = z.object({ + content: z.string().meta({ description: '系统弹窗内容' }) +}); +export type UpdateSystemModalBodyType = z.infer; + +// Update operational ad +export const UpdateOperationalAdBodySchema = z.object({ + operationalAdImage: z.string().meta({ description: '活动图片URL' }), + operationalAdLink: z.string().meta({ description: '活动链接' }) +}); +export type UpdateOperationalAdBodyType = z.infer; + +// Update activity ad +export const UpdateActivityAdBodySchema = z.object({ + activityAdImage: z.string().meta({ description: '底部广告图片URL' }), + activityAdLink: z.string().meta({ description: '底部广告链接' }) +}); +export type UpdateActivityAdBodyType = z.infer; + +// Response schemas +export const SystemMsgModalResponseSchema = z + .object({ + id: z.string().meta({ description: '弹窗ID' }), + content: z.string().meta({ description: '弹窗内容' }) + }) + .optional(); +export type SystemMsgModalValueType = z.infer; + +export const OperationalAdResponseSchema = z + .object({ + id: z.string().meta({ description: '广告ID' }), + operationalAdImage: z.string().meta({ description: '广告图片URL' }), + operationalAdLink: z.string().meta({ description: '广告链接' }) + }) + .optional(); +export type OperationalAdResponseType = z.infer; + +export const ActivityAdResponseSchema = z + .object({ + id: z.string().meta({ description: '广告ID' }), + activityAdImage: z.string().meta({ description: '广告图片URL' }), + activityAdLink: z.string().meta({ description: '广告链接' }) + }) + .optional(); +export type ActivityAdResponseType = z.infer; diff --git a/packages/global/openapi/admin/support/user/inform/index.ts b/packages/global/openapi/admin/support/user/inform/index.ts new file mode 100644 index 000000000..709fc2fc6 --- /dev/null +++ b/packages/global/openapi/admin/support/user/inform/index.ts @@ -0,0 +1,161 @@ +import type { OpenAPIPath } from '../../../../type'; +import { + SendSystemInformBodySchema, + UpdateSystemModalBodySchema, + UpdateOperationalAdBodySchema, + UpdateActivityAdBodySchema, + SystemMsgModalResponseSchema, + OperationalAdResponseSchema, + ActivityAdResponseSchema +} from './api'; +import { TagsMap } from '../../../../tag'; + +export const AdminInformPath: OpenAPIPath = { + '/admin/support/user/inform/sendSystemInform': { + post: { + summary: '发送系统通知给所有用户', + description: '向所有用户发送系统通知消息', + tags: [TagsMap.adminInform], + requestBody: { + content: { + 'application/json': { + schema: SendSystemInformBodySchema + } + } + }, + responses: { + 200: { + description: '成功发送系统通知', + content: { + 'application/json': { + schema: {} + } + } + } + } + } + }, + '/support/user/inform/getSystemMsgModal': { + get: { + summary: '获取系统弹窗内容', + description: '获取系统消息弹窗的内容', + tags: [TagsMap.adminInform], + responses: { + 200: { + description: '成功获取系统弹窗内容', + content: { + 'application/json': { + schema: SystemMsgModalResponseSchema + } + } + } + } + } + }, + '/admin/support/user/inform/updateSystemModal': { + post: { + summary: '更新系统弹窗内容', + description: '更新系统消息弹窗的内容', + tags: [TagsMap.adminInform], + requestBody: { + content: { + 'application/json': { + schema: UpdateSystemModalBodySchema + } + } + }, + responses: { + 200: { + description: '成功更新系统弹窗', + content: { + 'application/json': { + schema: {} + } + } + } + } + } + }, + '/support/user/inform/getOperationalAd': { + get: { + summary: '获取运营广告', + description: '获取运营广告的图片和链接', + tags: [TagsMap.adminInform], + responses: { + 200: { + description: '成功获取运营广告', + content: { + 'application/json': { + schema: OperationalAdResponseSchema + } + } + } + } + } + }, + '/admin/support/user/inform/updateOperationalAd': { + post: { + summary: '更新运营广告', + description: '更新运营广告的图片和链接', + tags: [TagsMap.adminInform], + requestBody: { + content: { + 'application/json': { + schema: UpdateOperationalAdBodySchema + } + } + }, + responses: { + 200: { + description: '成功更新运营广告', + content: { + 'application/json': { + schema: {} + } + } + } + } + } + }, + '/support/user/inform/getActivityAd': { + get: { + summary: '获取活动广告', + description: '获取活动广告的图片和链接', + tags: [TagsMap.adminInform], + responses: { + 200: { + description: '成功获取活动广告', + content: { + 'application/json': { + schema: ActivityAdResponseSchema + } + } + } + } + } + }, + '/admin/support/user/inform/updateActivityAd': { + post: { + summary: '更新活动广告', + description: '更新活动广告的图片和链接', + tags: [TagsMap.adminInform], + requestBody: { + content: { + 'application/json': { + schema: UpdateActivityAdBodySchema + } + } + }, + responses: { + 200: { + description: '成功更新活动广告', + content: { + 'application/json': { + schema: {} + } + } + } + } + } + } +}; diff --git a/packages/global/openapi/index.ts b/packages/global/openapi/index.ts index 535726021..a188edebf 100644 --- a/packages/global/openapi/index.ts +++ b/packages/global/openapi/index.ts @@ -1,11 +1,9 @@ import { createDocument } from 'zod-openapi'; import { ChatPath } from './core/chat'; -import { ApiKeyPath } from './support/openapi'; import { TagsMap } from './tag'; import { PluginPath } from './core/plugin'; -import { WalletPath } from './support/wallet'; -import { CustomDomainPath } from './support/customDomain'; import { AppPath } from './core/app'; +import { SupportPath } from './support'; export const openAPIDocument = createDocument({ openapi: '3.1.0', @@ -17,10 +15,8 @@ export const openAPIDocument = createDocument({ paths: { ...AppPath, ...ChatPath, - ...ApiKeyPath, ...PluginPath, - ...WalletPath, - ...CustomDomainPath + ...SupportPath }, servers: [{ url: '/api' }], 'x-tagGroups': [ @@ -37,8 +33,8 @@ export const openAPIDocument = createDocument({ tags: [TagsMap.pluginToolTag, TagsMap.pluginTeam] }, { - name: '支付系统', - tags: [TagsMap.walletBill, TagsMap.walletDiscountCoupon] + name: '用户体系', + tags: [TagsMap.userInform, TagsMap.walletBill, TagsMap.walletDiscountCoupon] }, { name: '通用-辅助功能', diff --git a/packages/global/openapi/support/index.ts b/packages/global/openapi/support/index.ts new file mode 100644 index 000000000..bb2e5b39f --- /dev/null +++ b/packages/global/openapi/support/index.ts @@ -0,0 +1,12 @@ +import { UserPath } from './user'; +import type { OpenAPIPath } from '../type'; +import { WalletPath } from './wallet'; +import { ApiKeyPath } from './openapi'; +import { CustomDomainPath } from './customDomain'; + +export const SupportPath: OpenAPIPath = { + ...UserPath, + ...WalletPath, + ...ApiKeyPath, + ...CustomDomainPath +}; diff --git a/packages/global/openapi/support/user/index.ts b/packages/global/openapi/support/user/index.ts new file mode 100644 index 000000000..de81a6b48 --- /dev/null +++ b/packages/global/openapi/support/user/index.ts @@ -0,0 +1,6 @@ +import { UserInformPath } from './inform'; +import type { OpenAPIPath } from '../../type'; + +export const UserPath: OpenAPIPath = { + ...UserInformPath +}; diff --git a/packages/global/openapi/support/user/inform/index.ts b/packages/global/openapi/support/user/inform/index.ts new file mode 100644 index 000000000..92468ada7 --- /dev/null +++ b/packages/global/openapi/support/user/inform/index.ts @@ -0,0 +1,61 @@ +import type { OpenAPIPath } from '../../../type'; +import { + SystemMsgModalResponseSchema, + OperationalAdResponseSchema, + ActivityAdResponseSchema +} from '../../../admin/support/user/inform/api'; +import { TagsMap } from '../../../tag'; + +export const UserInformPath: OpenAPIPath = { + '/proApi/support/user/inform/getSystemMsgModal': { + get: { + summary: '获取系统弹窗内容', + description: '获取系统消息弹窗的内容', + tags: [TagsMap.userInform], + responses: { + 200: { + description: '成功获取系统弹窗内容', + content: { + 'application/json': { + schema: SystemMsgModalResponseSchema + } + } + } + } + } + }, + '/proApi/support/user/inform/getOperationalAd': { + get: { + summary: '获取运营广告', + description: '获取运营广告的图片和链接', + tags: [TagsMap.userInform], + responses: { + 200: { + description: '成功获取运营广告', + content: { + 'application/json': { + schema: OperationalAdResponseSchema + } + } + } + } + } + }, + '/proApi/support/user/inform/getActivityAd': { + get: { + summary: '获取活动广告', + description: '获取活动广告的图片和链接', + tags: [TagsMap.userInform], + responses: { + 200: { + description: '成功获取活动广告', + content: { + 'application/json': { + schema: ActivityAdResponseSchema + } + } + } + } + } + } +}; diff --git a/packages/global/openapi/support/wallet/bill/api.ts b/packages/global/openapi/support/wallet/bill/api.ts index 3e746eecb..781d339ee 100644 --- a/packages/global/openapi/support/wallet/bill/api.ts +++ b/packages/global/openapi/support/wallet/bill/api.ts @@ -25,6 +25,32 @@ export const BillListResponseSchema = z.object({ }); export type GetBillListResponseType = z.infer; +// Bill detail +export const BillDetailQuerySchema = z.object({ + billId: ObjectIdSchema.meta({ description: '订单 ID' }) +}); +export type BillDetailQueryType = z.infer; +export const BillDetailResponseSchema = BillSchema.safeExtend({ + discountCouponName: z.string().optional(), + couponDetail: z + .object({ + key: z.string(), + type: z.enum(CouponTypeEnum), + subscriptions: z.array( + z.object({ + type: z.enum(SubTypeEnum), + durationDay: z.number(), + totalPoints: z.number().optional(), + level: z.enum(StandardSubLevelEnum).optional(), + extraDatasetSize: z.number().optional(), + customConfig: z.record(z.string(), z.any()).optional() + }) + ) + }) + .optional() +}); +export type BillDetailResponseType = z.infer; + // Create export const CreateStandPlanBillSchema = z .object({ @@ -88,30 +114,14 @@ export const CheckPayResultResponseSchema = z.object({ }); export type CheckPayResultResponseType = z.infer; -// Bill detail -export const BillDetailResponseSchema = BillSchema.safeExtend({ - discountCouponName: z.string().optional(), - couponDetail: z - .object({ - key: z.string(), - type: z.enum(CouponTypeEnum), - subscriptions: z.array( - z.object({ - type: z.enum(SubTypeEnum), - durationDay: z.number(), - totalPoints: z.number().optional(), - level: z.enum(StandardSubLevelEnum).optional(), - extraDatasetSize: z.number().optional(), - customConfig: z.record(z.string(), z.any()).optional() - }) - ) - }) - .optional() -}); -export type BillDetailResponseType = z.infer; - // Cancel bill export const CancelBillPropsSchema = z.object({ billId: ObjectIdSchema.meta({ description: '订单 ID' }) }); export type CancelBillPropsType = z.infer; + +// Check pay result +export const CheckPayResultQuerySchema = z.object({ + payId: ObjectIdSchema.meta({ description: '订单 ID' }) +}); +export type CheckPayResultQueryType = z.infer; diff --git a/packages/global/openapi/support/wallet/bill/index.ts b/packages/global/openapi/support/wallet/bill/index.ts index 9356dc811..2a738f2f4 100644 --- a/packages/global/openapi/support/wallet/bill/index.ts +++ b/packages/global/openapi/support/wallet/bill/index.ts @@ -8,7 +8,9 @@ import { CheckPayResultResponseSchema, BillDetailResponseSchema, BillListQuerySchema, - CancelBillPropsSchema + CancelBillPropsSchema, + CheckPayResultQuerySchema, + BillDetailQuerySchema } from './api'; import { TagsMap } from '../../../tag'; import { ObjectIdSchema } from '../../../../common/type/mongo'; @@ -68,11 +70,7 @@ export const BillPath: OpenAPIPath = { description: '检查订单的支付状态,用于轮询支付结果', tags: [TagsMap.walletBill], requestParams: { - query: z.object({ - payId: ObjectIdSchema.meta({ - description: '订单 ID' - }) - }) + query: CheckPayResultQuerySchema }, responses: { 200: { @@ -92,11 +90,7 @@ export const BillPath: OpenAPIPath = { description: '根据订单 ID 获取订单详细信息,包括优惠券名称等', tags: [TagsMap.walletBill], requestParams: { - query: z.object({ - billId: ObjectIdSchema.meta({ - description: '订单 ID' - }) - }) + query: BillDetailQuerySchema }, responses: { 200: { diff --git a/packages/global/openapi/tag.ts b/packages/global/openapi/tag.ts index bb603d09e..b39522236 100644 --- a/packages/global/openapi/tag.ts +++ b/packages/global/openapi/tag.ts @@ -22,6 +22,8 @@ export const TagsMap = { walletBill: '订单', walletDiscountCoupon: '优惠券', customDomain: '自定义域名', + // User + userInform: '用户通知', /* Common */ // APIKey @@ -33,5 +35,7 @@ export const TagsMap = { pluginAdmin: '管理员插件管理', pluginToolAdmin: '管理员系统工具管理', // Data - adminDashboard: '管理员仪表盘' + adminDashboard: '管理员仪表盘', + // Inform + adminInform: '管理员通知管理' }; diff --git a/packages/global/support/user/audit/constants.ts b/packages/global/support/user/audit/constants.ts index 8c60385ac..1c05b22b8 100644 --- a/packages/global/support/user/audit/constants.ts +++ b/packages/global/support/user/audit/constants.ts @@ -2,8 +2,7 @@ import type { auditLogMap, adminAuditLogMap } from '../../../../web/support/user export enum AdminAuditEventEnum { ADMIN_LOGIN = 'ADMIN_LOGIN', - ADMIN_UPDATE_SYSTEM_MODAL = 'ADMIN_UPDATE_SYSTEM_MODAL', - ADMIN_SEND_SYSTEM_INFORM = 'ADMIN_SEND_SYSTEM_INFORM', + ADMIN_ADD_USER = 'ADMIN_ADD_USER', ADMIN_UPDATE_USER = 'ADMIN_UPDATE_USER', ADMIN_UPDATE_TEAM = 'ADMIN_UPDATE_TEAM', @@ -21,7 +20,13 @@ export enum AdminAuditEventEnum { ADMIN_DELETE_PLUGIN = 'ADMIN_DELETE_PLUGIN', ADMIN_CREATE_PLUGIN_GROUP = 'ADMIN_CREATE_PLUGIN_GROUP', ADMIN_UPDATE_PLUGIN_GROUP = 'ADMIN_UPDATE_PLUGIN_GROUP', - ADMIN_DELETE_PLUGIN_GROUP = 'ADMIN_DELETE_PLUGIN_GROUP' + ADMIN_DELETE_PLUGIN_GROUP = 'ADMIN_DELETE_PLUGIN_GROUP', + + // Inform + ADMIN_UPDATE_SYSTEM_MODAL = 'ADMIN_UPDATE_SYSTEM_MODAL', + ADMIN_SEND_SYSTEM_INFORM = 'ADMIN_SEND_SYSTEM_INFORM', + ADMIN_UPDATE_ACTIVITY_AD = 'ADMIN_UPDATE_ACTIVITY_AD', + ADMIN_UPDATE_OPERATIONAL_AD = 'ADMIN_UPDATE_OPERATIONAL_AD' } export enum AuditEventEnum { diff --git a/packages/global/support/wallet/sub/api.d.ts b/packages/global/support/wallet/sub/api.d.ts index 9d716567c..feac8c9ca 100644 --- a/packages/global/support/wallet/sub/api.d.ts +++ b/packages/global/support/wallet/sub/api.d.ts @@ -1,5 +1,4 @@ import type { StandardSubLevelEnum, SubModeEnum } from './constants'; -import { TeamSubSchema } from './type.d'; export type StandardSubPlanParams = { level: `${StandardSubLevelEnum}`; diff --git a/packages/global/support/wallet/sub/type.d.ts b/packages/global/support/wallet/sub/type.d.ts deleted file mode 100644 index 54cc66fa9..000000000 --- a/packages/global/support/wallet/sub/type.d.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { StandardSubLevelEnum, SubModeEnum, SubTypeEnum } from './constants'; - -// Content of plan -export type TeamStandardSubPlanItemType = { - name?: string; - desc?: string; // Plan description - price: number; // read price / month - - pointPrice: number; // read price/ one thousand - - totalPoints: number; // n - maxTeamMember: number; - maxAppAmount: number; // max app or plugin amount - maxDatasetAmount: number; - maxDatasetSize: number; - - requestsPerMinute?: number; - appRegistrationCount?: number; - chatHistoryStoreDuration: number; // n day - websiteSyncPerDataset?: number; - auditLogStoreDuration?: number; - ticketResponseTime?: number; - customDomain?: number; - - // Custom plan specific fields - priceDescription?: string; - customFormUrl?: string; - customDescriptions?: string[]; -}; - -export type StandSubPlanLevelMapType = Record< - `${StandardSubLevelEnum}`, - TeamStandardSubPlanItemType ->; - -export type PointsPackageItem = { - points: number; - month: number; - price: number; -}; - -export type SubPlanType = { - [SubTypeEnum.standard]?: StandSubPlanLevelMapType; - planDescriptionUrl?: string; - appRegistrationUrl?: string; - communitySupportTip?: string; - [SubTypeEnum.extraDatasetSize]: { - price: number; - }; - [SubTypeEnum.extraPoints]: { - packages: PointsPackageItem[]; - }; -}; - -export type TeamSubSchema = { - _id: string; - teamId: string; - type: `${SubTypeEnum}`; - startTime: Date; - expiredTime: Date; - - currentMode: `${SubModeEnum}`; - nextMode: `${SubModeEnum}`; - currentSubLevel: StandardSubLevelEnum; - nextSubLevel: StandardSubLevelEnum; - maxTeamMember?: number; - maxApp?: number; - maxDataset?: number; - - // custom level configurations - requestsPerMinute?: number; - chatHistoryStoreDuration?: number; - maxDatasetSize?: number; - websiteSyncPerDataset?: number; - appRegistrationCount?: number; - auditLogStoreDuration?: number; - ticketResponseTime?: number; - customDomain?: number; - - totalPoints: number; - surplusPoints: number; - - currentExtraDatasetSize: number; -}; - -export type TeamPlanStatusType = { - [SubTypeEnum.standard]?: TeamSubSchema; - standardConstants?: TeamStandardSubPlanItemType; - - totalPoints: number; - usedPoints: number; - - // standard + extra - datasetMaxSize: number; -}; - -export type ClientTeamPlanStatusType = TeamPlanStatusType & { - usedMember: number; - usedAppAmount: number; - usedDatasetSize: number; - usedDatasetIndexSize: number; - usedRegistrationCount: number; -}; diff --git a/packages/global/support/wallet/sub/type.ts b/packages/global/support/wallet/sub/type.ts new file mode 100644 index 000000000..1e2d47343 --- /dev/null +++ b/packages/global/support/wallet/sub/type.ts @@ -0,0 +1,111 @@ +import z from 'zod'; +import { StandardSubLevelEnum, SubModeEnum, SubTypeEnum } from './constants'; +import { ObjectIdSchema } from '../../../common/type/mongo'; + +// Content of plan +export const TeamStandardSubPlanItemSchema = z.object({ + name: z.string().optional(), + desc: z.string().optional(), + price: z.number(), + + totalPoints: z.int(), // 总积分 + maxTeamMember: z.int(), + maxAppAmount: z.int(), + maxDatasetAmount: z.int(), + maxDatasetSize: z.int(), + + requestsPerMinute: z.int().optional(), // QPM + appRegistrationCount: z.int().optional(), // 应用备案数量 + chatHistoryStoreDuration: z.int(), // 历史记录保留天数 + websiteSyncPerDataset: z.int().optional(), // 站点同步最大页面 + auditLogStoreDuration: z.int().optional(), // 审计日志保留天数 + ticketResponseTime: z.int().optional(), // 工单支持时间 + customDomain: z.int().optional(), // 自定义域名数量 + + // 定制套餐 + priceDescription: z.string().optional(), // 价格描述 + customFormUrl: z.string().optional(), // 自定义表单 URL + customDescriptions: z.array(z.string()).optional(), // 自定义描述 + + // Active + annualBonusPoints: z.int().optional(), // 年度赠送积分 + + // @deprecated + pointPrice: z.number().optional() +}); +export type TeamStandardSubPlanItemType = z.infer; + +export const StandSubPlanLevelMapSchema = z.record( + z.enum(StandardSubLevelEnum), + TeamStandardSubPlanItemSchema +); +export type StandSubPlanLevelMapType = z.infer; + +export const PointsPackageItemSchema = z.object({ + points: z.int(), + month: z.int(), + price: z.number(), + activityBonusPoints: z.int().optional() // 活动赠送积分 +}); +export type PointsPackageItem = z.infer; + +export const SubPlanSchema = z.object({ + [SubTypeEnum.standard]: StandSubPlanLevelMapSchema.optional(), + [SubTypeEnum.extraDatasetSize]: z.object({ price: z.number() }).optional(), + [SubTypeEnum.extraPoints]: z.object({ packages: PointsPackageItemSchema.array() }).optional(), + planDescriptionUrl: z.string().optional(), + appRegistrationUrl: z.string().optional(), + communitySupportTip: z.string().optional(), + activityExpirationTime: z.date().optional() +}); +export type SubPlanType = z.infer; + +export const TeamSubSchema = z.object({ + _id: ObjectIdSchema, + teamId: ObjectIdSchema, + type: z.enum(SubTypeEnum), + startTime: z.date(), + expiredTime: z.date(), + + currentMode: z.enum(SubModeEnum), + nextMode: z.enum(SubModeEnum), + currentSubLevel: z.enum(StandardSubLevelEnum), + nextSubLevel: z.enum(StandardSubLevelEnum), + + maxTeamMember: z.int().optional(), + maxApp: z.int().optional(), + maxDataset: z.int().optional(), + totalPoints: z.int(), + annualBonusPoints: z.int().optional(), + surplusPoints: z.int(), + currentExtraDatasetSize: z.int(), + + // 定制版特有属性 + requestsPerMinute: z.int().optional(), + chatHistoryStoreDuration: z.int().optional(), + maxDatasetSize: z.int().optional(), + websiteSyncPerDataset: z.int().optional(), + appRegistrationCount: z.int().optional(), + auditLogStoreDuration: z.int().optional(), + ticketResponseTime: z.int().optional(), + customDomain: z.int().optional() +}); +export type TeamSubSchemaType = z.infer; + +export const TeamPlanStatusSchema = z.object({ + [SubTypeEnum.standard]: TeamSubSchema.optional(), + standardConstants: TeamStandardSubPlanItemSchema.optional(), + totalPoints: z.int(), + usedPoints: z.int(), + datasetMaxSize: z.int() +}); +export type TeamPlanStatusType = z.infer; + +export const ClientTeamPlanStatusSchema = TeamPlanStatusSchema.extend({ + usedMember: z.int(), + usedAppAmount: z.int(), + usedDatasetSize: z.int(), + usedDatasetIndexSize: z.int(), + usedRegistrationCount: z.int() +}); +export type ClientTeamPlanStatusType = z.infer; diff --git a/packages/service/support/user/inform/type.d.ts b/packages/service/support/user/inform/type.d.ts deleted file mode 100644 index a03318316..000000000 --- a/packages/service/support/user/inform/type.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type SystemMsgModalValueType = { - id: string; - content: string; -}; diff --git a/packages/service/support/wallet/sub/schema.ts b/packages/service/support/wallet/sub/schema.ts index 93532bd7d..8b934124c 100644 --- a/packages/service/support/wallet/sub/schema.ts +++ b/packages/service/support/wallet/sub/schema.ts @@ -11,7 +11,7 @@ import { SubModeEnum, SubTypeEnum } from '@fastgpt/global/support/wallet/sub/constants'; -import type { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type'; +import type { TeamSubSchemaType } from '@fastgpt/global/support/wallet/sub/type'; export const subCollectionName = 'team_subscriptions'; @@ -100,4 +100,4 @@ try { console.log(error); } -export const MongoTeamSub = getMongoModel(subCollectionName, SubSchema); +export const MongoTeamSub = getMongoModel(subCollectionName, SubSchema); diff --git a/packages/service/support/wallet/sub/utils.ts b/packages/service/support/wallet/sub/utils.ts index 2388b608b..941213505 100644 --- a/packages/service/support/wallet/sub/utils.ts +++ b/packages/service/support/wallet/sub/utils.ts @@ -7,8 +7,8 @@ import { import { MongoTeamSub } from './schema'; import { type TeamPlanStatusType, - type TeamSubSchema -} from '@fastgpt/global/support/wallet/sub/type.d'; + type TeamSubSchemaType +} from '@fastgpt/global/support/wallet/sub/type'; import dayjs from 'dayjs'; import { type ClientSession } from '../../../common/mongo'; import { addMonths } from 'date-fns'; @@ -29,7 +29,7 @@ export const getStandardPlanConfig = (level: `${StandardSubLevelEnum}`) => { return global.subPlans?.standard?.[level]; }; -export const sortStandPlans = (plans: TeamSubSchema[]) => { +export const sortStandPlans = (plans: TeamSubSchemaType[]) => { return plans.sort( (a, b) => standardSubLevelMap[b.currentSubLevel].weight - standardSubLevelMap[a.currentSubLevel].weight diff --git a/packages/web/common/file/hooks/useUploadAvatar.tsx b/packages/web/common/file/hooks/useUploadAvatar.tsx index b6df4c008..19a4a710e 100644 --- a/packages/web/common/file/hooks/useUploadAvatar.tsx +++ b/packages/web/common/file/hooks/useUploadAvatar.tsx @@ -8,7 +8,17 @@ import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants'; export const useUploadAvatar = ( api: (params: { filename: string }) => Promise, - { onSuccess }: { onSuccess?: (avatar: string) => void } = {} + { + onSuccess, + maxW = 300, + maxH = 300, + maxSize = 1024 * 500 // 500KB + }: { + onSuccess?: (avatar: string) => void; + maxW?: number; + maxH?: number; + maxSize?: number; + } = {} ) => { const { toast } = useToast(); const { t } = useTranslation(); @@ -32,8 +42,9 @@ export const useUploadAvatar = ( const compressed = base64ToFile( await compressBase64Img({ base64Img: await fileToBase64(file), - maxW: 300, - maxH: 300 + maxW, + maxH, + maxSize }), file.name ); diff --git a/packages/web/components/common/Icon/icons/price/right.svg b/packages/web/components/common/Icon/icons/price/right.svg index dfbe5859b..fe9a91de5 100644 --- a/packages/web/components/common/Icon/icons/price/right.svg +++ b/packages/web/components/common/Icon/icons/price/right.svg @@ -1,5 +1,3 @@ - - + + \ No newline at end of file diff --git a/packages/web/i18n/en/account_team.json b/packages/web/i18n/en/account_team.json index a79e7c89d..4c2a8abb3 100644 --- a/packages/web/i18n/en/account_team.json +++ b/packages/web/i18n/en/account_team.json @@ -19,7 +19,9 @@ "admin_login": "Administrator login", "admin_save_template_type": "Update template classification", "admin_send_system_inform": "Send system notifications", + "admin_update_activity_ad": "Bottom advertising configuration", "admin_update_app_template": "Update templates", + "admin_update_operational_ad": "Full screen ad configuration", "admin_update_plan": "Editorial Team Package", "admin_update_plugin": "Plugin Update", "admin_update_plugin_group": "Plugin group update", @@ -128,7 +130,9 @@ "log_admin_login": "【{{name}}】Logined in the administrator background", "log_admin_save_template_type": "【{{name}}】Added template classification called [{{typeName}}]", "log_admin_send_system_inform": "【{{name}}】Sent a system notification titled [{{informTitle}}], with the level of [{{level}}]", + "log_admin_update_activity_ad": "[{{name}}] has configured bottom ads", "log_admin_update_app_template": "【{{name}}】Updated template information named [{{templateName}}]", + "log_admin_update_operational_ad": "[{{name}}] has configured the event page", "log_admin_update_plan": "【{{name}}】Edited the package information of the team with the team id [{{teamId}}]", "log_admin_update_plugin": "【{{name}}】Updated plugin information called [{{pluginName}}]", "log_admin_update_plugin_group": "【{{name}}】Updated plug-in grouping called [{{groupName}}]", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 6c87d1c45..ff7d78539 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -98,6 +98,10 @@ "Warning": "Warning", "Website": "Website", "action_confirm": "Confirm", + "activity_ad.desc": "More points, same annual package price. Get up to 1.66M bonus points.", + "activity_ad.join_now": "Get Started", + "activity_ad.later": "Maybe later", + "activity_ad.title": "Festival Special · Limited Time", "add_new": "add_new", "add_new_param": "Add new param", "add_success": "Added Successfully", @@ -950,7 +954,7 @@ "n_ai_points": "{{amount}} points", "n_chat_records_retain": "{{amount}} Days of Chat History Retention", "n_custom_domain_amount": "{{amount}} Custom domains", - "n_custom_domain_amount tip": "The number of custom domain names that the team can configure, which can currently be used to access Wecom intelligent robots", + "n_custom_domain_amount_tip": "The number of custom domain names that the team can configure, which can currently be used to access Wecom intelligent robots", "n_dataset_amount": "{{amount}} Dataset limit", "n_dataset_size": "{{amount}} Dataset Indexes", "n_team_audit_day": "{{amount}} days team operation log records", @@ -1208,6 +1212,7 @@ "support.wallet.subscription.AI points": "AI Points", "support.wallet.subscription.AI points usage": "AI Points Usage", "support.wallet.subscription.AI points usage tip": "Each time the AI model is called, a certain amount of AI points will be consumed. For specific calculation standards, please refer to the 'Pricing' above.", + "support.wallet.subscription.Activity expiration time": "Activity ends on {{month}}/{{day}}/{{year}} at {{hour}}:{{minute}}", "support.wallet.subscription.Ai points": "AI Points Calculation Standards", "support.wallet.subscription.Current plan": "Current Package", "support.wallet.subscription.Dataset size": "Knowledge Base Index", @@ -1259,6 +1264,7 @@ "support.wallet.subscription.status.inactive": "Inactive", "support.wallet.subscription.team_operation_log": "Record team operation logs", "support.wallet.subscription.token_compute": "Click to View Online Tokens Calculator", + "support.wallet.subscription.total_points": "Total points", "support.wallet.subscription.type.balance": "Balance Recharge", "support.wallet.subscription.type.extraDatasetSize": "Dataset Expansion", "support.wallet.subscription.type.extraPoints": "AI Points Package", diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index e99dc78ff..7ef5c48b6 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -19,7 +19,9 @@ "admin_login": "管理员登录", "admin_save_template_type": "更新模板分类", "admin_send_system_inform": "发送系统通知", + "admin_update_activity_ad": "底部广告配置", "admin_update_app_template": "更新模板", + "admin_update_operational_ad": "全屏广告配置", "admin_update_plan": "编辑团队套餐", "admin_update_plugin": "插件更新", "admin_update_plugin_group": "插件分组更新", @@ -130,7 +132,9 @@ "log_admin_login": "【{{name}}】登录了管理员后台", "log_admin_save_template_type": "【{{name}}】添加了名为【{{typeName}}】的模板分类", "log_admin_send_system_inform": "【{{name}}】发送了标题为【{{informTitle}}】的系统通知,等级为【{{level}}】", + "log_admin_update_activity_ad": "【{{name}}】进行了底部广告配置", "log_admin_update_app_template": "【{{name}}】更新了名为【{{templateName}}】的模板信息", + "log_admin_update_operational_ad": "【{{name}}】进行了活动页配置", "log_admin_update_plan": "【{{name}}】编辑了团队id为【{{teamId}}】的团队的套餐信息", "log_admin_update_plugin": "【{{name}}】更新了名为【{{pluginName}}】的插件信息", "log_admin_update_plugin_group": "【{{name}}】更新了名为【{{groupName}}】的插件分组", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 591463c43..840a15879 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -99,6 +99,10 @@ "Warning": "警告", "Website": "网站", "action_confirm": "操作确认", + "activity_ad.desc": "充值最高可得额外166万积分,年费套餐旧价享新量", + "activity_ad.join_now": "立即参与", + "activity_ad.later": "稍等一会儿", + "activity_ad.title": "双节献礼 · 限时升级", "add_new": "新增", "add_new_param": "新增参数", "add_success": "添加成功", @@ -957,7 +961,7 @@ "n_app_registration_amount": "{{amount}} 个应用备案", "n_chat_records_retain": "{{amount}} 天对话记录保留", "n_custom_domain_amount": "{{amount}} 个自定义域名", - "n_custom_domain_amount tip": "团队可以配置的自定义域名数量,目前可用于接入企微智能机器人", + "n_custom_domain_amount_tip": "团队可以配置的自定义域名数量,目前可用于接入企微智能机器人", "n_dataset_amount": "{{amount}} 个知识库", "n_dataset_size": "{{amount}} 组知识库索引", "n_team_audit_day": "{{amount}} 天团队操作日志记录", @@ -1217,6 +1221,7 @@ "support.wallet.subscription.AI points": "AI 积分", "support.wallet.subscription.AI points usage": "AI 积分使用量", "support.wallet.subscription.AI points usage tip": "每次调用 AI 模型时,都会消耗一定的 AI 积分。具体的计算标准可参考上方的“计费标准”", + "support.wallet.subscription.Activity expiration time": "活动截至{{year}}年{{month}}月{{day}}日{{hour}}:{{minute}}", "support.wallet.subscription.Ai points": "AI 积分计算标准", "support.wallet.subscription.Current plan": "当前套餐", "support.wallet.subscription.Dataset size": "知识库索引量", @@ -1269,6 +1274,7 @@ "support.wallet.subscription.status.inactive": "待使用", "support.wallet.subscription.team_operation_log": "记录团队操作日志", "support.wallet.subscription.token_compute": "点击查看在线 Tokens 计算器", + "support.wallet.subscription.total_points": "总积分", "support.wallet.subscription.type.balance": "余额充值", "support.wallet.subscription.type.extraDatasetSize": "知识库扩容", "support.wallet.subscription.type.extraPoints": "AI 积分套餐", diff --git a/packages/web/i18n/zh-Hant/account_team.json b/packages/web/i18n/zh-Hant/account_team.json index a29ea1ae3..1ed5dbae1 100644 --- a/packages/web/i18n/zh-Hant/account_team.json +++ b/packages/web/i18n/zh-Hant/account_team.json @@ -19,7 +19,9 @@ "admin_login": "管理員登錄", "admin_save_template_type": "更新模板分類", "admin_send_system_inform": "發送系統通知", + "admin_update_activity_ad": "底部廣告配置", "admin_update_app_template": "更新模板", + "admin_update_operational_ad": "全屏廣告配置", "admin_update_plan": "編輯團隊套餐", "admin_update_plugin": "插件更新", "admin_update_plugin_group": "插件分組更新", @@ -128,7 +130,9 @@ "log_admin_login": "【{{name}}】登錄了管理員後台", "log_admin_save_template_type": "【{{name}}】添加了名為【{{typeName}}】的模板分類", "log_admin_send_system_inform": "【{{name}}】發送了標題為【{{informTitle}}】的系統通知,等級為【{{level}}】", + "log_admin_update_activity_ad": "【{{name}}】進行了底部廣告配置", "log_admin_update_app_template": "【{{name}}】更新了名為【{{templateName}}】的模板信息", + "log_admin_update_operational_ad": "【{{name}}】進行了活動頁配置", "log_admin_update_plan": "【{{name}}】編輯了團隊id為【{{teamId}}】的團隊的套餐信息", "log_admin_update_plugin": "【{{name}}】更新了名為【{{pluginName}}】的插件信息", "log_admin_update_plugin_group": "【{{name}}】更新了名為【{{groupName}}】的插件分組", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 9dfcb9544..6359908e0 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -98,6 +98,10 @@ "Warning": "警告", "Website": "網站", "action_confirm": "確認", + "activity_ad.desc": "充值最高可得額外166萬積分,年費套餐舊價享新量", + "activity_ad.join_now": "立即參與", + "activity_ad.later": "稍等一會兒", + "activity_ad.title": "雙節獻禮 · 限時升級", "add_new": "新增", "add_new_param": "新增參數", "add_success": "新增成功", @@ -948,7 +952,7 @@ "n_ai_points": "{{amount}} 積分", "n_chat_records_retain": "{{amount}} 天對話紀錄保留", "n_custom_domain_amount": "{{amount}} 個自定義域名", - "n_custom_domain_amount tip": "團隊可以配置的自定義域名數量,目前可用於接入企微智能機器人", + "n_custom_domain_amount_tip": "團隊可以配置的自定義域名數量,目前可用於接入企微智能機器人", "n_dataset_amount": "{{amount}} 個知識庫", "n_dataset_size": "{{amount}} 組知識庫索引", "n_team_audit_day": "{{amount}} 天團隊操作日誌記錄", @@ -1205,6 +1209,7 @@ "support.wallet.subscription.AI points": "AI 點數", "support.wallet.subscription.AI points usage": "AI 點數使用量", "support.wallet.subscription.AI points usage tip": "每次呼叫 AI 模型時,都會消耗一定的 AI 點數。具體的計算標準可參考上方的「計費標準」", + "support.wallet.subscription.Activity expiration time": "活動截至{{year}}年{{month}}月{{day}}日{{hour}}:{{minute}}", "support.wallet.subscription.Ai points": "AI 點數計算標準", "support.wallet.subscription.Current plan": "目前方案", "support.wallet.subscription.Dataset size": "知識庫索引量", @@ -1256,6 +1261,7 @@ "support.wallet.subscription.status.inactive": "未使用", "support.wallet.subscription.team_operation_log": "記錄團隊操作日誌", "support.wallet.subscription.token_compute": "點選檢視線上 Token 計算機", + "support.wallet.subscription.total_points": "總積分", "support.wallet.subscription.type.balance": "餘額儲值", "support.wallet.subscription.type.extraDatasetSize": "知識庫擴充容量", "support.wallet.subscription.type.extraPoints": "AI 點數方案", diff --git a/packages/web/support/user/audit/constants.ts b/packages/web/support/user/audit/constants.ts index 210f9819a..aa77ea0b9 100644 --- a/packages/web/support/user/audit/constants.ts +++ b/packages/web/support/user/audit/constants.ts @@ -7,16 +7,6 @@ export const adminAuditLogMap = { typeLabel: i18nT('account_team:admin_login'), params: {} as { name?: string } }, - [AdminAuditEventEnum.ADMIN_UPDATE_SYSTEM_MODAL]: { - content: i18nT('account_team:log_admin_update_system_modal'), - typeLabel: i18nT('account_team:admin_update_system_modal'), - params: {} as { name?: string } - }, - [AdminAuditEventEnum.ADMIN_SEND_SYSTEM_INFORM]: { - content: i18nT('account_team:log_admin_send_system_inform'), - typeLabel: i18nT('account_team:admin_send_system_inform'), - params: {} as { name?: string; informTitle?: string; level?: string } - }, [AdminAuditEventEnum.ADMIN_ADD_USER]: { content: i18nT('account_team:log_admin_add_user'), typeLabel: i18nT('account_team:admin_add_user'), @@ -108,6 +98,28 @@ export const adminAuditLogMap = { content: i18nT('account_team:log_admin_delete_plugin_group'), typeLabel: i18nT('account_team:admin_delete_plugin_group'), params: {} as { name?: string; groupName: string } + }, + + // Inform + [AdminAuditEventEnum.ADMIN_UPDATE_SYSTEM_MODAL]: { + content: i18nT('account_team:log_admin_update_system_modal'), + typeLabel: i18nT('account_team:admin_update_system_modal'), + params: {} as { name?: string } + }, + [AdminAuditEventEnum.ADMIN_SEND_SYSTEM_INFORM]: { + content: i18nT('account_team:log_admin_send_system_inform'), + typeLabel: i18nT('account_team:admin_send_system_inform'), + params: {} as { name?: string; informTitle?: string; level?: string } + }, + [AdminAuditEventEnum.ADMIN_UPDATE_ACTIVITY_AD]: { + content: i18nT('account_team:log_admin_update_activity_ad'), + typeLabel: i18nT('account_team:admin_update_activity_ad'), + params: {} + }, + [AdminAuditEventEnum.ADMIN_UPDATE_OPERATIONAL_AD]: { + content: i18nT('account_team:log_admin_update_operational_ad'), + typeLabel: i18nT('account_team:admin_update_operational_ad'), + params: {} } }; diff --git a/projects/app/public/imgs/system/extraSnowflake1.svg b/projects/app/public/imgs/system/extraSnowflake1.svg new file mode 100644 index 000000000..6de5f7c13 --- /dev/null +++ b/projects/app/public/imgs/system/extraSnowflake1.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/app/public/imgs/system/extraSnowflake2.svg b/projects/app/public/imgs/system/extraSnowflake2.svg new file mode 100644 index 000000000..950c3a599 --- /dev/null +++ b/projects/app/public/imgs/system/extraSnowflake2.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/app/public/imgs/system/extraSnowflake3.svg b/projects/app/public/imgs/system/extraSnowflake3.svg new file mode 100644 index 000000000..a8dffaca2 --- /dev/null +++ b/projects/app/public/imgs/system/extraSnowflake3.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/app/public/imgs/system/ribbonLeft.svg b/projects/app/public/imgs/system/ribbonLeft.svg new file mode 100644 index 000000000..ba968b0b6 --- /dev/null +++ b/projects/app/public/imgs/system/ribbonLeft.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/app/public/imgs/system/ribbonRight.svg b/projects/app/public/imgs/system/ribbonRight.svg new file mode 100644 index 000000000..4529bdb63 --- /dev/null +++ b/projects/app/public/imgs/system/ribbonRight.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/app/public/imgs/system/snowflake.svg b/projects/app/public/imgs/system/snowflake.svg new file mode 100644 index 000000000..47eef8d44 --- /dev/null +++ b/projects/app/public/imgs/system/snowflake.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/app/public/imgs/system/snowflakeLeft.svg b/projects/app/public/imgs/system/snowflakeLeft.svg new file mode 100644 index 000000000..4129ce536 --- /dev/null +++ b/projects/app/public/imgs/system/snowflakeLeft.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/app/public/imgs/system/snowflakeRight.svg b/projects/app/public/imgs/system/snowflakeRight.svg new file mode 100644 index 000000000..77d22b34b --- /dev/null +++ b/projects/app/public/imgs/system/snowflakeRight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index 3d98f7fc8..81aa309e4 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -40,6 +40,9 @@ const ManualCopyModal = dynamic( () => import('@fastgpt/web/hooks/useCopyData').then((mod) => mod.ManualCopyModal), { ssr: false } ); +const ActivityAdModal = dynamic(() => import('@/components/support/activity/ActivityAdModal'), { + ssr: false +}); const pcUnShowLayoutRoute: Record = { '/': true, @@ -187,6 +190,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { )} + ); diff --git a/projects/app/src/components/support/activity/ActivityAdModal.tsx b/projects/app/src/components/support/activity/ActivityAdModal.tsx new file mode 100644 index 000000000..61f485109 --- /dev/null +++ b/projects/app/src/components/support/activity/ActivityAdModal.tsx @@ -0,0 +1,209 @@ +import React, { useCallback, useMemo } from 'react'; +import { useDisclosure } from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import { getActivityAd } from '@/web/common/system/api'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { + Box, + Flex, + Button, + Image, + Modal, + ModalOverlay, + ModalContent, + ModalCloseButton +} from '@chakra-ui/react'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { useLocalStorageState } from 'ahooks'; +import { useRouter } from 'next/router'; +import { useUserStore } from '@/web/support/user/useUserStore'; + +const CLOSED_AD_KEY = 'logout-activity-ad'; +const CLOSED_AD_DURATION = 24 * 60 * 60 * 1000; // 24 hours + +const ActivityAdModal = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const { t } = useTranslation(); + const { feConfigs } = useSystemStore(); + const router = useRouter(); + const { userInfo } = useUserStore(); + + // Check if ad was recently closed + const [closedData, setClosedData] = useLocalStorageState(CLOSED_AD_KEY, { + listenStorageChange: true + }); + + const { data } = useRequest2( + async () => { + if (!feConfigs?.isPlus || !userInfo) return; + return getActivityAd(); + }, + { + manual: false, + onSuccess(res) { + const shouldShowAd = (() => { + if (!res?.id) return false; + if (!closedData) return true; + + try { + const { timestamp, adId } = JSON.parse(closedData) as { + timestamp: number; + adId: string; + }; + // 不同的广告 id,一定展示 + if (adId && res.id !== adId) return true; + const now = Date.now(); + // Show if 24 hours passed + return now - timestamp > CLOSED_AD_DURATION; + } catch { + return true; + } + })(); + + if (res?.activityAdImage && shouldShowAd) { + onOpen(); + } + }, + refreshDeps: [userInfo] + } + ); + + const handleClose = useCallback(() => { + if (data?.id) { + setClosedData(JSON.stringify({ timestamp: Date.now(), adId: data.id })); + } + onClose(); + }, [data?.id, onClose, setClosedData]); + + const handleJoin = useCallback(() => { + if (data?.activityAdLink) { + if (data.activityAdLink.startsWith('/')) { + router.push(data.activityAdLink); + handleClose(); + } else { + window.open(data.activityAdLink, '_blank'); + } + } + }, [data?.activityAdLink, handleClose, router]); + + if (!data?.activityAdImage || !userInfo) { + return null; + } + + return isOpen ? ( + + + + + + + {/* Activity Image */} + + + {/* Gradient overlay for smooth transition */} + + + + + {t('common:activity_ad.title')} + + + + {t('common:activity_ad.desc')} + + + + + + + + + + + + ) : null; +}; + +export default React.memo(ActivityAdModal); diff --git a/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx b/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx index ca263e53b..b31d04c39 100644 --- a/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx +++ b/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx @@ -135,7 +135,7 @@ export const RechargeModal = ({ {`${teamPlanStatus?.usedPoints || 0} / ${teamPlanStatus?.totalPoints ?? t('common:Unlimited')}`} + >{`${Math.round(teamPlanStatus?.usedPoints || 0)} / ${teamPlanStatus?.totalPoints ?? t('common:Unlimited')}`} { + const calculateQRSize = () => { + const windowHeight = window.innerHeight; + const reservedSpace = 470 + (tip ? 60 : 0) + (discountCouponName ? 30 : 0); + const availableHeight = windowHeight - reservedSpace; + + const newSize = Math.min(QR_CODE_SIZE, Math.max(MIN_QR_SIZE, availableHeight)); + + setDynamicQRSize(newSize); + }; + + window.addEventListener('resize', calculateQRSize); + + return () => { + window.removeEventListener('resize', calculateQRSize); + }; + }, [tip, discountCouponName]); + const [payWayRenderData, setPayWayRenderData] = useState<{ qrCode?: string; iframeCode?: string; @@ -99,7 +120,7 @@ const QRCodePayModal = ({ const canvas = document.createElement('canvas'); QRCode.toCanvas(canvas, payWayRenderData.qrCode, { - width: QR_CODE_SIZE, + width: dynamicQRSize, margin: 0, color: { dark: '#000000', @@ -113,7 +134,7 @@ const QRCodePayModal = ({ } }) .catch(console.error); - }, [payWayRenderData.qrCode]); + }, [payWayRenderData.qrCode, dynamicQRSize]); useEffect(() => { drawCode(); }, [drawCode]); @@ -140,15 +161,15 @@ const QRCodePayModal = ({ }); const renderPaymentContent = () => { if (payWayRenderData.qrCode) { - return ; + return ; } if (payWayRenderData.iframeCode) { return ( - +