From ac5c0b2dd76377bc77c1dfcd9602471c8f9ba950 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 17 Aug 2026 20:02:36 +0800 Subject: [PATCH] fix(admin): restore Vite admin source files --- assets/admin/extract-naive-styles.mjs | 179 ++ assets/admin/index.html | 12 + assets/admin/package-lock.json | 1853 +++++++++++++++++ assets/admin/package.json | 1 + .../images/header-layout/header_layout_1.png | Bin 0 -> 21733 bytes .../images/header-layout/header_layout_2.png | Bin 0 -> 21763 bytes .../images/header-layout/header_layout_3.png | Bin 0 -> 21660 bytes .../mobile_header_layout_center.png | Bin 0 -> 19180 bytes .../mobile_header_layout_left.png | Bin 0 -> 18804 bytes assets/admin/src/App.vue | 272 +++ assets/admin/src/api/modules/extensions.js | 15 + assets/admin/src/api/modules/global.js | 28 + assets/admin/src/api/modules/index.js | 19 + assets/admin/src/api/modules/page-display.js | 30 + assets/admin/src/api/modules/payment.js | 7 + assets/admin/src/api/modules/schema.js | 17 + assets/admin/src/api/modules/tools.js | 27 + assets/admin/src/components/DeferredPage.vue | 162 ++ assets/admin/src/components/EmptyState.vue | 48 + assets/admin/src/components/MobileTopBar.vue | 86 + .../src/components/OAuthProviderForm.vue | 164 ++ assets/admin/src/components/PageHeader.vue | 81 + .../src/components/captcha/AliyunConfig.vue | 66 + .../components/captcha/CloudflareConfig.vue | 44 + .../src/components/captcha/GeetestConfig.vue | 25 + .../src/components/captcha/TencentConfig.vue | 42 + .../src/components/form/AksArrayTable.vue | 128 ++ .../src/components/form/AksCodeEditor.vue | 105 + assets/admin/src/components/form/AksField.vue | 1156 ++++++++++ .../src/components/form/AksFieldGroup.vue | 180 ++ .../src/components/form/AksFormRenderer.vue | 408 ++++ .../src/components/form/AksMediaField.vue | 167 ++ .../src/components/form/AksRepeaterField.vue | 185 ++ assets/admin/src/components/form/FaIcon.vue | 67 + .../src/components/form/FaIconPicker.vue | 221 ++ .../src/components/homepage/ModuleCanvas.vue | 313 +++ .../components/homepage/ModuleInspector.vue | 399 ++++ .../homepage/ModulePresetPicker.vue | 143 ++ assets/admin/src/composables/useDirtyState.js | 50 + .../admin/src/composables/useSectionForm.js | 140 ++ assets/admin/src/constants/admin-sections.js | 10 + assets/admin/src/constants/sections/docs.js | 20 + assets/admin/src/constants/sections/global.js | 108 + .../admin/src/constants/sections/helpers.js | 39 + .../admin/src/constants/sections/post-list.js | 92 + .../src/constants/sections/theme-info.js | 29 + .../admin/src/constants/settings-defaults.js | 28 + assets/admin/src/main.js | 101 + assets/admin/src/pages/SchemaPage.vue | 508 +++++ .../admin/src/pages/content/BannerManager.vue | 403 ++++ .../src/pages/content/FooterSettings.vue | 986 +++++++++ .../admin/src/pages/content/HeroManager.vue | 518 +++++ .../content/NotificationCenterSettings.vue | 155 ++ .../src/pages/extensions/ExtensionAI.vue | 316 +++ .../src/pages/extensions/ExtensionTools.vue | 270 +++ .../extensions/ExtensionsSectionPage.vue | 39 + .../src/pages/payment/PaymentChargeCard.vue | 530 +++++ .../src/pages/payment/PaymentSectionPage.vue | 103 + .../components/ChargeCardDetailDrawer.vue | 89 + .../payment/components/RebateDetailDrawer.vue | 175 ++ .../pages/placeholder/NativeOpsRedirect.vue | 57 + .../src/pages/post-list/CommentSettings.vue | 7 + .../src/pages/post-list/PostPageSettings.vue | 7 + .../pages/theme/ThemeAuthorizationPage.vue | 42 + .../admin/src/pages/theme/ThemeInfoPage.vue | 265 +++ assets/admin/src/pages/tools/ToolsBackup.vue | 278 +++ .../src/pages/tools/ToolsEmailRollback.vue | 122 ++ assets/admin/src/pages/tools/ToolsHealth.vue | 111 + .../src/pages/tools/ToolsImportExport.vue | 190 ++ assets/admin/src/pages/users/UsersBadge.vue | 265 +++ assets/admin/src/pages/users/UsersBan.vue | 210 ++ assets/admin/src/pages/users/UsersLevel.vue | 290 +++ .../admin/src/pages/users/UsersMessages.vue | 308 +++ assets/admin/src/pages/users/UsersReport.vue | 357 ++++ .../src/pages/users/UsersSectionPage.vue | 40 + .../users/components/ReportDetailDrawer.vue | 242 +++ .../src/router/component-loaders/payment.js | 3 + .../src/router/component-loaders/tools.js | 6 + .../src/schema/sections/extensions/ip.js | 88 + .../src/schema/sections/extensions/push.js | 64 + .../schema/sections/global/code-highlight.js | 68 + .../src/schema/sections/global/custom-code.js | 39 + .../admin/src/schema/sections/global/email.js | 123 ++ .../src/schema/sections/global/floating.js | 8 + .../admin/src/schema/sections/global/i18n.js | 85 + .../admin/src/schema/sections/global/index.js | 32 + .../src/schema/sections/global/layout.js | 160 ++ .../admin/src/schema/sections/global/logo.js | 36 + .../admin/src/schema/sections/global/seo.js | 143 ++ .../schema/sections/global/system-notice.js | 149 ++ .../src/schema/sections/global/tabbar.js | 8 + .../src/schema/sections/payment/cardPass.js | 59 + .../src/schema/sections/payment/gateway.js | 167 ++ .../src/schema/sections/post-list/feature.js | 25 + .../src/schema/sections/post-list/index.js | 25 + .../src/schema/sections/post-list/pay.js | 46 + .../src/schema/sections/post-list/share.js | 42 + .../src/schema/sections/post-list/thumb.js | 70 + .../admin/src/schema/sections/tools/backup.js | 29 + .../schema/sections/tools/email-rollback.js | 29 + .../admin/src/schema/sections/tools/health.js | 21 + .../schema/sections/tools/import-export.js | 26 + .../schema/sections/users/notifications.js | 74 + .../admin/src/schema/sections/users/oauth.js | 211 ++ .../admin/src/schema/sections/users/report.js | 58 + .../admin/src/schema/sections/users/signin.js | 27 + assets/admin/src/schema/sections/users/sms.js | 144 ++ .../admin/src/schema/sections/users/wechat.js | 51 + assets/admin/src/stores/admin-ui.js | 40 + assets/admin/src/style-target.js | 7 + assets/admin/src/style.css | 404 ++++ assets/admin/src/utils/home-module-schema.js | 514 +++++ assets/admin/src/utils/media.js | 40 + assets/admin/tsconfig.json | 1 + 114 files changed, 17272 insertions(+) create mode 100644 assets/admin/extract-naive-styles.mjs create mode 100644 assets/admin/index.html create mode 100644 assets/admin/package-lock.json create mode 100644 assets/admin/package.json create mode 100644 assets/admin/public/images/header-layout/header_layout_1.png create mode 100644 assets/admin/public/images/header-layout/header_layout_2.png create mode 100644 assets/admin/public/images/header-layout/header_layout_3.png create mode 100644 assets/admin/public/images/header-layout/mobile_header_layout_center.png create mode 100644 assets/admin/public/images/header-layout/mobile_header_layout_left.png create mode 100644 assets/admin/src/App.vue create mode 100644 assets/admin/src/api/modules/extensions.js create mode 100644 assets/admin/src/api/modules/global.js create mode 100644 assets/admin/src/api/modules/index.js create mode 100644 assets/admin/src/api/modules/page-display.js create mode 100644 assets/admin/src/api/modules/payment.js create mode 100644 assets/admin/src/api/modules/schema.js create mode 100644 assets/admin/src/api/modules/tools.js create mode 100644 assets/admin/src/components/DeferredPage.vue create mode 100644 assets/admin/src/components/EmptyState.vue create mode 100644 assets/admin/src/components/MobileTopBar.vue create mode 100644 assets/admin/src/components/OAuthProviderForm.vue create mode 100644 assets/admin/src/components/PageHeader.vue create mode 100644 assets/admin/src/components/captcha/AliyunConfig.vue create mode 100644 assets/admin/src/components/captcha/CloudflareConfig.vue create mode 100644 assets/admin/src/components/captcha/GeetestConfig.vue create mode 100644 assets/admin/src/components/captcha/TencentConfig.vue create mode 100644 assets/admin/src/components/form/AksArrayTable.vue create mode 100644 assets/admin/src/components/form/AksCodeEditor.vue create mode 100644 assets/admin/src/components/form/AksField.vue create mode 100644 assets/admin/src/components/form/AksFieldGroup.vue create mode 100644 assets/admin/src/components/form/AksFormRenderer.vue create mode 100644 assets/admin/src/components/form/AksMediaField.vue create mode 100644 assets/admin/src/components/form/AksRepeaterField.vue create mode 100644 assets/admin/src/components/form/FaIcon.vue create mode 100644 assets/admin/src/components/form/FaIconPicker.vue create mode 100644 assets/admin/src/components/homepage/ModuleCanvas.vue create mode 100644 assets/admin/src/components/homepage/ModuleInspector.vue create mode 100644 assets/admin/src/components/homepage/ModulePresetPicker.vue create mode 100644 assets/admin/src/composables/useDirtyState.js create mode 100644 assets/admin/src/composables/useSectionForm.js create mode 100644 assets/admin/src/constants/admin-sections.js create mode 100644 assets/admin/src/constants/sections/docs.js create mode 100644 assets/admin/src/constants/sections/global.js create mode 100644 assets/admin/src/constants/sections/helpers.js create mode 100644 assets/admin/src/constants/sections/post-list.js create mode 100644 assets/admin/src/constants/sections/theme-info.js create mode 100644 assets/admin/src/constants/settings-defaults.js create mode 100644 assets/admin/src/main.js create mode 100644 assets/admin/src/pages/SchemaPage.vue create mode 100644 assets/admin/src/pages/content/BannerManager.vue create mode 100644 assets/admin/src/pages/content/FooterSettings.vue create mode 100644 assets/admin/src/pages/content/HeroManager.vue create mode 100644 assets/admin/src/pages/content/NotificationCenterSettings.vue create mode 100644 assets/admin/src/pages/extensions/ExtensionAI.vue create mode 100644 assets/admin/src/pages/extensions/ExtensionTools.vue create mode 100644 assets/admin/src/pages/extensions/ExtensionsSectionPage.vue create mode 100644 assets/admin/src/pages/payment/PaymentChargeCard.vue create mode 100644 assets/admin/src/pages/payment/PaymentSectionPage.vue create mode 100644 assets/admin/src/pages/payment/components/ChargeCardDetailDrawer.vue create mode 100644 assets/admin/src/pages/payment/components/RebateDetailDrawer.vue create mode 100644 assets/admin/src/pages/placeholder/NativeOpsRedirect.vue create mode 100644 assets/admin/src/pages/post-list/CommentSettings.vue create mode 100644 assets/admin/src/pages/post-list/PostPageSettings.vue create mode 100644 assets/admin/src/pages/theme/ThemeAuthorizationPage.vue create mode 100644 assets/admin/src/pages/theme/ThemeInfoPage.vue create mode 100644 assets/admin/src/pages/tools/ToolsBackup.vue create mode 100644 assets/admin/src/pages/tools/ToolsEmailRollback.vue create mode 100644 assets/admin/src/pages/tools/ToolsHealth.vue create mode 100644 assets/admin/src/pages/tools/ToolsImportExport.vue create mode 100644 assets/admin/src/pages/users/UsersBadge.vue create mode 100644 assets/admin/src/pages/users/UsersBan.vue create mode 100644 assets/admin/src/pages/users/UsersLevel.vue create mode 100644 assets/admin/src/pages/users/UsersMessages.vue create mode 100644 assets/admin/src/pages/users/UsersReport.vue create mode 100644 assets/admin/src/pages/users/UsersSectionPage.vue create mode 100644 assets/admin/src/pages/users/components/ReportDetailDrawer.vue create mode 100644 assets/admin/src/router/component-loaders/payment.js create mode 100644 assets/admin/src/router/component-loaders/tools.js create mode 100644 assets/admin/src/schema/sections/extensions/ip.js create mode 100644 assets/admin/src/schema/sections/extensions/push.js create mode 100644 assets/admin/src/schema/sections/global/code-highlight.js create mode 100644 assets/admin/src/schema/sections/global/custom-code.js create mode 100644 assets/admin/src/schema/sections/global/email.js create mode 100644 assets/admin/src/schema/sections/global/floating.js create mode 100644 assets/admin/src/schema/sections/global/i18n.js create mode 100644 assets/admin/src/schema/sections/global/index.js create mode 100644 assets/admin/src/schema/sections/global/layout.js create mode 100644 assets/admin/src/schema/sections/global/logo.js create mode 100644 assets/admin/src/schema/sections/global/seo.js create mode 100644 assets/admin/src/schema/sections/global/system-notice.js create mode 100644 assets/admin/src/schema/sections/global/tabbar.js create mode 100644 assets/admin/src/schema/sections/payment/cardPass.js create mode 100644 assets/admin/src/schema/sections/payment/gateway.js create mode 100644 assets/admin/src/schema/sections/post-list/feature.js create mode 100644 assets/admin/src/schema/sections/post-list/index.js create mode 100644 assets/admin/src/schema/sections/post-list/pay.js create mode 100644 assets/admin/src/schema/sections/post-list/share.js create mode 100644 assets/admin/src/schema/sections/post-list/thumb.js create mode 100644 assets/admin/src/schema/sections/tools/backup.js create mode 100644 assets/admin/src/schema/sections/tools/email-rollback.js create mode 100644 assets/admin/src/schema/sections/tools/health.js create mode 100644 assets/admin/src/schema/sections/tools/import-export.js create mode 100644 assets/admin/src/schema/sections/users/notifications.js create mode 100644 assets/admin/src/schema/sections/users/oauth.js create mode 100644 assets/admin/src/schema/sections/users/report.js create mode 100644 assets/admin/src/schema/sections/users/signin.js create mode 100644 assets/admin/src/schema/sections/users/sms.js create mode 100644 assets/admin/src/schema/sections/users/wechat.js create mode 100644 assets/admin/src/stores/admin-ui.js create mode 100644 assets/admin/src/style-target.js create mode 100644 assets/admin/src/style.css create mode 100644 assets/admin/src/utils/home-module-schema.js create mode 100644 assets/admin/src/utils/media.js create mode 100644 assets/admin/tsconfig.json diff --git a/assets/admin/extract-naive-styles.mjs b/assets/admin/extract-naive-styles.mjs new file mode 100644 index 00000000..8315f791 --- /dev/null +++ b/assets/admin/extract-naive-styles.mjs @@ -0,0 +1,179 @@ +/** + * extract-naive-styles.mjs + * + * 在构建时直接调用 NaiveUI 组件的 .cssr.mjs 样式函数的 render() 方法, + * 提取所有组件的运行时 JSS 为静态 CSS 文件。 + * 彻底解决 WordPress 后台环境中样式丢失问题。 + * + * 原理: + * 每个 NaiveUI 组件都有 src/styles/index.cssr.mjs 文件 + * 导出的函数返回 css-render 的 CNode 对象 + * 调用 CNode.render({ clsPrefix: 'n' }) 即可获取纯 CSS 字符串 + * 无需虚拟 DOM、无需 Vue、无任何浏览器 API 依赖 + */ + +import { writeFile, mkdir } from 'fs/promises' +import { dirname, join } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const BUILD_DIR = join(__dirname, 'build') + +// ============================================================ +// 需要提取样式的组件列表(对应各组件的 .cssr.mjs 入口) +// 格式:[导入路径在 naive-ui/es 内部的相对路径] +// ============================================================ +const COMPONENT_STYLES = [ + // 内部基础组件 + ['_internal/scrollbar/src/styles/index.cssr.mjs'], + // 布局 + ['layout/src/styles/index.cssr.mjs'], + ['layout-sider/src/styles/index.cssr.mjs'], + // 导航 + ['menu/src/styles/index.cssr.mjs'], + ['breadcrumb/src/styles/index.cssr.mjs'], + ['tabs/src/styles/index.cssr.mjs'], + ['steps/src/styles/index.cssr.mjs'], + ['pagination/src/styles/index.cssr.mjs'], + // 表单 + ['form/src/styles/index.cssr.mjs'], + ['form-item/src/styles/index.cssr.mjs'], + ['input/src/styles/index.cssr.mjs'], + ['input-number/src/styles/index.cssr.mjs'], + ['switch/src/styles/index.cssr.mjs'], + ['select/src/styles/index.cssr.mjs'], + ['radio/src/styles/index.cssr.mjs'], + ['checkbox/src/styles/index.cssr.mjs'], + ['slider/src/styles/index.cssr.mjs'], + ['rate/src/styles/index.cssr.mjs'], + ['date-picker/src/styles/indexcode/index.cssr.mjs'], + ['time-picker/src/styles/index.cssr.mjs'], + // 展示 + ['card/src/styles/index.cssr.mjs'], + ['tag/src/styles/index.cssr.mjs'], + ['alert/src/styles/index.cssr.mjs'], + ['data-table/src/styles/index.cssr.mjs'], + ['descriptions/src/styles/index.cssr.mjs'], + ['empty/src/styles/index.cssr.mjs'], + ['result/src/styles/index.cssr.mjs'], + ['statistic/src/styles/index.cssr.mjs'], + ['list/src/styles/index.cssr.mjs'], + ['collapse/src/styles/index.cssr.mjs'], + ['timeline/src/styles/index.cssr.mjs'], + // 反馈 + ['button/src/styles/index.cssr.mjs'], + ['modal/src/styles/index.cssr.mjs'], + ['dialog/src/styles/index.cssr.mjs'], + ['message/src/styles/index.cssr.mjs'], + ['notification/src/styles/index.cssr.mjs'], + ['popconfirm/src/styles/index.cssr.mjs'], + ['popover/src/styles/index.cssr.mjs'], + ['tooltip/src/styles/index.cssr.mjs'], + ['dropdown/src/styles/index.cssr.mjs'], + // 其他 + ['icon/src/styles/index.cssr.mjs'], + ['icon-wrapper/src/styles/index.cssr.mjs'], + ['space/src/styles/index.cssr.mjs'], + ['divider/src/styles/index.cssr.mjs'], + ['gradient-text/src/styles/index.cssr.mjs'], + ['ellipsis/src/styles/index.cssr.mjs'], + ['spin/src/styles/index.cssr.mjs'], + ['loading-bar/src/styles/index.cssr.mjs'], + ['upload/src/styles/index.cssr.mjs'], + ['image/src/styles/index.cssr.mjs'], + ['carousel/src/styles/index.cssr.mjs'], + ['transfer/src/styles/index.cssr.mjs'], + ['tree-select/src/styles/index.cssr.mjs'], + ['tree/src/styles/index.cssr.mjs'], + ['thing/src/styles/index.cssr.mjs'], + ['color-picker/src/styles/indexcode/index.cssr.mjs'], + ['typography/src/styles/index.cssr.mjs'], + ['auto-complete/src/styles/index.cssr.mjs'], + ['cascader/src/styles/index.cssr.mjs'], + ['dynamic-input/src/styles/index.cssr.mjs'], + ['dynamic-tags/src/styles/index.cssr.mjs'], + ['input-otp/src/styles/indexcode/index.cssr.mjs'], + // 过渡动画 + ['_styles/transitions/fade-in-width-expand.cssr.mjs'], + ['_styles/transitions/fade-in-height-expand.cssr.mjs'], + ['_styles/transitions/icon-switch.cssr.mjs'], + ['_styles/transitions/scale-transition.cssr.mjs'], + // 全局 + ['global-style/src/styles/index.cssr.mjs'], +] + +async function extractStyles() { + console.log('\n[AKSRT] 开始提取 NaiveUI 组件样式...\n') + + const allCss = [] + let successCount = 0 + let failCount = 0 + + for (const [componentPath] of COMPONENT_STYLES) { + try { + // 动态导入每个组件的 cssr 样式模块 + const styleModule = await import( + `naive-ui/es/${componentPath}` + ) + + const defaultExport = styleModule.default || styleModule + + if (typeof defaultExport === 'function') { + // 直接调用 render() 提取 CSS + const css = defaultExport({ + clsPrefix: 'n', + // 使用 light 主题变量 + }) + + if (typeof css === 'string' && css.trim()) { + allCss.push(`/* ${componentPath} */\n${css}`) + successCount++ + } + } else if (defaultExport && typeof defaultExport.render === 'function') { + // 如果是 CNode 对象,调用 render() + const css = defaultExport.render() + if (typeof css === 'string' && css.trim()) { + allCss.push(`/* ${componentPath} */\n${css}`) + successCount++ + } + } + } catch (err) { + // 某些路径可能不存在或结构不同,跳过但不中断 + failCount++ + } + } + + const cssOutput = allCss.join('\n\n') + + if (!cssOutput.trim()) { + console.warn('[AKSRT] 警告:未提取到任何 NaiveUI 样式!') + return null + } + + // 确保 build 目录存在 + await mkdir(BUILD_DIR, { recursive: true }) + + // 写入 CSS 文件 + const cssFile = join(BUILD_DIR, 'naive-ui.css') + const fullContent = `/* Auto-extracted by extract-naive-styles.mjs */ +/* Naive UI v2 Component Styles - Build-time extraction */ +/* DO NOT EDIT MANUALLY */ + +${cssOutput}` + + await writeFile(cssFile, fullContent, 'utf-8') + + console.log(`[AKSRT] NaiveUI 样式提取完成:`) + console.log(` 成功: ${successCount} 个组件`) + console.log(` 跳过: ${failCount} 个组件`) + console.log(` 输出: ${cssFile}`) + console.log(` 大小: ${(Buffer.byteLength(fullContent) / 1024).toFixed(1)} KB\n`) + + return cssFile +} + +// 执行 +extractStyles().catch((err) => { + console.error('[AKSRT] 样式提取失败:', err) + process.exit(1) +}) diff --git a/assets/admin/index.html b/assets/admin/index.html new file mode 100644 index 00000000..7418cf13 --- /dev/null +++ b/assets/admin/index.html @@ -0,0 +1,12 @@ + + + + + + AKSRT 主题设置 + + +
+ + + diff --git a/assets/admin/package-lock.json b/assets/admin/package-lock.json new file mode 100644 index 00000000..25de0a6e --- /dev/null +++ b/assets/admin/package-lock.json @@ -0,0 +1,1853 @@ +{ + "name": "aksrt-admin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aksrt-admin", + "version": "1.0.0", + "dependencies": { + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/theme-one-dark": "^6.1.3", + "codemirror": "^6.0.2", + "naive-ui": "^2.40.0", + "pinia": "^2.2.0", + "vue": "^3.5.0", + "vue-router": "^4.4.0", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.0", + "vite": "^6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", + "integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.1.tgz", + "integrity": "sha512-+BIjw/AG3tDQ4pJgTLPYdAW25eDE66YsvM4LKyVPgGzVgZ4a9Wj1SRX8kPVKgBDdPt8oHtZ15F0qx7p0oOHdHw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@css-render/plugin-bem": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz", + "integrity": "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==", + "license": "MIT", + "peerDependencies": { + "css-render": "~0.15.14" + } + }, + "node_modules/@css-render/vue3-ssr": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/@css-render/vue3-ssr/-/vue3-ssr-0.15.14.tgz", + "integrity": "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "license": "Apache-2.0" + }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz", + "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.33.tgz", + "integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.33", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz", + "integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz", + "integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.33", + "@vue/compiler-dom": "3.5.33", + "@vue/compiler-ssr": "3.5.33", + "@vue/shared": "3.5.33", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.10", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz", + "integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.33.tgz", + "integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.33.tgz", + "integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.33", + "@vue/shared": "3.5.33" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz", + "integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.33", + "@vue/runtime-core": "3.5.33", + "@vue/shared": "3.5.33", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.33.tgz", + "integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.33", + "@vue/shared": "3.5.33" + }, + "peerDependencies": { + "vue": "3.5.33" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.33.tgz", + "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==", + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/css-render": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/css-render/-/css-render-0.15.14.tgz", + "integrity": "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "~0.8.0", + "csstype": "~3.0.5" + } + }, + "node_modules/css-render/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "license": "MIT", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/evtd": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/evtd/-/evtd-0.2.4.tgz", + "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/naive-ui": { + "version": "2.44.1", + "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.44.1.tgz", + "integrity": "sha512-reo8Esw0p58liZwbUutC7meW24Xbn3EwNv91zReWKm2W4JPu+zfgJRn/F7aO0BFmvN+h2brA2M5lRvYqLq4kuA==", + "license": "MIT", + "dependencies": { + "@css-render/plugin-bem": "^0.15.14", + "@css-render/vue3-ssr": "^0.15.14", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "async-validator": "^4.2.5", + "css-render": "^0.15.14", + "csstype": "^3.1.3", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", + "evtd": "^0.2.4", + "highlight.js": "^11.8.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "seemly": "^0.3.10", + "treemate": "^0.3.11", + "vdirs": "^0.1.8", + "vooks": "^0.2.12", + "vueuc": "^0.4.65" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/seemly": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.10.tgz", + "integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==", + "license": "MIT" + }, + "node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/treemate": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/treemate/-/treemate-0.3.11.tgz", + "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", + "license": "MIT" + }, + "node_modules/vdirs": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/vdirs/-/vdirs-0.1.8.tgz", + "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vooks": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/vooks/-/vooks-0.2.12.tgz", + "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz", + "integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.33", + "@vue/compiler-sfc": "3.5.33", + "@vue/runtime-dom": "3.5.33", + "@vue/server-renderer": "3.5.33", + "@vue/shared": "3.5.33" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "license": "MIT", + "dependencies": { + "sortablejs": "1.14.0" + }, + "peerDependencies": { + "vue": "^3.0.1" + } + }, + "node_modules/vueuc": { + "version": "0.4.65", + "resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.65.tgz", + "integrity": "sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ==", + "license": "MIT", + "dependencies": { + "@css-render/vue3-ssr": "^0.15.10", + "@juggle/resize-observer": "^3.3.1", + "css-render": "^0.15.10", + "evtd": "^0.2.4", + "seemly": "^0.3.6", + "vdirs": "^0.1.4", + "vooks": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + } + } +} diff --git a/assets/admin/package.json b/assets/admin/package.json new file mode 100644 index 00000000..eb06652b --- /dev/null +++ b/assets/admin/package.json @@ -0,0 +1 @@ +{"name":"aksrt-admin","version":"1.0.0","private":true,"type":"module","scripts":{"dev":"vite","build":"vite build && node extract-naive-styles.mjs","preview":"vite preview"},"dependencies":{"@codemirror/lang-css":"^6.3.1","@codemirror/lang-html":"^6.4.11","@codemirror/lang-javascript":"^6.2.5","@codemirror/theme-one-dark":"^6.1.3","codemirror":"^6.0.2","naive-ui":"^2.40.0","pinia":"^2.2.0","vue":"^3.5.0","vue-router":"^4.4.0","vuedraggable":"^4.1.0"},"devDependencies":{"@vitejs/plugin-vue":"^5.1.0","vite":"^6.0.0"}} \ No newline at end of file diff --git a/assets/admin/public/images/header-layout/header_layout_1.png b/assets/admin/public/images/header-layout/header_layout_1.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6a029402c67343911a46cf65aa0c3e3d3be36a GIT binary patch literal 21733 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 V9C>3O7z~ZEx4>GUzW)Jl{{`Pk)vN#j literal 0 HcmV?d00001 diff --git a/assets/admin/public/images/header-layout/header_layout_2.png b/assets/admin/public/images/header-layout/header_layout_2.png new file mode 100644 index 0000000000000000000000000000000000000000..71b6392683005748c3ea99dd2c943be5ecc99858 GIT binary patch literal 21763 zcmeI4c|4Te-}o;}McMZ(5owVz_Lvy5EBh|%7=vNPh{4!pNhw>RTS6!zYblZ~OA?Yq zvScfgoh18nQTIL7ec#{TJg?vL{PVnAxUP?LKFc}p^EqeEnb&LPlAew_?Ox8k007Wx zYN!|h0J$+J%TnzI@4SWY%fSb=n})du0Eh@|{gDywD0l(DUTw6pvYwt34v+J2!nyHj zDl7B3x#Jwr7!&{ydQuF%&El8X-~&r2dK0DY+2ZS%^8mj<9@TRO^9+Wo03wWmNwFdP z;$9OLR>qqe#`Phcdo@xn4&ORj=6EXd;@Qi_mNL`4ec2BKd}pSnR^P5xjNKetd+F0g zAI!*fIBqn<8IUyymxFcl#^`F)H&q6cvuX@d@-d7y*E;TM0DxHvA3`9$NMVhv5C@P0 zZk-~Bm>P#RL(BCJn*w}sz#u=<*gmpY1K_!c(Q_8yIR{W=_`rTIa18)3KK@GlKrSsX z`9%}H3+T>??_&qLGq`i8fG9E`>8Sc4a>XZrt?_vSVe*<<;8va@zX)Yb0a>v5BXM=| z;L2TqSF~9yy`n7;oTMb&0`SR@1Ih=OTPgUoC~k?qt{%*>aSJcgAOmWe6I!pIc`d&XJl8epKgayP=qrUy~WS zeh%U4RbUW3)=a4+=DIW{u+bPEv>tt|eOR*CQkXSz&g@c|9=~>@&tro)m4i(Q!%X8r z;&Z%->(ueyv_Rek-5Q-odmc7pY67Rp$S>2Gz4ihW0~a7yIQPW?433iGK1%=)U%eco zc$^Gqy?%QH04hFlSl+$HuJw!x090G@pWX6k$4k;%1t(^>v~!jmkPm*8k==gglP4Ys zh>S)Cw~Ns^TzQAEyb_^md&!LRa3dS?qnVURj1-c`?1UF9S4}EnhUyUcFNiay$1zj#WnY7x@h!jKyDk}hQDS&-_$xh5sPhl|AFd$a30_FxYAhq zSlz3uO?4kiSzrwphqb6P4jJ6O#&lW=rSOsRPMWw<(yd(m)|}KgRJj*nX=gRDx4iVA zh7mc;lAbpNTa($8n{PuhJ73*EJEE7IpyT**>i*CJs@Gp#f9>*V&-mDQ=ofDq+6%Oy zjT&}G_q*)(+n>!*x*wXPm9?wmdeUlFDFK#Xk`S0M zmTP3}VXV;Ule^F8o-zGBYm*zR@a@EAYIkuG>?;B642*QRfiS!J^x zvVx?&e<57$)&-4BBV_5b{ZR+mo{6L8%#^y^O(-UWUH;Ivv$(%JspYk6mCFn2r)8+@ zk?WA>5a*CGdajj>?pg8Rtn9m+r(Yg?N&C_dLIF{L6n8o2JLCuEpXl-Jsp_$ta-U+G zn&_GFbW2f85l&fzVPQ+2)aBCU`sLx}y`F8K;T^~cpK+t&xQ@%Wo&jTkE?geY}d>@Qg(Pc!f`~3zz%oF z!*Sc?Nxnz*xyT{x(Bi$f>u)!Fs5;hq&E_`Uejc>Z0AjvR7BOg zir%%T@H?tE8%|%AzTBADljz^PD^WXfDen&xxQUO+qRHDl%`VFc;R$S)DMpXWG?^wD zmfZa4Zhmrpy;m}JfMu8^=Gg0Fi-xW}Vv}o=dna#9?w*qUiufw{75lY~r-hq|r|TsC zq`y1fv&9SJRxxpVNU|=cKBsQn)RD_E-6Lf|iaqO0Y%|k!uj}lF*zCw`*EHYegpZul z<*y%hEM_J@N$$&khkeuEBHs{jZ&h0)nXAdFPJZUpm)Rw=;d3n`DcQ-{wF|LhP-`!1 zbL-X01G*1x9!~8N#Xj$?w-~H!_Ope-2nbt~9U5e5)SvnP;2R*=0-ilrZxJyogK~QUdiHfdvs3f^#C5%4 zGXvv!XP>_LXoJw#ckU=34>6TY$hp?k>{{^U>|-P=BEjBx#`$`ug-MVQdxA}#Ti)G* zZ$)Zlqr;{;Hl6;9KG>d~_oqu=OigG^+@5gFTQaXMJYDI0s`J%_S=xNAxu}m^HrUhe z@#((Ki_xtmtuInZ(+i!s`l!x`? z`RN4=_aGjK+11nJo*6=RxuE9I#C-13Kz_>pY;?9>z; z1^G8geLhQCYe_Cv`c{>W%5A9%?D%P|Qk$iwh4*7IRCcLph>d<@slx zRP*zp?dADRrL;x0-IP&IXboR?l%cPV5z^NMDQnNK0N*P|fPoIMC_I9XfW^3azzFjE z+kRo7yj2Y4=i64nyU6pO+6u^Lrme@PjB`ivNr^~9kfPEj_)f@*h)JC|A$dZWPh3q&wOTkH)$3ZTUsm;XLv3{QO&i z{_Cc80>TX{CL#*`7b9)$|7?cE{-dP_Ueyb1$&a4>V?YlhA2$@# z0Of)6bVs68y-==rfq!a*+n?u%e|Gm*^}qJQ9{HE0o2NTw8?ZeRio&3FJ@ac-_5pD;rj^5f%ykhc1I!bICmo)4g>$5WyI4HczD{% zD!!xI2qfBd>qvD>4*I(ham0!KP$~#K3N9`xE(#G9hlqmk{6=puRgT3VVbTUc!VnwrKti3`!0e;+r#XU z;mY2x%!`_#9` zAwlAc6Uy~JrT=$V+gktb!~^Gm z_eQv*PC0@ZLfYK??)O*2-vh`&x7KS6`hQ_4JP=<0e*;0}!MSVW>``!upUsHH#Af_t1-_4|_x){5xQBtd5}&53vb312v@}Fq1bm?(GWbdF zCqMQ`m;=roi@?LtScD@A>gMVw2PGE%q`9@dfPt$#xZ)tTS69&c-^za1H``jRF`nD{ z5~AA|RBY? z`dwQOO+aDHRM23adThPgNXee~Vf1^=KU$jqWciEXPZd85zbDpD`|9o4_q`O{wSxx+ z=wBy>zfH`osq{a75qtDM?waUuCkT-R3DOQO5+D&(c5o3{kRa{gA^{RnWd|3L1qsp) zE)pOSRd#R@S&$&@;35GMQDp}gkp&6T4lWWP5mk0@5m}HR?cgE-5>aIb7m)=C(he>X zAQ4q|a1mLMAno8H0TNMV2N#hA3DOQO5+D&(c5o3{kRa{gA^{RnWd|3L1qsp)E)pOS zRd#R@S&$&@;35GMQDp}gkp&6T4lWWP5mk0@5m}HR?cgE-5>aIb7m)=C(he>XAQ4q| za1mLMAno8H0TNMV2N#hA3DOQO5+D&(c5o3{kRa{gA^{RnWd|3L1qsp)E)pOSRd#R@ zS&$(8M_hY}pTR}Bf*-u~20vlDE-UH)ema*AsbQcE0KS3%5F82s%bVbH8~{8;0pPPO z0KonL0CwC}n+7!ipmWevIb}rXne4Z~SS$tXnVpf1FpbNA9DjsSFPISeT4JsrclPq( zn$S_9ODKrz4Lqj2UgYH490Pg|2KjH{^wjfAb@{^w8zf!ku8B3xi|+Oq%SydKt7=Tp zpL*S_q?=6qZXx7fuy{@jJqEfG? z`rBQF#uzO5BT7YPeBBrZjI`oaLe_TRy*GBt$DdDZ z0&>eM-nLzUYA~rpP=wTxT@=pSif6@{i#NZJdivD$kG;LYxk_j5@O^M+uoR&(Io`$z zD)d{WpKkkjOEA9qo(|;5O+hAx{^=x{||8z(~h?x~F6?x%de*TOnMu+21!BTbuR3Wzhtm@1>poc_M z_mr0mceHGwSm=0v&@(GPL6d@Y21Z7G)amRFjqDBvO0tZjY`X;YS@YhL)g1uDXxM;no1(q zC2}ns#2>Eerls^fu>4|4SDp)w48fDJoo9UhV>1Ugcm3?FC~zx3pEe~WrTc*+Jy%4R zBuMl|>V@nc3Vlm+)0?@uYT-h<9g;FBI$*oQM5-h9&Cq48ezJKwj^~flV9AGSGTnbC zoM&<`$Gh>YmTUUFNi;sdAq~tEe8leDjg3IOm)G#~H~}DdXbACTX({u`ljNt7U6QK$ zj4?+By&fNoJpN=(IJuQPm@)T|W=O6OVO$-W*)#R*Q$%&0-;B-qjJx0LhYkJL+csF} zy(g8$lO!JK)G$N?u; zS2bOdaTLTjl<4?MbCryZJEo~5OY03^oZK++NlVwG0)L~TqBwYX zq!Rt)4ovnxVIONtJCPW5<;q}pF|rIl&ZLtDmv^+VE@Xe=HKoMIAR3}QBz@%Y;i7XR zYLw(;B_$;sywPzQ_Al?>`LJ|u;#|y?D||^>Y>+eA9pD%=951XeD3ROrgKw@aJQI8K z_N~By81T2PR|lTfMs;!lVxc9;X;p+RuzGS~AG-+4)Z3Y*x^evQho*Rs=N<4 zo7puoMI5fMSfP3V7nZV|2yrNWH0N7MwL4UG%4fKqI;?KUxLWP5J1spu7i+E(7K`QJ z;IO4=&($+#4cesNycp!W4sj}vwz1hegbP|@a_%aG8it=aUoi2^Oemu6{N_pnbPkH! zJYKaNrO;SBo%za>_I}=Aaj2D(>-#9?BR0=2@ut*zr)|(VJ9h~rw<Et+Gt~nz=g*UUTMs12 z9iR+7m1#!?EDZz&wXcMewF-NBdU`|`KYYjU;Q^d;1a5d5 zJp~2Dr>Q9^a-*u@YYIVNz3K$JNofniC6!U`k=fbVn-f{0dd9Ue>BDzoiUNrk9%yF3u~OIKp(v z*2#$~PNV)?ECfGTb6$2L8C!+K;Be~7%AxJ7Q~|3TD{zLqQk#YX23Ik#ybC+OPG+Z zg+;QMqWtElg0%DnThWV;9!X0x#srO#=O)H!_8dQ`!+oZ72 zFXp0J)$E*aqSR$zQP}Ac_9FYi1LpPhb+wvEwM@cLIQ#6=J-mYt-&!NV#QfHZ+t^dw z?f9*0WKa50A&d!Epx1`NY(DOQ@HpmUTF@kWn<}cbcdqUHiMb9Bg~p+3eep8x8|~+O;ug}z&s@2) z#nFiK$z~Cw)hci8yP(zmf!*r?Z{EC7G&a__8s_$G)6K)5J0fjGT9|Dgi)zYAO(($% zLK;lBj((J{V_jLR9vyWMxCPD+#L{&CAE~L*yN@q@`665%h{ZwxTRfg6ARwT-1Z`-Z ztIos2Lq$nWn8+^`m6g4en=6Ej#Uqg;SFHk0dQB=GWl<94P!UR~J(A{c@J7m12+Uay zPR`mK!|Ye>?PP$A-&bL9PFGmZT%8zev`LZm*uAz3w;iX!}ed-wh;2Z+CXR2o=vD|`{*XaO_+W>_UirnXuYFhihRLfhJ!=?%^#k5x;Qo~>#zV#n9@Q2wYf&s zk#nc_>-fXd#LQp}R2&p<(|ksg1m6yn;&{)VJqxtp50+bsAY*-w$uED1pY^Q-7aNe6 zn)i*j6Ae`XtBIDIuhNyl{mWC^S5yvn3XJj_T*6G!4r$7GjMCH&iN5z=(3Yl3&=$F_ z%pX@{FMPpL=R$7oACbBTH_W+{-aXEIvHgOdg?_+#gsjPE@0>k(%mD&rN{n3CV6Wp` z$;^I5hS|y)c+`|l&u-Pk^rMlCcjvtsP9J7-4C6ZCD%_J^=Cd%0!fBbZIHd)cRGl*p z0nV-azPzhrf8R;+g_;?Kb-E6%7~0Fm;hH%235w)OkRHcvb_IT zZxGq#PCL2R4psZR7Zy$z)GO7U)uJ_i<>_>(zC~jBMO@CL*Tv3GRt2*ZaOZ8ptjwCE zsn2pdt0UHd7m_&+%VTT4+PP?PM`FJ}Y=1*yt7lQ3sL^#_8^?=vT0;P$9@sw)2^z`oZQTvLy9Pf4_G9`}6c>}5k^1nvhvB8$z6 zUK*~4mm*8w>6OkM&?pttvt*zI_ijfc$+bJ+Gq3AfY8Eik5g2L5imzRYyIQWeNco-K z)qzW4#z<31TQuo9KmMu=x*BCxWt2{ydK4OUH1cji!G%ue@GSHhotRq_<%~6buBAE1 zoGUfxz#c}va!WndJNTH`xI-63*zXys2>L(hFd3qzHoD=Vi9cd9AaZ@*I}F=9Oc!B5 zh{k!jC?T!mg=^{c$(amEfeJzDj9ywu9MBSoWK+H9>fNAoM-hJn+{J^rO=IHyd=FiF zity%|LUQYF^Teo#L+l$oX2NV{`CV5xxPSj7LUB zFoIyr48fw+nBNa--;Zt_xm)F0i<=a{L~0R*pURostuK$#RMk-_P_hkyLXjvBC$tk{ I`v<)J7l;AeApigX literal 0 HcmV?d00001 diff --git a/assets/admin/public/images/header-layout/header_layout_3.png b/assets/admin/public/images/header-layout/header_layout_3.png new file mode 100644 index 0000000000000000000000000000000000000000..ecec00208691e68b11d8da8464e76ac031c73e88 GIT binary patch literal 21660 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 literal 0 HcmV?d00001 diff --git a/assets/admin/public/images/header-layout/mobile_header_layout_center.png b/assets/admin/public/images/header-layout/mobile_header_layout_center.png new file mode 100644 index 0000000000000000000000000000000000000000..6a7d68c3fdb43316d98b353d178cbf6d3ee9bcd1 GIT binary patch literal 19180 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 literal 0 HcmV?d00001 diff --git a/assets/admin/public/images/header-layout/mobile_header_layout_left.png b/assets/admin/public/images/header-layout/mobile_header_layout_left.png new file mode 100644 index 0000000000000000000000000000000000000000..3574ccc1e14515f1a99360d0495a84e3a27de2f6 GIT binary patch literal 18804 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 K2;)EekADFpnHzrq literal 0 HcmV?d00001 diff --git a/assets/admin/src/App.vue b/assets/admin/src/App.vue new file mode 100644 index 00000000..cbcd5788 --- /dev/null +++ b/assets/admin/src/App.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/assets/admin/src/api/modules/extensions.js b/assets/admin/src/api/modules/extensions.js new file mode 100644 index 00000000..36c98474 --- /dev/null +++ b/assets/admin/src/api/modules/extensions.js @@ -0,0 +1,15 @@ +export const extensionsApiMethods = { + /* ================================================================ + * 扩展&增强 (Extensions) + * ================================================================ */ + + testIpGeo(ip) { return this.request('/extensions/ip-test', { method: 'POST', data: { ip } }) }, + testContentAudit(text) { return this.request('/extensions/audit-test', { method: 'POST', data: { text } }) }, + + /* AI 辅助 */ + getAiStatus() { return this.request('/ai/status') }, + updateAiSettings(data) { return this.request('/ai/settings', { method: 'POST', data }) }, + generateSeo(data) { return this.request('/ai/seo-generate', { method: 'POST', data }) }, + suggestSummary(data) { return this.request('/ai/summary-suggest', { method: 'POST', data }) }, + suggestTags(data) { return this.request('/ai/tags-suggest', { method: 'POST', data }) }, +} diff --git a/assets/admin/src/api/modules/global.js b/assets/admin/src/api/modules/global.js new file mode 100644 index 00000000..f4095d35 --- /dev/null +++ b/assets/admin/src/api/modules/global.js @@ -0,0 +1,28 @@ +export const globalApiMethods = { + /* ================================================================ + * 全局&功能 (Global & Functions) + * ================================================================ */ + + getMailSettings() { return this.request('/settings/mail') }, + saveMailSettings(data) { return this.request('/settings/mail', { method: 'POST', body: JSON.stringify(data) }) }, + testMail(to) { return this.request('/mail/test', { method: 'POST', body: JSON.stringify({ to }) }) }, + + getFooterSettings() { return this.request('/settings/footer') }, + saveFooterSettings(data) { return this.request('/settings/footer', { method: 'POST', body: JSON.stringify(data) }) }, + + getFooterTabbarSettings() { return this.request('/settings/footer-tabbar') }, + saveFooterTabbarSettings(data) { return this.request('/settings/footer-tabbar', { method: 'POST', body: JSON.stringify(data) }) }, + reorderFooterTabbar(ids) { return this.request('/settings/footer-tabbar/reorder', { method: 'POST', body: JSON.stringify({ ids }) }) }, + + getFloatingButtons() { return this.request('/settings/floating-buttons') }, + saveFloatingButtons(data) { return this.request('/settings/floating-buttons', { method: 'POST', body: JSON.stringify(data) }) }, + reorderFloatingButtons(ids) { return this.request('/settings/floating-buttons/reorder', { method: 'POST', body: JSON.stringify({ ids }) }) }, + + getFriendLinks() { return this.request('/friend-links') }, + addFriendLink(data) { return this.request('/friend-links', { method: 'POST', body: JSON.stringify(data) }) }, + deleteFriendLink(id) { return this.request(`/friend-links/${id}`, { method: 'DELETE' }) }, + reorderFriendLinks(ids) { return this.request('/friend-links/reorder', { method: 'POST', body: JSON.stringify({ ids }) }) }, + + getCmsFeatures() { return this.request('/settings/cms-features') }, + saveCmsFeatures(cms_features) { return this.request('/settings/cms-features', { method: 'POST', body: JSON.stringify({ cms_features }) }) }, +} diff --git a/assets/admin/src/api/modules/index.js b/assets/admin/src/api/modules/index.js new file mode 100644 index 00000000..0c07bee0 --- /dev/null +++ b/assets/admin/src/api/modules/index.js @@ -0,0 +1,19 @@ +import { globalApiMethods } from './global.js' +import { pageDisplayApiMethods } from './page-display.js' +import { permissionsApiMethods } from './permissions.js' +import { usersApiMethods } from './users.js' +import { paymentApiMethods } from './payment.js' +import { extensionsApiMethods } from './extensions.js' +import { toolsApiMethods } from './tools.js' +import { schemaApiMethods } from './schema.js' + +export const apiMethods = { + ...globalApiMethods, + ...pageDisplayApiMethods, + ...permissionsApiMethods, + ...usersApiMethods, + ...paymentApiMethods, + ...extensionsApiMethods, + ...toolsApiMethods, + ...schemaApiMethods, +} diff --git a/assets/admin/src/api/modules/page-display.js b/assets/admin/src/api/modules/page-display.js new file mode 100644 index 00000000..998b912e --- /dev/null +++ b/assets/admin/src/api/modules/page-display.js @@ -0,0 +1,30 @@ +export const pageDisplayApiMethods = { + /* ================================================================ + * 页面&显示 (Page Display) + * ================================================================ */ + + getHomepageSettings() { return this.request('/settings/homepage') }, + saveHomepageSettings(data) { return this.request('/settings/homepage', { method: 'POST', body: JSON.stringify(data) }) }, + getHomepageCategories() { return this.request('/homepage/categories') }, + + getCategoryRecommendSettings() { return this.request('/settings/category-recommend') }, + saveCategoryRecommendSettings(data) { return this.request('/settings/category-recommend', { method: 'POST', body: JSON.stringify(data) }) }, + getCategories() { return this.request('/categories') }, + updateCategoryMeta(id, data) { return this.request(`/categories/${id}/meta`, { method: 'POST', body: JSON.stringify(data) }) }, + searchUsers(search = '', number = 20) { return this.request(`/users/search?search=${encodeURIComponent(search)}&number=${number}`) }, + + getBanners(kind = 'hero') { return this.request(`/banners?kind=${encodeURIComponent(kind)}`) }, + createBanner(data) { return this.request('/banners', { method: 'POST', body: JSON.stringify(data) }) }, + updateBanner(id, data) { return this.request(`/banners/${id}`, { method: 'PUT', body: JSON.stringify(data) }) }, + deleteBanner(id) { return this.request(`/banners/${id}`, { method: 'DELETE' }) }, + + getHeroes() { return this.getBanners('hero') }, + createHero(data) { return this.createBanner({ ...data, kind: 'hero' }) }, + updateHero(id, data) { return this.updateBanner(id, { ...data, kind: 'hero' }) }, + deleteHero(id) { return this.deleteBanner(id) }, + + getHomepageBanners() { return this.getBanners('banner') }, + createHomepageBanner(data) { return this.createBanner({ ...data, kind: 'banner' }) }, + updateHomepageBanner(id, data) { return this.updateBanner(id, { ...data, kind: 'banner' }) }, + deleteHomepageBanner(id) { return this.deleteBanner(id) }, +} diff --git a/assets/admin/src/api/modules/payment.js b/assets/admin/src/api/modules/payment.js new file mode 100644 index 00000000..0707c187 --- /dev/null +++ b/assets/admin/src/api/modules/payment.js @@ -0,0 +1,7 @@ +export const paymentApiMethods = { + /* ================================================================ + * 支付&付费 (Payment) + * ================================================================ */ + + getPaymentOverview() { return this.request('/payment/overview') }, +} diff --git a/assets/admin/src/api/modules/schema.js b/assets/admin/src/api/modules/schema.js new file mode 100644 index 00000000..98789349 --- /dev/null +++ b/assets/admin/src/api/modules/schema.js @@ -0,0 +1,17 @@ +export const schemaApiMethods = { + getSchema(domain) { return this.request(`/settings/schema/${domain}`) }, + getSchemaPage(domain, section) { + return this.request(`/settings/schema/${this.normalizeDomainKey(domain)}/${encodeURIComponent(section)}`) + }, + saveSchemaPage(domain, section, data, version = '1.0.0') { + return this.request(`/settings/schema/${this.normalizeDomainKey(domain)}/${encodeURIComponent(section)}`, { + method: 'POST', + body: JSON.stringify({ + domain: this.normalizeDomainKey(domain), + section, + data, + version, + }), + }) + }, +} diff --git a/assets/admin/src/api/modules/tools.js b/assets/admin/src/api/modules/tools.js new file mode 100644 index 00000000..7da837bf --- /dev/null +++ b/assets/admin/src/api/modules/tools.js @@ -0,0 +1,27 @@ +export const toolsApiMethods = { + /* ================================================================ + * 扩展&增强 / 备份&导入 (Extensions & Tools) + * ================================================================ */ + + getHealthEmailVerification() { return this.request('/health/email-verification') }, + getUpdateInfo() { return this.request('/tools/update-info') }, + detectUpdate() { return this.request('/tools/update-check', { method: 'POST', body: JSON.stringify({}) }) }, + dismissUpdate(version) { return this.request('/tools/update-dismiss', { method: 'POST', body: JSON.stringify({ version }) }) }, + + getEmailRollbackUsers() { return this.request('/email-rollback/users') }, + executeEmailRollback(id) { return this.request(`/email-rollback/users/${id}/rollback`, { method: 'POST' }) }, + + /* ================================================================ + * Settings Management (Import / Export / Reset / Health / Schema) + * ================================================================ */ + + exportSettings() { return this.request('/settings/export') }, + importSettings(data) { return this.request('/settings/import', { method: 'POST', body: JSON.stringify(data) }) }, + listSettingBackups() { return this.request('/settings/backups') }, + createSettingBackup(data = {}) { return this.request('/settings/backups', { method: 'POST', body: JSON.stringify(data) }) }, + restoreSettingBackup(id) { return this.request(`/settings/backups/${id}/restore`, { method: 'POST' }) }, + deleteSettingBackup(id) { return this.request(`/settings/backups/${id}`, { method: 'DELETE' }) }, + resetDomain(domain) { return this.request(`/settings/reset/${domain}`, { method: 'POST' }) }, + resetAllSettings() { return this.request('/settings/reset-all', { method: 'POST' }) }, + getHealthCheck() { return this.request('/settings/health') }, +} diff --git a/assets/admin/src/components/DeferredPage.vue b/assets/admin/src/components/DeferredPage.vue new file mode 100644 index 00000000..10f5e1d6 --- /dev/null +++ b/assets/admin/src/components/DeferredPage.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/assets/admin/src/components/EmptyState.vue b/assets/admin/src/components/EmptyState.vue new file mode 100644 index 00000000..f092d1c4 --- /dev/null +++ b/assets/admin/src/components/EmptyState.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/assets/admin/src/components/MobileTopBar.vue b/assets/admin/src/components/MobileTopBar.vue new file mode 100644 index 00000000..09d0c699 --- /dev/null +++ b/assets/admin/src/components/MobileTopBar.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/assets/admin/src/components/OAuthProviderForm.vue b/assets/admin/src/components/OAuthProviderForm.vue new file mode 100644 index 00000000..db6f1b32 --- /dev/null +++ b/assets/admin/src/components/OAuthProviderForm.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/assets/admin/src/components/PageHeader.vue b/assets/admin/src/components/PageHeader.vue new file mode 100644 index 00000000..dcd8e6b9 --- /dev/null +++ b/assets/admin/src/components/PageHeader.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/assets/admin/src/components/captcha/AliyunConfig.vue b/assets/admin/src/components/captcha/AliyunConfig.vue new file mode 100644 index 00000000..f216b1ac --- /dev/null +++ b/assets/admin/src/components/captcha/AliyunConfig.vue @@ -0,0 +1,66 @@ + + + diff --git a/assets/admin/src/components/captcha/CloudflareConfig.vue b/assets/admin/src/components/captcha/CloudflareConfig.vue new file mode 100644 index 00000000..38b3bf9f --- /dev/null +++ b/assets/admin/src/components/captcha/CloudflareConfig.vue @@ -0,0 +1,44 @@ + + + diff --git a/assets/admin/src/components/captcha/GeetestConfig.vue b/assets/admin/src/components/captcha/GeetestConfig.vue new file mode 100644 index 00000000..caf96c8f --- /dev/null +++ b/assets/admin/src/components/captcha/GeetestConfig.vue @@ -0,0 +1,25 @@ + + + diff --git a/assets/admin/src/components/captcha/TencentConfig.vue b/assets/admin/src/components/captcha/TencentConfig.vue new file mode 100644 index 00000000..9effbfb5 --- /dev/null +++ b/assets/admin/src/components/captcha/TencentConfig.vue @@ -0,0 +1,42 @@ + + + diff --git a/assets/admin/src/components/form/AksArrayTable.vue b/assets/admin/src/components/form/AksArrayTable.vue new file mode 100644 index 00000000..d60de64f --- /dev/null +++ b/assets/admin/src/components/form/AksArrayTable.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/assets/admin/src/components/form/AksCodeEditor.vue b/assets/admin/src/components/form/AksCodeEditor.vue new file mode 100644 index 00000000..07d99bf1 --- /dev/null +++ b/assets/admin/src/components/form/AksCodeEditor.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/assets/admin/src/components/form/AksField.vue b/assets/admin/src/components/form/AksField.vue new file mode 100644 index 00000000..b395a720 --- /dev/null +++ b/assets/admin/src/components/form/AksField.vue @@ -0,0 +1,1156 @@ + + + + + diff --git a/assets/admin/src/components/form/AksFieldGroup.vue b/assets/admin/src/components/form/AksFieldGroup.vue new file mode 100644 index 00000000..f6da1023 --- /dev/null +++ b/assets/admin/src/components/form/AksFieldGroup.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/assets/admin/src/components/form/AksFormRenderer.vue b/assets/admin/src/components/form/AksFormRenderer.vue new file mode 100644 index 00000000..5a1b7bf4 --- /dev/null +++ b/assets/admin/src/components/form/AksFormRenderer.vue @@ -0,0 +1,408 @@ + + + + + diff --git a/assets/admin/src/components/form/AksMediaField.vue b/assets/admin/src/components/form/AksMediaField.vue new file mode 100644 index 00000000..b51bdb41 --- /dev/null +++ b/assets/admin/src/components/form/AksMediaField.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/assets/admin/src/components/form/AksRepeaterField.vue b/assets/admin/src/components/form/AksRepeaterField.vue new file mode 100644 index 00000000..ad61ae36 --- /dev/null +++ b/assets/admin/src/components/form/AksRepeaterField.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/assets/admin/src/components/form/FaIcon.vue b/assets/admin/src/components/form/FaIcon.vue new file mode 100644 index 00000000..21deaab1 --- /dev/null +++ b/assets/admin/src/components/form/FaIcon.vue @@ -0,0 +1,67 @@ + + + diff --git a/assets/admin/src/components/form/FaIconPicker.vue b/assets/admin/src/components/form/FaIconPicker.vue new file mode 100644 index 00000000..16803e18 --- /dev/null +++ b/assets/admin/src/components/form/FaIconPicker.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/assets/admin/src/components/homepage/ModuleCanvas.vue b/assets/admin/src/components/homepage/ModuleCanvas.vue new file mode 100644 index 00000000..e9863814 --- /dev/null +++ b/assets/admin/src/components/homepage/ModuleCanvas.vue @@ -0,0 +1,313 @@ + + + + + + + diff --git a/assets/admin/src/components/homepage/ModuleInspector.vue b/assets/admin/src/components/homepage/ModuleInspector.vue new file mode 100644 index 00000000..88528079 --- /dev/null +++ b/assets/admin/src/components/homepage/ModuleInspector.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/assets/admin/src/components/homepage/ModulePresetPicker.vue b/assets/admin/src/components/homepage/ModulePresetPicker.vue new file mode 100644 index 00000000..e15f1249 --- /dev/null +++ b/assets/admin/src/components/homepage/ModulePresetPicker.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/assets/admin/src/composables/useDirtyState.js b/assets/admin/src/composables/useDirtyState.js new file mode 100644 index 00000000..e3d11f76 --- /dev/null +++ b/assets/admin/src/composables/useDirtyState.js @@ -0,0 +1,50 @@ +import { ref, watch, computed } from 'vue' + +/** + * useDirtyState — tracks whether a form has unsaved changes. + * + * Usage: + * const { isDirty, markDirty, markClean, confirmLeave } = useDirtyState() + * + * Call markDirty() whenever form data changes from its initial state. + * Call markClean() after a successful save. + * confirmLeave() returns a boolean — false means the user cancelled navigation. + */ +export function useDirtyState() { + const dirty = ref(false) + const saveCount = ref(0) + + function markDirty() { + dirty.value = true + } + + function markClean() { + dirty.value = false + saveCount.value++ + } + + /** Reset dirty without incrementing save count (e.g. on initial load) */ + function reset() { + dirty.value = false + } + + /** + * Confirm navigation away from dirty form. + * Returns true if safe to leave, false if user cancelled. + */ + function confirmLeave() { + if (!dirty.value) return true + return window.confirm('您有未保存的更改,确定要离开此页面吗?') + } + + const isDirty = computed(() => dirty.value) + + return { + isDirty, + markDirty, + markClean, + reset, + confirmLeave, + saveCount, + } +} diff --git a/assets/admin/src/composables/useSectionForm.js b/assets/admin/src/composables/useSectionForm.js new file mode 100644 index 00000000..9d5a3774 --- /dev/null +++ b/assets/admin/src/composables/useSectionForm.js @@ -0,0 +1,140 @@ +import { ref, watch, onMounted, onBeforeUnmount, computed } from 'vue' +import { useDirtyState } from './useDirtyState' + +/** + * useSectionForm — standard composable for schema-driven admin settings pages. + * + * Encapsulates: data loading, dirty tracking, save/reload logic, and loading states. + * + * Usage: + * const { + * formData, loading, saving, loadError, + * dirty, load, save, reset, + * } = useSectionForm({ + * fetch: () => api.getBasicSettings(), + * submit: (data) => api.saveBasicSettings(data), + * defaults: { comment_enabled: true, lazy_load: true }, + * }) + * + * @param {Object} options + * @param {Function} options.fetch — async function returning current settings + * @param {Function} options.submit — async function accepting form data to save + * @param {Object} options.defaults — default values merged under fetched data + * @param {Function} [options.onLoaded] — callback after data is loaded + * @param {Function} [options.onSaved] — callback after successful save + * @param {Function} [options.transform] — transform fetched data before applying + */ +export function useSectionForm(options) { + const { + fetch: fetchData, + submit: submitData, + defaults = {}, + onLoaded, + onSaved, + transform, + } = options + + const formData = ref({ ...defaults }) + const initialSnapshot = ref('') + const loading = ref(false) + const saving = ref(false) + const loadError = ref(null) + + const { isDirty, markDirty, markClean, reset: resetDirty, confirmLeave } = useDirtyState() + + /** Snapshot the current form state for dirty comparison */ + function snapshot() { + initialSnapshot.value = JSON.stringify(formData.value) + } + + /** Check if form data differs from snapshot */ + function checkDirty() { + const current = JSON.stringify(formData.value) + if (current !== initialSnapshot.value) { + markDirty() + } else { + resetDirty() + } + } + + /** Deep watch form data for dirty tracking */ + let watcherHandle = null + + function startWatching() { + if (watcherHandle) watcherHandle() + watcherHandle = watch(formData, checkDirty, { deep: true }) + } + + /** Load settings from API */ + async function load() { + loading.value = true + loadError.value = null + try { + const data = await fetchData() + const raw = data?.data ?? data ?? {} + const merged = { ...defaults, ...(transform ? transform(raw) : raw) } + formData.value = merged + snapshot() + startWatching() + onLoaded?.(merged) + } catch (err) { + loadError.value = err?.message || '加载失败' + console.error('[useSectionForm] load error:', err) + } finally { + loading.value = false + } + } + + /** Save current form data */ + async function save() { + saving.value = true + try { + await submitData(formData.value) + snapshot() + markClean() + onSaved?.(formData.value) + } catch (err) { + console.error('[useSectionForm] save error:', err) + throw err // let caller handle the error display + } finally { + saving.value = false + } + } + + /** Reload form data from API (discards changes) */ + async function reload() { + if (watcherHandle) watcherHandle() + resetDirty() + await load() + } + + /** Reset form to defaults */ + function resetToDefaults() { + if (watcherHandle) watcherHandle() + formData.value = { ...defaults } + snapshot() + startWatching() + resetDirty() + } + + // Auto-load on mount + onMounted(load) + + // Cleanup watcher + onBeforeUnmount(() => { + if (watcherHandle) watcherHandle() + }) + + return { + formData, + loading, + saving, + loadError, + dirty: isDirty, + load, + save, + reload, + resetToDefaults, + confirmLeave, + } +} diff --git a/assets/admin/src/constants/admin-sections.js b/assets/admin/src/constants/admin-sections.js new file mode 100644 index 00000000..ceaffb35 --- /dev/null +++ b/assets/admin/src/constants/admin-sections.js @@ -0,0 +1,10 @@ +/** + * Admin sections — central menu and route registry. + * + * Sidebar menu, route metadata, schema domain mapping, and deferred-page + * planning details are all registered in domain modules to avoid drift between + * the navigation tree and router definitions while keeping each file focused. + */ + +export { ADMIN_SECTIONS, getAdminSections, getAllAdminPages } from './sections/index.js' +export { renderFaIcon, customPage, schemaPage, deferredPage } from './sections/helpers.js' diff --git a/assets/admin/src/constants/sections/docs.js b/assets/admin/src/constants/sections/docs.js new file mode 100644 index 00000000..fb6af1dd --- /dev/null +++ b/assets/admin/src/constants/sections/docs.js @@ -0,0 +1,20 @@ +import { renderFaIcon, customPage } from './helpers' + +export const docsSection = { + key: 'tools-compat', + label: '兼容&工具', + icon: renderFaIcon('fas fa-toolbox'), + route: '/tools', + hidden: true, + children: [ + customPage({ + key: 'docs-dashboard', + name: 'DocsDashboard', + label: '文档中心', + route: '/docs', + component: 'DocsPage', + title: '文档中心', + desc: '更新日志、文档链接和发布说明。', + }), + ], +} diff --git a/assets/admin/src/constants/sections/global.js b/assets/admin/src/constants/sections/global.js new file mode 100644 index 00000000..3e39dcc2 --- /dev/null +++ b/assets/admin/src/constants/sections/global.js @@ -0,0 +1,108 @@ +import { renderFaIcon, schemaPage, customPage } from './helpers' + +export const globalSection = { + key: 'global', + label: '全局&功能', + icon: renderFaIcon('fas fa-globe'), + route: '/global', + children: [ + schemaPage({ + key: 'global-common', + name: 'GlobalCommon', + label: '常用功能', + route: '/global/common', + title: '常用功能', + desc: '评论、性能、头像源与备案等全局基础配置。', + canonicalDomain: 'global', + section: 'common', + }), + schemaPage({ + key: 'global-logo', + name: 'GlobalLogo', + label: 'LOGO图像', + route: '/global/logo', + title: 'LOGO图像', + desc: '管理站点 Logo、站点图标和品牌标识。', + canonicalDomain: 'global', + section: 'logo', + }), + schemaPage({ + key: 'global-seo', + name: 'GlobalSeo', + label: 'SEO优化', + route: '/global/seo', + title: 'SEO优化', + desc: '站点标题、描述、关键词、Sitemap 等搜索引擎优化设置。', + canonicalDomain: 'global', + section: 'seo', + }), + schemaPage({ + key: 'global-layout', + name: 'GlobalLayout', + label: '显示&布局', + route: '/global/layout', + title: '显示&布局', + desc: '全局布局风格、颜色方案、字体设置。', + canonicalDomain: 'global', + section: 'layout', + }), + schemaPage({ + key: 'global-search', + name: 'GlobalSearch', + label: '搜索功能', + route: '/global/search', + title: '搜索功能', + desc: '搜索页面样式、搜索建议、热门搜索配置。', + canonicalDomain: 'global', + section: 'search', + }), + customPage({ + key: 'global-tabbar', + name: 'GlobalTabbar', + label: '手机底部Tab', + route: '/global/tabbar', + title: '手机底部Tab', + desc: '移动端底部导航栏配置。', + component: 'FooterTabbarSettings', + }), + customPage({ + key: 'global-floating', + name: 'GlobalFloating', + label: '右侧悬浮按钮', + route: '/global/floating', + title: '右侧悬浮按钮', + desc: '页面右侧浮动按钮的显示与配置。', + component: 'FloatingButtonsSettings', + }), + schemaPage({ + key: 'global-i18n', + name: 'GlobalI18n', + label: '多语言翻译', + route: '/global/i18n', + title: '多语言翻译', + desc: '配置站点语言、语言切换器和翻译管理。', + canonicalDomain: 'global', + section: 'i18n', + }), + schemaPage({ + key: 'global-email', + name: 'GlobalEmail', + label: 'Email邮件', + route: '/global/email', + title: 'Email邮件', + desc: 'SMTP 发信配置、邮件模板与测试。', + canonicalDomain: 'global', + section: 'email', + }), + schemaPage({ + key: 'global-custom-code', + name: 'GlobalCustomCode', + label: '自定义代码', + route: '/global/custom-code', + title: '自定义代码', + desc: '自定义 CSS、JS 代码注入。', + canonicalDomain: 'global', + section: 'custom-code', + }), + ], +} diff --git a/assets/admin/src/constants/sections/helpers.js b/assets/admin/src/constants/sections/helpers.js new file mode 100644 index 00000000..3c22f3f1 --- /dev/null +++ b/assets/admin/src/constants/sections/helpers.js @@ -0,0 +1,39 @@ +import { h } from 'vue' + +export function renderFaIcon(faClass) { + return () => h('i', { class: faClass, style: 'font-size:18px;width:18px;text-align:center' }) +} + +function sectionPage(config) { + return { + status: 'available', + component: 'SchemaPage', + canonicalDomain: '', + section: '', + phase: '', + dependsOn: [], + reason: '', + features: [], + iconClass: '', + ...config, + } +} + +export function customPage(config) { + return sectionPage(config) +} + +export function schemaPage(config) { + return sectionPage({ + component: 'SchemaPage', + ...config, + }) +} + +export function deferredPage(config) { + return sectionPage({ + component: 'DeferredPage', + status: 'deferred', + ...config, + }) +} diff --git a/assets/admin/src/constants/sections/post-list.js b/assets/admin/src/constants/sections/post-list.js new file mode 100644 index 00000000..ac70e174 --- /dev/null +++ b/assets/admin/src/constants/sections/post-list.js @@ -0,0 +1,92 @@ +import { renderFaIcon, schemaPage, customPage } from './helpers' + +export const postListSection = { + key: 'post-list', + label: '文章&列表', + icon: renderFaIcon('fas fa-file-alt'), + route: '/post-list', + children: [ + schemaPage({ + key: 'post-list-list', + name: 'PostListList', + label: '文章列表', + route: '/post-list/list', + title: '文章列表', + desc: '列表样式、元信息开关、分页方式配置。', + canonicalDomain: 'post_list', + section: 'list', + }), + schemaPage({ + key: 'post-list-thumb', + name: 'PostListThumb', + label: '列表缩略图', + route: '/post-list/thumb', + title: '列表缩略图', + desc: '缩略图策略、封面回退、多图模式配置。', + canonicalDomain: 'post_list', + section: 'thumb', + }), + schemaPage({ + key: 'post-list-page', + name: 'PostListPage', + label: '文章页面', + route: '/post-list/page', + component: 'PostPageSettings', + title: '文章页面', + desc: '文章页布局、目录树、灯箱、版权区配置。', + canonicalDomain: 'post_list', + section: 'page', + }), + schemaPage({ + key: 'post-list-filter', + name: 'PostListFilter', + label: '高级筛选', + route: '/post-list/filter', + title: '高级筛选', + desc: '筛选栏、排序方式、分类筛选配置。', + canonicalDomain: 'post_list', + section: 'filter', + }), + schemaPage({ + key: 'post-list-feature', + name: 'PostListFeature', + label: '文章功能', + route: '/post-list/feature', + title: '文章功能', + desc: '收藏、分享、阅读进度、相关推荐等功能开关。', + canonicalDomain: 'post_list', + section: 'feature', + }), + schemaPage({ + key: 'post-list-share', + name: 'PostListShare', + label: '分享设置', + route: '/post-list/share', + title: '分享设置', + desc: '配置微信分享凭据、海报分享和社交分享选项。', + canonicalDomain: 'post_list', + section: 'share', + }), + schemaPage({ + key: 'post-list-comment', + name: 'PostListComment', + label: '评论设置', + route: '/post-list/comment', + component: 'CommentSettings', + title: '评论设置', + desc: '评论排序、楼层展示、登录限制、回复限制等配置。', + canonicalDomain: 'post_list', + section: 'comment', + }), + schemaPage({ + key: 'post-list-submit', + name: 'PostListSubmit', + label: '前台投稿', + route: '/post-list/submit', + title: '前台投稿', + desc: '配置前台投稿表单字段、审核流程、附件权限和封面策略。', + canonicalDomain: 'post_list', + section: 'submit', + }), + ], +} diff --git a/assets/admin/src/constants/sections/theme-info.js b/assets/admin/src/constants/sections/theme-info.js new file mode 100644 index 00000000..f7b70eaa --- /dev/null +++ b/assets/admin/src/constants/sections/theme-info.js @@ -0,0 +1,29 @@ +import { renderFaIcon, customPage } from './helpers' + +export const themeInfoSection = { + key: 'tools-compat', + label: '兼容&工具', + icon: renderFaIcon('fas fa-toolbox'), + route: '/tools', + hidden: true, + children: [ + customPage({ + key: 'theme-info-dashboard', + name: 'ThemeInfoDashboard', + label: '主题信息', + route: '/theme-info', + component: 'ThemeInfoPage', + title: '主题信息', + desc: '主题版本、授权信息和帮助入口。', + }), + customPage({ + key: 'theme-authorization', + name: 'ThemeAuthorization', + label: '主题授权', + route: '/theme-info/authorization', + component: 'ThemeAuthorizationPage', + title: '主题授权', + desc: '当前版本的授权策略与产品决策说明。', + }), + ], +} diff --git a/assets/admin/src/constants/settings-defaults.js b/assets/admin/src/constants/settings-defaults.js new file mode 100644 index 00000000..9c4b5b3c --- /dev/null +++ b/assets/admin/src/constants/settings-defaults.js @@ -0,0 +1,28 @@ +import { ALL_SCHEMAS, CANONICAL_SCHEMA_DOMAINS, normalizeSchemaDomain } from '@/schema/index.js' + +function cloneDefaults(defaults = {}) { + return JSON.parse(JSON.stringify(defaults)) +} + +export function getSectionDefaults(domain, section) { + const domainKey = normalizeSchemaDomain(domain) + const sectionSchema = ALL_SCHEMAS[domainKey]?.[section] + return cloneDefaults(sectionSchema?.defaults || {}) +} + +export function getDomainDefaults(domain) { + const domainKey = normalizeSchemaDomain(domain) + const sections = Object.values(ALL_SCHEMAS[domainKey] || {}) + + return sections.reduce((defaults, schema) => { + return { + ...defaults, + ...cloneDefaults(schema?.defaults || {}), + } + }, {}) +} + +export const SETTINGS_DEFAULTS_BY_DOMAIN = CANONICAL_SCHEMA_DOMAINS.reduce((defaults, domain) => { + defaults[domain] = getDomainDefaults(domain) + return defaults +}, {}) diff --git a/assets/admin/src/main.js b/assets/admin/src/main.js new file mode 100644 index 00000000..a8c872aa --- /dev/null +++ b/assets/admin/src/main.js @@ -0,0 +1,101 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import NaiveUI from 'naive-ui' +import App from './App.vue' +import router from './router' +import './style.css' +import { styleMountTarget } from './style-target' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.use(NaiveUI) + +// 全局错误处理 +app.config.errorHandler = (err, vm, info) => { + console.error('[AKSRT] 全局错误:', err, info) +} + +// 兜底:立即显示加载状态(防止白屏) +function showFallback(el) { + el.innerHTML = '
' + + '

AKSRT 正在加载...

' + + '

如果长时间停留在此页面,请按 F12 查看控制台错误

' + + '
' +} +showFallback(document.getElementById('aksrt-admin-app') || document.body.appendChild( + Object.assign(document.createElement('div'), { id: 'aksrt-admin-app' }) +)) + +/** + * 获取或创建一个干净的挂载容器 + * 核心问题:WordPress 后台的 jQuery/脚本会在页面加载后持续操作 DOM, + * 导致 PHP 输出的
可能被外部修改或其兄弟节点被移除, + * Vue patch 时遍历到 null 节点 → nextSibling 报错。 + * + * 解决方案:在挂载前一刻,动态创建一个全新的容器元素替换旧的, + * 确保该节点及其父级在 Vue 接管前不被任何外部脚本污染。 + */ +function getCleanContainer() { + const existing = document.getElementById('aksrt-admin-app') + if (!existing) { + // 极端情况:PHP 没有输出容器,手动创建 + const fresh = document.createElement('div') + fresh.id = 'aksrt-admin-app' + document.body.appendChild(fresh) + return fresh + } + + // 创建全新容器替换旧容器 + // 这确保新容器没有 WP 脚本留下的任何副作用(事件监听、属性等) + const parent = existing.parentNode + if (!parent) { + // 旧容器已脱离文档树,重新创建 + const fresh = document.createElement('div') + fresh.id = 'aksrt-admin-app' + document.body.appendChild(fresh) + return fresh + } + + const fresh = document.createElement('div') + fresh.id = 'aksrt-admin-app' + + // 将旧容器的位置信息转移给新容器(保留 fallback 内容) + fresh.innerHTML = existing.innerHTML || '' + parent.replaceChild(fresh, existing) + + return fresh +} + +/** + * 显示错误信息 + */ +function showError(container, err) { + container.innerHTML = '
' + + '

页面加载失败

' + + '

' + (err.message || String(err)) + '

' + + '' + + '
' +} + +// 等待 router 就绪后延迟挂载 +router.isReady().then(() => { + // 三重 rAF:确保 load-scripts.php 加载的所有 jQuery 插件完成初始化和 DOM 操作 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + try { + const container = getCleanContainer() + // 关键:在挂载前设置样式注入目标,确保 NaiveUI 组件渲染时目标已就绪 + styleMountTarget.value = container + app.mount(container) + } catch (err) { + console.error('[AKSRT] Vue 挂载失败:', err) + showError(getCleanContainer(), err) + } + }) + }) + }) +}).catch((err) => { + console.error('[AKSRT] Router 初始化失败:', err) +}) diff --git a/assets/admin/src/pages/SchemaPage.vue b/assets/admin/src/pages/SchemaPage.vue new file mode 100644 index 00000000..8ded7c11 --- /dev/null +++ b/assets/admin/src/pages/SchemaPage.vue @@ -0,0 +1,508 @@ + + + + + diff --git a/assets/admin/src/pages/content/BannerManager.vue b/assets/admin/src/pages/content/BannerManager.vue new file mode 100644 index 00000000..38e2d7bb --- /dev/null +++ b/assets/admin/src/pages/content/BannerManager.vue @@ -0,0 +1,403 @@ + + + + + diff --git a/assets/admin/src/pages/content/FooterSettings.vue b/assets/admin/src/pages/content/FooterSettings.vue new file mode 100644 index 00000000..0fe3c40d --- /dev/null +++ b/assets/admin/src/pages/content/FooterSettings.vue @@ -0,0 +1,986 @@ + + + + + diff --git a/assets/admin/src/pages/content/HeroManager.vue b/assets/admin/src/pages/content/HeroManager.vue new file mode 100644 index 00000000..23e1779b --- /dev/null +++ b/assets/admin/src/pages/content/HeroManager.vue @@ -0,0 +1,518 @@ + + + + + diff --git a/assets/admin/src/pages/content/NotificationCenterSettings.vue b/assets/admin/src/pages/content/NotificationCenterSettings.vue new file mode 100644 index 00000000..e108dfcf --- /dev/null +++ b/assets/admin/src/pages/content/NotificationCenterSettings.vue @@ -0,0 +1,155 @@ + + + diff --git a/assets/admin/src/pages/extensions/ExtensionAI.vue b/assets/admin/src/pages/extensions/ExtensionAI.vue new file mode 100644 index 00000000..7205b031 --- /dev/null +++ b/assets/admin/src/pages/extensions/ExtensionAI.vue @@ -0,0 +1,316 @@ + + + + + diff --git a/assets/admin/src/pages/extensions/ExtensionTools.vue b/assets/admin/src/pages/extensions/ExtensionTools.vue new file mode 100644 index 00000000..4d9d7598 --- /dev/null +++ b/assets/admin/src/pages/extensions/ExtensionTools.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/assets/admin/src/pages/extensions/ExtensionsSectionPage.vue b/assets/admin/src/pages/extensions/ExtensionsSectionPage.vue new file mode 100644 index 00000000..e31e033c --- /dev/null +++ b/assets/admin/src/pages/extensions/ExtensionsSectionPage.vue @@ -0,0 +1,39 @@ + + + diff --git a/assets/admin/src/pages/payment/PaymentChargeCard.vue b/assets/admin/src/pages/payment/PaymentChargeCard.vue new file mode 100644 index 00000000..c9b0c19d --- /dev/null +++ b/assets/admin/src/pages/payment/PaymentChargeCard.vue @@ -0,0 +1,530 @@ + + + + + diff --git a/assets/admin/src/pages/payment/PaymentSectionPage.vue b/assets/admin/src/pages/payment/PaymentSectionPage.vue new file mode 100644 index 00000000..83cbbd75 --- /dev/null +++ b/assets/admin/src/pages/payment/PaymentSectionPage.vue @@ -0,0 +1,103 @@ + + + diff --git a/assets/admin/src/pages/payment/components/ChargeCardDetailDrawer.vue b/assets/admin/src/pages/payment/components/ChargeCardDetailDrawer.vue new file mode 100644 index 00000000..552ade6d --- /dev/null +++ b/assets/admin/src/pages/payment/components/ChargeCardDetailDrawer.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/assets/admin/src/pages/payment/components/RebateDetailDrawer.vue b/assets/admin/src/pages/payment/components/RebateDetailDrawer.vue new file mode 100644 index 00000000..a61b2596 --- /dev/null +++ b/assets/admin/src/pages/payment/components/RebateDetailDrawer.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/assets/admin/src/pages/placeholder/NativeOpsRedirect.vue b/assets/admin/src/pages/placeholder/NativeOpsRedirect.vue new file mode 100644 index 00000000..37894db6 --- /dev/null +++ b/assets/admin/src/pages/placeholder/NativeOpsRedirect.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/assets/admin/src/pages/post-list/CommentSettings.vue b/assets/admin/src/pages/post-list/CommentSettings.vue new file mode 100644 index 00000000..815ec391 --- /dev/null +++ b/assets/admin/src/pages/post-list/CommentSettings.vue @@ -0,0 +1,7 @@ + + + diff --git a/assets/admin/src/pages/post-list/PostPageSettings.vue b/assets/admin/src/pages/post-list/PostPageSettings.vue new file mode 100644 index 00000000..8d901ac4 --- /dev/null +++ b/assets/admin/src/pages/post-list/PostPageSettings.vue @@ -0,0 +1,7 @@ + + + diff --git a/assets/admin/src/pages/theme/ThemeAuthorizationPage.vue b/assets/admin/src/pages/theme/ThemeAuthorizationPage.vue new file mode 100644 index 00000000..79ea0c6f --- /dev/null +++ b/assets/admin/src/pages/theme/ThemeAuthorizationPage.vue @@ -0,0 +1,42 @@ + + + diff --git a/assets/admin/src/pages/theme/ThemeInfoPage.vue b/assets/admin/src/pages/theme/ThemeInfoPage.vue new file mode 100644 index 00000000..50899ed3 --- /dev/null +++ b/assets/admin/src/pages/theme/ThemeInfoPage.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/assets/admin/src/pages/tools/ToolsBackup.vue b/assets/admin/src/pages/tools/ToolsBackup.vue new file mode 100644 index 00000000..662207fa --- /dev/null +++ b/assets/admin/src/pages/tools/ToolsBackup.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/assets/admin/src/pages/tools/ToolsEmailRollback.vue b/assets/admin/src/pages/tools/ToolsEmailRollback.vue new file mode 100644 index 00000000..974d00a0 --- /dev/null +++ b/assets/admin/src/pages/tools/ToolsEmailRollback.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/assets/admin/src/pages/tools/ToolsHealth.vue b/assets/admin/src/pages/tools/ToolsHealth.vue new file mode 100644 index 00000000..7662bf9c --- /dev/null +++ b/assets/admin/src/pages/tools/ToolsHealth.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/assets/admin/src/pages/tools/ToolsImportExport.vue b/assets/admin/src/pages/tools/ToolsImportExport.vue new file mode 100644 index 00000000..79e91023 --- /dev/null +++ b/assets/admin/src/pages/tools/ToolsImportExport.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/assets/admin/src/pages/users/UsersBadge.vue b/assets/admin/src/pages/users/UsersBadge.vue new file mode 100644 index 00000000..cc05e113 --- /dev/null +++ b/assets/admin/src/pages/users/UsersBadge.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/assets/admin/src/pages/users/UsersBan.vue b/assets/admin/src/pages/users/UsersBan.vue new file mode 100644 index 00000000..89c3caba --- /dev/null +++ b/assets/admin/src/pages/users/UsersBan.vue @@ -0,0 +1,210 @@ + + + diff --git a/assets/admin/src/pages/users/UsersLevel.vue b/assets/admin/src/pages/users/UsersLevel.vue new file mode 100644 index 00000000..def142e8 --- /dev/null +++ b/assets/admin/src/pages/users/UsersLevel.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/assets/admin/src/pages/users/UsersMessages.vue b/assets/admin/src/pages/users/UsersMessages.vue new file mode 100644 index 00000000..5aa866eb --- /dev/null +++ b/assets/admin/src/pages/users/UsersMessages.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/assets/admin/src/pages/users/UsersReport.vue b/assets/admin/src/pages/users/UsersReport.vue new file mode 100644 index 00000000..89e01d1d --- /dev/null +++ b/assets/admin/src/pages/users/UsersReport.vue @@ -0,0 +1,357 @@ + + + + + diff --git a/assets/admin/src/pages/users/UsersSectionPage.vue b/assets/admin/src/pages/users/UsersSectionPage.vue new file mode 100644 index 00000000..c812af04 --- /dev/null +++ b/assets/admin/src/pages/users/UsersSectionPage.vue @@ -0,0 +1,40 @@ + + + diff --git a/assets/admin/src/pages/users/components/ReportDetailDrawer.vue b/assets/admin/src/pages/users/components/ReportDetailDrawer.vue new file mode 100644 index 00000000..89232c3c --- /dev/null +++ b/assets/admin/src/pages/users/components/ReportDetailDrawer.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/assets/admin/src/router/component-loaders/payment.js b/assets/admin/src/router/component-loaders/payment.js new file mode 100644 index 00000000..bef47b42 --- /dev/null +++ b/assets/admin/src/router/component-loaders/payment.js @@ -0,0 +1,3 @@ +export const paymentPageLoaders = { + PaymentGateway: () => import('@/pages/payment/PaymentGateway.vue'), +} diff --git a/assets/admin/src/router/component-loaders/tools.js b/assets/admin/src/router/component-loaders/tools.js new file mode 100644 index 00000000..257fbf4d --- /dev/null +++ b/assets/admin/src/router/component-loaders/tools.js @@ -0,0 +1,6 @@ +export const toolsPageLoaders = { + ToolsImportExport: () => import('@/pages/tools/ToolsImportExport.vue'), + ToolsHealth: () => import('@/pages/tools/ToolsHealth.vue'), + ToolsEmailRollback: () => import('@/pages/tools/ToolsEmailRollback.vue'), + ToolsUpdate: () => import('@/pages/tools/ToolsUpdate.vue'), +} diff --git a/assets/admin/src/schema/sections/extensions/ip.js b/assets/admin/src/schema/sections/extensions/ip.js new file mode 100644 index 00000000..8fbf165c --- /dev/null +++ b/assets/admin/src/schema/sections/extensions/ip.js @@ -0,0 +1,88 @@ +export const ipSchema = { + key: 'extensions.ip', + title: 'IP归属地', + icon: 'fas fa-map-marker-alt', + description: '配置 IP 数据源、缓存策略和前台归属地展示。', + defaults: { + ip_geo_enabled: false, + ip_addr_sdk: 'null', + ip_geo_cache_ttl: 86400, + ip_geo_display: ['comment', 'user_home', 'user_center', 'post'], + ip_geo_format: 'province_city', + ip_geo_fallback_text: '', + ip_addr_sdk_qq_appkey: '', + ip_addr_sdk_qq_secretkey: '', + ip_addr_sdk_amap_appkey: '', + ip_addr_sdk_amap_secretkey: '', + }, + groups: [ + { + key: 'basic', + title: '基础配置', + icon: 'fas fa-globe', + fields: [ + { key: 'ip_geo_enabled', label: '启用 IP 归属地', type: 'switch', default: false, help: '开启后将在评论、用户主页等位置显示 IP 归属地信息' }, + { + key: 'ip_addr_sdk', + label: 'IP 接口服务商', + type: 'radio', + default: 'null', + options: [ + { label: '轮流查询', value: 'polling' }, + { label: '腾讯位置服务', value: 'qq' }, + { label: '高德位置服务', value: 'amap' }, + { label: '太平洋公共接口', value: 'null' }, + ], + dependsOn: { key: 'ip_geo_enabled', value: true }, + help: '优先使用国内接口,轮流查询会按腾讯→高德→公共接口顺序回退。', + }, + { key: 'ip_geo_cache_ttl', label: '缓存时间 (秒)', type: 'number', default: 86400, min: 3600, max: 604800, dependsOn: { key: 'ip_geo_enabled', value: true }, help: 'IP 查询结果缓存时长,避免频繁请求外部接口' }, + ], + }, + { + key: 'providers', + title: '接口凭据', + icon: 'fas fa-key', + fields: [ + { key: 'ip_addr_sdk_qq_appkey', label: '腾讯 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['qq', 'polling'] }, help: '「腾讯位置服务」或「轮流查询」时填写;未填则轮询会跳过腾讯。' }, + { key: 'ip_addr_sdk_qq_secretkey', label: '腾讯 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['qq', 'polling'] }, help: '启用 SK 签名时必填;未启用可留空。' }, + { key: 'ip_addr_sdk_amap_appkey', label: '高德 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['amap', 'polling'] }, help: '必须在高德开放平台创建「Web服务」类型 Key,并开通 IP 定位。选错平台会报 USERKEY_PLAT_NOMATCH。「轮流查询」时未填则跳过高德。' }, + { key: 'ip_addr_sdk_amap_secretkey', label: '高德 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', values: ['amap', 'polling'] }, help: '若控制台启用了「数字签名」,必填安全密钥;未启用可留空。' }, + ], + }, + { + key: 'display', + title: '展示设置', + icon: 'fas fa-eye', + fields: [ + { + key: 'ip_geo_display', + label: '展示位置', + type: 'checkbox', + default: ['comment', 'user_home', 'user_center', 'post'], + options: [ + { label: '评论区', value: 'comment' }, + { label: '用户主页', value: 'user_home' }, + { label: '用户中心', value: 'user_center' }, + { label: '文章详细页', value: 'post' }, + ], + dependsOn: { key: 'ip_geo_enabled', value: true }, + help: '用户主页/用户中心还需在「用户 → 用户功能」开启「显示城市」;评论区还需在评论设置开启「评论城市」。', + }, + { + key: 'ip_geo_format', + label: '显示格式', + type: 'radio', + default: 'province_city', + options: [ + { label: '省份+城市', value: 'province_city' }, + { label: '仅省份', value: 'province' }, + { label: '国家+省份', value: 'country_province' }, + ], + dependsOn: { key: 'ip_geo_enabled', value: true }, + }, + { key: 'ip_geo_fallback_text', label: '查询失败时文案', type: 'input', default: '', placeholder: '留空则不显示', dependsOn: { key: 'ip_geo_enabled', value: true } }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/extensions/push.js b/assets/admin/src/schema/sections/extensions/push.js new file mode 100644 index 00000000..e847d2be --- /dev/null +++ b/assets/admin/src/schema/sections/extensions/push.js @@ -0,0 +1,64 @@ +export const pushSchema = { + key: 'extensions.push', + title: '百度推送', + icon: 'fas fa-paper-plane', + description: '配置搜索引擎主动推送、批量提交和状态追踪。', + defaults: { + baidu_push_enabled: false, + baidu_push_token: '', + baidu_push_auto: true, + baidu_push_on_publish: true, + baidu_push_on_update: false, + baidu_push_batch_size: 50, + baidu_push_log_enabled: true, + xzh_on: false, + xzh_appid: '', + xzh_render_tail: true, + xzh_jsonld_single: true, + xzh_jsonld_page: false, + xzh_jsonld_img: false, + }, + groups: [ + { + key: 'basic', + title: '推送配置', + icon: 'fas fa-rocket', + fields: [ + { key: 'baidu_push_enabled', label: '启用百度推送', type: 'switch', default: false, help: '开启后将自动向百度搜索推送页面链接' }, + { key: 'baidu_push_token', label: '推送 Token', type: 'input', default: '', dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在百度搜索资源平台获取的推送准入 Token' }, + ], + }, + { + key: 'trigger', + title: '触发规则', + icon: 'fas fa-bolt', + fields: [ + { key: 'baidu_push_auto', label: '自动推送 (页面加载时)', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在前台页面中嵌入百度自动推送 JS 代码' }, + { key: 'baidu_push_on_publish', label: '发布时主动推送', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true } }, + { key: 'baidu_push_on_update', label: '更新时推送', type: 'switch', default: false, dependsOn: { key: 'baidu_push_enabled', value: true } }, + ], + }, + { + key: 'xzh', + title: '熊掌号 / JSON-LD', + icon: 'fas fa-paw', + fields: [ + { key: 'xzh_on', label: '启用熊掌号', type: 'switch', default: false }, + { key: 'xzh_appid', label: '百度 AppID', type: 'input', default: '', dependsOn: { key: 'xzh_on', value: true } }, + { key: 'xzh_render_tail', label: '文章底部显示熊掌号尾部', type: 'switch', default: true, dependsOn: { key: 'xzh_on', value: true } }, + { key: 'xzh_jsonld_single', label: '文章页 JSON-LD', type: 'switch', default: true, dependsOn: { key: 'xzh_on', value: true } }, + { key: 'xzh_jsonld_page', label: '页面 JSON-LD', type: 'switch', default: false, dependsOn: { key: 'xzh_on', value: true } }, + { key: 'xzh_jsonld_img', label: '不添加图片', type: 'switch', default: false, dependsOn: { key: 'xzh_on', value: true } }, + ], + }, + { + key: 'advanced', + title: '高级设置', + icon: 'fas fa-cog', + fields: [ + { key: 'baidu_push_batch_size', label: '批量推送条数', type: 'number', default: 50, min: 1, max: 500, dependsOn: { key: 'baidu_push_enabled', value: true } }, + { key: 'baidu_push_log_enabled', label: '记录推送日志', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true } }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/code-highlight.js b/assets/admin/src/schema/sections/global/code-highlight.js new file mode 100644 index 00000000..1c98f607 --- /dev/null +++ b/assets/admin/src/schema/sections/global/code-highlight.js @@ -0,0 +1,68 @@ +export const codeHighlightSchema = { + key: 'global.code-highlight', + title: '代码高亮', + icon: 'fas fa-code', + description: '代码块语法高亮主题、行号、最大高度等配置。', + defaults: { + highlight_kg: true, + highlight_hh: false, + highlight_btn: true, + highlight_maxheight: 400, + highlight_zt: 'enlighter', + highlight_dark_zt: 'monokai', + }, + groups: [ + { + key: 'basic', + title: '代码高亮设置', + icon: 'fas fa-highlighter', + fields: [ + { key: 'highlight_kg', label: '代码高亮开关', type: 'switch', default: true }, + { key: 'highlight_hh', label: '显示行号', type: 'switch', default: false }, + { key: 'highlight_btn', label: '显示扩展按钮', type: 'switch', default: true, help: '控制原始代码 / 信息 / 新窗口按钮的显示' }, + { key: 'highlight_maxheight', label: '最大高度(px,0=不限)', type: 'number', default: 400, min: 0, max: 2000, help: '代码块最大高度,超出滚动显示。0 表示不限制' }, + ], + }, + { + key: 'theme', + title: '主题设置', + icon: 'fas fa-palette', + fields: [ + { + key: 'highlight_zt', + label: '日间默认主题', + type: 'select', + default: 'enlighter', + options: [ + { label: 'Enlighter', value: 'enlighter' }, + { label: 'Bootstrap 4', value: 'bootstrap4' }, + { label: 'Classic', value: 'classic' }, + { label: 'Beyond', value: 'beyond' }, + { label: 'Mowtwo', value: 'mowtwo' }, + { label: 'Eclipse', value: 'eclipse' }, + { label: 'Droide', value: 'droide' }, + { label: 'Minimal', value: 'minimal' }, + { label: 'Rowhammer', value: 'rowhammer' }, + { label: 'Godzilla', value: 'godzilla' }, + { label: 'Dracula', value: 'dracula' }, + { label: 'Atomic', value: 'atomic' }, + { label: 'Monokai', value: 'monokai' }, + ], + }, + { + key: 'highlight_dark_zt', + label: '暗色默认主题', + type: 'select', + default: 'monokai', + options: [ + { label: 'Dracula', value: 'dracula' }, + { label: 'Atomic', value: 'atomic' }, + { label: 'Monokai', value: 'monokai' }, + { label: 'Enlighter', value: 'enlighter' }, + { label: 'Bootstrap 4', value: 'bootstrap4' }, + ], + }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/custom-code.js b/assets/admin/src/schema/sections/global/custom-code.js new file mode 100644 index 00000000..86b28720 --- /dev/null +++ b/assets/admin/src/schema/sections/global/custom-code.js @@ -0,0 +1,39 @@ +export const customCodeSchema = { + key: 'global.custom-code', + title: '自定义代码', + icon: 'fas fa-code', + description: '自定义 CSS、JS 代码注入。', + defaults: { + custom_css: '', + head_code: '', + foot_code: '', + trackcode: '', + }, + groups: [ + { + key: 'css', + title: '自定义 CSS', + icon: 'fab fa-css3-alt', + fields: [ + { key: 'custom_css', label: '自定义 CSS', type: 'code', language: 'css', placeholder: '/* 在此输入自定义 CSS */', rows: 10, help: '将在页面 中加载' }, + ], + }, + { + key: 'code', + title: '头部 & 底部代码', + icon: 'fas fa-code', + fields: [ + { key: 'head_code', label: '头部 HTML 代码', type: 'code', placeholder: '', rows: 6, help: '在 中输出,支持 meta/link/style/script 等任意 HTML' }, + { key: 'foot_code', label: '底部 HTML 代码', type: 'code', placeholder: '', rows: 6, help: '在页面底部 前输出,支持任意 HTML' }, + ], + }, + { + key: 'stats', + title: '统计代码', + icon: 'fas fa-chart-bar', + fields: [ + { key: 'trackcode', label: '统计代码', type: 'code', placeholder: '', rows: 6, help: '百度统计、Google Analytics、CNZZ、51la 等统计代码,自动在 head 中输出' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/email.js b/assets/admin/src/schema/sections/global/email.js new file mode 100644 index 00000000..99df372e --- /dev/null +++ b/assets/admin/src/schema/sections/global/email.js @@ -0,0 +1,123 @@ +export const emailSchema = { + key: 'global.email', + title: '邮件配置', + icon: 'fas fa-envelope', + description: 'SMTP 发信配置、邮件事件开关与模板自定义。', + defaults: { + email_footer_text: '', + email_site_branding: true, + email_template_mode: 'plain', + email_private_receive_limit: 'all', + email_reg_verify_link_enabled: true, + email_reg_verify_code_enabled: true, + email_reg_success_enabled: true, + email_password_reset_enabled: true, + email_change_code_enabled: true, + email_change_notice_enabled: true, + }, + groups: [ + { + key: 'global', + title: '全局设置', + icon: 'fas fa-cog', + fields: [ + { key: 'email_footer_text', label: '邮件页脚文字', type: 'textarea', rows: 2, placeholder: '留空则使用站点名称', help: '附加在每封邮件底部的文字' }, + { key: 'email_site_branding', label: '显示站点品牌', type: 'switch', default: true, help: '在邮件开头显示「[站点名]」前缀' }, + { key: 'email_template_mode', label: '邮件格式', type: 'radio', default: 'plain', options: [ + { label: '纯文本', value: 'plain' }, + { label: 'HTML', value: 'html' }, + ]}, + { + key: 'email_private_receive_limit', + label: '私信邮件接收组', + type: 'select', + default: 'all', + options: [ + { label: '所有用户', value: 'all' }, + { label: 'VIP 及以上', value: 'vip' }, + { label: 'VIP2 及以上', value: 'vip2' }, + { label: '仅管理员', value: 'admin' }, + ], + help: '限制哪些用户组可以收到私信邮件通知', + }, + ], + }, + { + key: 'branding', + title: '邮件品牌', + icon: 'fas fa-signature', + fields: [ + { key: 'mail_showname', label: '发件人名称', type: 'input', default: '', help: '覆盖系统默认发件人名称' }, + { key: 'mail_title_prefix', label: '主题前缀', type: 'input', default: '', help: '自动添加在所有邮件主题前面,如【站点名】' }, + { key: 'mail_description', label: '邮件底部说明', type: 'textarea', rows: 3, default: '', help: '附加在邮件页脚文字之后,留空不显示' }, + { key: 'mail_more_content', label: '邮件附加内容', type: 'textarea', rows: 4, default: '', help: '附加在邮件内容末尾的补充说明' }, + ], + }, + { + key: 'reg_verify_link', + title: '注册验证链接', + icon: 'fas fa-link', + fields: [ + { key: 'email_reg_verify_link_enabled', label: '启用', type: 'switch', default: true, help: '注册后发送验证链接邮件' }, + { key: 'email_reg_verify_link_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '验证你的邮箱地址' }, + { key: 'email_reg_verify_link_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {url}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{url} 验证链接' }, + { key: 'email_reg_verify_link_html', label: 'HTML 格式', type: 'switch', default: false }, + ], + }, + { + key: 'reg_verify_code', + title: '注册验证码', + icon: 'fas fa-keyboard', + fields: [ + { key: 'email_reg_verify_code_enabled', label: '启用', type: 'switch', default: true, help: '注册后发送 6 位验证码邮件' }, + { key: 'email_reg_verify_code_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '邮箱验证码' }, + { key: 'email_reg_verify_code_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {code}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{code} 验证码' }, + { key: 'email_reg_verify_code_html', label: 'HTML 格式', type: 'switch', default: false }, + ], + }, + { + key: 'reg_success', + title: '注册成功通知', + icon: 'fas fa-check-circle', + fields: [ + { key: 'email_reg_success_enabled', label: '启用', type: 'switch', default: true, help: '邮箱验证成功后发送通知邮件' }, + { key: 'email_reg_success_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '邮箱验证成功' }, + { key: 'email_reg_success_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name}', help: '可用占位符:{username} 用户名、{site_name} 站点名' }, + { key: 'email_reg_success_html', label: 'HTML 格式', type: 'switch', default: false }, + ], + }, + { + key: 'password_reset', + title: '密码重置', + icon: 'fas fa-unlock-alt', + fields: [ + { key: 'email_password_reset_enabled', label: '启用', type: 'switch', default: true, help: '发送密码重置链接邮件' }, + { key: 'email_password_reset_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '重置密码' }, + { key: 'email_password_reset_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {url}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{url} 重置链接' }, + { key: 'email_password_reset_html', label: 'HTML 格式', type: 'switch', default: false }, + ], + }, + { + key: 'change_code', + title: '邮箱变更验证码', + icon: 'fas fa-shield-alt', + fields: [ + { key: 'email_change_code_enabled', label: '启用', type: 'switch', default: true, help: '变更邮箱时发送验证码邮件' }, + { key: 'email_change_code_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '邮箱验证码' }, + { key: 'email_change_code_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {code}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{code} 验证码' }, + { key: 'email_change_code_html', label: 'HTML 格式', type: 'switch', default: false }, + ], + }, + { + key: 'change_notice', + title: '邮箱变更通知', + icon: 'fas fa-bell', + fields: [ + { key: 'email_change_notice_enabled', label: '启用', type: 'switch', default: true, help: '邮箱变更成功后向旧邮箱发送通知' }, + { key: 'email_change_notice_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '账号邮箱已变更' }, + { key: 'email_change_notice_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {new_email}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{new_email} 新邮箱' }, + { key: 'email_change_notice_html', label: 'HTML 格式', type: 'switch', default: false }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/floating.js b/assets/admin/src/schema/sections/global/floating.js new file mode 100644 index 00000000..d9df88c5 --- /dev/null +++ b/assets/admin/src/schema/sections/global/floating.js @@ -0,0 +1,8 @@ +export const floatingSchema = { + key: 'global.floating', + title: '右侧悬浮按钮', + icon: 'fas fa-ellipsis-v', + description: '页面右侧浮动按钮的显示与配置。', + defaults: {}, + groups: [], +} diff --git a/assets/admin/src/schema/sections/global/i18n.js b/assets/admin/src/schema/sections/global/i18n.js new file mode 100644 index 00000000..ef80bbb4 --- /dev/null +++ b/assets/admin/src/schema/sections/global/i18n.js @@ -0,0 +1,85 @@ +export const i18nSchema = { + key: 'global.i18n', + title: '多语言翻译', + icon: 'fas fa-language', + description: '配置站点语言、语言切换器和翻译管理。', + defaults: { + i18n_enabled: false, + i18n_default_lang: 'zh-CN', + i18n_switcher_position: 'footer', + i18n_auto_detect: true, + i18n_available_langs: 'zh-CN|简体中文\nen|English\nzh-TW|繁體中文\nja|日本語\nko|한국어\nfr|Français\nde|Deutsch\nes|Español\nru|Русский', + i18n_nomenclature: '', + i18n_url_strategy: 'query', + }, + groups: [ + { + key: 'basic', + title: '基础配置', + icon: 'fas fa-globe', + fields: [ + { key: 'i18n_enabled', label: '启用多语言', type: 'switch', default: false, help: '开启后站点将支持多语言切换' }, + { + key: 'i18n_default_lang', + label: '默认语言', + type: 'select', + default: 'zh-CN', + options: [ + { label: '简体中文', value: 'zh-CN' }, + { label: 'English', value: 'en' }, + { label: '繁體中文', value: 'zh-TW' }, + { label: '日本語', value: 'ja' }, + { label: '한국어', value: 'ko' }, + { label: 'Français', value: 'fr' }, + { label: 'Deutsch', value: 'de' }, + { label: 'Español', value: 'es' }, + { label: 'Русский', value: 'ru' }, + ], + dependsOn: { key: 'i18n_enabled', value: true }, + }, + { key: 'i18n_auto_detect', label: '自动检测浏览器语言', type: 'switch', default: true, dependsOn: { key: 'i18n_enabled', value: true }, help: '基于浏览器 Accept-Language 自动切换(AKSRT 方案,区别于 zibll IP 定位方案)' }, + ], + }, + { + key: 'languages', + title: '语言管理', + icon: 'fas fa-language', + fields: [ + { key: 'i18n_available_langs', label: '可用语言列表', type: 'textarea', default: 'zh-CN|简体中文\nen|English\nzh-TW|繁體中文\nja|日本語\nko|한국어\nfr|Français\nde|Deutsch\nes|Español\nru|Русский', dependsOn: { key: 'i18n_enabled', value: true }, help: '每行一个语言:语言代码|显示名称(9 种内置语言)' }, + { key: 'i18n_nomenclature', label: '自定义术语', type: 'code', default: '', dependsOn: { key: 'i18n_enabled', value: true }, help: 'JSON 格式自定义翻译术语(key => 翻译),对齐 zibll nomenclature' }, + ], + }, + { + key: 'display', + title: '展示设置', + icon: 'fas fa-eye', + fields: [ + { + key: 'i18n_switcher_position', + label: '语言切换器位置', + type: 'radio', + default: 'footer', + options: [ + { label: '导航栏', value: 'header' }, + { label: '页脚', value: 'footer' }, + { label: '导航栏+页脚', value: 'both' }, + { label: '隐藏', value: 'none' }, + ], + dependsOn: { key: 'i18n_enabled', value: true }, + }, + { + key: 'i18n_url_strategy', + label: 'URL 策略', + type: 'radio', + default: 'query', + options: [ + { label: '查询参数 (?lang=en)', value: 'query' }, + { label: '路径前缀 (/en/page)', value: 'path' }, + { label: '子域名 (en.site.com)', value: 'subdomain' }, + ], + dependsOn: { key: 'i18n_enabled', value: true }, + }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/index.js b/assets/admin/src/schema/sections/global/index.js new file mode 100644 index 00000000..5a788423 --- /dev/null +++ b/assets/admin/src/schema/sections/global/index.js @@ -0,0 +1,32 @@ +/** + * Global & Functions domain schemas. + * Each key corresponds to a sub-page under the "全局&功能" section. + */ + +import { logoSchema } from './logo.js' +import { commonSchema } from './common.js' +import { layoutSchema } from './layout.js' +import { searchSchema } from './search.js' +import { emailSchema } from './email.js' +import { tabbarSchema } from './tabbar.js' +import { floatingSchema } from './floating.js' +import { i18nSchema } from './i18n.js' +import { customCodeSchema } from './custom-code.js' +import { seoSchema } from './seo.js' +import { codeHighlightSchema } from './code-highlight.js' +import { systemNoticeSchema } from './system-notice.js' + +export const globalSchemas = { + logo: logoSchema, + common: commonSchema, + layout: layoutSchema, + search: searchSchema, + email: emailSchema, + tabbar: tabbarSchema, + floating: floatingSchema, + i18n: i18nSchema, + 'custom-code': customCodeSchema, + seo: seoSchema, + 'code-highlight': codeHighlightSchema, + 'system-notice': systemNoticeSchema, +} diff --git a/assets/admin/src/schema/sections/global/layout.js b/assets/admin/src/schema/sections/global/layout.js new file mode 100644 index 00000000..c7f06d2b --- /dev/null +++ b/assets/admin/src/schema/sections/global/layout.js @@ -0,0 +1,160 @@ +export const layoutSchema = { + key: 'global.layout', + title: '显示 & 布局', + icon: 'fas fa-desktop', + description: '全局布局风格、颜色方案、字体设置。', + defaults: { + theme_mode: 'time-auto', + theme_mode_button: ['pc_nav', 'm_menu'], + primary_color: '#1890ff', + theme_skin: 'f04494', + dark_primary_color: '', + border_radius: 8, + theme_main_radius: 12, + container_max_width: 1200, + sidebar_enabled: true, + sidebar_position: 'right', + sidebar_width: 311, + sidebar_home_enabled: true, + sidebar_category_enabled: true, + sidebar_tag_enabled: true, + sidebar_single_enabled: true, + sidebar_page_enabled: true, + sidebar_search_enabled: true, + font_family: 'system', + qj_loading: false, + qj_dh_xs: 'no1', + }, + groups: [ + { + key: 'loading-animation', + title: '加载动画', + icon: 'fas fa-spinner', + fields: [ + { key: 'qj_loading', label: '全站加载动画', type: 'switch', default: false, help: '开启后页面加载时显示全屏加载动画覆盖层' }, + { + key: 'qj_dh_xs', + label: '加载动画样式', + type: 'select', + default: 'no1', + dependsOn: { key: 'qj_loading', value: true }, + options: [ + { label: '样式 1', value: 'no1' }, + { label: '样式 2', value: 'no2' }, + { label: '样式 3', value: 'no3' }, + { label: '样式 4', value: 'no4' }, + { label: '样式 5', value: 'no5' }, + { label: '样式 6', value: 'no6' }, + { label: '样式 7', value: 'no7' }, + { label: '样式 8', value: 'no8' }, + { label: '样式 9', value: 'no9' }, + { label: '样式 10', value: 'no10' }, + ], + help: '10 种加载动画样式可选', + }, + ], + }, + { + key: 'color', + title: '色彩方案', + icon: 'fas fa-palette', + fields: [ + { + key: 'theme_mode', + label: '默认主题模式', + type: 'radio', + default: 'time-auto', + options: [ + { label: '浅色', value: 'white-theme' }, + { label: '深色', value: 'dark-theme' }, + { label: '跟随系统', value: 'time-auto' }, + ], + }, + { + key: 'theme_mode_button', + label: '主题切换按钮位置', + type: 'checkbox', + default: ['pc_nav', 'm_menu'], + options: [ + { label: 'PC 顶部导航', value: 'pc_nav' }, + { label: '移动端菜单', value: 'm_menu' }, + ], + help: '控制主题切换按钮在哪些位置显示', + }, + { key: 'primary_color', label: '主题色', type: 'color', default: '#f04494', help: '按钮、链接和强调元素的主色调' }, + { + key: 'theme_skin', + label: '预设主题色', + type: 'palette', + default: 'f04494', + options: [ + { label: '玫红', value: 'ff1856' }, + { label: '粉色', value: 'f04494' }, + { label: '紫色', value: 'ae53f3' }, + { label: '靛蓝', value: '627bf5' }, + { label: '天蓝', value: '00a2e3' }, + { label: '青绿', value: '16b597' }, + { label: '翠绿', value: '36af18' }, + { label: '黄绿', value: '8fb107' }, + { label: '棕金', value: 'b18c07' }, + { label: '橘橙', value: 'e06711' }, + { label: '朱红', value: 'f74735' }, + ], + help: '选择预设主题色,将覆盖上方自定义主题色', + }, + { key: 'dark_primary_color', label: '暗色模式主题色', type: 'color', default: '', help: '暗色模式下独立的主题色,留空则跟随浅色模式' }, + { key: 'border_radius', label: '全局圆角 (px)', type: 'slider', min: 0, max: 20, step: 1, default: 8 }, + { key: 'theme_main_radius', label: '卡片圆角 (px)', type: 'number', min: 0, max: 30, default: 12, help: '独立卡片圆角,留空则使用全局圆角 +4' }, + ], + }, + { + key: 'layout', + title: '布局设置', + icon: 'fas fa-columns', + fields: [ + { key: 'container_max_width', label: '内容最大宽度 (px)', type: 'select', default: 1200, options: [ + { label: '1200px', value: 1200 }, + { label: '1320px', value: 1320 }, + { label: '1440px', value: 1440 }, + { label: '1600px', value: 1600 }, + { label: '1800px', value: 1800 }, + { label: '2000px', value: 2000 }, + { label: '2200px', value: 2200 }, + { label: '全宽', value: 0 }, + ]}, + { key: 'sidebar_enabled', label: '开启侧边栏', type: 'switch', default: true }, + { key: 'sidebar_position', label: '侧边栏位置', type: 'radio', default: 'right', dependsOn: { key: 'sidebar_enabled', value: true }, options: [ + { label: '左侧', value: 'left' }, + { label: '右侧', value: 'right' }, + ]}, + { key: 'sidebar_width', label: '侧边栏宽度 (px)', type: 'number', default: 300, min: 200, max: 400, step: 10, dependsOn: { key: 'sidebar_enabled', value: true } }, + ], + }, + { + key: 'sidebar-scope', + title: '侧边栏显示范围', + icon: 'fas fa-th-list', + fields: [ + { key: 'sidebar_home_enabled', label: '首页侧边栏', type: 'switch', default: true, dependsOn: { key: 'sidebar_enabled', value: true } }, + { key: 'sidebar_category_enabled', label: '分类页侧边栏', type: 'switch', default: true, dependsOn: { key: 'sidebar_enabled', value: true } }, + { key: 'sidebar_tag_enabled', label: '标签页侧边栏', type: 'switch', default: true, dependsOn: { key: 'sidebar_enabled', value: true } }, + { key: 'sidebar_single_enabled', label: '文章页侧边栏', type: 'switch', default: true, dependsOn: { key: 'sidebar_enabled', value: true } }, + { key: 'sidebar_page_enabled', label: '页面侧边栏', type: 'switch', default: true, dependsOn: { key: 'sidebar_enabled', value: true } }, + { key: 'sidebar_search_enabled', label: '搜索页侧边栏', type: 'switch', default: true, dependsOn: { key: 'sidebar_enabled', value: true } }, + ], + }, + { + key: 'typography', + title: '字体设置', + icon: 'fas fa-font', + fields: [ + { key: 'font_family', label: '正文字体', type: 'select', default: 'system', options: [ + { label: '系统默认', value: 'system' }, + { label: '思源黑体', value: 'noto-sans-sc' }, + { label: '思源宋体', value: 'noto-serif-sc' }, + { label: '自定义', value: 'custom' }, + ]}, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/logo.js b/assets/admin/src/schema/sections/global/logo.js new file mode 100644 index 00000000..0c0aab87 --- /dev/null +++ b/assets/admin/src/schema/sections/global/logo.js @@ -0,0 +1,36 @@ +export const logoSchema = { + key: 'global.logo', + title: 'LOGO & 品牌', + icon: 'fas fa-image', + description: '管理站点 Logo、站点图标和品牌标识。', + defaults: { + site_logo: '', + site_logo_dark: '', + site_favicon: '', + site_icon: '', + logo_width: 150, + logo_text: '', + }, + groups: [ + { + key: 'logo', + title: 'Logo 设置', + icon: 'fas fa-image', + fields: [ + { key: 'site_logo', label: '站点 Logo', type: 'media', help: '建议尺寸 300x80,支持 PNG/SVG 透明背景' }, + { key: 'site_logo_dark', label: '暗色模式 Logo', type: 'media', help: '暗色模式下显示的 Logo,留空则使用默认 Logo' }, + { key: 'logo_width', label: 'Logo 宽度 (px)', type: 'number', min: 50, max: 400, step: 10, help: '导航栏中 Logo 显示宽度' }, + { key: 'logo_text', label: 'Logo 文字', type: 'input', placeholder: '当无 Logo 图片时显示的文字', help: '同时作为首页标题和 alt 文本使用' }, + ], + }, + { + key: 'favicon', + title: '站点图标', + icon: 'fas fa-fingerprint', + fields: [ + { key: 'site_favicon', label: '浏览器图标 (Favicon)', type: 'media', help: '建议 32x32 或 16x16 的 ICO/PNG 文件' }, + { key: 'site_icon', label: '移动端图标', type: 'media', help: '建议 192x192 的 PNG,用于移动端添加到桌面' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/seo.js b/assets/admin/src/schema/sections/global/seo.js new file mode 100644 index 00000000..c11fb852 --- /dev/null +++ b/assets/admin/src/schema/sections/global/seo.js @@ -0,0 +1,143 @@ +export const seoSchema = { + key: 'global.seo', + title: 'SEO 优化', + icon: 'fas fa-search', + description: '站点标题、描述、关键词与 AI SEO。AI 生成入口在文章「独立 SEO」框;连接配置在「扩展 → AI 连接配置」。优先使用 WordPress AI Connectors。', + defaults: { + seo_enabled: true, + seo_ai_enabled: false, + seo_connector: '-', + seo_title: '', + seo_keywords: '', + seo_description: '', + ai_seo_title_prompt: '', + ai_seo_keywords_prompt: '', + ai_seo_description_prompt: '', + seo_baidu_push: false, + seo_baidu_normal: false, + seo_baidu_fast: false, + seo_baidu_token: '', + seo_image_optimize: true, + seo_image_default: '', + seo_image_always_default: false, + seo_auto_alt: true, + seo_go_link: true, + seo_go_link_nonce: true, + seo_go_link_post: true, + seo_go_link_exclude: '', + seo_no_category: false, + seo_jsonld: true, + seo_jsonld_single: true, + seo_jsonld_page: true, + }, + groups: [ + { + key: 'basic', + title: 'SEO 基础', + icon: 'fas fa-globe', + fields: [ + { + key: 'seo_enabled', + label: '启用核心 SEO 优化', + type: 'switch', + default: true, + help: '关闭后主题不再输出 SEO title/description/keywords 等 head 标签。', + }, + { + key: 'seo_ai_enabled', + label: '启用 AI SEO 生成', + type: 'switch', + default: false, + help: '开启后文章/分类「独立 SEO」框显示「AI 生成 SEO」按钮。需先配置 AI 连接(WP Connectors 或扩展 → AI 连接配置)。', + dependsOn: { key: 'seo_enabled', value: true }, + }, + { key: 'seo_connector', label: '标题连接符', type: 'input', default: '-', placeholder: '例如:- 或 |', help: 'AI 生成标题时会用此连接符追加站点名。' }, + { key: 'seo_title', label: '站点 SEO 标题', type: 'textarea', rows: 2, default: '' }, + { key: 'seo_keywords', label: '站点 SEO 关键词', type: 'textarea', rows: 3, default: '' }, + { key: 'seo_description', label: '站点 SEO 描述', type: 'textarea', rows: 4, default: '' }, + ], + }, + { + key: 'ai-seo', + title: 'AI SEO 提示词', + icon: 'fas fa-robot', + description: '留空则使用内置 Zibll 风格默认提示词。填写后作为 system instruction 覆盖默认。', + fields: [ + { + key: 'ai_seo_title_prompt', + label: '标题提示词', + type: 'textarea', + rows: 5, + default: '', + dependsOn: { key: 'seo_ai_enabled', value: true }, + placeholder: '你是一名资深的网站 SEO 编辑……直接输出标题文本本身,不要任何前缀、引号或解释。', + help: '控制 AI 如何生成 SEO 标题。留空使用主题内置默认。', + }, + { + key: 'ai_seo_keywords_prompt', + label: '关键词提示词', + type: 'textarea', + rows: 5, + default: '', + dependsOn: { key: 'seo_ai_enabled', value: true }, + placeholder: '你是一名资深 SEO 编辑……使用英文逗号分隔,直接输出关键词列表。', + help: '控制 AI 如何生成 meta keywords。留空使用主题内置默认。', + }, + { + key: 'ai_seo_description_prompt', + label: '描述提示词', + type: 'textarea', + rows: 5, + default: '', + dependsOn: { key: 'seo_ai_enabled', value: true }, + placeholder: '你是一名资深的中文 SEO 编辑……字数控制在 80 至 150 个汉字之间。', + help: '控制 AI 如何生成 meta description。留空使用主题内置默认。', + }, + ], + }, + { + key: 'baidu', + title: '百度推送', + icon: 'fas fa-paper-plane', + fields: [ + { key: 'seo_baidu_push', label: '启用百度自动推送', type: 'switch', default: false }, + { key: 'seo_baidu_normal', label: '普通收录推送', type: 'switch', default: false }, + { key: 'seo_baidu_fast', label: '快速收录推送', type: 'switch', default: false }, + { key: 'seo_baidu_token', label: '百度推送 Token', type: 'input', default: '', placeholder: '百度资源平台 Token' }, + ], + }, + { + key: 'image', + title: '图片 SEO', + icon: 'fas fa-image', + fields: [ + { key: 'seo_image_optimize', label: '优化搜索引擎图片展示', type: 'switch', default: true }, + { key: 'seo_image_default', label: '默认分享图 / SEO 图', type: 'media', default: '' }, + { key: 'seo_image_always_default', label: '固定使用默认图', type: 'switch', default: false }, + { key: 'seo_auto_alt', label: '自动补全图片 alt', type: 'switch', default: true }, + ], + }, + { + key: 'redirect', + title: '外链重定向', + icon: 'fas fa-external-link-alt', + fields: [ + { key: 'seo_go_link', label: '启用外链重定向', type: 'switch', default: true }, + { key: 'seo_go_link_nonce', label: '启用外链鉴权', type: 'switch', default: true }, + { key: 'seo_go_link_post', label: '文章内容链接走重定向', type: 'switch', default: true }, + { key: 'seo_go_link_exclude', label: '重定向排除域名', type: 'textarea', rows: 4, default: '', placeholder: '每行一个域名' }, + ], + }, + { + key: 'advanced', + title: '高级 SEO', + icon: 'fas fa-sliders-h', + fields: [ + { key: 'seo_no_category', label: '分类 URL 去掉 category', type: 'switch', default: false }, + { key: 'seo_jsonld', label: '启用 JSON-LD', type: 'switch', default: true }, + { key: 'seo_jsonld_single', label: '文章页输出 JSON-LD', type: 'switch', default: true }, + { key: 'seo_jsonld_page', label: '页面输出 JSON-LD', type: 'switch', default: true }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/system-notice.js b/assets/admin/src/schema/sections/global/system-notice.js new file mode 100644 index 00000000..fa80e2c9 --- /dev/null +++ b/assets/admin/src/schema/sections/global/system-notice.js @@ -0,0 +1,149 @@ +export const systemNoticeSchema = { + key: 'global.system-notice', + title: '弹窗通知', + icon: 'fas fa-bullhorn', + description: '全站弹窗公告配置,支持按用户身份定向、多按钮组、弹窗尺寸与过期周期。', + defaults: { + system_notice_enabled: true, + system_notice_policy: '', + system_notice_title: '', + system_notice_content: '', + system_notice_btn1_text: '', + system_notice_btn1_link: '', + system_notice_btn1_color: 'primary', + system_notice_btn2_text: '', + system_notice_btn2_link: '', + system_notice_btn2_color: 'primary', + system_notice_btn3_text: '', + system_notice_btn3_link: '', + system_notice_btn3_color: 'primary', + system_notice_btn4_text: '', + system_notice_btn4_link: '', + system_notice_btn4_color: 'primary', + system_notice_size: '', + system_notice_radius: false, + system_notice_class: '', + system_notice_title_icon: '', + system_notice_title_class: '', + system_notice_title_style: '', + system_notice_expires: 24, + }, + groups: [ + { + key: 'basic', + title: '公告设置', + icon: 'fas fa-bullhorn', + fields: [ + { key: 'system_notice_enabled', label: '启用系统公告', type: 'switch', default: true }, + { + key: 'system_notice_policy', + label: '显示对象', + type: 'select', + default: '', + options: [ + { label: '所有访客', value: '' }, + { label: '仅登录用户', value: 'signin' }, + { label: '仅 VIP 会员', value: 'vip' }, + { label: '仅非 VIP', value: 'free' }, + { label: '仅认证用户', value: 'auth' }, + ], + dependsOn: { key: 'system_notice_enabled', value: true }, + }, + { key: 'system_notice_title', label: '公告标题', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_content', label: '公告内容', type: 'textarea', default: '', rows: 5, dependsOn: { key: 'system_notice_enabled', value: true }, help: '支持 HTML 标签' }, + ], + }, + { + key: 'buttons', + title: '按钮组(留空则不显示)', + icon: 'fas fa-th-large', + fields: [ + { key: 'system_notice_btn1_text', label: '按钮 1 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn1_link', label: '按钮 1 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true }, help: '可选,填写后按钮变为链接' }, + { key: 'system_notice_btn1_color', label: '按钮 1 颜色', type: 'select', default: 'primary', options: [ + { label: '主题色', value: 'primary' }, + { label: '成功绿', value: 'success' }, + { label: '警告橙', value: 'warning' }, + { label: '危险红', value: 'danger' }, + { label: '信息蓝', value: 'info' }, + { label: '深紫', value: 'purple' }, + { label: '深灰', value: 'dark' }, + ], dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn2_text', label: '按钮 2 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn2_link', label: '按钮 2 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn2_color', label: '按钮 2 颜色', type: 'select', default: 'primary', options: [ + { label: '主题色', value: 'primary' }, + { label: '成功绿', value: 'success' }, + { label: '警告橙', value: 'warning' }, + { label: '危险红', value: 'danger' }, + { label: '信息蓝', value: 'info' }, + { label: '深紫', value: 'purple' }, + { label: '深灰', value: 'dark' }, + ], dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn3_text', label: '按钮 3 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn3_link', label: '按钮 3 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn3_color', label: '按钮 3 颜色', type: 'select', default: 'primary', options: [ + { label: '主题色', value: 'primary' }, + { label: '成功绿', value: 'success' }, + { label: '警告橙', value: 'warning' }, + { label: '危险红', value: 'danger' }, + { label: '信息蓝', value: 'info' }, + { label: '深紫', value: 'purple' }, + { label: '深灰', value: 'dark' }, + ], dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn4_text', label: '按钮 4 文案', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn4_link', label: '按钮 4 链接', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_btn4_color', label: '按钮 4 颜色', type: 'select', default: 'primary', options: [ + { label: '主题色', value: 'primary' }, + { label: '成功绿', value: 'success' }, + { label: '警告橙', value: 'warning' }, + { label: '危险红', value: 'danger' }, + { label: '信息蓝', value: 'info' }, + { label: '深紫', value: 'purple' }, + { label: '深灰', value: 'dark' }, + ], dependsOn: { key: 'system_notice_enabled', value: true } }, + ], + }, + { + key: 'style', + title: '样式设置', + icon: 'fas fa-paint-brush', + fields: [ + { + key: 'system_notice_size', + label: '弹窗尺寸', + type: 'select', + default: '', + options: [ + { label: '默认', value: '' }, + { label: '迷你', value: 'modal-mini' }, + { label: '小号', value: 'modal-sm' }, + { label: '大号', value: 'modal-lg' }, + ], + dependsOn: { key: 'system_notice_enabled', value: true }, + }, + { key: 'system_notice_radius', label: '按钮圆角', type: 'switch', default: false, dependsOn: { key: 'system_notice_enabled', value: true }, help: '开启后弹窗按钮使用圆角样式' }, + { key: 'system_notice_class', label: '公告样式类', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + { key: 'system_notice_title_icon', label: '标题图标', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true }, help: '如 fas fa-bullhorn' }, + { key: 'system_notice_title_class', label: '标题背景配色', type: 'select', default: '', dependsOn: { key: 'system_notice_enabled', value: true }, options: [ + { label: '无', value: '' }, + { label: '炫彩黄', value: 'jb-yellow' }, + { label: '炫彩蓝', value: 'jb-blue' }, + { label: '炫彩绿', value: 'jb-green' }, + { label: '炫彩紫', value: 'jb-purple' }, + { label: '炫彩红', value: 'jb-red' }, + { label: '炫彩橙', value: 'jb-orange' }, + ], help: '弹窗标题区域的背景配色方案' }, + { key: 'system_notice_title_style', label: '标题样式', type: 'input', default: '', dependsOn: { key: 'system_notice_enabled', value: true } }, + ], + }, + { + key: 'expire', + title: '过期设置', + icon: 'fas fa-clock', + fields: [ + { key: 'system_notice_expires', label: '弹窗周期(小时)', type: 'number', default: 24, min: 0, max: 720, dependsOn: { key: 'system_notice_enabled', value: true }, help: '0 表示永不过期' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/global/tabbar.js b/assets/admin/src/schema/sections/global/tabbar.js new file mode 100644 index 00000000..08e664e9 --- /dev/null +++ b/assets/admin/src/schema/sections/global/tabbar.js @@ -0,0 +1,8 @@ +export const tabbarSchema = { + key: 'global.tabbar', + title: '手机底部Tab', + icon: 'fas fa-mobile-alt', + description: '移动端底部导航栏配置。', + defaults: {}, + groups: [], +} diff --git a/assets/admin/src/schema/sections/payment/cardPass.js b/assets/admin/src/schema/sections/payment/cardPass.js new file mode 100644 index 00000000..c039a4d6 --- /dev/null +++ b/assets/admin/src/schema/sections/payment/cardPass.js @@ -0,0 +1,59 @@ +export const cardPassSchema = { + key: 'payment.card-pass', + title: '充值卡', + icon: 'fas fa-ticket-alt', + description: '配置充值卡类型、卡号规则、批量生成和兑换逻辑。', + defaults: { + card_pass_enabled: false, + card_pass_desc: '', + card_pass_type: 'balance', + card_pass_prefix: 'AK', + card_pass_length: 16, + card_pass_batch_size: 50, + card_pass_max_redeem: 1, + card_pass_expiry: 0, + card_pass_amount: 10, + card_pass_vip_days: 30, + card_pass_credits: 0, + card_pass_redeem_page: '', + card_pass_success_msg: '兑换成功!', + card_pass_fail_msg: '卡号无效或已过期', + }, + groups: [ + { + key: 'basic', + title: '充值卡设置', + icon: 'fas fa-id-card', + fields: [ + { key: 'card_pass_enabled', label: '启用充值卡', type: 'switch', default: false }, + { key: 'card_pass_desc', label: '充值卡说明', type: 'textarea', default: '', dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_type', label: '充值卡类型', type: 'select', default: 'balance', options: [{ label: '余额卡', value: 'balance' }, { label: 'VIP卡', value: 'vip' }, { label: '积分卡', value: 'credits' }], dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_prefix', label: '卡号前缀', type: 'input', default: 'AK', dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_length', label: '卡号长度', type: 'number', default: 16, min: 8, max: 32, dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_batch_size', label: '批量生成数量', type: 'number', default: 50, min: 1, max: 500, dependsOn: { key: 'card_pass_enabled', value: true } }, + ], + }, + { + key: 'redeem', + title: '兑换设置', + icon: 'fas fa-exchange-alt', + fields: [ + { key: 'card_pass_max_redeem', label: '单卡最大兑换次数', type: 'number', default: 1, min: 1, max: 100, dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_expiry', label: '有效期(天)', type: 'number', default: 0, min: 0, help: '0为永久有效', dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_amount', label: '面额(元)', type: 'number', default: 10, min: 1, max: 9999, dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_vip_days', label: 'VIP天数', type: 'number', default: 30, min: 1, max: 3650, dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_credits', label: '赠送积分', type: 'number', default: 0, min: 0, max: 99999, dependsOn: { key: 'card_pass_enabled', value: true } }, + ], + }, + { + key: 'display', + title: '展示设置', + icon: 'fas fa-desktop', + fields: [ + { key: 'card_pass_redeem_page', label: '兑换页面', type: 'input', default: '', dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_success_msg', label: '兑换成功提示', type: 'input', default: '兑换成功!', dependsOn: { key: 'card_pass_enabled', value: true } }, + { key: 'card_pass_fail_msg', label: '兑换失败提示', type: 'input', default: '卡号无效或已过期', dependsOn: { key: 'card_pass_enabled', value: true } }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/payment/gateway.js b/assets/admin/src/schema/sections/payment/gateway.js new file mode 100644 index 00000000..809dadbc --- /dev/null +++ b/assets/admin/src/schema/sections/payment/gateway.js @@ -0,0 +1,167 @@ +export const gatewaySchema = { + key: 'payment.gateway', + title: '收款接口', + icon: 'fas fa-credit-card', + description: '管理已支持的收款通道:余额、支付宝、微信、PayPal、Stripe、易支付与卡密兑换。开启渠道后展开填写凭据。', + defaults: { + payment_wechat_enabled: false, + payment_alipay_enabled: false, + payment_paypal_enabled: false, + payment_stripe_enabled: false, + payment_epay_enabled: false, + payment_balance_enabled: true, + payment_card_pass_enabled: false, + wechat_merchantid: '', + wechat_appid: '', + wechat_payment_type: 'native', + wechat_api_v3_key: '', + wechat_private_key_path: '', + wechat_cert_path: '', + alipay_appid: '', + alipay_privatekey: '', + alipay_payment_type: 'page', + alipay_app_public_cert_path: '', + alipay_public_cert_path: '', + alipay_root_cert_path: '', + paypal_client_id: '', + paypal_client_secret: '', + paypal_currency: 'USD', + paypal_sandbox: true, + stripe_secret_key: '', + stripe_publishable_key: '', + stripe_webhook_secret: '', + stripe_currency: 'usd', + stripe_test_mode: true, + epay_apiurl: '', + epay_pid: '', + epay_key: '', + epay_channel_alipay: true, + epay_channel_wxpay: true, + epay_device: 'auto', + }, + groups: [ + { + key: 'notice', + title: '说明', + icon: 'fas fa-info-circle', + fields: [ + { + key: 'payment_gateway_notice', + label: '通道说明', + type: 'notice', + alertType: 'info', + alertContent: '以渠道开关 + 凭据是否齐全为准。异步回调由各网关验签(支付宝/微信/Stripe/易支付等),无需额外口令。卡密为兑换入口,不出现在收银台支付方式列表。', + }, + ], + }, + { + key: 'gateways', + title: '支付渠道开关', + icon: 'fas fa-toggle-on', + description: '先开启渠道,下方对应配置卡片会展开;填写凭据后网关状态变为可用。余额与卡密无第三方凭据,仅开关即可。', + fields: [ + { key: 'payment_balance_enabled', label: '余额支付', type: 'switch', default: true, help: '需同时在「余额充值」启用余额功能。关闭后用户中心仍可有钱包,但结账不可用余额支付。' }, + { key: 'payment_alipay_enabled', label: '支付宝', type: 'switch', default: false }, + { key: 'payment_wechat_enabled', label: '微信支付', type: 'switch', default: false }, + { key: 'payment_paypal_enabled', label: 'PayPal', type: 'switch', default: false }, + { key: 'payment_stripe_enabled', label: 'Stripe', type: 'switch', default: false }, + { key: 'payment_epay_enabled', label: '易支付', type: 'switch', default: false, help: '兼容彩虹易支付协议的聚合收银台,可同时开启支付宝/微信子通道。' }, + { key: 'payment_card_pass_enabled', label: '卡密充值', type: 'switch', default: false, help: '仅控制卡密兑换入口是否开放;卡密规则与批量发卡在「充值卡」页。不进入结账支付方式列表。' }, + ], + }, + { + key: 'wechat', + title: '微信支付配置', + icon: 'fab fa-weixin', + description: '开启「微信支付」后填写商户号与密钥。', + collapsible: true, + expandWhen: 'payment_wechat_enabled', + dependsOn: { key: 'payment_wechat_enabled', value: true }, + fields: [ + { key: 'wechat_merchantid', label: '商户号', type: 'input', default: '' }, + { key: 'wechat_appid', label: 'AppID', type: 'input', default: '' }, + { key: 'wechat_payment_type', label: '支付方式', type: 'select', default: 'native', options: [ + { label: '扫码支付(Native)', value: 'native' }, + { label: 'H5支付', value: 'h5' }, + ] }, + { key: 'wechat_api_v3_key', label: 'APIv3密钥', type: 'input', default: '', help: '微信商户平台设置的 APIv3 密钥(32位)。' }, + { key: 'wechat_private_key_path', label: '商户私钥路径', type: 'input', default: '', help: 'apiclient_key.pem 绝对路径,用于签名。' }, + { key: 'wechat_cert_path', label: '商户API证书路径', type: 'input', default: '', help: 'apiclient_cert.pem 绝对路径。' }, + ], + }, + { + key: 'alipay', + title: '支付宝配置', + icon: 'fab fa-alipay', + description: '开启「支付宝」后填写应用与证书。', + collapsible: true, + expandWhen: 'payment_alipay_enabled', + dependsOn: { key: 'payment_alipay_enabled', value: true }, + fields: [ + { key: 'alipay_appid', label: 'AppID', type: 'input', default: '' }, + { key: 'alipay_privatekey', label: '应用私钥', type: 'textarea', rows: 3, default: '' }, + { key: 'alipay_payment_type', label: '支付方式', type: 'select', default: 'page', options: [ + { label: '电脑网站支付(web)', value: 'page' }, + { label: '手机网站支付(wap)', value: 'wap' }, + ] }, + { key: 'alipay_app_public_cert_path', label: '应用公钥证书路径', type: 'input', default: '', help: 'appCertPublicKey.crt 绝对路径。' }, + { key: 'alipay_public_cert_path', label: '支付宝公钥证书路径', type: 'input', default: '', help: 'alipayCertPublicKey_RSA2.crt 绝对路径。' }, + { key: 'alipay_root_cert_path', label: '支付宝根证书路径', type: 'input', default: '', help: 'alipayRootCert.crt 绝对路径。' }, + { key: 'alipay_notify_help', label: '回调地址说明', type: 'notice', alertType: 'info', alertContent: '异步通知 / 同步返回已固定为 /wp-json/aksrt/v1/payment/alipay/{notify|return},无需在此填写。' }, + ], + }, + { + key: 'paypal', + title: 'PayPal 配置', + icon: 'fab fa-paypal', + description: '开启「PayPal」后填写 Client 凭据。', + collapsible: true, + expandWhen: 'payment_paypal_enabled', + dependsOn: { key: 'payment_paypal_enabled', value: true }, + fields: [ + { key: 'paypal_client_id', label: 'Client ID', type: 'input', default: '' }, + { key: 'paypal_client_secret', label: 'Client Secret', type: 'input', default: '' }, + { key: 'paypal_currency', label: '货币', type: 'input', default: 'USD' }, + { key: 'paypal_sandbox', label: '沙箱模式', type: 'switch', default: true }, + ], + }, + { + key: 'stripe', + title: 'Stripe 配置', + icon: 'fab fa-stripe', + description: '开启「Stripe」后填写 API Key 与 Webhook。', + collapsible: true, + expandWhen: 'payment_stripe_enabled', + dependsOn: { key: 'payment_stripe_enabled', value: true }, + fields: [ + { key: 'stripe_secret_key', label: 'Secret Key', type: 'input', default: '' }, + { key: 'stripe_publishable_key', label: 'Publishable Key', type: 'input', default: '' }, + { key: 'stripe_webhook_secret', label: 'Webhook Secret', type: 'input', default: '', help: 'Stripe Dashboard 配置 Webhook 后获得,用于验签。' }, + { key: 'stripe_currency', label: '货币', type: 'input', default: 'usd' }, + { key: 'stripe_test_mode', label: '测试模式', type: 'switch', default: true }, + ], + }, + { + key: 'epay', + title: '易支付配置', + icon: 'fas fa-university', + description: '开启「易支付」后填写接口地址与商户信息,并选择支付宝/微信子通道。', + collapsible: true, + expandWhen: 'payment_epay_enabled', + dependsOn: { key: 'payment_epay_enabled', value: true }, + fields: [ + { key: 'epay_apiurl', label: '接口地址', type: 'input', default: '', help: '易支付站点根地址,如 https://pay.example.com/(末尾斜杠可选)。' }, + { key: 'epay_pid', label: '商户ID', type: 'input', default: '' }, + { key: 'epay_key', label: '商户密钥', type: 'input', default: '', help: 'MD5 通信密钥。' }, + { key: 'epay_channel_alipay', label: '启用支付宝通道', type: 'switch', default: true }, + { key: 'epay_channel_wxpay', label: '启用微信通道', type: 'switch', default: true }, + { key: 'epay_device', label: '下单终端', type: 'select', default: 'auto', options: [ + { label: '自动(按 UA)', value: 'auto' }, + { label: '电脑端 submit', value: 'pc' }, + { label: '移动端 mapi', value: 'mobile' }, + ] }, + { key: 'epay_notify_help', label: '回调地址说明', type: 'notice', alertType: 'info', alertContent: '异步/同步回调固定为 /wp-json/aksrt/v1/payment/epay_alipay/{notify|return} 与 /payment/epay_wxpay/{notify|return},请在易支付后台放行对应 URL。' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/post-list/feature.js b/assets/admin/src/schema/sections/post-list/feature.js new file mode 100644 index 00000000..17d8ebe7 --- /dev/null +++ b/assets/admin/src/schema/sections/post-list/feature.js @@ -0,0 +1,25 @@ +export const featureSchema = { + key: 'post_list.feature', + title: '文章功能', + icon: 'fas fa-magic', + description: '配置文章页的互动功能开关。', + defaults: { + feature_pdf_export: false, + feature_print: true, + feature_word_count: true, + feature_reading_time: true, + }, + groups: [ + { + key: 'tools', + title: '工具功能', + icon: 'fas fa-tools', + fields: [ + { key: 'feature_print', label: '打印文章', type: 'switch', default: true }, + { key: 'feature_word_count', label: '字数统计', type: 'switch', default: true }, + { key: 'feature_reading_time', label: '预计阅读时间', type: 'switch', default: true }, + { key: 'feature_pdf_export', label: 'PDF 导出', type: 'switch', default: false, help: '实验性功能' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/post-list/index.js b/assets/admin/src/schema/sections/post-list/index.js new file mode 100644 index 00000000..37d57fc4 --- /dev/null +++ b/assets/admin/src/schema/sections/post-list/index.js @@ -0,0 +1,25 @@ +/** + * Post & List domain schemas. + */ + +import { listSchema } from './list.js' +import { thumbSchema } from './thumb.js' +import { pageSchema } from './page.js' +import { filterSchema } from './filter.js' +import { featureSchema } from './feature.js' +import { paySchema } from './pay.js' +import { shareSchema } from './share.js' +import { commentSchema } from './comment.js' +import { submitSchema } from './submit.js' + +export const postListSchemas = { + list: listSchema, + thumb: thumbSchema, + page: pageSchema, + filter: filterSchema, + feature: featureSchema, + pay: paySchema, + share: shareSchema, + comment: commentSchema, + submit: submitSchema, +} diff --git a/assets/admin/src/schema/sections/post-list/pay.js b/assets/admin/src/schema/sections/post-list/pay.js new file mode 100644 index 00000000..e7724c59 --- /dev/null +++ b/assets/admin/src/schema/sections/post-list/pay.js @@ -0,0 +1,46 @@ +export const paySchema = { + key: 'post_list.pay', + title: '付费内容', + icon: 'fas fa-yen-sign', + description: '配置文章付费阅读、下载功能和购买用户显示。', + defaults: { + post_pay_enable: false, + post_pay_price_default: 0, + post_pay_free_logged_show: false, + post_pay_box_position: 'content', + post_pay_mark: '付费', + post_pay_show_user: false, + post_download_enable: false, + post_download_remote_s: false, + post_download_verify_nonce: true, + post_download_user_log: true, + post_download_post_log: true, + }, + groups: [ + { + key: 'basic', + title: '付费设置', + icon: 'fas fa-coins', + fields: [ + { key: 'post_pay_enable', label: '启用付费内容', type: 'switch', default: false }, + { key: 'post_pay_price_default', label: '默认价格', type: 'number', default: 0, min: 0, max: 9999, dependsOn: { key: 'post_pay_enable', value: true } }, + { key: 'post_pay_free_logged_show', label: '登录用户显示免费', type: 'switch', default: false, dependsOn: { key: 'post_pay_enable', value: true } }, + { key: 'post_pay_box_position', label: '付费框位置', type: 'radio', default: 'content', options: [{ label: '内容前', value: 'content' }, { label: '全文遮罩', value: 'mask' }], dependsOn: { key: 'post_pay_enable', value: true } }, + { key: 'post_pay_mark', label: '付费标记', type: 'input', default: '付费', dependsOn: { key: 'post_pay_enable', value: true } }, + { key: 'post_pay_show_user', label: '显示购买用户', type: 'switch', default: false, dependsOn: { key: 'post_pay_enable', value: true } }, + ], + }, + { + key: 'download', + title: '下载设置', + icon: 'fas fa-download', + fields: [ + { key: 'post_download_enable', label: '启用下载功能', type: 'switch', default: false }, + { key: 'post_download_remote_s', label: '远程文件下载', type: 'switch', default: false, dependsOn: { key: 'post_download_enable', value: true } }, + { key: 'post_download_verify_nonce', label: '下载鉴权', type: 'switch', default: true, dependsOn: { key: 'post_download_enable', value: true } }, + { key: 'post_download_user_log', label: '用户下载日志', type: 'switch', default: true, dependsOn: { key: 'post_download_enable', value: true } }, + { key: 'post_download_post_log', label: '文章下载日志', type: 'switch', default: true, dependsOn: { key: 'post_download_enable', value: true } }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/post-list/share.js b/assets/admin/src/schema/sections/post-list/share.js new file mode 100644 index 00000000..b65d0fc4 --- /dev/null +++ b/assets/admin/src/schema/sections/post-list/share.js @@ -0,0 +1,42 @@ +export const shareSchema = { + key: 'post_list.share', + title: '分享设置', + icon: 'fas fa-share-alt', + description: '配置微信分享凭据、海报分享和社交分享选项。', + defaults: { + share_wechat_s: false, + share_wechat_appid: '', + share_wechat_app_secret: '', + share_only_logo: false, + share_img_compatible_s: false, + share_img_byimg: '', + share_logo: '', + share_img: '', + share_desc: '扫一扫 立即查看', + }, + groups: [ + { + key: 'wechat', + title: '微信分享', + icon: 'fab fa-weixin', + fields: [ + { key: 'share_wechat_s', label: '启用微信分享', type: 'switch', default: false, help: '启用后文章页将支持微信 JSAPI 分享' }, + { key: 'share_wechat_appid', label: 'AppID', type: 'input', default: '', dependsOn: { key: 'share_wechat_s', value: true }, help: '微信公众号 AppID' }, + { key: 'share_wechat_app_secret', label: 'AppSecret', type: 'input', default: '', dependsOn: { key: 'share_wechat_s', value: true }, help: '微信公众号 AppSecret(请确保 JS 安全域名已配置)' }, + { key: 'share_only_logo', label: '仅显示 Logo', type: 'switch', default: false, help: '分享时仅显示站点 Logo' }, + ], + }, + { + key: 'poster', + title: '海报分享', + icon: 'fas fa-image', + fields: [ + { key: 'share_img_compatible_s', label: '兼容模式', type: 'switch', default: false, help: '启用后使用兼容模式生成分享海报' }, + { key: 'share_img_byimg', label: '海报默认图', type: 'media', default: '', help: '海报分享使用的默认背景图' }, + { key: 'share_logo', label: '海报 LOGO', type: 'media', default: '', help: '海报上叠加的站点 LOGO' }, + { key: 'share_img', label: '分享图片', type: 'media', default: '', help: '社交平台分享预览图' }, + { key: 'share_desc', label: '分享描述', type: 'textarea', default: '扫一扫 立即查看', help: '海报或分享卡片的描述文案' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/post-list/thumb.js b/assets/admin/src/schema/sections/post-list/thumb.js new file mode 100644 index 00000000..3345c629 --- /dev/null +++ b/assets/admin/src/schema/sections/post-list/thumb.js @@ -0,0 +1,70 @@ +export const thumbSchema = { + key: 'post_list.thumb', + title: '列表缩略图', + icon: 'fas fa-images', + description: '配置缩略图来源优先级、尺寸、占位图和多图模式。', + defaults: { + thumb_priority: 'featured,attachment,resource,custom', + thumb_fallback: 'placeholder', + thumb_spare_images: [], + thumb_lazy: true, + thumb_aspect_ratio: '16:9', + list_thumb_fit: 'cover', + thumb_multi_enabled: false, + thumb_multi_count: 3, + list_thumb_slides_s: true, + list_thumb_video_s: true, + list_thumb_video_mute_s: true, + thumb_postfirstimg_s: true, + thumb_catimg_s: true, + mult_thumb_cat: [], + }, + groups: [ + { + key: 'source', + title: '图片来源', + icon: 'fas fa-image', + fields: [ + { key: 'thumb_priority', label: '图片优先级', type: 'input', default: 'featured,attachment,resource,custom', help: '逗号分隔: featured(特色图), attachment(附件), resource(资源封面), custom(自定义), post_first_img(文章首图), category_cover(分类封面)' }, + { key: 'thumb_fallback', label: '无图时处理', type: 'radio', default: 'placeholder', options: [ + { label: '显示占位图', value: 'placeholder' }, + { label: '隐藏缩略图', value: 'hide' }, + { label: '随机封面', value: 'random' }, + ]}, + { key: 'thumb_spare_images', label: '备用缩略图', type: 'gallery', default: [], help: '当无图处理选择"随机封面"时从此图库随机选取', dependsOn: { key: 'thumb_fallback', value: 'random' } }, + { key: 'thumb_aspect_ratio', label: '图片比例', type: 'select', default: '16:9', options: [ + { label: '16:9 宽屏', value: '16:9' }, + { label: '4:3 标准', value: '4:3' }, + { label: '1:1 正方形', value: '1:1' }, + { label: '自适应', value: 'auto' }, + ]}, + { key: 'list_thumb_fit', label: '缩略图填充方式', type: 'radio', default: 'cover', options: [ + { label: '填充裁剪', value: 'cover' }, + { label: '完整显示', value: 'contain' }, + ]}, + { key: 'list_thumb_slides_s', label: '幻灯片缩略图', type: 'switch', default: true, help: '允许幻灯片作为列表缩略图' }, + { key: 'list_thumb_video_s', label: '视频缩略图', type: 'switch', default: true, help: '鼠标悬停时自动播放视频缩略图' }, + { key: 'list_thumb_video_mute_s', label: '视频缩略图静音', type: 'switch', default: true, dependsOn: { key: 'list_thumb_video_s', value: true }, help: '视频缩略图播放时静音' }, + { key: 'thumb_postfirstimg_s', label: '自动获取文章首图', type: 'switch', default: true, help: '自动从文章内容中提取第一张图片作为缩略图' }, + { key: 'thumb_postfirstimg_size', label: '首图尺寸', type: 'select', default: 'medium', options: [ + { label: '缩略图', value: 'thumbnail' }, + { label: '中等', value: 'medium' }, + { label: '大', value: 'large' }, + { label: '原始', value: 'full' }, + ], dependsOn: { key: 'thumb_postfirstimg_s', value: true }, help: '自动获取文章首图时使用的 WP 图片尺寸' }, + { key: 'thumb_catimg_s', label: '自动获取分类封面', type: 'switch', default: true, help: '无特色图时使用分类封面图作为缩略图' }, + ], + }, + { + key: 'multi', + title: '多图模式', + icon: 'fas fa-images', + fields: [ + { key: 'thumb_multi_enabled', label: '多图卡片', type: 'switch', default: false, help: '当文章有多张图片时,在列表中展示多图预览' }, + { key: 'thumb_multi_count', label: '最多显示几张', type: 'number', default: 3, min: 2, max: 6, dependsOn: { key: 'thumb_multi_enabled', value: true } }, + { key: 'mult_thumb_cat', label: '按分类多图显示', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '选择的分类下所有文章自动显示多张缩略图' }, + { key: 'thumb_lazy', label: '懒加载', type: 'switch', default: true }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/tools/backup.js b/assets/admin/src/schema/sections/tools/backup.js new file mode 100644 index 00000000..8700e521 --- /dev/null +++ b/assets/admin/src/schema/sections/tools/backup.js @@ -0,0 +1,29 @@ +export const backupSchema = { + key: 'tools.backup', + title: '数据备份', + icon: 'fas fa-cloud-download-alt', + description: '站点数据备份与恢复。', + defaults: {}, + groups: [ + { + key: 'backup', + title: '备份设置', + icon: 'fas fa-save', + fields: [ + { key: 'backup_auto', label: '自动备份', type: 'switch', help: '启用自动定时备份' }, + { + key: 'backup_frequency', + label: '备份频率', + type: 'select', + options: [ + { label: '每天', value: 'daily' }, + { label: '每周', value: 'weekly' }, + { label: '每月', value: 'monthly' }, + ], + }, + { key: 'backup_retain', label: '保留份数', type: 'number', min: 3, max: 20, default: 7 }, + { key: 'backup_now', label: '立即备份', type: 'button', help: '手动触发一次完整备份' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/tools/email-rollback.js b/assets/admin/src/schema/sections/tools/email-rollback.js new file mode 100644 index 00000000..c83880ce --- /dev/null +++ b/assets/admin/src/schema/sections/tools/email-rollback.js @@ -0,0 +1,29 @@ +export const emailRollbackSchema = { + key: 'tools.email-rollback', + title: '邮件回滚', + icon: 'fas fa-undo', + description: '批量回滚已发送的通知邮件。', + defaults: {}, + groups: [ + { + key: 'rollback', + title: '邮件回滚', + icon: 'fas fa-envelope-open', + fields: [ + { + key: 'rollback_type', + label: '回滚类型', + type: 'select', + options: [ + { label: '按时间范围', value: 'date_range' }, + { label: '按邮件类型', value: 'email_type' }, + { label: '全部回滚', value: 'all' }, + ], + }, + { key: 'rollback_date_from', label: '开始日期', type: 'date' }, + { key: 'rollback_date_to', label: '结束日期', type: 'date' }, + { key: 'rollback_execute', label: '执行回滚', type: 'button', danger: true, help: '此操作不可逆' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/tools/health.js b/assets/admin/src/schema/sections/tools/health.js new file mode 100644 index 00000000..b439bdc1 --- /dev/null +++ b/assets/admin/src/schema/sections/tools/health.js @@ -0,0 +1,21 @@ +export const healthSchema = { + key: 'tools.health', + title: '健康检查', + icon: 'fas fa-heartbeat', + description: '检查主题运行环境和依赖状态。', + defaults: {}, + groups: [ + { + key: 'check', + title: '环境检查', + icon: 'fas fa-stethoscope', + fields: [ + { key: 'php_version', label: 'PHP 版本', type: 'display', help: '建议 7.4+' }, + { key: 'wp_version', label: 'WordPress 版本', type: 'display', help: '建议 5.8+' }, + { key: 'memory_limit', label: '内存限制', type: 'display', help: '建议 256M+' }, + { key: 'upload_max_size', label: '上传限制', type: 'display' }, + { key: 'extensions', label: '必要扩展', type: 'display', help: 'curl, mbstring, json, gd' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/tools/import-export.js b/assets/admin/src/schema/sections/tools/import-export.js new file mode 100644 index 00000000..a9d6effb --- /dev/null +++ b/assets/admin/src/schema/sections/tools/import-export.js @@ -0,0 +1,26 @@ +export const importExportSchema = { + key: 'tools.import-export', + title: '导入导出', + icon: 'fas fa-exchange-alt', + description: '主题配置的导入和导出。', + defaults: {}, + groups: [ + { + key: 'export', + title: '导出配置', + icon: 'fas fa-file-export', + fields: [ + { key: 'export_settings', label: '导出主题设置', type: 'button', help: '导出当前所有主题配置为 JSON 文件' }, + { key: 'export_options', label: '导出 WordPress 选项', type: 'button', help: '导出 aksrt_settings 等选项' }, + ], + }, + { + key: 'import', + title: '导入配置', + icon: 'fas fa-file-import', + fields: [ + { key: 'import_settings', label: '导入主题设置', type: 'file', accept: '.json', help: '导入主题配置文件(将覆盖当前设置)' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/users/notifications.js b/assets/admin/src/schema/sections/users/notifications.js new file mode 100644 index 00000000..61d792c6 --- /dev/null +++ b/assets/admin/src/schema/sections/users/notifications.js @@ -0,0 +1,74 @@ +export const notificationsSchema = { + key: 'users.notifications', + title: '消息通知', + icon: 'fas fa-bell', + description: '站内通知、私信、通知策略配置。', + defaults: { + message_s: true, + msg_center_rewrite_slug: 'message', + message_icon_show: ['nav_menu'], + message_paginate_type: 'ajax_lists', + message_user_set: true, + message_close_msg_type: [], + message_auto_clear_s: true, + message_auto_clear_time: 365, + message_auto_clear_types: ['posts', 'comment', 'favorite', 'like', 'hot', 'followed', 'system'], + private_s: true, + private_smilie_s: true, + private_code_s: false, + private_image_s: true, + private_upload_img: false, + private_quick_s: true, + private_quick_often: '你好,请问有什么可以帮你的?\n感谢你的反馈\n已处理,请查收', + private_submit_text: '发送', + private_placeholder: '请输入消息...', + }, + groups: [ + { + key: 'basic', + title: '基础设置', + icon: 'fas fa-cog', + fields: [ + { key: 'message_s', label: '站内通知总开关', type: 'switch', default: true }, + { key: 'msg_center_rewrite_slug', label: '消息中心 URL 别名', type: 'input', default: 'message' }, + { key: 'message_icon_show', label: '通知图标位置', type: 'checkbox', default: ['nav_menu'], options: [{ label: '导航菜单', value: 'nav_menu' }, { label: '用户头像', value: 'avatar' }, { label: '移动端底部', value: 'mobile_bar' }] }, + { key: 'message_paginate_type', label: '列表翻页模式', type: 'radio', default: 'ajax_lists', options: [{ label: 'AJAX 翻页', value: 'ajax_lists' }, { label: '传统翻页', value: 'default' }] }, + { key: 'message_user_set', label: '允许用户设置推送', type: 'switch', default: true, help: '允许用户在前台自行设置通知推送偏好' }, + ], + }, + { + key: 'types', + title: '通知类型', + icon: 'fas fa-list-check', + fields: [ + { key: 'message_close_msg_type', label: '关闭部分推送', type: 'checkbox', default: [], options: [{ label: '文章通知', value: 'posts' }, { label: '评论回复', value: 'comment' }, { label: '收藏通知', value: 'favorite' }, { label: '点赞通知', value: 'like' }, { label: '热门通知', value: 'hot' }, { label: '关注通知', value: 'followed' }, { label: '系统通知', value: 'system' }, { label: '支付通知', value: 'pay' }, { label: '提现回复', value: 'withdraw_reply' }, { label: '勋章通知', value: 'medal' }, { label: '认证回复', value: 'auth_apply_reply' }], help: '勾选的类型将不向用户推送通知' }, + ], + }, + { + key: 'auto_clear', + title: '自动清理', + icon: 'fas fa-broom', + fields: [ + { key: 'message_auto_clear_s', label: '自动清理已读消息', type: 'switch', default: true }, + { key: 'message_auto_clear_time', label: '过期天数', type: 'number', default: 365, min: 1, max: 3650, help: '已读消息超过此天数将被自动清理', dependsOn: { key: 'message_auto_clear_s', value: true } }, + { key: 'message_auto_clear_types', label: '清理类型', type: 'checkbox', default: ['posts', 'comment', 'favorite', 'like', 'hot', 'followed', 'system'], options: [{ label: '文章通知', value: 'posts' }, { label: '评论回复', value: 'comment' }, { label: '收藏通知', value: 'favorite' }, { label: '点赞通知', value: 'like' }, { label: '热门通知', value: 'hot' }, { label: '关注通知', value: 'followed' }, { label: '系统通知', value: 'system' }, { label: '支付通知', value: 'pay' }, { label: '提现回复', value: 'withdraw_reply' }, { label: '勋章通知', value: 'medal' }, { label: '认证回复', value: 'auth_apply_reply' }, { label: '下载通知', value: 'download' }, { label: '积分变动', value: 'credits' }, { label: '私信', value: 'private' }, { label: '举报反馈', value: 'report' }], dependsOn: { key: 'message_auto_clear_s', value: true } }, + ], + }, + { + key: 'private', + title: '私信设置', + icon: 'fas fa-envelope', + fields: [ + { key: 'private_s', label: '启用私信功能', type: 'switch', default: true }, + { key: 'private_smilie_s', label: '支持表情', type: 'switch', default: true, dependsOn: { key: 'private_s', value: true } }, + { key: 'private_code_s', label: '支持代码', type: 'switch', default: false, dependsOn: { key: 'private_s', value: true } }, + { key: 'private_image_s', label: '支持图片', type: 'switch', default: true, dependsOn: { key: 'private_s', value: true } }, + { key: 'private_upload_img', label: '支持上传图片', type: 'switch', default: false, dependsOn: { key: 'private_s', value: true } }, + { key: 'private_quick_s', label: '快捷回复', type: 'switch', default: true, dependsOn: { key: 'private_s', value: true } }, + { key: 'private_quick_often', label: '常用快捷回复', type: 'textarea', default: '你好,请问有什么可以帮你的?\n感谢你的反馈\n已处理,请查收', help: '一行一条快捷回复文案', dependsOn: { key: 'private_s', value: true } }, + { key: 'private_submit_text', label: '发送按钮文案', type: 'input', default: '发送', dependsOn: { key: 'private_s', value: true } }, + { key: 'private_placeholder', label: '输入占位符', type: 'input', default: '请输入消息...', dependsOn: { key: 'private_s', value: true } }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/users/oauth.js b/assets/admin/src/schema/sections/users/oauth.js new file mode 100644 index 00000000..a8abd3c3 --- /dev/null +++ b/assets/admin/src/schema/sections/users/oauth.js @@ -0,0 +1,211 @@ +export const oauthSchema = { + key: 'users.oauth', + title: '第三方登录', + icon: 'fas fa-share-alt', + description: 'GitHub、Gitee、QQ、Google 等第三方登录配置。', + defaults: { + oauth_s: false, + oauth_proxy_mode: 'close', + oauth_proxy_secret: '', + oauth_proxy_server_url: '', + oauth_proxy_server_providers: ['github', 'gitee', 'qq', 'google'], + oauth_proxy_client_providers: ['github', 'gitee', 'qq', 'google'], + oauth_proxy_gzh_type: 'service', + oauth_bind_type: 'auto', + oauth_button_lg: false, + weixingzh_priority: false, + weixingzh_auto: false, + weixingzh_auto_expires: 3600, + oauth_github_s: false, + oauth_gitee_s: false, + oauth_qq_s: false, + oauth_weixingzh_s: false, + oauth_weixin_s: false, + oauth_weibo_s: false, + oauth_baidu_s: false, + oauth_alipay_s: false, + clogin_s: false, + clogin_url: '', + clogin_id: '', + clogin_key: '', + clogin_oauth_s: ['qq', 'weixin', 'weibo', 'alipay'], + github_appid: '', + github_appkey: '', + gitee_appid: '', + gitee_appkey: '', + qq_appid: '', + qq_appkey: '', + weixin_appid: '', + weixin_appsecret: '', + weixingzh_gzh_type: 'service', + weixingzh_token: '', + weixingzh_token_nocheck: false, + weixingzh_encoding_aes_key: '', + weixingzh_appid: '', + weixingzh_appsecret: '', + weixingzh_code_qrcode: '', + weixingzh_code_keyword: '登录', + weixingzh_code_reply: '您的验证码是:%s,有效期 %d 分钟。', + weixingzh_code_subscribe_reply: '欢迎关注!回复「%s」获取登录验证码。', + weixingzh_subscribe_msg: '', + weixingzh_new_subscribe_msg: '', + weixingzh_scan_msg: '', + weixingzh_auto_reply_text: '', + weixingzh_auto_reply_search: '', + weixingzh_auto_reply_image: '', + weixingzh_auto_reply_voice: '', + weixingzh_auto_reply_default: '暂不支持此类型的消息', + weixingzh_gzh_menu: '', + weibo_appid: '', + weibo_appkey: '', + baidu_appid: '', + baidu_appkey: '', + alipay_appid: '', + alipay_privatekey: '', + alipay_publickey: '', + }, + groups: [ + { + key: 'basic', + title: '基础设置', + icon: 'fas fa-cog', + fields: [ + { key: 'oauth_s', label: '启用第三方登录', type: 'switch', default: false }, + { key: 'oauth_proxy_mode', label: '代理登录模式', type: 'radio', default: 'close', options: [{ label: '关闭', value: 'close' }, { label: '服务端', value: 'server' }, { label: '客户端', value: 'client' }] }, + { key: 'oauth_proxy_secret', label: '代理 HMAC 密钥', type: 'input', default: '', dependsOn: { key: 'oauth_proxy_mode', not: 'close' } }, + { key: 'oauth_proxy_server_url', label: '服务端地址', type: 'input', default: '', dependsOn: { key: 'oauth_proxy_mode', value: 'client' } }, + { key: 'oauth_proxy_server_providers', label: '服务端允许平台', type: 'checkbox', default: ['github', 'gitee', 'qq', 'google'], options: [{ label: 'GitHub', value: 'github' }, { label: 'Gitee', value: 'gitee' }, { label: 'QQ', value: 'qq' }, { label: 'Google', value: 'google' }, { label: '微博', value: 'weibo' }, { label: '微信', value: 'weixin' }, { label: '百度', value: 'baidu' }, { label: '支付宝', value: 'alipay' }], dependsOn: { key: 'oauth_proxy_mode', value: 'server' } }, + { key: 'oauth_proxy_client_providers', label: '客户端启用平台', type: 'checkbox', default: ['github', 'gitee', 'qq', 'google'], options: [{ label: 'GitHub', value: 'github' }, { label: 'Gitee', value: 'gitee' }, { label: 'QQ', value: 'qq' }, { label: 'Google', value: 'google' }, { label: '微博', value: 'weibo' }, { label: '微信', value: 'weixin' }, { label: '百度', value: 'baidu' }, { label: '支付宝', value: 'alipay' }], dependsOn: { key: 'oauth_proxy_mode', value: 'client' } }, + { key: 'oauth_proxy_gzh_type', label: '代理公众号类型', type: 'radio', default: 'service', options: [{ label: '服务号', value: 'service' }, { label: '订阅号', value: 'subscribe' }], dependsOn: { key: 'oauth_proxy_mode', not: 'close' } }, + { key: 'oauth_bind_type', label: '绑定方式', type: 'radio', default: 'auto', options: [{ label: '自动绑定', value: 'auto' }, { label: '手动绑定', value: 'manual' }] }, + { key: 'oauth_button_lg', label: '大按钮样式', type: 'switch', default: false }, + { key: 'weixingzh_priority', label: 'PC端优先微信扫码', type: 'switch', default: false, help: '开启后 PC 端登录框优先显示微信公众号扫码' }, + { key: 'weixingzh_auto', label: '微信APP内自动登录', type: 'switch', default: false, help: '在微信内置浏览器中自动使用公众号授权登录' }, + { key: 'weixingzh_auto_expires', label: '自动登录频率限制 (秒)', type: 'number', default: 3600, min: 0, max: 86400, dependsOn: { key: 'weixingzh_auto', value: true } }, + ], + }, + { + key: 'providers', + title: '登录提供商', + icon: 'fas fa-th', + fields: [ + { key: 'oauth_github_s', label: 'GitHub登录', type: 'switch', default: false }, + { key: 'oauth_gitee_s', label: 'Gitee登录', type: 'switch', default: false }, + { key: 'oauth_qq_s', label: 'QQ登录', type: 'switch', default: false }, + { key: 'oauth_weixingzh_s', label: '微信公众号登录', type: 'switch', default: false }, + { key: 'oauth_weixin_s', label: '微信开放平台登录', type: 'switch', default: false }, + { key: 'oauth_weibo_s', label: '微博登录', type: 'switch', default: false }, + { key: 'oauth_baidu_s', label: '百度登录', type: 'switch', default: false }, + { key: 'oauth_alipay_s', label: '支付宝登录', type: 'switch', default: false }, + { key: 'clogin_s', label: '彩虹聚合登录', type: 'switch', default: false, help: '通过第三方聚合登录平台统一接入 QQ/微信/微博/支付宝等' }, + ], + }, + { + key: 'github', + title: 'GitHub配置', + icon: 'fab fa-github', + fields: [ + { key: 'github_appid', label: 'Client ID', type: 'input', default: '' }, + { key: 'github_appkey', label: 'Client Secret', type: 'input', default: '' }, + ], + }, + { + key: 'gitee', + title: 'Gitee配置', + icon: 'fas fa-code-branch', + fields: [ + { key: 'gitee_appid', label: 'Client ID', type: 'input', default: '' }, + { key: 'gitee_appkey', label: 'Client Secret', type: 'input', default: '' }, + ], + }, + { + key: 'qq', + title: 'QQ配置', + icon: 'fab fa-qq', + fields: [ + { key: 'qq_appid', label: 'App ID', type: 'input', default: '' }, + { key: 'qq_appkey', label: 'App Key', type: 'input', default: '' }, + ], + }, + { + key: 'weixin', + title: '微信开放平台配置', + icon: 'fab fa-weixin', + fields: [ + { key: 'weixin_appid', label: 'AppID', type: 'input', default: '' }, + { key: 'weixin_appsecret', label: 'AppSecret', type: 'input', default: '' }, + ], + }, + { + key: 'weixingzh', + title: '微信公众号配置', + icon: 'fab fa-weixin', + fields: [ + { key: 'weixingzh_gzh_type', label: '公众号类型', type: 'radio', default: 'service', options: [{ label: '服务号', value: 'service' }, { label: '订阅号', value: 'subscribe' }] }, + { key: 'weixingzh_token', label: 'Token', type: 'input', default: '' }, + { key: 'weixingzh_token_nocheck', label: '不验证Token', type: 'switch', default: false, help: '开发调试时可跳过 Token 验证' }, + { key: 'weixingzh_encoding_aes_key', label: 'EncodingAESKey', type: 'input', default: '' }, + { key: 'weixingzh_appid', label: 'AppID', type: 'input', default: '' }, + { key: 'weixingzh_appsecret', label: 'AppSecret', type: 'input', default: '' }, + { key: 'weixingzh_code_qrcode', label: '公众号二维码', type: 'media', default: '', help: '引导用户扫码关注登录的二维码图片' }, + { key: 'weixingzh_code_keyword', label: '登录回复关键词', type: 'input', default: '登录', help: '用户在公众号内回复此关键词触发登录验证码' }, + { key: 'weixingzh_code_reply', label: '验证码回复模板', type: 'textarea', default: '您的验证码是:%s,有效期 %d 分钟。', help: '%s=验证码,%d=有效分钟数' }, + { key: 'weixingzh_code_subscribe_reply', label: '新关注回复模板', type: 'textarea', default: '欢迎关注!回复「%s」获取登录验证码。', help: '%s=登录关键词' }, + { key: 'weixingzh_subscribe_msg', label: '关注后回复', type: 'textarea', default: '', help: '用户关注公众号后的自动回复文案' }, + { key: 'weixingzh_new_subscribe_msg', label: '新用户关注回复', type: 'textarea', default: '', help: '首次关注用户的专属回复文案,留空使用通用回复' }, + { key: 'weixingzh_scan_msg', label: '扫码回复', type: 'textarea', default: '', help: '用户扫码关注公众号后的回复文案' }, + { key: 'weixingzh_auto_reply_text', label: '文本自动回复', type: 'textarea', default: '', help: '收到文本消息的自动回复' }, + { key: 'weixingzh_auto_reply_search', label: '搜索回复', type: 'textarea', default: '', help: '未匹配关键词时的搜索提示回复' }, + { key: 'weixingzh_auto_reply_image', label: '图片回复', type: 'textarea', default: '', help: '收到图片消息的自动回复' }, + { key: 'weixingzh_auto_reply_voice', label: '语音回复', type: 'textarea', default: '', help: '收到语音消息的自动回复' }, + { key: 'weixingzh_auto_reply_default', label: '默认回复', type: 'textarea', default: '暂不支持此类型的消息', help: '其他未匹配消息类型的兜底回复' }, + { key: 'weixingzh_gzh_menu', label: '自定义菜单 JSON', type: 'textarea', default: '', help: '微信公众号自定义菜单 JSON 配置,留空不创建菜单' }, + ], + }, + { + key: 'weibo', + title: '微博配置', + icon: 'fab fa-weibo', + fields: [ + { key: 'weibo_appid', label: 'App Key', type: 'input', default: '' }, + { key: 'weibo_appkey', label: 'App Secret', type: 'input', default: '' }, + ], + }, + { + key: 'baidu', + title: '百度配置', + icon: 'fas fa-paw', + fields: [ + { key: 'baidu_appid', label: 'API Key', type: 'input', default: '' }, + { key: 'baidu_appkey', label: 'Secret Key', type: 'input', default: '' }, + ], + }, + { + key: 'clogin', + title: '彩虹聚合登录', + icon: 'fas fa-cloud', + fields: [ + { key: 'clogin_url', label: '接口地址', type: 'input', default: '', help: '如 https://xxx.com 不要带 connect.php', dependsOn: { key: 'clogin_s', value: true } }, + { key: 'clogin_id', label: 'AppID', type: 'input', default: '', dependsOn: { key: 'clogin_s', value: true } }, + { key: 'clogin_key', label: 'AppKey', type: 'input', default: '', dependsOn: { key: 'clogin_s', value: true } }, + { key: 'clogin_oauth_s', label: '启用平台', type: 'checkbox', default: ['qq', 'weixin', 'weibo', 'alipay'], options: [ + { label: 'QQ', value: 'qq' }, + { label: '微信', value: 'weixin' }, + { label: '微博', value: 'weibo' }, + { label: '支付宝', value: 'alipay' }, + { label: '百度', value: 'baidu' }, + ], dependsOn: { key: 'clogin_s', value: true } }, + ], + }, + { + key: 'alipay', + title: '支付宝配置', + icon: 'fab fa-alipay', + fields: [ + { key: 'alipay_appid', label: 'AppID', type: 'input', default: '' }, + { key: 'alipay_privatekey', label: '应用私钥', type: 'textarea', rows: 3, default: '' }, + { key: 'alipay_publickey', label: '支付宝公钥', type: 'input', default: '' }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/users/report.js b/assets/admin/src/schema/sections/users/report.js new file mode 100644 index 00000000..c601b28c --- /dev/null +++ b/assets/admin/src/schema/sections/users/report.js @@ -0,0 +1,58 @@ +export const reportSchema = { + key: 'users.report', + title: '禁封/举报', + icon: 'fas fa-flag', + description: '配置举报入口、公开限制提示和后续治理分类,为封禁/举报流程预留稳定结构。', + defaults: { + user_report_enabled: false, + user_report_public_entry: true, + user_report_public_notice: true, + user_report_ban_notice_text: '当前账号处于限制状态,部分互动能力已关闭。', + user_report_categories: 'spam|垃圾信息\nabuse|人身攻击\ncopyright|版权投诉', + ban_preset_reason: 'spam|垃圾信息\nabuse|人身攻击\nviolation|违规操作', + ban_appeal_desc: '', + ban_appeal_keys: '', + user_report_img_s: true, + user_report_desc: '', + }, + groups: [ + { + key: 'entry', + title: '前台入口', + icon: 'fas fa-door-open', + fields: [ + { key: 'user_report_enabled', label: '启用举报/禁封结构', type: 'switch', default: false }, + { key: 'user_report_public_entry', label: '公开主页展示举报入口', type: 'switch', default: true, dependsOn: { key: 'user_report_enabled', value: true } }, + { key: 'user_report_public_notice', label: '公开显示限制提示', type: 'switch', default: true, dependsOn: { key: 'user_report_enabled', value: true } }, + { key: 'user_report_ban_notice_text', label: '限制提示文案', type: 'input', default: '当前账号处于限制状态,部分互动能力已关闭。', dependsOn: { key: 'user_report_public_notice', value: true } }, + ], + }, + { + key: 'taxonomy', + title: '举报分类', + icon: 'fas fa-list', + fields: [ + { key: 'user_report_categories', label: '举报分类', type: 'textarea', default: 'spam|垃圾信息\nabuse|人身攻击\ncopyright|版权投诉', help: '每行一个分类:slug|显示名称', dependsOn: { key: 'user_report_enabled', value: true } }, + ], + }, + { + key: 'ban', + title: '禁封设置', + icon: 'fas fa-ban', + fields: [ + { key: 'ban_preset_reason', label: '预设封禁原因', type: 'textarea', default: 'spam|垃圾信息\nabuse|人身攻击\nviolation|违规操作', help: '每行一条:slug|显示名称', dependsOn: { key: 'user_report_enabled', value: true } }, + { key: 'ban_appeal_desc', label: '申诉说明', type: 'textarea', default: '', dependsOn: { key: 'user_report_enabled', value: true } }, + { key: 'ban_appeal_keys', label: '申诉关键词', type: 'input', default: '', help: '逗号分隔', dependsOn: { key: 'user_report_enabled', value: true } }, + ], + }, + { + key: 'image', + title: '图片举报', + icon: 'fas fa-image', + fields: [ + { key: 'user_report_img_s', label: '启用图片举报', type: 'switch', default: true, dependsOn: { key: 'user_report_enabled', value: true } }, + { key: 'user_report_desc', label: '举报说明', type: 'textarea', default: '', dependsOn: { key: 'user_report_img_s', value: true } }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/users/signin.js b/assets/admin/src/schema/sections/users/signin.js new file mode 100644 index 00000000..8a8c90f7 --- /dev/null +++ b/assets/admin/src/schema/sections/users/signin.js @@ -0,0 +1,27 @@ +export const signinSchema = { + key: 'users.signin', + title: '签到奖励', + icon: 'fas fa-calendar-check', + description: '配置签到规则、连续签到奖励和签到页面。', + defaults: { + signin_enabled: true, + signin_reward: 1, + signin_consecutive_bonus: true, + signin_consecutive_days: 7, + signin_bonus_reward: 5, + }, + groups: [ + { + key: 'rules', + title: '签到规则', + icon: 'fas fa-calendar-alt', + fields: [ + { key: 'signin_enabled', label: '启用签到', type: 'switch', default: true }, + { key: 'signin_reward', label: '每日签到奖励', type: 'number', default: 1, min: 0, max: 100, dependsOn: { key: 'signin_enabled', value: true } }, + { key: 'signin_consecutive_bonus', label: '连续签到加成', type: 'switch', default: true, dependsOn: { key: 'signin_enabled', value: true } }, + { key: 'signin_consecutive_days', label: '加成周期 (天)', type: 'number', default: 7, min: 2, max: 30, dependsOn: { key: 'signin_consecutive_bonus', value: true } }, + { key: 'signin_bonus_reward', label: '周期奖励', type: 'number', default: 5, min: 1, max: 100, dependsOn: { key: 'signin_consecutive_bonus', value: true } }, + ], + }, + ], +} diff --git a/assets/admin/src/schema/sections/users/sms.js b/assets/admin/src/schema/sections/users/sms.js new file mode 100644 index 00000000..eb0bbe0c --- /dev/null +++ b/assets/admin/src/schema/sections/users/sms.js @@ -0,0 +1,144 @@ +export const smsSchema = { + key: 'users.sms', + title: '短信接口', + icon: 'fas fa-sms', + description: '配置短信验证码和短信通知服务的供应商、模板和限流策略。', + defaults: { + sms_enabled: false, + sms_provider: 'aliyun', + sms_number_limit_ch: true, + sms_access_key: '', + sms_access_secret: '', + sms_sign_name: '', + sms_template_code: '', + sms_code_length: 6, + sms_code_expire: 300, + sms_rate_limit: 60, + sms_daily_limit: 10, + sms_notify_bind: true, + sms_notify_login: false, + sms_notify_register: true, + sms_notify_reset_password: true, + sms_ali_api_type: 'sms', + sms_region_id: 'cn-hangzhou', + sms_tencent_app_id: '', + sms_tencent_app_key: '', + sms_tencent_secret_id: '', + sms_tencent_secret_key: '', + sms_tencent_region: 'ap-guangzhou', + sms_huawei_sender: '', + sms_huawei_signature: '', + sms_huawei_endpoint: '', + sms_custom_url: '', + sms_custom_method: 'POST', + sms_custom_auth_type: 'header', + sms_custom_auth_key: '', + sms_custom_auth_value: '', + sms_custom_headers: '', + sms_custom_body: '', + sms_custom_success_rule: '', + }, + groups: [ + { + key: 'provider', + title: '供应商配置', + icon: 'fas fa-plug', + fields: [ + { key: 'sms_enabled', label: '启用短信服务', type: 'switch', default: false, help: '开启后支持短信验证码和短信通知' }, + { + key: 'sms_provider', + label: '短信供应商', + type: 'select', + default: 'aliyun', + options: [ + { label: '阿里云短信', value: 'aliyun' }, + { label: '腾讯云短信', value: 'tencent' }, + { label: '华为云短信', value: 'huawei' }, + { label: '自定义接口', value: 'custom' }, + ], + dependsOn: { key: 'sms_enabled', value: true }, + }, + { key: 'sms_number_limit_ch', label: '仅发国内', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true }, help: '开启后仅支持发送国内短信,关闭后可发送港澳台及国际短信' }, + { key: 'sms_access_key', label: 'AccessKey / SecretId / AppKey', type: 'input', default: '', dependsOn: { key: 'sms_enabled', value: true }, help: '阿里云填 AccessKey,腾讯云填 SecretId,华为云填 AppKey' }, + { key: 'sms_access_secret', label: 'AccessSecret / SecretKey / AppSecret', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_enabled', value: true } }, + ], + }, + { + key: 'aliyun', + title: '阿里云配置', + icon: 'fas fa-cloud', + fields: [ + { key: 'sms_ali_api_type', label: '接口类型', type: 'radio', default: 'sms', options: [{ label: '短信服务', value: 'sms' }, { label: '号码认证', value: 'verify' }], dependsOn: { key: 'sms_provider', value: 'aliyun' } }, + { key: 'sms_region_id', label: 'RegionId', type: 'input', default: 'cn-hangzhou', dependsOn: { key: 'sms_provider', value: 'aliyun' } }, + ], + }, + { + key: 'tencent', + title: '腾讯云配置', + icon: 'fas fa-cloud', + fields: [ + { key: 'sms_tencent_app_id', label: 'SdkAppId', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' }, help: '腾讯云短信应用 ID' }, + { key: 'sms_tencent_app_key', label: 'AppKey', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' } }, + { key: 'sms_tencent_secret_id', label: 'SecretId', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' } }, + { key: 'sms_tencent_secret_key', label: 'SecretKey', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_provider', value: 'tencent' } }, + { key: 'sms_tencent_region', label: '地域', type: 'input', default: 'ap-guangzhou', dependsOn: { key: 'sms_provider', value: 'tencent' } }, + ], + }, + { + key: 'huawei', + title: '华为云配置', + icon: 'fas fa-cloud', + fields: [ + { key: 'sms_huawei_sender', label: '通道号 (sender)', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'huawei' } }, + { key: 'sms_huawei_signature', label: '短信签名', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'huawei' }, help: '华为云短信签名内容' }, + { key: 'sms_huawei_endpoint', label: 'API Endpoint', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'huawei' }, help: '留空使用默认 endpoint' }, + ], + }, + { + key: 'custom', + title: '自定义接口配置', + icon: 'fas fa-plug', + fields: [ + { key: 'sms_custom_url', label: '请求 URL', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '支持 {phone} {code} {time} 变量' }, + { key: 'sms_custom_method', label: '请求方法', type: 'select', default: 'POST', options: [{ label: 'POST', value: 'POST' }, { label: 'GET', value: 'GET' }, { label: 'PUT', value: 'PUT' }], dependsOn: { key: 'sms_provider', value: 'custom' } }, + { key: 'sms_custom_auth_type', label: '鉴权方式', type: 'select', default: 'header', options: [{ label: 'Header', value: 'header' }], dependsOn: { key: 'sms_provider', value: 'custom' } }, + { key: 'sms_custom_auth_key', label: '鉴权 Header 名', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'custom' } }, + { key: 'sms_custom_auth_value', label: '鉴权 Header 值', type: 'input', inputType: 'password', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '支持 {phone} {code} {time} 变量' }, + { key: 'sms_custom_headers', label: '自定义 Headers', type: 'textarea', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '每行一个,格式:Key: Value' }, + { key: 'sms_custom_body', label: '请求 Body', type: 'textarea', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: 'JSON 格式,支持 {phone} {code} {time} 变量' }, + { key: 'sms_custom_success_rule', label: '成功判定规则', type: 'input', default: '', dependsOn: { key: 'sms_provider', value: 'custom' }, help: '格式:status:200 或 json:code=0 或 contains:success' }, + ], + }, + { + key: 'template', + title: '短信模板', + icon: 'fas fa-sms', + fields: [ + { key: 'sms_sign_name', label: '短信签名', type: 'input', default: '', dependsOn: { key: 'sms_enabled', value: true }, help: '在短信服务商申请的短信签名(所有供应商共用此字段)' }, + { key: 'sms_template_code', label: '验证码模板 ID', type: 'input', default: '', dependsOn: { key: 'sms_enabled', value: true }, help: '所有供应商共用此模板 ID' }, + { key: 'sms_code_length', label: '验证码长度', type: 'number', default: 6, min: 4, max: 8, dependsOn: { key: 'sms_enabled', value: true } }, + { key: 'sms_code_expire', label: '验证码有效期 (秒)', type: 'number', default: 300, min: 60, max: 600, dependsOn: { key: 'sms_enabled', value: true } }, + ], + }, + { + key: 'rate_limit', + title: '限流策略', + icon: 'fas fa-shield-alt', + fields: [ + { key: 'sms_rate_limit', label: '发送间隔 (秒)', type: 'number', default: 60, min: 10, max: 300, dependsOn: { key: 'sms_enabled', value: true }, help: '同一手机号两次发送的最小间隔' }, + { key: 'sms_daily_limit', label: '每日发送上限', type: 'number', default: 10, min: 1, max: 100, dependsOn: { key: 'sms_enabled', value: true }, help: '同一手机号每天最多发送条数' }, + ], + }, + { + key: 'triggers', + title: '通知场景', + icon: 'fas fa-bell', + fields: [ + { key: 'sms_notify_bind', label: '绑定手机时验证', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true } }, + { key: 'sms_notify_login', label: '短信登录', type: 'switch', default: false, dependsOn: { key: 'sms_enabled', value: true } }, + { key: 'sms_notify_register', label: '注册验证', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true } }, + { key: 'sms_notify_reset_password', label: '找回密码', type: 'switch', default: true, dependsOn: { key: 'sms_enabled', value: true } }, + ], + }, + ], +} \ No newline at end of file diff --git a/assets/admin/src/schema/sections/users/wechat.js b/assets/admin/src/schema/sections/users/wechat.js new file mode 100644 index 00000000..2ba16fa5 --- /dev/null +++ b/assets/admin/src/schema/sections/users/wechat.js @@ -0,0 +1,51 @@ +export const wechatSchema = { + key: 'users.wechat', + title: '微信通知', + icon: 'fab fa-weixin', + description: '配置微信模板消息、订阅/解绑和站外通知策略。', + defaults: { + wechat_notify_enabled: false, + wechat_app_id: '', + wechat_app_secret: '', + wechat_template_id: '', + wechat_token: '', + wechat_encoding_aes_key: '', + wechat_notify_comment: true, + wechat_notify_reply: true, + wechat_notify_system: true, + wechat_bind_entry: true, + }, + groups: [ + { + key: 'account', + title: '公众号配置', + icon: 'fab fa-weixin', + fields: [ + { key: 'wechat_notify_enabled', label: '启用微信通知', type: 'switch', default: false, help: '开启后用户可绑定微信接收通知' }, + { key: 'wechat_app_id', label: 'AppID', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } }, + { key: 'wechat_app_secret', label: 'AppSecret', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } }, + { key: 'wechat_template_id', label: '模板消息 ID', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '在微信公众平台申请的模板消息 ID' }, + ], + }, + { + key: 'verify', + title: '服务器验证', + icon: 'fas fa-check-circle', + fields: [ + { key: 'wechat_token', label: 'Token', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '微信公众平台服务器配置中的 Token' }, + { key: 'wechat_encoding_aes_key', label: 'EncodingAESKey', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } }, + ], + }, + { + key: 'triggers', + title: '通知触发器', + icon: 'fas fa-bell', + fields: [ + { key: 'wechat_notify_comment', label: '新评论通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } }, + { key: 'wechat_notify_reply', label: '评论回复通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } }, + { key: 'wechat_notify_system', label: '系统通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } }, + { key: 'wechat_bind_entry', label: '显示绑定入口', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '在用户中心显示微信绑定/解绑按钮' }, + ], + }, + ], +} diff --git a/assets/admin/src/stores/admin-ui.js b/assets/admin/src/stores/admin-ui.js new file mode 100644 index 00000000..d36c266c --- /dev/null +++ b/assets/admin/src/stores/admin-ui.js @@ -0,0 +1,40 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +/** + * useAdminUiStore — global UI state for the admin panel. + * + * Tracks sidebar collapse, search, and breadcrumb state. + */ +export const useAdminUiStore = defineStore('admin-ui', () => { + /** Sidebar collapsed state */ + const sidebarCollapsed = ref(false) + + /** Current page search query (for inline page search) */ + const pageSearch = ref('') + + /** Global notification count (for badge display) */ + const notificationCount = ref(0) + + /** Whether any form on the current page has unsaved changes */ + const hasUnsavedChanges = ref(false) + + /** Toggle sidebar */ + function toggleSidebar() { + sidebarCollapsed.value = !sidebarCollapsed.value + } + + /** Clear page search */ + function clearSearch() { + pageSearch.value = '' + } + + return { + sidebarCollapsed, + pageSearch, + notificationCount, + hasUnsavedChanges, + toggleSidebar, + clearSearch, + } +}) diff --git a/assets/admin/src/style-target.js b/assets/admin/src/style-target.js new file mode 100644 index 00000000..5c6d8682 --- /dev/null +++ b/assets/admin/src/style-target.js @@ -0,0 +1,7 @@ +/** + * 共享的 NaiveUI 样式注入目标 + * 必须在 app.mount() 之前设置,确保组件渲染时目标已就绪 + */ +import { ref } from 'vue' + +export const styleMountTarget = ref(null) diff --git a/assets/admin/src/style.css b/assets/admin/src/style.css new file mode 100644 index 00000000..b3aeaf7a --- /dev/null +++ b/assets/admin/src/style.css @@ -0,0 +1,404 @@ +#aksrt-admin-app, +#aksrt-admin-app * { + box-sizing: border-box; +} + +#aksrt-admin-app { + min-height: 100%; + color: #333; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", sans-serif; + font-size: 14px; + line-height: 1.6; +} + +/* ==================== FA5 Icon in NaiveUI Menu ==================== */ +#aksrt-admin-app .n-menu .n-menu-item-content i[class*="fa-"], +#aksrt-admin-app .n-menu-item-content i[class*="fa-"] { + font-size: 18px; + width: 18px; + text-align: center; + display: inline-flex; + align-items: center; + justify-content: center; + color: inherit; + opacity: 0.75; + transition: opacity 0.15s; +} + +#aksrt-admin-app .n-menu-item-content--selected i[class*="fa-"], +#aksrt-admin-app .n-menu-item-content:hover i[class*="fa-"] { + opacity: 1; +} + +#aksrt-admin-app .n-layout, +#aksrt-admin-app .n-layout-sider { + min-height: 100vh; +} + +#aksrt-admin-app .aksrt-admin-layout { + min-height: 100vh; + background: #f5f7fb; +} + +#aksrt-admin-app .aksrt-page-container, +#aksrt-admin-app .content-page { + max-width: 1100px; + margin: 0 auto; +} + +#aksrt-admin-app .aksrt-page-title { + margin: 0 0 12px; + color: #1a1a2e; + font-size: 22px; + font-weight: 700; + letter-spacing: -0.02em; +} + +#aksrt-admin-app .aksrt-page-desc { + margin: 0 0 24px; + color: #7a7f8a; + font-size: 13px; + line-height: 1.7; +} + +#aksrt-admin-app .aksrt-section-card { + margin-bottom: 18px; + border-radius: 12px !important; + border: 1px solid #eceff5 !important; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04); +} + +#aksrt-admin-app .aksrt-section-card .n-card__content { + padding: 18px 20px 20px !important; +} + +#aksrt-admin-app .aksrt-section-card .n-card-header { + padding: 18px 20px 0 !important; +} + +#aksrt-admin-app .aksrt-section-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 14px; +} + +#aksrt-admin-app .aksrt-section-header h3 { + margin: 0; + color: #1a1a2e; + font-size: 15px; + font-weight: 600; +} + +#aksrt-admin-app .section-icon, +#aksrt-admin-app .header-icon { + font-size: 17px; + opacity: 0.8; + display: inline-flex; + align-items: center; +} + +#aksrt-admin-app .section-desc, +#aksrt-admin-app .aksrt-hint-text { + color: #8a909c; + font-size: 12px; + line-height: 1.6; +} + +#aksrt-admin-app .section-desc { + margin: 0 0 14px; +} + +#aksrt-admin-app .aksrt-form .n-form-item { + margin-bottom: 18px; +} + +#aksrt-admin-app .aksrt-form .n-form-item:last-child { + margin-bottom: 0; +} + +#aksrt-admin-app .aksrt-form .n-form-item-label { + color: #555 !important; + font-size: 13px; + font-weight: 500; +} + +#aksrt-admin-app .aksrt-form .n-input, +#aksrt-admin-app .aksrt-form .n-select .n-base-selection, +#aksrt-admin-app .aksrt-form .n-input-number { + min-height: 38px; + --n-height: 38px !important; +} + +#aksrt-admin-app .aksrt-form .n-input--textarea textarea { + min-height: 84px; +} + +#aksrt-admin-app .aksrt-form .n-switch { + --n-rail-height: 26px !important; + --n-rail-width: 48px !important; +} + +#aksrt-admin-app .aksrt-form-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 12px; + padding: 24px 0 8px; + margin-top: 28px; + border-top: 1px solid #eef1f6; +} + +#aksrt-admin-app .aksrt-form-actions .n-button { + min-width: 120px; + height: 40px; + font-weight: 500; +} + +#aksrt-admin-app .aksrt-tabs .n-tabs-nav { + margin-bottom: 20px !important; +} + +#aksrt-admin-app .aksrt-tabs .n-tabs-tab { + padding: 10px 20px !important; + font-size: 14px !important; + font-weight: 500; +} + +#aksrt-admin-app .n-data-table { + border-radius: 10px; + overflow: hidden; +} + +#aksrt-admin-app .n-data-table .n-data-table-th { + background: #f8fafc; +} + +#aksrt-admin-app .n-empty { + padding: 24px 0; +} + +#aksrt-admin-app .aksrt-card-header-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + flex-wrap: wrap; +} + +#aksrt-admin-app .aksrt-card-header-row__left { + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +#aksrt-admin-app .aksrt-card-header-row__title { + font-weight: 600; +} + +/* ==================== 卡片 header 行(标题 + 按钮) ==================== */ +#aksrt-admin-app .aksrt-card-header-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + flex-wrap: wrap; +} + +#aksrt-admin-app .aksrt-card-header-row__left { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +#aksrt-admin-app .aksrt-card-header-row__title { + margin: 0; + font-size: 15px; + font-weight: 600; + color: #1a1a2e; +} + +/* ==================== 过渡 ==================== */ +.fade-slide-enter-active, +.fade-slide-leave-active { + transition: opacity 0.24s ease, transform 0.24s ease; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translateY(12px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translateY(-8px); +} + +/* ==================== Modal 样式(弹层) ==================== */ +.aksrt-modal-card { + width: 100%; + max-width: 520px; +} + +/* ==================== 移动端适配 ==================== */ +@media (max-width: 768px) { + /* 容器 */ + #aksrt-admin-app .aksrt-page-container, + #aksrt-admin-app .content-page { + max-width: 100%; + padding: 0; + } + + #aksrt-admin-app .aksrt-page-title { + font-size: 18px; + margin-bottom: 8px; + } + + #aksrt-admin-app .aksrt-page-desc { + font-size: 12px; + margin-bottom: 14px; + } + + /* 卡片紧凑 */ + #aksrt-admin-app .aksrt-section-card { + margin-bottom: 12px; + border-radius: 10px !important; + } + + #aksrt-admin-app .aksrt-section-card .n-card__content { + padding: 14px !important; + } + + #aksrt-admin-app .aksrt-section-card .n-card-header { + padding: 14px 14px 0 !important; + } + + #aksrt-admin-app .aksrt-section-header { + margin-bottom: 10px; + } + + /* 表单:label 改为顶部排列(覆盖 left 模式) */ + #aksrt-admin-app .aksrt-form .n-form-item { + margin-bottom: 14px; + } + + #aksrt-admin-app .aksrt-form .n-form-item .n-form-item-label { + text-align: left !important; + padding: 0 0 6px !important; + width: auto !important; + height: auto !important; + line-height: 1.5 !important; + justify-content: flex-start !important; + } + + #aksrt-admin-app .aksrt-form .n-form-item-blank { + width: 100%; + } + + /* 强制行内表单单列 */ + #aksrt-admin-app .aksrt-form .n-form-item .n-form-item-label, + #aksrt-admin-app .aksrt-form .n-form-item-row { + display: block !important; + } + + /* radio button 自动换行 */ + #aksrt-admin-app .aksrt-form .n-radio-group { + width: 100%; + } + + #aksrt-admin-app .aksrt-form .n-radio-group .n-space { + flex-wrap: wrap !important; + gap: 6px 8px !important; + } + + #aksrt-admin-app .aksrt-form .n-radio-button { + flex: 1 1 auto; + min-width: 0; + } + + /* 输入组在窄屏堆叠 */ + #aksrt-admin-app .aksrt-form .n-input-group { + flex-wrap: wrap; + gap: 6px; + } + + #aksrt-admin-app .aksrt-form .n-input-group > .n-input, + #aksrt-admin-app .aksrt-form .n-input-group > .n-button { + border-radius: 6px !important; + } + + /* 表单底部按钮区 */ + #aksrt-admin-app .aksrt-form-actions { + flex-direction: column-reverse; + align-items: stretch; + padding: 16px 0 8px; + margin-top: 20px; + gap: 8px; + } + + #aksrt-admin-app .aksrt-form-actions .n-button { + width: 100%; + min-width: 0; + height: 42px; + } + + /* 卡片 header 行:换行排列 */ + #aksrt-admin-app .aksrt-card-header-row { + gap: 8px; + } + + /* Naive 弹窗全屏 */ + .n-modal-mask + .n-modal-body-wrapper .n-modal, + .n-modal-body-wrapper > .n-modal { + width: 100vw !important; + max-width: 100vw !important; + border-radius: 0 !important; + margin: 0 !important; + min-height: 100vh; + } + + .aksrt-modal-card { + max-width: 100vw !important; + width: 100vw !important; + min-height: 100vh; + border-radius: 0 !important; + } + + /* 数据表格隐藏次要列 */ + .aksrt-table-mobile-hide, + #aksrt-admin-app .n-data-table .aksrt-table-mobile-hide { + display: none !important; + } + + /* 表格内边距收紧 */ + #aksrt-admin-app .n-data-table .n-data-table-td, + #aksrt-admin-app .n-data-table .n-data-table-th { + padding: 8px 10px !important; + } + + /* tabs */ + #aksrt-admin-app .aksrt-tabs .n-tabs-tab { + padding: 8px 12px !important; + font-size: 13px !important; + } + + /* drawer 内菜单 */ + .n-drawer .n-menu .n-menu-item-content { + padding-left: 18px !important; + } +} + +/* ==================== 超窄设备(<= 480px)追加 ==================== */ +@media (max-width: 480px) { + #aksrt-admin-app .aksrt-section-card .n-card__content { + padding: 12px !important; + } + + #aksrt-admin-app .aksrt-section-card .n-card-header { + padding: 12px 12px 0 !important; + } +} diff --git a/assets/admin/src/utils/home-module-schema.js b/assets/admin/src/utils/home-module-schema.js new file mode 100644 index 00000000..ddda5607 --- /dev/null +++ b/assets/admin/src/utils/home-module-schema.js @@ -0,0 +1,514 @@ +/** + * AKSRT CMS Home Module Schema + * + * 定义每类模块的: + * - 元数据(label / icon / desc) + * - 默认 config + * - 表单字段(用于配置抽屉自动生成 UI) + * + * 字段类型 type: + * - text n-input + * - number n-input-number + * - select n-select (options) + * - radio n-radio-group (options) + * - switch n-switch + * - cats n-select (multiple, 动态加载分类) + * - cat n-select (single, 动态加载分类) + * - textarea n-input type=textarea + */ + +export const MODULE_TYPES = [ + 'hero', + 'banner', + 'category', + 'tabs', + 'ranking', + 'recent', + 'tag_cloud', + 'friend_links', + 'html', + 'hotspot', + 'category_recommend_cards', + 'text_title', + 'button_group', + 'spacing', + 'faq', + 'data_grid', + 'highlight_cards', + 'partner_marquee', + 'product_showcase', + 'user', + 'more', +] + +export const MODULE_LIBRARY = [ + { + type: 'hero', + label: 'Hero 首屏', + icon: 'image', + desc: '固定首页骨架:大图轮播首屏。幻灯片内容在「页面&显示 → Hero 轮播」维护;搜索/卡片参数见「顶部多功能组件」。', + defaultConfig: { with_search: true, show_cards: false, card_columns: 4, icon_cards: [] }, + fields: [ + { key: 'with_search', type: 'switch', label: '显示搜索覆盖框' }, + { key: 'show_cards', type: 'switch', label: '显示图标卡片覆盖' }, + { key: 'card_columns', type: 'number', label: '卡片列数', min: 2, max: 6 }, + ], + }, + { + type: 'banner', + label: 'Banner 轮播', + icon: 'rectangle', + desc: '固定首页骨架:正文区轮播条。幻灯片内容在「页面&显示 → Banner 轮播」维护。', + defaultConfig: {}, + fields: [], + }, + { + type: 'category', + label: '分类块', + icon: 'folder', + desc: '指定分类的最新内容网格', + defaultConfig: { cat_id: 0, count: 8, layout: 'card', columns: 4, title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题(留空用分类名)' }, + { key: 'cat_id', type: 'cat', label: '分类' }, + { key: 'count', type: 'number', label: '数量', min: 1, max: 20 }, + { key: 'columns', type: 'number', label: '列数', min: 1, max: 6 }, + { + key: 'layout', type: 'select', label: '布局', + options: [ + { label: '卡片', value: 'card' }, + { label: '列表', value: 'list' }, + { label: '瀑布流', value: 'masonry' }, + { label: '资源网格', value: 'grid-cms' }, + ], + }, + ], + }, + { + type: 'tabs', + label: '分类 Tabs', + icon: 'switcher', + desc: '多分类 Tab 切换', + defaultConfig: { cat_ids: [], count: 8, layout: 'grid-cms', title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'cat_ids', type: 'cats', label: '分类列表' }, + { key: 'count', type: 'number', label: '每个 Tab 数量', min: 1, max: 20 }, + { + key: 'layout', type: 'select', label: '布局', + options: [ + { label: '卡片', value: 'card' }, + { label: '列表', value: 'list' }, + { label: '瀑布流', value: 'masonry' }, + { label: '资源网格', value: 'grid-cms' }, + ], + }, + ], + }, + { + type: 'ranking', + label: '排行榜', + icon: 'trophy', + desc: '按下载量 / 阅读量 / 评论数排序(普通文章)', + defaultConfig: { metric: 'downloads', count: 6, title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { + key: 'metric', type: 'radio', label: '排序依据', + options: [ + { label: '下载量', value: 'downloads' }, + { label: '阅读量', value: 'views' }, + { label: '评论数', value: 'comments' }, + ], + }, + { key: 'count', type: 'number', label: '数量', min: 1, max: 20 }, + ], + }, + { + type: 'recent', + label: '最新文章', + icon: 'clock', + desc: '按时间倒序的最新文章', + defaultConfig: { count: 6, layout: 'card', title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'count', type: 'number', label: '数量', min: 1, max: 20 }, + { + key: 'layout', type: 'select', label: '布局', + options: [ + { label: '卡片', value: 'card' }, + { label: '列表', value: 'list' }, + { label: '瀑布流', value: 'masonry' }, + { label: '资源网格', value: 'grid-cms' }, + ], + }, + ], + }, + { + type: 'tag_cloud', + label: '标签云', + icon: 'tag', + desc: '按使用频率展示热门标签', + defaultConfig: { limit: 30, title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'limit', type: 'number', label: '数量', min: 5, max: 100 }, + ], + }, + { + type: 'friend_links', + label: '友情链接', + icon: 'link', + desc: '展示已配置的友链', + defaultConfig: { category: '', title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'category', type: 'text', label: '分类筛选(可空)' }, + ], + }, + { + type: 'html', + label: '自定义 HTML', + icon: 'code', + desc: '自由插入 HTML 片段(受 wp_kses_post 清洗)', + defaultConfig: { html: '', title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题(可空)' }, + { key: 'html', type: 'textarea', label: 'HTML 内容', rows: 6 }, + ], + }, + { + type: 'hotspot', + label: '热点推荐', + icon: 'fire', + desc: '文章推荐列表(纯文本,仅桌面端显示)', + defaultConfig: { count: 6, source: 'auto', title: '' }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'count', type: 'number', label: '数量', min: 3, max: 20 }, + { + key: 'source', type: 'select', label: '数据来源', + options: [ + { label: '自动推荐(推荐文章优先)', value: 'auto' }, + { label: '最新文章', value: 'recent' }, + { label: '最热文章(按阅读量)', value: 'views' }, + ], + }, + ], + }, + { + type: 'category_recommend_cards', + label: '分类推荐卡片', + icon: 'grid', + desc: '4 张分类卡片 2×2 布局,含封面图、浏览量胶囊和文章列表', + defaultConfig: { + title: '分类推荐', + cat_ids: [], + show_views: true, + show_dates: true, + }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'cat_ids', type: 'cats', label: '分类(按选择顺序对应卡片 1-4)' }, + { key: 'show_views', type: 'switch', label: '显示浏览量胶囊' }, + { key: 'show_dates', type: 'switch', label: '显示发布时间' }, + ], + }, + { + type: 'text_title', + label: '标题文案', + icon: 'font-colors', + desc: '带标题、副标题和可选按钮的文案区块', + defaultConfig: { + title: '', + desc: '', + align: 'left', + style: 'default', + button_text: '', + button_url: '', + button_style: 'primary', + }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'desc', type: 'textarea', label: '副标题/描述', rows: 2 }, + { key: 'align', type: 'radio', label: '对齐', options: [ + { label: '左对齐', value: 'left' }, + { label: '居中', value: 'center' }, + { label: '右对齐', value: 'right' }, + ]}, + { key: 'style', type: 'select', label: '样式', options: [ + { label: '默认', value: 'default' }, + { label: '带左边框', value: 'bordered' }, + { label: '带背景', value: 'bg' }, + ]}, + { key: 'button_text', type: 'text', label: '按钮文字(可空)' }, + { key: 'button_url', type: 'text', label: '按钮链接' }, + { key: 'button_style', type: 'select', label: '按钮样式', options: [ + { label: '主色', value: 'primary' }, + { label: '轮廓', value: 'outline' }, + { label: '文字', value: 'text' }, + ]}, + ], + }, + { + type: 'button_group', + label: '按钮组', + icon: 'th-large', + desc: '多按钮横向排列,适合快捷入口', + defaultConfig: { + title: '', + buttons: [], + columns: 4, + style: 'card', + }, + fields: [ + { key: 'title', type: 'text', label: '标题(可空)' }, + { key: 'columns', type: 'number', label: '每行列数', min: 2, max: 6 }, + { key: 'style', type: 'select', label: '按钮样式', options: [ + { label: '卡片', value: 'card' }, + { label: '图标', value: 'icon' }, + { label: '文字', value: 'text' }, + ]}, + ], + }, + { + type: 'spacing', + label: '间距分隔', + icon: 'swap', + desc: '模块间间距或分隔线', + defaultConfig: { + height: 40, + divider: false, + divider_style: 'solid', + }, + fields: [ + { key: 'height', type: 'number', label: '高度 (px)', min: 0, max: 200 }, + { key: 'divider', type: 'switch', label: '显示分隔线' }, + { key: 'divider_style', type: 'select', label: '分隔线样式', options: [ + { label: '实线', value: 'solid' }, + { label: '虚线', value: 'dashed' }, + { label: '点线', value: 'dotted' }, + ]}, + ], + }, + { + type: 'faq', + label: '常见问题', + icon: 'question-circle', + desc: '折叠式 FAQ 列表', + defaultConfig: { + title: '常见问题', + items: [], + style: 'accordion', + }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'style', type: 'radio', label: '样式', options: [ + { label: '手风琴', value: 'accordion' }, + { label: '卡片', value: 'card' }, + ]}, + ], + }, + { + type: 'data_grid', + label: '数据网格', + icon: 'table', + desc: '多列数据卡片展示(统计、特征等)', + defaultConfig: { + title: '', + columns: 4, + items: [], + style: 'number', + }, + fields: [ + { key: 'title', type: 'text', label: '标题(可空)' }, + { key: 'columns', type: 'number', label: '列数', min: 2, max: 6 }, + { key: 'style', type: 'select', label: '样式', options: [ + { label: '数字突出', value: 'number' }, + { label: '图标突出', value: 'icon' }, + { label: '简洁', value: 'minimal' }, + ]}, + ], + }, + { + type: 'highlight_cards', + label: '高亮卡片', + icon: 'star', + desc: '带配色的特色卡片网格', + defaultConfig: { + title: '', + columns: 3, + cards: [], + }, + fields: [ + { key: 'title', type: 'text', label: '标题(可空)' }, + { key: 'columns', type: 'number', label: '列数', min: 2, max: 4 }, + ], + }, + { + type: 'partner_marquee', + label: '合作伙伴', + icon: 'handshake', + desc: 'Logo 滚动展示(合作伙伴/品牌墙)', + defaultConfig: { + title: '合作伙伴', + logos: [], + speed: 30, + pause_on_hover: true, + columns: 5, + }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'speed', type: 'number', label: '滚动速度', min: 10, max: 100 }, + { key: 'pause_on_hover', type: 'switch', label: '鼠标悬停暂停' }, + { key: 'columns', type: 'number', label: '可见列数', min: 3, max: 8 }, + ], + }, + { + type: 'product_showcase', + label: '产品展示', + icon: 'shopping-bag', + desc: '产品/商品卡片网格(为商城 Phase 预留)', + defaultConfig: { + title: '产品展示', + columns: 3, + count: 6, + source: 'manual', + show_price: true, + show_button: true, + }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'columns', type: 'number', label: '列数', min: 2, max: 4 }, + { key: 'count', type: 'number', label: '数量', min: 1, max: 12 }, + { key: 'source', type: 'select', label: '数据来源', options: [ + { label: '手动配置', value: 'manual' }, + { label: '最新商品', value: 'latest' }, + { label: '热门商品', value: 'popular' }, + ]}, + { key: 'show_price', type: 'switch', label: '显示价格' }, + { key: 'show_button', type: 'switch', label: '显示购买按钮' }, + ], + }, + { + type: 'user', + label: '用户卡片', + icon: 'user', + desc: '推荐用户卡片(头像、简介、关注按钮)', + defaultConfig: { + title: '推荐用户', + user_ids: [], + count: 4, + columns: 4, + order: 'recent', + show_stats: true, + }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'count', type: 'number', label: '用户数量', min: 1, max: 12 }, + { key: 'columns', type: 'number', label: '列数', min: 2, max: 6 }, + { key: 'order', type: 'select', label: '排序', options: [ + { label: '最近注册', value: 'recent' }, + { label: '文章最多', value: 'posts' }, + { label: '手动指定', value: 'manual' }, + ]}, + { key: 'show_stats', type: 'switch', label: '显示统计数据' }, + ], + }, + { + type: 'more', + label: '发现更多', + icon: 'th', + desc: '分类卡片网格(封面、文章数、最近文章)', + defaultConfig: { + title: '发现更多', + cat_ids: [], + count: 6, + columns: 3, + show_post_count: true, + show_latest_posts: true, + latest_count: 5, + }, + fields: [ + { key: 'title', type: 'text', label: '标题' }, + { key: 'count', type: 'number', label: '分类数量', min: 1, max: 12 }, + { key: 'columns', type: 'number', label: '列数', min: 2, max: 4 }, + { key: 'show_post_count', type: 'switch', label: '显示文章数' }, + { key: 'show_latest_posts', type: 'switch', label: '显示最近文章' }, + { key: 'latest_count', type: 'number', label: '最近文章数', min: 1, max: 10 }, + ], + }, +] + +export function getModuleMeta(type) { + return MODULE_LIBRARY.find(m => m.type === type) || null +} + +export function makeDefaultModule(type) { + const meta = getModuleMeta(type) + if (!meta) return null + return { + type, + config: JSON.parse(JSON.stringify(meta.defaultConfig || {})), + } +} + +export const PRESET_TEMPLATES = [ + { + key: 'resource_standard', + label: '资源站标准', + desc: 'Hero(固定骨架)+ Tabs + 最新文章 + 下载排行', + modules: [ + { type: 'hero', config: {} }, + { type: 'tabs', config: { count: 8, layout: 'grid-cms' } }, + { type: 'recent', config: { count: 8, layout: 'grid-cms' } }, + { type: 'ranking', config: { metric: 'downloads', count: 6 } }, + ], + }, + { + key: 'news', + label: '资讯站', + desc: 'Banner(固定骨架)+ 多个分类块 + 标签云', + modules: [ + { type: 'banner', config: {} }, + { type: 'category', config: { count: 8, columns: 4, layout: 'card' } }, + { type: 'category', config: { count: 8, columns: 4, layout: 'card' } }, + { type: 'tag_cloud', config: { limit: 30 } }, + ], + }, + { + key: 'mixed', + label: '混合站', + desc: 'Hero(固定骨架)+ 最新文章 + 多个分类块', + modules: [ + { type: 'hero', config: {} }, + { type: 'recent', config: { count: 6, layout: 'card' } }, + { type: 'category', config: { count: 6, columns: 3, layout: 'card' } }, + { type: 'category', config: { count: 6, columns: 3, layout: 'card' } }, + ], + }, + { + key: 'landing', + label: '落地页', + desc: 'Hero + 标题 + 高亮卡片 + FAQ + 按钮组', + modules: [ + { type: 'hero', config: {} }, + { type: 'text_title', config: { title: '为什么选择我们', align: 'center', style: 'default' } }, + { type: 'highlight_cards', config: { columns: 3 } }, + { type: 'spacing', config: { height: 40 } }, + { type: 'faq', config: { title: '常见问题' } }, + { type: 'button_group', config: { columns: 3, style: 'card' } }, + ], + }, + { + key: 'corporate', + label: '企业站', + desc: 'Hero + 数据网格 + 产品展示 + 合作伙伴', + modules: [ + { type: 'hero', config: {} }, + { type: 'data_grid', config: { title: '核心数据', columns: 4, style: 'number' } }, + { type: 'product_showcase', config: { title: '产品展示', columns: 3 } }, + { type: 'partner_marquee', config: { title: '合作伙伴', speed: 30 } }, + ], + }, +] diff --git a/assets/admin/src/utils/media.js b/assets/admin/src/utils/media.js new file mode 100644 index 00000000..1e779986 --- /dev/null +++ b/assets/admin/src/utils/media.js @@ -0,0 +1,40 @@ +export function openMediaPicker(options = {}) { + const wpMedia = window.wp?.media + + if (!wpMedia) { + throw new Error('当前后台未加载 WordPress 媒体库。') + } + + const frame = wpMedia({ + title: options.title || '选择图片', + button: { + text: options.buttonText || '使用此图片', + }, + library: { + type: options.type || 'image', + }, + multiple: false, + }) + + return new Promise((resolve, reject) => { + frame.on('select', () => { + const attachment = frame.state().get('selection').first()?.toJSON() + + if (!attachment?.url) { + reject(new Error('未选择有效的媒体资源。')) + return + } + + resolve(attachment) + }) + + frame.on('close', () => { + const hasSelection = frame.state().get('selection').length > 0 + if (!hasSelection) { + resolve(null) + } + }) + + frame.open() + }) +} diff --git a/assets/admin/tsconfig.json b/assets/admin/tsconfig.json new file mode 100644 index 00000000..3d23e6b2 --- /dev/null +++ b/assets/admin/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions": {"target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "jsx": "preserve", "strict": true, "noEmit": true, "baseUrl": ".", "paths": {"@/*": ["src/*"]}}, "include": ["src/**/*.ts", "src/**/*.vue"]} \ No newline at end of file