From 5a1affe830becb98535099f23e2087656b80c02e Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Thu, 28 May 2026 15:11:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=BA=E5=8C=96=E5=95=86=E4=B8=9A=E5=8C=96?= =?UTF-8?q?=E5=9C=B0=E5=9F=BA=E6=9D=83=E9=99=90=E4=B8=8E=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/_components/CategoriesTab.tsx | 43 +++- app/admin/page.tsx | 6 +- .../destructive-actions-superadmin.test.ts | 8 - .../global-dictionaries-superadmin.test.ts | 64 +++++- .../materials/export/__tests__/route.test.ts | 61 ++++++ app/api/admin/materials/export/route.ts | 12 +- .../admin/reset-material-rankings/route.ts | 4 - app/api/admin/upload-templates/[id]/route.ts | 26 +-- .../__tests__/template-crud.test.ts | 144 ++++++++++++- app/api/admin/upload-templates/route.ts | 13 +- .../organization-permissions.test.ts | 49 ++++- app/api/admin/users/update/route.ts | 19 +- app/api/categories/route.ts | 7 + .../category-fields/__tests__/route.test.ts | 137 +++++++++++++ app/api/category-fields/route.ts | 29 ++- .../__tests__/route-auth.test.ts | 2 + app/api/home-filter-fields/route.ts | 3 +- app/api/invitations/[id]/route.ts | 11 +- app/api/invitations/__tests__/route.test.ts | 171 ++++++++++++++++ app/api/invitations/route.ts | 11 +- .../activate-free/__tests__/route.test.ts | 26 +++ app/api/license/activate-free/route.ts | 9 + .../license/activate/__tests__/route.test.ts | 26 +++ app/api/license/activate/route.ts | 9 + app/api/license/check/__tests__/route.test.ts | 10 +- app/api/license/check/route.ts | 4 +- .../license/heartbeat/__tests__/route.test.ts | 56 ++++++ app/api/license/heartbeat/route.ts | 90 ++------- .../license/verify/__tests__/route.test.ts | 56 ++++++ app/api/license/verify/route.ts | 113 ++--------- .../material-rankings/__tests__/route.test.ts | 48 +++++ app/api/material-rankings/route.ts | 4 - app/api/materials/[id]/route.ts | 21 +- .../__tests__/template-fields-routes.test.ts | 80 +++++++- .../download/__tests__/route.test.ts | 71 ++++++- app/api/materials/download/route.ts | 83 ++++---- .../materials/share/__tests__/route.test.ts | 108 +++++++++- app/api/materials/share/route.ts | 8 +- .../share/tokens/[id]/__tests__/route.test.ts | 109 +++++++++- app/api/materials/share/tokens/[id]/route.ts | 8 +- .../share/tokens/__tests__/route.test.ts | 90 ++++++++- app/api/materials/share/tokens/route.ts | 8 +- .../transcode/__tests__/route.test.ts | 151 +++++++++++++- app/api/materials/transcode/route.ts | 41 ++-- app/api/materials/update/route.ts | 15 +- app/api/scenes/route.ts | 7 + .../system-settings/__tests__/route.test.ts | 41 ++++ app/api/system-settings/route.ts | 56 +----- app/api/system/init/__tests__/route.test.ts | 69 ++++++- app/api/system/init/route.ts | 14 +- .../template-categories/[fieldKey]/route.ts | 12 +- .../__tests__/route.test.ts | 190 ++++++++++++++++++ app/api/template-categories/route.ts | 10 +- docs/api/模板系统API文档.md | 13 +- docs/user-guide/模板系统用户手册.md | 2 +- docs/商业化地基排查问题清单_20260524.md | 131 +++++++++--- docs/当前工作流.md | 9 + lib/__tests__/field-pool.test.ts | 116 +++++++++++ lib/__tests__/license-service.test.ts | 66 +++++- lib/__tests__/public-share-settings.test.ts | 37 ++++ lib/field-pool.ts | 72 +++---- lib/license/index.ts | 2 + lib/license/license-route-guards.ts | 32 +++ lib/license/license-service.ts | 160 ++++++++++++++- lib/license/premium-module-service.ts | 16 +- lib/material-download-log.ts | 55 ----- lib/material-permissions.ts | 134 ++++++++++++ lib/material-share-permissions.ts | 60 ++---- lib/public-share-settings.ts | 3 - package.json | 6 + .../migration.sql | 2 + prisma/schema.prisma | 1 + scripts/prisma-local-dev.mjs | 126 ++++++++++++ 73 files changed, 2914 insertions(+), 592 deletions(-) create mode 100644 app/api/category-fields/__tests__/route.test.ts create mode 100644 app/api/invitations/__tests__/route.test.ts create mode 100644 app/api/license/heartbeat/__tests__/route.test.ts create mode 100644 app/api/license/verify/__tests__/route.test.ts create mode 100644 app/api/material-rankings/__tests__/route.test.ts create mode 100644 app/api/template-categories/__tests__/route.test.ts create mode 100644 lib/__tests__/public-share-settings.test.ts create mode 100644 lib/license/license-route-guards.ts delete mode 100644 lib/material-download-log.ts create mode 100644 lib/material-permissions.ts create mode 100644 prisma/migrations/20260528090000_add_home_delete_button_mode/migration.sql create mode 100644 scripts/prisma-local-dev.mjs diff --git a/app/admin/_components/CategoriesTab.tsx b/app/admin/_components/CategoriesTab.tsx index 768768c..0629f83 100644 --- a/app/admin/_components/CategoriesTab.tsx +++ b/app/admin/_components/CategoriesTab.tsx @@ -52,6 +52,7 @@ interface CategoryFieldMeta { interface CategoriesTabProps { showToast: (message: string, type?: 'success' | 'error') => void onDataChange?: () => void + isSuperAdmin?: boolean } const sanitizeOptionLabel = (value: string): string => { @@ -115,7 +116,7 @@ const normalizeOptionArray = (values: string[]): string[] => { return normalized } -export default function CategoriesTab({ showToast, onDataChange }: CategoriesTabProps) { +export default function CategoriesTab({ showToast, onDataChange, isSuperAdmin = false }: CategoriesTabProps) { const styles = useThemeStyles() const theme = useTheme() const { confirm, prompt } = useDialog() @@ -127,6 +128,7 @@ export default function CategoriesTab({ showToast, onDataChange }: CategoriesTab const [renamingOption, setRenamingOption] = useState>(new Map()) const [categoryFieldOptions, setCategoryFieldOptions] = useState([]) const [categoryFieldLoading, setCategoryFieldLoading] = useState(false) + const [categoryFieldSyncing, setCategoryFieldSyncing] = useState(false) const [savingFieldKey, setSavingFieldKey] = useState(null) const [isEditingFields, setIsEditingFields] = useState(false) const [categoryOrder, setCategoryOrder] = useState([]) @@ -268,6 +270,33 @@ export default function CategoriesTab({ showToast, onDataChange }: CategoriesTab fetchCategoryFieldOptions() }, [fetchCategoryFieldOptions]) + const handleSyncCategoryFields = useCallback(async () => { + if (!isSuperAdmin) { + showToast('仅最高权限账号可同步分类字段', 'error') + return + } + + try { + setCategoryFieldSyncing(true) + const response = await fetch('/api/category-fields', { + method: 'POST', + cache: 'no-store', + }) + if (!response.ok) { + const data = await response.json().catch(() => ({})) + throw new Error(data.error || '同步失败') + } + showToast('字段池已同步') + await Promise.all([fetchCategoryFieldOptions(), fetchData()]) + onDataChange?.() + } catch (error: any) { + console.error('[CategoriesTab] Failed to sync category fields:', error) + showToast(error?.message || '同步分类字段失败', 'error') + } finally { + setCategoryFieldSyncing(false) + } + }, [fetchCategoryFieldOptions, fetchData, isSuperAdmin, onDataChange, showToast]) + const handleCategoryLabelChange = useCallback((fieldKey: string, value: string) => { setCategoryLabelDrafts((prev) => { const next = new Map(prev) @@ -746,6 +775,16 @@ export default function CategoriesTab({ showToast, onDataChange }: CategoriesTab > {isEditingFields ? (categoryConfigSaving ? '保存中…' : '保存') : '编辑'} + {!isEditingFields && isSuperAdmin && ( + + )} diff --git a/app/admin/page.tsx b/app/admin/page.tsx index c52e492..a666050 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -295,8 +295,8 @@ const departmentCollator = useMemo(() => getDepartmentCollator(), []) )} - {/* 邀请管理 - 只有管理员及以上权限才能看到 */} - {sessionUser?.role === 'ADMIN' && ( + {/* 邀请管理 - 只有最高权限账号才能看到 */} + {sessionUser?.isSuperAdmin && (