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 @@
+
+
+
+ 可能被外部修改或其兄弟节点被移除,
+ * 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 使用 ISO 国家代码,如 CN / US / RU。
+
+
+
+
+
+
+
+
+
+
+ 测试验证
+
+
+
+
+
+
+
+
+
+
{{ testResult.message }}
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭后,首页 Banner 区块将不再渲染。
+
+
+
+
+ 保存 Banner 开关
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 媒体库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ 保存
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择
+ 清除
+
+
+
+
+
+
+ 选择
+ 清除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 页脚中的社交平台或联系入口。
+
+
+
+
+ 标准联系字段
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择
+ 清除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 更多联系项
+
+
+
+
+ 每项含描述、图标、链接、图片(hover 显示),用于补充标准联系方式。
+
+
+
+
+
+
+
+
+
+
+
+
+ 页脚第二列的常用导航链接。
+
+
+
+
+
+
+
+
+
+
+
+
+ 页脚补充链接区域。
+
+
+
+
+
+
+
+
+
+
+
+
+ 站点友情链接列表,支持 Logo 头像、名称、URL 与简短描述。
+
+
+
+
+
+
+
+
+
+
+
+
+ 显示在页脚第三列区域,每项含文字描述和二维码图片。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择
+
+ 删除
+
+
![]()
+
+
+
+
+
+
+
+
+
+ 插入在页脚底部区域(版权信息之后),支持 HTML。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 模板一(Zibll)
+
+
+
+
+
+ 范围 1 - 20
+
+
+
+
+
+
+
+
+
+
+
+ 保存页脚配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 加载失败:{{ loadError }}
+
+
+
+
+
+
+
+
+
可用状态
+
{{ status.available ? '可用' : '不可用' }}
+
+
+
+
+
+
+
+
WP AI Client
+
{{ status.wp_ai_available ? '已就绪' : '不可用' }}
+
+
+
+
+
+
+
+
主题 Provider
+
{{ status.theme_available ? '已配置' : '未配置' }}
+
+
+
+
+
+
+
+
当前后端
+
{{ backendLabel }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ svc.available ? '可用' : '未配置' }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 清除缓存
+
+
+
+ 优化数据表
+
+
+
+ 重建索引
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ link.label }}
+
+
+
+
+
+
+
+ {{ note }}
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ link.label }}
+
+
+
+
+
+
+
+ {{ note }}
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ detail.card.code || '-' }}
+ {{ detail.card.amount || 0 }}
+ {{ detail.card.balance || 0 }}
+ {{ detail.card.status_label || detail.card.status }}
+ {{ detail.card.created_at || '-' }}
+ {{ detail.card.expires_at || '-' }}
+ {{ detail.card.used_at || '-' }}
+
+ {{ detail.card.used_by_user?.display_name || '-' }}
+
+
+
+
+
备注
+
+ {{ detail.card.note || '暂无备注' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ detail.record.order_number || '-' }}
+ {{ detail.record.user?.display_name || '-' }}
+ {{ detail.record.source_user?.display_name || '-' }}
+ {{ detail.record.amount || 0 }}
+ {{ detail.record.type_label || detail.record.type }}
+ {{ detail.record.status_label || detail.record.status }}
+ {{ detail.record.created_at || '-' }}
+ {{ detail.record.settled_at || '-' }}
+
+
+
+
备注
+
+ {{ detail.record.note || '暂无备注' }}
+
+
+
+
+
处理记录
+
+
+
+ {{ entry.event || '-' }}
+ {{ entry.time || '-' }}
+
+
{{ entry.note || '无备注' }}
+
{{ entry.operator_name || '系统' }}
+
+
+
+
+
+
+
处理返佣
+
+
+
+
+ 结算返佣
+
+
+ 取消返佣
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
{{ title }}
+
+ 此运营功能已迁移到 WordPress 原生后台侧栏(对齐 zibll)。正在跳转…
+
+
+ 立即打开原生页面
+
+
未配置原生跳转地址,请从左侧 WP 菜单进入。
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 兼容&工具
+ 更新中心
+
+
+
+
+
+
+ 本主题当前不实现 zibll 风格的授权码校验、域名绑定、撤销授权、本地免授权等机制。
+ `AKSRT_Update_Checker` 仅负责匿名版本检测与更新提示,不与授权系统耦合。
+
+
+
+
+
+ 不提供授权码输入框或“立即授权”按钮
+ 不提供“已授权 / 未授权 / 本地免授权”三态逻辑
+ 更新能力统一收口到“更新中心”与“主题信息”页
+ 若未来恢复授权体系,应单独立项,不与现有更新检测器混接
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ info.themeVersion }}
+
+
+ {{ info.wpVersion }}
+
+
+
+ {{ info.phpVersion }}
+
+
+
+ {{ info.dbSchemaVersion }}
+
+
+
+ {{ info.emailBackfillDone ? '已完成' : '未触发' }}
+
+
+
+
+
+
+
+
+
+ {{ info.siteUrl }}
+
+
+ {{ info.apiUrl }}
+
+
+ {{ info.themeDir }}
+
+
+ {{ info.uploadDir }}
+
+
+
+ {{ info.isMultisite ? '是' : '否' }}
+
+
+
+
+ {{ info.debugMode ? '开启' : '关闭' }}
+
+
+
+
+
+
+
+
+
+
+ {{ mod.label }}
+
+
+
+ 暂无模块信息
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 文档中心
+
+
+
+ 更新中心
+
+
+
+ 数据备份
+
+
+
+ 健康检查
+
+
+
+ 导入导出
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 导入导出
+
+
+
+
+
+
+
+
+
+
+ 自动备份:{{ settings.backup_auto ? '已开启' : '未开启' }}
+
+ 频率:{{ frequencyLabel }}
+ 保留:{{ settings.backup_retain }} 份
+ 现有快照:{{ backups.length }} 份
+
+
+ 自动备份
+
+ 频率
+
+ 保留
+
+ 保存策略
+
+
+
+
+
+
+
+
+
+ 立即创建快照
+ 刷新列表
+
+
+
+
+
+
+
+
+
+ 恢复非当前网站 / 非当前版本的快照可能导致配置异常,恢复前请确认站点环境一致。
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 健康检查
+ 刷新列表
+
+
+
+
+
+
+
+
+
+
+
+
{{ item.display_name || item.login }}
+
+ {{ item.current_email }} → {{ item.previous_email }}
+
+
+ 剩余窗口:{{ Math.max(0, Math.floor((item.expires_in || 0) / 3600)) }} 小时
+
+
+
+ 执行回滚
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 导入导出
+ 邮件回滚
+
+
+
+
+
+
+
+
+
+ 功能开关:{{ emailHealth.feature_enabled ? '已启用' : '未启用' }}
+
+
+ REST 阻断:{{ emailHealth.rest_blocking_active ? '已开启' : '仅记录/关闭' }}
+
+
+ 待验证用户:{{ emailHealth.pending_users }}
+
+
+
+
+
+
+
+ {{ check.value }}
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 数据备份
+ 健康检查
+
+
+
+
+
+
+
+ 导出当前配置
+ Dry Run 校验
+ 执行导入
+
+
+
+ 正式导入前会自动创建一份“导入前自动快照”,与“数据备份”页的快照体系共用,便于回滚。
+
+
+
+
+
+
+
+ 重置所选域
+ 重置全部
+
+ 仅支持重置 schema 托管的业务域。健康检查、迁移状态和邮件回滚属于运维面板,不参与按域回滚。
+
+
+
+
+ {{ resultText || '尚未执行操作。' }}
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 消息通知
+ 用户等级
+
+
+
+
+
+
+
+
+
+ 徽章系统:{{ form.user_medal_s ? '已启用' : '已关闭' }}
+
+
+ 公开展示:{{ form.user_medal_public_display ? '开启' : '关闭' }}
+
+ 最多显示:{{ form.user_medal_limit }} 枚
+ 分类:{{ form.user_medal_args.length }}
+ 条目:{{ itemCount }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 添加条目
+ 删除分类
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除条目
+
+
+
+
+
+
+
+
+
+ 前台已授予徽章保存在用户 meta aksrt_user_badges;佩戴为 aksrt_wear_badge(slug)。
+ 字典来自 user_medal_args;运行时 AKSRT_User_Badges 读取 identity badge.dictionary 派生列表。
+
+
+
+
+ 保存配置
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ stats.active }}
+
+
+
+
+
+
+ 严重
+
+
+
+
+
+
+
+
+
+ 查询
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 添加限制
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ 用户积分
+ 用户徽章
+
+
+
+
+
+
+
+
+
+ 等级系统:{{ form.user_level_s ? '已启用' : '已关闭' }}
+
+ 最高等级:{{ form.user_level_max }}
+ 日经验上限:{{ form.user_integral_opt.day_max }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lv.1 通常为 0。数值为升到该级所需的累计经验。修改最高等级后会自动补齐/截断行。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ group }}
+
+
+
+
+
+
+
+
+
+ 保存配置
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ stats.unread }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 刷新
+ 全部已读
+
+
+
+
+
+
+
+
+
+
+
+ {{ getTypeLabel(detailMessage.type) }}
+
+
+
+ {{ detailMessage.is_read ? '已读' : '未读' }}
+
+
+ {{ detailMessage.user_name || '-' }}
+ {{ detailMessage.created_at || '-' }}
+
+
+
+
{{ detailMessage.title }}
+
{{ detailMessage.description || detailMessage.content || '暂无详细内容' }}
+
+ 查看目标
+
+
+
+
+
+ 关闭
+
+ 标记已读
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+ 举报入口:{{ form.user_report_enabled ? '已启用' : '已关闭' }}
+
+
+ 公开入口:{{ form.user_report_public_entry ? '显示' : '隐藏' }}
+
+
+ 审核系统:{{ moderationSummary.enabled ? '已启用' : '未启用' }}
+
+
+ 待处理:{{ counts.pending }}
+
+
+ 已解决:{{ counts.resolved }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 筛选工单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ link.label }}
+
+
+
+
+
+
+
+ {{ note }}
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ {{ detail.report.reporter?.display_name || `用户 #${detail.report.reporter?.id || 0}` }}
+
+
+ {{ targetTitle }}
+
+
+ {{ detail.report.target_type }}
+
+
+ {{ detail.report.category || '-' }}
+
+
+ {{ detail.report.status }}
+
+
+ {{ detail.report.created_at || '-' }}
+
+
+
+
+
证据链
+
+ {{ detail.report.reason || '暂无举报原因' }}
+
+
+
+ 目标 ID:{{ detail.report.target_id }}
+
+
+ 目标状态:{{ detail.report.target.status }}
+
+
+ 打开目标内容
+
+
+
+
+
+
处理记录
+
+
+
+ {{ entry.status || '-' }}
+ {{ entry.timestamp || '-' }}
+
+
{{ entry.note || '无备注' }}
+
{{ entry.operator_name || '系统' }} {{ entry.effect || '' }}
+
+
+
+
+
+
+
处理工单
+
+
+
+
+
+ 提交处理
+ 刷新详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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