统一授权提示私有化口径

This commit is contained in:
WuYongJie 2026-05-28 17:37:27 +08:00
parent 7b9260d8d7
commit ec440c5c60
18 changed files with 60 additions and 60 deletions

View File

@ -2011,7 +2011,7 @@ export default function TemplateManager({ showToast }: { showToast: ToastFn }) {
onClose={() => setShowUpgradeModal(false)} onClose={() => setShowUpgradeModal(false)}
featureKey="customTemplates" featureKey="customTemplates"
featureName="自定义模板" featureName="自定义模板"
featureDescription="创建、复制、编辑和迁移自定义模板需要升级后才能使用。" featureDescription="当前许可证未包含创建、复制、编辑和迁移自定义模板的授权。"
/> />
{/* 素材迁移弹窗 */} {/* 素材迁移弹窗 */}

View File

@ -117,12 +117,12 @@ export default function BuiltInTemplateGrid({
'inline-flex items-center gap-1.5 shadow-none', 'inline-flex items-center gap-1.5 shadow-none',
!canQuickCreate && 'opacity-75' !canQuickCreate && 'opacity-75'
)} )}
title={canQuickCreate ? '快速创建副本' : '需要升级后才能创建自定义模板'} title={canQuickCreate ? '快速创建副本' : '当前许可证未包含自定义模板功能'}
> >
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor"> <svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 3.5a.75.75 0 01.75.75v5h5a.75.75 0 010 1.5h-5v5a.75.75 0 01-1.5 0v-5h-5a.75.75 0 010-1.5h5v-5A.75.75 0 0110 3.5z" /> <path d="M10 3.5a.75.75 0 01.75.75v5h5a.75.75 0 010 1.5h-5v5a.75.75 0 01-1.5 0v-5h-5a.75.75 0 010-1.5h5v-5A.75.75 0 0110 3.5z" />
</svg> </svg>
{canQuickCreate ? '快速创建副本' : '升级后创建'} {canQuickCreate ? '快速创建副本' : '授权后创建'}
</button> </button>
</div> </div>
</div> </div>

View File

@ -58,7 +58,7 @@ export default function CustomTemplateSection({
<p className="text-xs text-slate-500 dark:text-slate-400 mt-0.5"> <p className="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
{canManageCustomTemplates {canManageCustomTemplates
? '这里列出你自己创建的模板,可继续编辑、复制或删除' ? '这里列出你自己创建的模板,可继续编辑、复制或删除'
: '当前许可证未开放自定义模板功能,升级后可新建、复制、编辑和迁移模板'} : '当前许可证未包含自定义模板功能,请联系管理员处理授权'}
</p> </p>
</div> </div>
</div> </div>
@ -90,12 +90,12 @@ export default function CustomTemplateSection({
</div> </div>
<div className="text-left"> <div className="text-left">
<p className="text-sm font-semibold text-slate-800 dark:text-slate-200"> <p className="text-sm font-semibold text-slate-800 dark:text-slate-200">
{canManageCustomTemplates ? '新建自定义模板' : '升级后新建模板'} {canManageCustomTemplates ? '新建自定义模板' : '授权后可新建'}
</p> </p>
<p className="text-[11px] text-slate-500 dark:text-slate-400 mt-1 leading-snug"> <p className="text-[11px] text-slate-500 dark:text-slate-400 mt-1 leading-snug">
{canManageCustomTemplates {canManageCustomTemplates
? '从空白模板起步,按照业务需求配置字段' ? '从空白模板起步,按照业务需求配置字段'
: '升级后可新建、复制和迁移自定义模板'} : '当前不可新建、复制或迁移自定义模板'}
</p> </p>
</div> </div>
</button> </button>

View File

@ -79,10 +79,10 @@ describe('CustomTemplateSection 授权提示', () => {
it('许可证不包含自定义模板时应显示前置授权提示并拦截新建入口', () => { it('许可证不包含自定义模板时应显示前置授权提示并拦截新建入口', () => {
renderSection(false) renderSection(false)
expect(screen.getByText('当前许可证未开放自定义模板功能,升级后可新建、复制、编辑和迁移模板')).toBeInTheDocument() expect(screen.getByText('当前许可证未包含自定义模板功能,请联系管理员处理授权')).toBeInTheDocument()
expect(screen.getByTestId('pro-badge')).toBeInTheDocument() expect(screen.getByTestId('pro-badge')).toBeInTheDocument()
fireEvent.click(screen.getByText('升级后新建模板')) fireEvent.click(screen.getByText('授权后可新建'))
expect(callbacks.onRequestUpgrade).toHaveBeenCalledTimes(1) expect(callbacks.onRequestUpgrade).toHaveBeenCalledTimes(1)
expect(callbacks.onCreate).not.toHaveBeenCalled() expect(callbacks.onCreate).not.toHaveBeenCalled()

View File

@ -321,7 +321,7 @@ export async function POST(
if (!(await canUseCustomTemplates())) { if (!(await canUseCustomTemplates())) {
return NextResponse.json({ return NextResponse.json({
error: '自定义模板功能需要标准版或专业版许可证,请升级后使用', error: '当前许可证未包含自定义模板功能,请联系管理员处理授权',
errorCode: 'LICENSE_REQUIRED', errorCode: 'LICENSE_REQUIRED',
}, { status: 403 }) }, { status: 403 })
} }

View File

@ -384,7 +384,7 @@ export async function PUT(req: NextRequest, { params }: { params: { id: string }
if (!(await canUseCustomTemplates())) { if (!(await canUseCustomTemplates())) {
return NextResponse.json({ return NextResponse.json({
error: '自定义模板功能需要标准版或专业版许可证,请升级后使用', error: '当前许可证未包含自定义模板功能,请联系管理员处理授权',
errorCode: 'LICENSE_REQUIRED', errorCode: 'LICENSE_REQUIRED',
}, { status: 403 }) }, { status: 403 })
} }
@ -713,7 +713,7 @@ export async function DELETE(_: NextRequest, { params }: { params: { id: string
if (!(await canUseCustomTemplates())) { if (!(await canUseCustomTemplates())) {
return NextResponse.json({ return NextResponse.json({
error: '自定义模板功能需要标准版或专业版许可证,请升级后使用', error: '当前许可证未包含自定义模板功能,请联系管理员处理授权',
errorCode: 'LICENSE_REQUIRED', errorCode: 'LICENSE_REQUIRED',
}, { status: 403 }) }, { status: 403 })
} }

View File

@ -108,7 +108,7 @@ export async function POST(req: NextRequest) {
// 检查自定义模板功能是否可用 // 检查自定义模板功能是否可用
if (!(await canUseCustomTemplates())) { if (!(await canUseCustomTemplates())) {
return NextResponse.json({ return NextResponse.json({
error: '自定义模板功能需要标准版或专业版许可证,请升级后使用', error: '当前许可证未包含自定义模板功能,请联系管理员处理授权',
errorCode: 'LICENSE_REQUIRED', errorCode: 'LICENSE_REQUIRED',
}, { status: 403 }) }, { status: 403 })
} }

View File

@ -93,7 +93,7 @@ describe('departments route license guard', () => {
const data = await response.json() const data = await response.json()
expect(response.status).toBe(403) expect(response.status).toBe(403)
expect(data.error).toContain('部门隔离功能需要专业版许可证') expect(data.error).toContain('当前许可证未包含部门隔离功能')
expect(prisma.department.findUnique).not.toHaveBeenCalled() expect(prisma.department.findUnique).not.toHaveBeenCalled()
}) })
@ -128,7 +128,7 @@ describe('departments route license guard', () => {
const data = await response.json() const data = await response.json()
expect(response.status).toBe(403) expect(response.status).toBe(403)
expect(data.error).toContain('部门隔离功能需要专业版许可证') expect(data.error).toContain('当前许可证未包含部门隔离功能')
expect(prisma.department.findUnique).not.toHaveBeenCalled() expect(prisma.department.findUnique).not.toHaveBeenCalled()
}) })

View File

@ -39,7 +39,7 @@ export async function POST(req: NextRequest) {
// 检查部门隔离功能是否可用 // 检查部门隔离功能是否可用
if (!(await canUseDepartmentIsolation())) { if (!(await canUseDepartmentIsolation())) {
return NextResponse.json( return NextResponse.json(
{ error: '部门隔离功能需要专业版许可证,请升级后使用' }, { error: '当前许可证未包含部门隔离功能,请联系管理员处理授权' },
{ status: 403 } { status: 403 }
) )
} }
@ -93,7 +93,7 @@ export async function PATCH(req: NextRequest) {
if (!(await canUseDepartmentIsolation())) { if (!(await canUseDepartmentIsolation())) {
return NextResponse.json( return NextResponse.json(
{ error: '部门隔离功能需要专业版许可证,请升级后使用' }, { error: '当前许可证未包含部门隔离功能,请联系管理员处理授权' },
{ status: 403 } { status: 403 }
) )
} }
@ -257,7 +257,7 @@ export async function DELETE(req: NextRequest) {
if (!(await canUseDepartmentIsolation())) { if (!(await canUseDepartmentIsolation())) {
return NextResponse.json( return NextResponse.json(
{ error: '部门隔离功能需要专业版许可证,请升级后使用' }, { error: '当前许可证未包含部门隔离功能,请联系管理员处理授权' },
{ status: 403 } { status: 403 }
) )
} }

View File

@ -209,7 +209,7 @@ export async function PUT(request: NextRequest) {
// 检查系统设置功能是否可用(免费版只能查看,不能修改) // 检查系统设置功能是否可用(免费版只能查看,不能修改)
if (!(await canUseSystemSettings())) { if (!(await canUseSystemSettings())) {
return NextResponse.json( return NextResponse.json(
{ error: '系统设置功能需要标准版或专业版许可证,请升级后使用' }, { error: '当前许可证未包含系统设置功能,请联系管理员处理授权' },
{ status: 403 } { status: 403 }
) )
} }

View File

@ -4,7 +4,7 @@
* RestrictedToggle 组件 * RestrictedToggle 组件
* *
* 受限功能开关组件,集成 PRO 徽章和点击拦截逻辑 * 受限功能开关组件,集成 PRO 徽章和点击拦截逻辑
* 当功能受限时显示禁用状态,点击时触发升级弹窗 * 当功能受限时显示禁用状态,点击时触发授权提示弹窗
* *
* @created 2025-12-16 * @created 2025-12-16
* @component * @component
@ -96,7 +96,7 @@ export default function RestrictedToggle({
)} )}
data-testid="restricted-hint" data-testid="restricted-hint"
> >
升级到付费版解锁此功能 当前许可证未包含此功能
</p> </p>
)} )}
</div> </div>
@ -142,7 +142,7 @@ export default function RestrictedToggle({
</button> </button>
</div> </div>
{/* 升级弹窗 */} {/* 授权提示弹窗 */}
<UpgradeModal <UpgradeModal
isOpen={showUpgradeModal} isOpen={showUpgradeModal}
onClose={() => setShowUpgradeModal(false)} onClose={() => setShowUpgradeModal(false)}

View File

@ -3,8 +3,8 @@
/** /**
* UpgradeModal 组件 * UpgradeModal 组件
* *
* 升级提示弹窗,当免费版用户点击受限功能时显示 * 授权提示弹窗,当当前许可证不包含某个功能时显示
* 基于现有 Modal 组件构建,显示功能名称、限制说明和升级好处 * 基于现有 Modal 组件构建,显示功能名称和限制说明
* *
* @created 2025-12-16 * @created 2025-12-16
* @component * @component
@ -41,9 +41,8 @@ export default function UpgradeModal({
const displayName = featureName || feature.name const displayName = featureName || feature.name
const displayDescription = featureDescription || feature.description const displayDescription = featureDescription || feature.description
const handleUpgradeClick = () => { const handleContactAdminClick = () => {
// TODO: 跳转到升级页面或显示联系方式 // 目前先关闭弹窗,后续可接入客户自己的授权处理入口。
// 目前先关闭弹窗
onClose() onClose()
} }
@ -112,7 +111,7 @@ export default function UpgradeModal({
</button> </button>
<button <button
type="button" type="button"
onClick={handleUpgradeClick} onClick={handleContactAdminClick}
className={cn( className={cn(
'flex-1 px-4 py-2 rounded-xl text-sm font-medium transition-all', 'flex-1 px-4 py-2 rounded-xl text-sm font-medium transition-all',
'bg-slate-900 text-white hover:bg-slate-800', 'bg-slate-900 text-white hover:bg-slate-800',

View File

@ -181,13 +181,13 @@ describe('RestrictedToggle 组件', () => {
expect(toggleButton.className).not.toContain('opacity-50') expect(toggleButton.className).not.toContain('opacity-50')
}) })
it('受限时应该显示升级提示文字', () => { it('受限时应该显示授权提示文字', () => {
render(<RestrictedToggle {...defaultProps} isRestricted={true} />) render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
expect(screen.getByTestId('restricted-hint')).toBeInTheDocument() expect(screen.getByTestId('restricted-hint')).toBeInTheDocument()
expect(screen.getByText('升级到付费版解锁此功能')).toBeInTheDocument() expect(screen.getByText('当前许可证未包含此功能')).toBeInTheDocument()
}) })
it('不受限时不应该显示升级提示文字', () => { it('不受限时不应该显示授权提示文字', () => {
render(<RestrictedToggle {...defaultProps} isRestricted={false} />) render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
expect(screen.queryByTestId('restricted-hint')).not.toBeInTheDocument() expect(screen.queryByTestId('restricted-hint')).not.toBeInTheDocument()
}) })

View File

@ -160,9 +160,9 @@
- 预制模板是否被禁用 - 预制模板是否被禁用
- 是否是管理员草稿模板 - 是否是管理员草稿模板
### 为什么保存自定义模板提示需要升级? ### 为什么保存自定义模板提示许可证未包含?
说明当前许可证没有开启 `customTemplates`。需要激活标准版或专业版,或调整许可证能力。 说明当前许可证没有开启 `customTemplates`。请联系管理员确认当前授权范围,必要时激活包含自定义模板能力的许可证。
### 能不能删除正在使用的模板? ### 能不能删除正在使用的模板?

View File

@ -854,17 +854,17 @@ AI 改图以后会消耗外部额度、CPU、存储和客户素材数据。即
- 已完成:监控页按管理员能力收口。 - 已完成:监控页按管理员能力收口。
- 后续如果要给普通成员开放部分统计,应单独做低敏概览接口,不复用当前系统状态接口。 - 后续如果要给普通成员开放部分统计,应单独做低敏概览接口,不复用当前系统状态接口。
### P2-02 模板管理免费版缺少前置提示(已修复) ### P2-02 模板管理许可证限制缺少前置提示(已修复)
客户影响: 客户影响:
历史风险是免费版用户在后台看到新建自定义模板入口,点击后才收到后端许可证错误,体验不完整。当前前端已读取许可证状态,在模板管理页提前展示授权限制提示;受限动作会打开中性的授权提示弹窗,后端仍保留强制拦截。 历史风险是当前许可证不包含自定义模板时,用户在后台仍看到新建入口,点击后才收到后端许可证错误,体验不完整。当前前端已读取许可证状态,在模板管理页提前展示授权限制提示;受限动作会打开中性的授权提示弹窗,后端仍保留强制拦截。
证据: 证据:
- `hooks/useLicenseStatus.ts`:前端通过 `/api/license/info` 读取当前许可证和功能开关。 - `hooks/useLicenseStatus.ts`:前端通过 `/api/license/info` 读取当前许可证和功能开关。
- `app/admin/_components/TemplateManager.tsx`:模板管理页用 `isFree + customTemplates` 计算自定义模板能力;新建、编辑、复制和从预制模板快速创建都会先检查能力,未授权时打开 `UpgradeModal`。 - `app/admin/_components/TemplateManager.tsx`:模板管理页用许可证能力计算自定义模板能力;新建、编辑、复制和从预制模板快速创建都会先检查能力,未授权时打开授权提示弹窗。
- `app/admin/_components/template-manager/CustomTemplateSection.tsx`:自定义模板区免费版显示 `ProBadge` 和“当前许可证未开放自定义模板功能”提示;点击受限动作只触发授权提示,不直接打开编辑器。 - `app/admin/_components/template-manager/CustomTemplateSection.tsx`:自定义模板区在未授权时显示“当前许可证未包含自定义模板功能,请联系管理员处理授权”提示;点击受限动作只触发授权提示,不直接打开编辑器。
- `components/ui/UpgradeModal.tsx`:授权提示已收口为“当前许可证未包含此功能,请联系管理员处理授权”的私有化工作提示。 - `components/ui/UpgradeModal.tsx`:授权提示已收口为“当前许可证未包含此功能,请联系管理员处理授权”的私有化工作提示。
- `app/api/admin/upload-templates/route.ts`:后端创建模板前仍调用 `canUseCustomTemplates()`,失败返回 `403 + LICENSE_REQUIRED`。 - `app/api/admin/upload-templates/route.ts`:后端创建模板前仍调用 `canUseCustomTemplates()`,失败返回 `403 + LICENSE_REQUIRED`。
- `app/admin/_components/template-manager/__tests__/CustomTemplateSection.test.tsx`:新增测试覆盖未授权时显示前置提示并拦截新建、编辑、复制和启停动作;授权允许时新建动作正常执行。 - `app/admin/_components/template-manager/__tests__/CustomTemplateSection.test.tsx`:新增测试覆盖未授权时显示前置提示并拦截新建、编辑、复制和启停动作;授权允许时新建动作正常执行。
@ -872,9 +872,9 @@ AI 改图以后会消耗外部额度、CPU、存储和客户素材数据。即
建议动作: 建议动作:
- 已完成:免费版自定义模板入口有前置授权提示,受限动作不会直接进入编辑流程。 - 已完成:自定义模板入口有前置授权提示,受限动作不会直接进入编辑流程。
- 已完成:后端创建、更新、删除和迁移入口继续保留许可证强制。 - 已完成:后端创建、更新、删除和迁移入口继续保留许可证强制,错误信息统一为“当前许可证未包含/联系管理员处理授权”口径。
- 仍需继续:后续统一把“升级后”话术改成“当前许可证未包含/联系管理员处理授权”,保持私有化交付语气一致。 - 已完成:前端授权提示、按钮、通用受限开关、数量限制和系统设置/部门隔离/模板 API 的“升级后”话术已统一成私有化交付口径。
### P2-03 必填 checkbox 前后端语义不一致(已修复) ### P2-03 必填 checkbox 前后端语义不一致(已修复)
@ -1069,25 +1069,26 @@ NAS 私有化系统可能在平板或窄屏浏览器里使用。模板编辑器
- 拖拽只保留虚线占位和轻背景,不使用旋转、放大、bounce、pulse、强阴影。 - 拖拽只保留虚线占位和轻背景,不使用旋转、放大、bounce、pulse、强阴影。
- 实时预览默认可以折叠,避免占据过多编辑空间。 - 实时预览默认可以折叠,避免占据过多编辑空间。
### P2-11 授权/付费提示偏公网 SaaS 营销话术(升级弹窗已先收口) ### P2-11 授权/付费提示偏公网 SaaS 营销话术(已完成第一轮收口)
客户影响: 客户影响:
当前项目目标是私有化交付。许可证限制应该像“当前许可证不包含此能力”的工作提示,而不是“升级后可享受”的营销弹窗。否则客户会感觉产品不是为私有部署准备的。升级弹窗已先改成中性限制提示,但 PRO 徽章、其它授权入口和部分页面样式仍需继续统一。 当前项目目标是私有化交付。许可证限制应该像“当前许可证不包含此能力”的工作提示,而不是“升级后可享受”的营销弹窗。否则客户会感觉产品不是为私有部署准备的。当前第一轮已把前端授权提示、通用受限开关、数量限制和核心授权 API 的可见话术统一成“当前许可证未包含/联系管理员处理授权”。
证据: 证据:
- `components/ui/ProBadge.tsx:6`:组件说明是付费功能 PRO 徽章。 - `components/ui/UpgradeModal.tsx`:授权提示弹窗已改成中性图标、限制提示文案和普通按钮,不再显示“升级后可享受”。
- `components/ui/ProBadge.tsx:38`:PRO 徽章使用紫蓝渐变。 - `components/ui/RestrictedToggle.tsx`:受限开关提示已改为“当前许可证未包含此功能”。
- `components/ui/UpgradeModal.tsx:62`、`components/ui/UpgradeModal.tsx:86`、`components/ui/UpgradeModal.tsx:111`、`components/ui/UpgradeModal.tsx:122`:升级弹窗已改成中性图标、限制提示文案和普通按钮,不再显示“升级后可享受”。 - `lib/license/check-limits.ts`:用户数和素材数超限提示已改为联系管理员处理授权。
- `components/ui/__tests__/UpgradeModal.test.tsx`:测试已改为锁定限制提示和中性按钮文案。 - `app/api/admin/upload-templates/route.ts`、`app/api/admin/upload-templates/[id]/route.ts`、`app/api/admin/upload-templates/[id]/migrate/route.ts`、`app/api/departments/route.ts`、`app/api/system-settings/route.ts`:授权不足错误已统一成私有化口径。
- `components/ui/ProBadge.tsx`:PRO 徽章仍使用紫色渐变,属于后续统一风格的一部分。 - `components/ui/ProBadge.tsx`:PRO 徽章仍使用紫色渐变,属于后续统一风格的一部分。
- `components/ui/__tests__/UpgradeModal.test.tsx`:测试已改为锁定限制提示和中性按钮文案。
建议动作: 建议动作:
- 改成许可证状态提示:功能不可用、当前版本、需要的版本、联系管理员/服务商。 - 已完成:可见“升级后/请升级”话术第一轮清理,改成许可证状态提示和联系管理员处理授权。
- 去掉营销化“升级好处”列表。 - 已完成:通用授权弹窗不再展示营销化“升级好处”。
- PRO 徽章改为中性许可证标签,不用紫蓝渐变。 - 仍需继续:PRO 徽章改为中性许可证标签,不用紫蓝渐变。
### P2-12 登录、激活、回调页风格和后台不统一 ### P2-12 登录、激活、回调页风格和后台不统一

View File

@ -54,16 +54,16 @@ describe('canCreateUser', () => {
expect(mockCheckLimit).toHaveBeenCalledWith('users'); expect(mockCheckLimit).toHaveBeenCalledWith('users');
}); });
it('当达到限制时(FREE 版本)应该返回升级提示', async () => { it('当达到限制时(FREE 版本)应该返回授权处理提示', async () => {
mockCheckLimit.mockResolvedValue({ allowed: false, current: 10, max: 10 }); mockCheckLimit.mockResolvedValue({ allowed: false, current: 10, max: 10 });
mockGetLicenseType.mockResolvedValue('FREE'); mockGetLicenseType.mockResolvedValue('FREE');
const result = await canCreateUser(); const result = await canCreateUser();
expect(result.allowed).toBe(false); expect(result.allowed).toBe(false);
expect(result.message).toContain('免费版'); expect(result.message).toContain('当前许可证');
expect(result.message).toContain('10'); expect(result.message).toContain('10');
expect(result.message).toContain('升级'); expect(result.message).toContain('联系管理员');
}); });
it('当达到限制时(非 FREE 版本)应该返回联系管理员提示', async () => { it('当达到限制时(非 FREE 版本)应该返回联系管理员提示', async () => {
@ -94,16 +94,16 @@ describe('canUploadMaterial', () => {
expect(mockCheckLimit).toHaveBeenCalledWith('materials'); expect(mockCheckLimit).toHaveBeenCalledWith('materials');
}); });
it('当达到限制时(FREE 版本)应该返回升级提示', async () => { it('当达到限制时(FREE 版本)应该返回授权处理提示', async () => {
mockCheckLimit.mockResolvedValue({ allowed: false, current: 100, max: 100 }); mockCheckLimit.mockResolvedValue({ allowed: false, current: 100, max: 100 });
mockGetLicenseType.mockResolvedValue('FREE'); mockGetLicenseType.mockResolvedValue('FREE');
const result = await canUploadMaterial(); const result = await canUploadMaterial();
expect(result.allowed).toBe(false); expect(result.allowed).toBe(false);
expect(result.message).toContain('免费版'); expect(result.message).toContain('当前许可证');
expect(result.message).toContain('素材'); expect(result.message).toContain('素材');
expect(result.message).toContain('升级'); expect(result.message).toContain('联系管理员');
}); });
it('当达到限制时(非 FREE 版本)应该返回联系管理员提示', async () => { it('当达到限制时(非 FREE 版本)应该返回联系管理员提示', async () => {

View File

@ -25,7 +25,7 @@ export async function canCreateUser(): Promise<LimitCheckResult> {
...result, ...result,
message: message:
licenseType === 'FREE' licenseType === 'FREE'
? `免费版最多支持 ${result.max} 个用户,当前已有 ${result.current} 个用户。请升级到标准版或专业版以解除限制。` ? `当前许可证最多支持 ${result.max} 个用户,当前已有 ${result.current} 个用户。请联系管理员处理授权。`
: `已达到用户数量上限(${result.max}),请联系管理员。`, : `已达到用户数量上限(${result.max}),请联系管理员。`,
}; };
} }
@ -45,7 +45,7 @@ export async function canUploadMaterial(): Promise<LimitCheckResult> {
...result, ...result,
message: message:
licenseType === 'FREE' licenseType === 'FREE'
? `免费版最多支持 ${result.max} 个素材,当前已有 ${result.current} 个素材。请升级到标准版或专业版以解除限制。` ? `当前许可证最多支持 ${result.max} 个素材,当前已有 ${result.current} 个素材。请联系管理员处理授权。`
: `已达到素材数量上限(${result.max}),请联系管理员。`, : `已达到素材数量上限(${result.max}),请联系管理员。`,
}; };
} }

View File

@ -1,7 +1,7 @@
/** /**
* 功能描述映射 * 功能描述映射
* *
* 定义每个付费功能的名称、描述和升级好处 * 定义每个授权功能的名称和描述
* 用于 UpgradeModal 和其他需要展示功能信息的地方 * 用于 UpgradeModal 和其他需要展示功能信息的地方
*/ */
@ -12,7 +12,7 @@ export interface FeatureDescription {
name: string; name: string;
/** 功能描述 */ /** 功能描述 */
description: string; description: string;
/** 升级好处列表 */ /** 功能说明要点 */
benefits: string[]; benefits: string[];
} }
@ -80,7 +80,7 @@ export const FEATURE_DESCRIPTIONS: Record<FeatureKey, FeatureDescription> = {
export function getFeatureDescription(featureKey: FeatureKey): FeatureDescription { export function getFeatureDescription(featureKey: FeatureKey): FeatureDescription {
return FEATURE_DESCRIPTIONS[featureKey] || { return FEATURE_DESCRIPTIONS[featureKey] || {
name: '高级功能', name: '高级功能',
description: '此功能需要升级许可证才能使用', description: '当前许可证未包含此功能,请联系管理员处理授权',
benefits: ['解锁更多功能', '获得技术支持', '享受更新服务'], benefits: ['联系管理员处理授权', '确认当前许可证范围', '完成后重新尝试操作'],
}; };
} }