fix: Phase3 终审修复(env入库/通用配置组面板/锁屏通知摘除/记档修正)
- I-2: admin/.env(被根 .gitignore 误伤)删除,内容并入入库的 .env.development/.env.production(公共块两份都写,PORT 仅 dev) - I-1: 配置中心补 contact/engine/misc 三组通用 JSON 编辑面板 (JsonGroupPane:等宽 textarea 整组编辑 + JSON.parse 预检 + PUT 整组 + 重置出厂 + 顶部字段说明;挂进 GROUP_NAV 三入口); 修组键缺失租户的禁存 bug(父层快照加载后挂载,空组可建组) - I-3: config_routes.py docstring 改「接线归 Phase 4」; CLAUDE.md Phase 4 清理单补 resolver 回退链与 email/push test 限流 - M-1: 锁屏摘除入口(用户菜单项删 + 全局组件 enabled:false) - M-2: 顶栏通知铃铛隐藏(headerBar notification.enabled:false) - M-5/M-8: admin/README 换三行指针(删模板 README.zh-CN.md); quickLinks 删「登录」项 - M-3/M-4 不做记档入 Phase 4 清理单 - 测试基线:后端 pytest 595 / 前端 vitest 156(+3 通用 JSON 组) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -106,7 +106,7 @@
|
|||||||
|
|
||||||
## Phase 3 完成状态(2026-10-13 收尾核对)
|
## Phase 3 完成状态(2026-10-13 收尾核对)
|
||||||
|
|
||||||
**测试基线:后端 pytest 595 passed / 前端 vitest 153 passed**(Phase 3 起步 139,Task 9 平台设置页 +14;两端数字均已实跑核对,后端唯一 warning 仍为 starlette TestClient 弃用提示,无害)。`pnpm build`(vue-tsc --noEmit && vite build)零错误。后续回归以此为准。
|
**测试基线:后端 pytest 595 passed / 前端 vitest 156 passed**(Phase 3 起步 139,Task 9 平台设置页 +14、终审修复通用 JSON 组 +3;两端数字均已实跑核对,后端唯一 warning 仍为 starlette TestClient 弃用提示,无害)。`pnpm build`(vue-tsc --noEmit && vite build)零错误。后续回归以此为准。
|
||||||
|
|
||||||
**交付形态**:`admin/` 子目录的 Vue3 + TS + Vite + Element Plus SPA(art-design-pro 模板,pnpm 管理),与后端同仓。本地起法:
|
**交付形态**:`admin/` 子目录的 Vue3 + TS + Vite + Element Plus SPA(art-design-pro 模板,pnpm 管理),与后端同仓。本地起法:
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
- 仪表盘:平台总览 + 租户卡片墙(超管)/ 我的指标(租户管理员),时间窗切换。
|
- 仪表盘:平台总览 + 租户卡片墙(超管)/ 我的指标(租户管理员),时间窗切换。
|
||||||
- 租户管理(仅超管):列表 / 创建向导(消费出厂档案)/ 详情编辑 / 站点与域名子资源 / 停用复启。
|
- 租户管理(仅超管):列表 / 创建向导(消费出厂档案)/ 详情编辑 / 站点与域名子资源 / 停用复启。
|
||||||
- 会话与线索:列表(筛选 / 分页 / 租户切换)/ 详情消息流 / 推送日志 / 删除。
|
- 会话与线索:列表(筛选 / 分页 / 租户切换)/ 详情消息流 / 推送日志 / 删除。
|
||||||
- 配置中心:品牌(表单 / 本地简化预览 / 嵌入代码 / 头像上传)、提示词四件套与结构化词表、试金石面板、凭据(邮件 / 推送 / AI 表单 + SMTP 测试 + 推送探测)、版本历史(两版对比 / 回滚 / 组级重置出厂)。
|
- 配置中心:品牌(表单 / 本地简化预览 / 嵌入代码 / 头像上传)、提示词四件套与结构化词表、试金石面板、凭据(邮件 / 推送 / AI 表单 + SMTP 测试 + 推送探测)、contact/engine/misc 三组通用 JSON 编辑(终审 I-1 补齐 spec §7.3 全组覆盖)、版本历史(两版对比 / 回滚 / 组级重置出厂)。
|
||||||
- 统计分析:卡片 / 转化漏斗 / 按日趋势 / 意向·来源·站点·国家分布(ECharts)/ 推送健康表;时间与租户筛选(口径同 stats.py docstring 口径表)。
|
- 统计分析:卡片 / 转化漏斗 / 按日趋势 / 意向·来源·站点·国家分布(ECharts)/ 推送健康表;时间与租户筛选(口径同 stats.py docstring 口径表)。
|
||||||
- 平台设置(仅超管):自由 KV 表的推荐键表单(ai.* / smtp.*,键名是前端约定)+ 存储总览表格;保存 = 逐键 PUT(只提交脏键)。**当前仅存储与展示——resolver 的平台默认回退链未接线(页头注记),引擎不读取这些值**;密钥键掩码哨兵交互与凭据页同款(清除 / 恢复保持)。
|
- 平台设置(仅超管):自由 KV 表的推荐键表单(ai.* / smtp.*,键名是前端约定)+ 存储总览表格;保存 = 逐键 PUT(只提交脏键)。**当前仅存储与展示——resolver 的平台默认回退链未接线(页头注记),引擎不读取这些值**;密钥键掩码哨兵交互与凭据页同款(清除 / 恢复保持)。
|
||||||
|
|
||||||
@@ -140,11 +140,16 @@
|
|||||||
- **TENANT_SOURCE=db 默认**(生产装配形态);`file` 为冷备/本地调试回退。
|
- **TENANT_SOURCE=db 默认**(生产装配形态);`file` 为冷备/本地调试回退。
|
||||||
- **admin 域名反代区分**:`/api/platform/*` 前缀豁免租户 Host 解析——平台后台 API 可与租户聊天域名同源反代(按路径分流)或独立域名,两形态皆可;租户聊天面(`/`、`/static`、WS、`/api/public`、`/api/chat*`)仍严格按 Host 解析租户,未知 Host 404。
|
- **admin 域名反代区分**:`/api/platform/*` 前缀豁免租户 Host 解析——平台后台 API 可与租户聊天域名同源反代(按路径分流)或独立域名,两形态皆可;租户聊天面(`/`、`/static`、WS、`/api/public`、`/api/chat*`)仍严格按 Host 解析租户,未知 Host 404。
|
||||||
- **ADMIN_PASSWORD 默认值警示**(终审 I-4):lifespan 启动检测到 ADMIN_PASSWORD 仍是出厂默认(admin123)时打 WARNING 日志——生产部署必须设 ADMIN_PASSWORD 环境变量换掉,且超管首登立即改密(must_change_password 门强制)。
|
- **ADMIN_PASSWORD 默认值警示**(终审 I-4):lifespan 启动检测到 ADMIN_PASSWORD 仍是出厂默认(admin123)时打 WARNING 日志——生产部署必须设 ADMIN_PASSWORD 环境变量换掉,且超管首登立即改密(must_change_password 门强制)。
|
||||||
- **Phase 4 清理单增补**(Phase 3 冒烟沉淀;Phase 3 清理单未结项 5/6/8/9/10 一并滚入):
|
- **Phase 4 清理单增补**(Phase 3 冒烟沉淀 + 终审记档;Phase 3 清理单未结项 5/6/8/9/10 一并滚入):
|
||||||
1. `components.d.ts` 构建行尾漂移(长期收口 = 生成物出库或 EOL 规范钩子,现阶段靠手工还原)。
|
1. `components.d.ts` 构建行尾漂移(长期收口 = 生成物出库或 EOL 规范钩子,现阶段靠手工还原)。
|
||||||
2. 推送探测 env 回退真址:租户 push URL 空槽时 `resolve_push_settings` 回退 env `PUSH_API_URL`(默认即真 UniService 生产址)——后台点「探测」会对真实服务发空批量 POST(`{"messages": []}`,按契约文档应为无操作,但属真实出站)。收口 = 部署设 stub 域或探测端点拒绝 env 回退 URL。
|
2. 推送探测 env 回退真址:租户 push URL 空槽时 `resolve_push_settings` 回退 env `PUSH_API_URL`(默认即真 UniService 生产址)——后台点「探测」会对真实服务发空批量 POST(`{"messages": []}`,按契约文档应为无操作,但属真实出站)。收口 = 部署设 stub 域或探测端点拒绝 env 回退 URL。
|
||||||
3. 生产反代需规划 admin 域的头像/品牌公开端点:`/api/public/*` 按租户 Host 解析,admin 域直挂必 404——开发期靠 Vite Host 改写代理,生产要么同源反代改 Host(单租户形态)要么前端改用租户域名绝对 URL。
|
3. 生产反代需规划 admin 域的头像/品牌公开端点:`/api/public/*` 按租户 Host 解析,admin 域直挂必 404——开发期靠 Vite Host 改写代理,生产要么同源反代改 Host(单租户形态)要么前端改用租户域名绝对 URL。
|
||||||
4. `VITE_TENANT_PREVIEW_HOST` 单一预览域名:开发代理只指向一个租户 Host,切换租户时头像等公开资源预览不准(.env.development 已注记)。
|
4. `VITE_TENANT_PREVIEW_HOST` 单一预览域名:开发代理只指向一个租户 Host,切换租户时头像等公开资源预览不准(.env.development 已注记)。
|
||||||
|
5. **resolver 平台默认回退链接入 platform_settings**(终审 I-3 记档):平台设置当前仅存储与展示(admin 页头有注记),引擎不读取——回退链接线是 Phase 4 的功能项。
|
||||||
|
6. **email/test 与 push/test 速率限制**(终审 I-3 记档):两测试端点无限流,复用登录限流器模式收口。
|
||||||
|
7. **锁屏功能已摘除入口**(终审 M-1):用户菜单项删除 + 全局组件 enabled:false 不再挂载(AES 存 localStorage + 公开模板默认密钥,防御价值为零);代码待 Phase 4 决定删否。
|
||||||
|
8. **租户切换器 composable 收拢**(终审 M-3 不做记档):仪表盘/会话/配置三页各自内联实现同一模式,收拢归后续。
|
||||||
|
9. **ECharts 按需收窄**(终审 M-4 不做记档):stats 的 charts.ts 全量注册,bundle 体积优化归后续。
|
||||||
|
|
||||||
## 文档索引
|
## 文档索引
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
- Host 路由:按域名解析租户,未知 Host 404;WS 通道租户化客户端 key 防串库,另有限流与每 IP 连接上限。
|
- Host 路由:按域名解析租户,未知 Host 404;WS 通道租户化客户端 key 防串库,另有限流与每 IP 连接上限。
|
||||||
- 引擎行为:cc 区号补全判定树、多语言墙(中/西/葡借道/俄/阿)、dual-contact 分步双要、CC 强制墙、lead 邮件推送。
|
- 引擎行为:cc 区号补全判定树、多语言墙(中/西/葡借道/俄/阿)、dual-contact 分步双要、CC 强制墙、lead 邮件推送。
|
||||||
- 平台层:scrypt 认证 + 内存 token + 登录限流;租户管理(创建向导 / 站点 / 域名 / 停用);配置版本管线(整组替换 / 回滚 / 重置出厂 / 试金石);头像上传(魔数校验 + uuid 重命名);数据视图(会话 / 推送日志 / 删除,租户 scope 强制);统计(卡片 / 漏斗 / 趋势 / 分布 / 推送健康,逐库聚合禁 ATTACH);密钥掩码哨兵(`__SET__` 往返);DB 源生产装配(TENANT_SOURCE=db 默认 + bootstrap_platform.py 引导)。admin API 全在 `/api/platform` 前缀(Host 豁免、路由依赖守护),端点清单见 [CLAUDE.md](CLAUDE.md)。
|
- 平台层:scrypt 认证 + 内存 token + 登录限流;租户管理(创建向导 / 站点 / 域名 / 停用);配置版本管线(整组替换 / 回滚 / 重置出厂 / 试金石);头像上传(魔数校验 + uuid 重命名);数据视图(会话 / 推送日志 / 删除,租户 scope 强制);统计(卡片 / 漏斗 / 趋势 / 分布 / 推送健康,逐库聚合禁 ATTACH);密钥掩码哨兵(`__SET__` 往返);DB 源生产装配(TENANT_SOURCE=db 默认 + bootstrap_platform.py 引导)。admin API 全在 `/api/platform` 前缀(Host 豁免、路由依赖守护),端点清单见 [CLAUDE.md](CLAUDE.md)。
|
||||||
- 管理后台 SPA(Phase 3,`admin/` 目录,Vue3 + Element Plus + pnpm):登录 / 强制改密 / 角色化菜单(超管 vs 租户管理员);仪表盘(平台总览 + 租户卡片墙 / 我的指标);租户管理与站点域名;会话与线索(筛选 / 详情消息流 / 推送日志 / 删除);配置中心(品牌 + 本地预览 + 嵌入代码 + 头像上传 / 提示词词表 / 试金石 / 凭据掩码编辑 + SMTP 测试 + 推送探测 / 版本对比回滚重置);统计分析(ECharts 漏斗 / 趋势 / 分布 / 推送健康);平台设置(ai.* / smtp.* 推荐键 KV 表单 + 存储总览,现阶段仅存储不接引擎回退链)。
|
- 管理后台 SPA(Phase 3,`admin/` 目录,Vue3 + Element Plus + pnpm):登录 / 强制改密 / 角色化菜单(超管 vs 租户管理员);仪表盘(平台总览 + 租户卡片墙 / 我的指标);租户管理与站点域名;会话与线索(筛选 / 详情消息流 / 推送日志 / 删除);配置中心(品牌 + 本地预览 + 嵌入代码 + 头像上传 / 提示词词表 / 试金石 / 凭据掩码编辑 + SMTP 测试 + 推送探测 / contact·engine·misc 通用 JSON 编辑 / 版本对比回滚重置);统计分析(ECharts 漏斗 / 趋势 / 分布 / 推送健康);平台设置(ai.* / smtp.* 推荐键 KV 表单 + 存储总览,现阶段仅存储不接引擎回退链)。
|
||||||
|
|
||||||
**本地跑法**:后端 `PYTHONUTF8=1 .venv/Scripts/python -m uvicorn app.main:app --port 8018 --workers 1`;管理后台 `cd admin && pnpm install && pnpm dev`(Vite 代理 /api → 8018)。工作约定与测试基线(后端 pytest 595 / 前端 vitest 153)见 [CLAUDE.md](CLAUDE.md)。
|
**本地跑法**:后端 `PYTHONUTF8=1 .venv/Scripts/python -m uvicorn app.main:app --port 8018 --workers 1`;管理后台 `cd admin && pnpm install && pnpm dev`(Vite 代理 /api → 8018)。工作约定与测试基线(后端 pytest 595 / 前端 vitest 156)见 [CLAUDE.md](CLAUDE.md)。
|
||||||
|
|
||||||
设计文档(spec):[docs/superpowers/specs/2026-10-12-unified-kefu-platform-design.md](docs/superpowers/specs/2026-10-12-unified-kefu-platform-design.md)
|
设计文档(spec):[docs/superpowers/specs/2026-10-12-unified-kefu-platform-design.md](docs/superpowers/specs/2026-10-12-unified-kefu-platform-design.md)
|
||||||
|
|||||||
@@ -1,8 +1,31 @@
|
|||||||
# 【开发】环境变量
|
# 【开发】环境变量
|
||||||
|
#
|
||||||
|
# 说明:模板原把通用变量放 .env(全模式加载),但根 .gitignore 的
|
||||||
|
# `.env` 规则会误伤 admin/.env——入库文件只有 .env.development /
|
||||||
|
# .env.production 两份(Phase 3 终审 I-2 收口)。通用变量因此按
|
||||||
|
# "两份都写"纪律维护:改通用项时两文件同步改。
|
||||||
|
|
||||||
|
# 版本号(vite.config.ts 的 __APP_VERSION__ define)
|
||||||
|
VITE_VERSION = 0.1.0
|
||||||
|
|
||||||
|
# dev server 端口号(被占自动顺延)
|
||||||
|
VITE_PORT = 3006
|
||||||
|
|
||||||
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
||||||
VITE_BASE_URL = /
|
VITE_BASE_URL = /
|
||||||
|
|
||||||
|
# 权限模式【 frontend 前端模式 / backend 后端模式 】
|
||||||
|
VITE_ACCESS_MODE = frontend
|
||||||
|
|
||||||
|
# 跨域请求时是否携带 Cookie(开启前需确保后端支持)
|
||||||
|
VITE_WITH_CREDENTIALS = false
|
||||||
|
|
||||||
|
# 是否打开路由信息
|
||||||
|
VITE_OPEN_ROUTE_INFO = false
|
||||||
|
|
||||||
|
# 锁屏加密密钥(锁屏入口已摘除——Phase 3 终审 M-1;密钥保留防遗留代码异常)
|
||||||
|
VITE_LOCK_ENCRYPT_KEY = s3cur3k3y4adpro
|
||||||
|
|
||||||
# API 请求基础路径(开发环境使用 / 走 Vite 代理,见 vite.config.ts 的 server.proxy)
|
# API 请求基础路径(开发环境使用 / 走 Vite 代理,见 vite.config.ts 的 server.proxy)
|
||||||
VITE_API_URL = /
|
VITE_API_URL = /
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
# 【生产】环境变量
|
# 【生产】环境变量
|
||||||
|
#
|
||||||
|
# 说明:模板原把通用变量放 .env(全模式加载),但根 .gitignore 的
|
||||||
|
# `.env` 规则会误伤 admin/.env——入库文件只有 .env.development /
|
||||||
|
# .env.production 两份(Phase 3 终审 I-2 收口)。通用变量因此按
|
||||||
|
# "两份都写"纪律维护:改通用项时两文件同步改。
|
||||||
|
|
||||||
|
# 版本号(vite.config.ts 的 __APP_VERSION__ define)
|
||||||
|
VITE_VERSION = 0.1.0
|
||||||
|
|
||||||
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
||||||
VITE_BASE_URL = /
|
VITE_BASE_URL = /
|
||||||
|
|
||||||
|
# 权限模式【 frontend 前端模式 / backend 后端模式 】
|
||||||
|
VITE_ACCESS_MODE = frontend
|
||||||
|
|
||||||
|
# 跨域请求时是否携带 Cookie(开启前需确保后端支持)
|
||||||
|
VITE_WITH_CREDENTIALS = false
|
||||||
|
|
||||||
|
# 是否打开路由信息
|
||||||
|
VITE_OPEN_ROUTE_INFO = false
|
||||||
|
|
||||||
|
# 锁屏加密密钥(锁屏入口已摘除——Phase 3 终审 M-1;密钥保留防遗留代码异常)
|
||||||
|
VITE_LOCK_ENCRYPT_KEY = s3cur3k3y4adpro
|
||||||
|
|
||||||
# API 地址前缀(生产由反代把 /api 与 /static 指向后端,前端同源相对路径)
|
# API 地址前缀(生产由反代把 /api 与 /static 指向后端,前端同源相对路径)
|
||||||
VITE_API_URL = /
|
VITE_API_URL = /
|
||||||
|
|
||||||
|
|||||||
+2
-103
@@ -1,104 +1,3 @@
|
|||||||
<img src="https://www.qiniu.lingchen.kim/github-cover-light6.webp" />
|
# all-kefu 管理后台(admin SPA)
|
||||||
|
|
||||||
<br />
|
Vue3 + TS + Vite + Element Plus(art-design-pro 模板)。本地起法与工作约定见根目录 [CLAUDE.md](../CLAUDE.md) 的「Phase 3 完成状态」段(后端 8018 + `pnpm install && pnpm dev`)。
|
||||||
<h1 align="center">Art Design Pro</h1>
|
|
||||||
<p align="center">A backend system template that combines design aesthetics with efficient development, helping you quickly build professional-grade applications</p>
|
|
||||||
<div align="center">English | <a href="./README.zh-CN.md">简体中文</a></div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
[](./LICENSE) [](https://github.com/Daymychen/art-design-pro/stargazers) [](https://github.com/Daymychen/art-design-pro/network/members)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
## What makes this project special?
|
|
||||||
|
|
||||||
**Interface Design**: Modern UI design with smooth interactions, focusing on user experience and visual design
|
|
||||||
|
|
||||||
**Quick Start**: Clean architecture + comprehensive documentation, easy for backend developers to use
|
|
||||||
|
|
||||||
**Rich Components**: Built-in high-quality components for data display, forms, and more to meet different business scenarios
|
|
||||||
|
|
||||||
**Smooth Interactions**: Button clicks, theme switching, page transitions, chart animations - experience comparable to commercial products
|
|
||||||
|
|
||||||
**Efficient Development**: Built-in practical APIs like useTable and ArtForm to significantly improve development efficiency
|
|
||||||
|
|
||||||
**Clean Scripts**: Built-in one-click cleanup script to quickly remove demo data and get a ready-to-develop base project
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
Development Framework: Vue3, TypeScript, Vite, Element-Plus, Tailwind CSS
|
|
||||||
|
|
||||||
Code Standards: Eslint, Prettier, Stylelint, Husky, Lint-staged, cz-git
|
|
||||||
|
|
||||||
## Preview
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c1.webp" alt="Light Theme"/></kbd>
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c2.webp" alt="Light Theme"/></kbd>
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c4.webp" alt="Dark Theme"/></kbd>
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c5.webp" alt="Dark Theme"/></kbd>
|
|
||||||
|
|
||||||
## Quick Access
|
|
||||||
|
|
||||||
[Live Demo](https://www.artd.pro) | [Official Documentation](https://www.artd.pro/docs) | [Changelog](./CHANGELOG.en.md)
|
|
||||||
|
|
||||||
## Installation & Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install dependencies
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
# If pnpm install fails, try using the command below
|
|
||||||
pnpm install --ignore-scripts
|
|
||||||
|
|
||||||
# Start local development environment
|
|
||||||
pnpm dev
|
|
||||||
|
|
||||||
# Build for production
|
|
||||||
pnpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Clean Version
|
|
||||||
|
|
||||||
The project includes a cleanup script to quickly remove demo data and provide developers with a ready-to-develop base project
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm clean:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technical Support
|
|
||||||
|
|
||||||
QQ Group: <a href="https://qm.qq.com/cgi-bin/qm/qr?k=Gg6yzZLFaNgmRhK0T5Qcjf7-XcAFWWXm&jump_from=webapi&authKey=YpRKVJQyFKYbGTiKw0GJ/YQXnNF+GdXNZC5beQQqnGZTvuLlXoMO7nw5fNXvmVhA">1038930070</a> (Click the link to join the group chat)
|
|
||||||
|
|
||||||
## Browser Compatibility
|
|
||||||
|
|
||||||
Supports modern mainstream browsers including Chrome, Safari, Firefox, and more.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
We sincerely welcome and appreciate the support of every contributor! Whether you have new ideas, feature suggestions, or code optimizations, you can participate in the following ways:
|
|
||||||
|
|
||||||
Submit Pull Requests: Share your code and help the project grow.
|
|
||||||
|
|
||||||
Create GitHub Issues: Provide bug feedback or new feature suggestions to help us improve together.
|
|
||||||
|
|
||||||
Every contribution you make takes this project one step further! Come join our open source community!
|
|
||||||
|
|
||||||
## Continuous Optimization & Extension
|
|
||||||
|
|
||||||
The project maintains active updates, supports the latest frontend tech stack, is compatible with mainstream frameworks, and ensures long-term stability and extensibility. Community-driven feedback mechanisms allow your needs to be quickly integrated into project iterations.
|
|
||||||
|
|
||||||
## Donation
|
|
||||||
|
|
||||||
If you feel this project has reduced your development costs and solved problems in your work/life, you can support us through the following ways:
|
|
||||||
|
|
||||||
<img src="https://www.qiniu.lingchen.kim/%E7%BB%84%202%402x%202.png" alt="Donation QR Code"/>
|
|
||||||
|
|
||||||
## Star History
|
|
||||||
|
|
||||||
[](https://www.star-history.com/#Daymychen/art-design-pro&Date)
|
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
<img src="https://www.qiniu.lingchen.kim/github-cover-light6.webp" />
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<h1 align="center">Art Design Pro</h1>
|
|
||||||
<p align="center">一款兼具设计美学与高效开发的后台系统模版,助你快速构建专业级应用</p>
|
|
||||||
<div align="center">简体中文 | <a href="./README.md">English</a></div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
[](./LICENSE) [](https://github.com/Daymychen/art-design-pro/stargazers) [](https://github.com/Daymychen/art-design-pro/network/members)
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
## 这个项目有什么特别的呢?
|
|
||||||
|
|
||||||
**界面设计**:现代化 UI 设计,流畅交互,以用户体验与视觉设计为核心
|
|
||||||
|
|
||||||
**极速上手**:简洁架构 + 完整文档,后端开发者也能轻松使用
|
|
||||||
|
|
||||||
**丰富组件**:内置数据展示、表单等多种高质量组件,满足不同业务场景的需求
|
|
||||||
|
|
||||||
**丝滑交互**:按钮点击、主题切换、页面过渡、图表动画,体验媲美商业产品
|
|
||||||
|
|
||||||
**高效开发**:内置 useTable、ArtForm 等实用 API,显著提升开发效率
|
|
||||||
|
|
||||||
**精简脚本**:内置一键清理脚本,可快速清理演示数据,立即得到可开发的基础项目
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
开发框架:Vue3、TypeScript、Vite、Element-Plus、Tailwind CSS
|
|
||||||
|
|
||||||
代码规范:Eslint、Prettier、Stylelint、Husky、Lint-staged、cz-git
|
|
||||||
|
|
||||||
## 预览
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c1.webp" alt="浅色主题"/></kbd>
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c2.webp" alt="浅色主题"/></kbd>
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c4.webp" alt="暗黑主题"/></kbd>
|
|
||||||
|
|
||||||
<kbd><img src="https://www.qiniu.lingchen.kim/github-c5.webp" alt="暗黑主题"/></kbd>
|
|
||||||
|
|
||||||
## 快速访问
|
|
||||||
|
|
||||||
[演示地址](https://www.artd.pro) | [官方文档](https://www.artd.pro/docs) | [更新日志](./CHANGELOG.md)
|
|
||||||
|
|
||||||
## 安装运行
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 安装依赖
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
# 如果 pnpm install 安装失败,尝试使用下面的命令安装依赖
|
|
||||||
pnpm install --ignore-scripts
|
|
||||||
|
|
||||||
# 本地开发环境启动
|
|
||||||
pnpm dev
|
|
||||||
|
|
||||||
# 生产环境打包
|
|
||||||
pnpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
## 精简版本
|
|
||||||
|
|
||||||
项目内置精简脚本,可快速移除项目中的演示数据,让开发者获得一个可快速开发的基础项目
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm clean:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术支持
|
|
||||||
|
|
||||||
QQ群:<a href="https://qm.qq.com/cgi-bin/qm/qr?k=Gg6yzZLFaNgmRhK0T5Qcjf7-XcAFWWXm&jump_from=webapi&authKey=YpRKVJQyFKYbGTiKw0GJ/YQXnNF+GdXNZC5beQQqnGZTvuLlXoMO7nw5fNXvmVhA">1038930070</a>(点击链接加入群聊)
|
|
||||||
|
|
||||||
## 兼容性
|
|
||||||
|
|
||||||
支持 Chrome、Safari、Firefox 等现代主流浏览器。
|
|
||||||
|
|
||||||
## 贡献
|
|
||||||
|
|
||||||
我们真诚欢迎并感谢每一位贡献者的支持!无论您有新想法、功能建议还是代码优化,都可以通过以下方式参与:
|
|
||||||
|
|
||||||
提交 Pull Request:分享您的代码,助力项目成长。
|
|
||||||
|
|
||||||
创建 GitHub Issue:提出 bug 反馈或新功能建议,让我们一起完善。
|
|
||||||
|
|
||||||
您的每一点贡献都让这个项目更进一步!快来加入我们的开源社区吧!
|
|
||||||
|
|
||||||
## 持续优化与扩展
|
|
||||||
|
|
||||||
项目保持活跃更新,支持最新前端技术栈,兼容主流框架,确保长期稳定性和扩展性。社区驱动的反馈机制,让你的需求快速融入项目迭代。
|
|
||||||
|
|
||||||
## 捐赠
|
|
||||||
|
|
||||||
如果你觉得这个项目为你减少了开发成本、化解了工作 / 生活里的难题,可以通过以下方式支持一下~
|
|
||||||
|
|
||||||
<img src="https://www.qiniu.lingchen.kim/%E7%BB%84%202%402x%202.png" alt="捐赠二维码"/>
|
|
||||||
|
|
||||||
## Star History
|
|
||||||
|
|
||||||
[](https://www.star-history.com/#Daymychen/art-design-pro&Date)
|
|
||||||
@@ -37,10 +37,8 @@
|
|||||||
<ArtSvgIcon icon="ri:key-2-line" />
|
<ArtSvgIcon icon="ri:key-2-line" />
|
||||||
<span>{{ $t('topBar.user.changePassword') }}</span>
|
<span>{{ $t('topBar.user.changePassword') }}</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="btn-item" @click="lockScreen()">
|
<!-- 锁屏入口已摘除(Phase 3 终审 M-1:AES 存 localStorage + 公开模板
|
||||||
<ArtSvgIcon icon="ri:lock-line" />
|
默认密钥,防御价值为零;组件代码保留,Phase 4 决定删否) -->
|
||||||
<span>{{ $t('topBar.user.lockScreen') }}</span>
|
|
||||||
</li>
|
|
||||||
<div class="w-full h-px my-2 bg-g-300/80"></div>
|
<div class="w-full h-px my-2 bg-g-300/80"></div>
|
||||||
<div class="log-out c-p" @click="loginOut">
|
<div class="log-out c-p" @click="loginOut">
|
||||||
{{ $t('topBar.user.logout') }}
|
{{ $t('topBar.user.logout') }}
|
||||||
@@ -57,7 +55,6 @@
|
|||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStore } from '@/store/modules/auth'
|
||||||
import { mittBus } from '@/utils/sys'
|
|
||||||
|
|
||||||
defineOptions({ name: 'ArtUserMenu' })
|
defineOptions({ name: 'ArtUserMenu' })
|
||||||
|
|
||||||
@@ -77,13 +74,6 @@
|
|||||||
router.push(path)
|
router.push(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 打开锁屏功能
|
|
||||||
*/
|
|
||||||
const lockScreen = (): void => {
|
|
||||||
mittBus.emit('openLockScreen')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户登出确认
|
* 用户登出确认
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ export const globalComponentsConfig: GlobalComponentConfig[] = [
|
|||||||
component: defineAsyncComponent(
|
component: defineAsyncComponent(
|
||||||
() => import('@/components/core/layouts/art-screen-lock/index.vue')
|
() => import('@/components/core/layouts/art-screen-lock/index.vue')
|
||||||
),
|
),
|
||||||
enabled: true
|
// 入口已摘除(Phase 3 终审 M-1):锁屏把真实密码 AES 存 localStorage
|
||||||
|
// 且密钥是公开模板默认值,防御价值为零——enabled: false 连全局挂载
|
||||||
|
// 也不注册;组件代码保留,Phase 4 决定删否。
|
||||||
|
enabled: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '聊天窗口',
|
name: '聊天窗口',
|
||||||
|
|||||||
@@ -46,18 +46,12 @@ const fastEnterConfig: FastEnterConfig = {
|
|||||||
routeName: 'StatsOverview'
|
routeName: 'StatsOverview'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
// 快速链接
|
// 快速链接(登录项已删——终审 M-8:已登录态下无意义)
|
||||||
quickLinks: [
|
quickLinks: [
|
||||||
{
|
|
||||||
name: '登录',
|
|
||||||
enabled: true,
|
|
||||||
order: 1,
|
|
||||||
routeName: 'Login'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: '修改密码',
|
name: '修改密码',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
order: 2,
|
order: 1,
|
||||||
routeName: 'ForceChangePassword'
|
routeName: 'ForceChangePassword'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ export const headerBarConfig: HeaderBarFeatureConfig = {
|
|||||||
description: '全屏切换功能'
|
description: '全屏切换功能'
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
enabled: true,
|
// 入口已摘除(Phase 3 终审 M-2):模板通知中心是硬编码演示数据,
|
||||||
|
// 无真实数据源——enabled: false 隐藏铃铛;组件保留,接真数据源时再开。
|
||||||
|
enabled: false,
|
||||||
description: '通知中心,显示系统通知和消息'
|
description: '通知中心,显示系统通知和消息'
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
|
|||||||
@@ -0,0 +1,254 @@
|
|||||||
|
<!--
|
||||||
|
通用 JSON 组编辑(Phase 3 终审 I-1:contact / engine / misc 三组)
|
||||||
|
- spec §7.3 要求配置中心覆盖全部配置组——这三组字段少 / 无密钥 /
|
||||||
|
结构低频变更,表单化收益低,统一走等宽 textarea 整组 JSON 编辑;
|
||||||
|
- 保存前 JSON.parse 预检(语法 + 顶层须为对象),类型/边界交后端
|
||||||
|
_check_contact/_check_engine/_check_misc 校验兜底(400 detail 带
|
||||||
|
字段路径,内联展示);
|
||||||
|
- 重置出厂接既有 reset.ts(确认框 → POST config/{group}/reset →
|
||||||
|
emit saved 回写快照组 → watch 重置编辑器为出厂档案值)。
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<ElCard shadow="never" class="form-card json-pane">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
{{ RESET_GROUP_LABELS[groupKey] }}({{ groupKey }} 组 · 整组 JSON 编辑)
|
||||||
|
</span>
|
||||||
|
<div class="header-side">
|
||||||
|
<span class="card-sub">保存即写新版本并热生效</span>
|
||||||
|
<ElButton
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:loading="resetting"
|
||||||
|
@click="onReset"
|
||||||
|
>
|
||||||
|
重置出厂
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ElAlert
|
||||||
|
v-if="actionError"
|
||||||
|
type="error"
|
||||||
|
:closable="true"
|
||||||
|
:title="actionError"
|
||||||
|
class="pane-error"
|
||||||
|
@close="actionError = ''"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 字段说明(事实源 app/platform/defaults.py 的 DEFAULT_CONFIG) -->
|
||||||
|
<ul class="field-docs">
|
||||||
|
<li v-for="field in JSON_GROUP_FIELDS[groupKey]" :key="field.key">
|
||||||
|
<span class="mono field-key">{{ field.key }}</span>
|
||||||
|
<span class="field-desc">{{ field.desc }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ElInput
|
||||||
|
v-model="draft"
|
||||||
|
type="textarea"
|
||||||
|
:rows="16"
|
||||||
|
spellcheck="false"
|
||||||
|
class="json-editor"
|
||||||
|
placeholder='{ "key": "value" }'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="save-bar">
|
||||||
|
<ElInput
|
||||||
|
v-model="saveComment"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="保存备注(可选;写入版本历史 comment)"
|
||||||
|
class="comment-input"
|
||||||
|
/>
|
||||||
|
<ElButton type="primary" :loading="saving" @click="onSave">
|
||||||
|
保存(整组提交,热生效)
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
JSON_GROUP_FIELDS,
|
||||||
|
RESET_GROUP_LABELS,
|
||||||
|
jsonGroupDraft,
|
||||||
|
parseJsonGroupDraft,
|
||||||
|
type JsonGroupKey
|
||||||
|
} from './model'
|
||||||
|
import { putConfigGroup } from '@/api/config'
|
||||||
|
import { confirmAndResetGroup } from './reset'
|
||||||
|
|
||||||
|
defineOptions({ name: 'JsonGroupPane' })
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/** 租户 slug(保存目标) */
|
||||||
|
slug: string
|
||||||
|
/** 组键(contact / engine / misc——通用 pane 的三元实例) */
|
||||||
|
groupKey: JsonGroupKey
|
||||||
|
/** GET config 回显的组(原样快照;undefined = 快照里无该组 = 未存储
|
||||||
|
* 覆盖,编辑器从 {} 起步可建组——父层仅在快照加载完成后挂载本 pane) */
|
||||||
|
group: Record<string, unknown> | null | undefined
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
/** 保存/重置成功:父层更新版本号与快照组 */
|
||||||
|
(
|
||||||
|
e: 'saved',
|
||||||
|
payload: { version: number; group: JsonGroupKey; config: Record<string, unknown> }
|
||||||
|
): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// ---- 编辑态(group 引用变化 = 保存回写/重置/快照重载 → 重置编辑器;
|
||||||
|
// 挂载契约见 props.group 注——父层快照加载后才挂载,无「未加载先存」窗口) ----------
|
||||||
|
|
||||||
|
const draft = ref('')
|
||||||
|
const saveComment = ref('')
|
||||||
|
const saving = ref(false)
|
||||||
|
const resetting = ref(false)
|
||||||
|
const actionError = ref('')
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.group,
|
||||||
|
(group) => {
|
||||||
|
draft.value = jsonGroupDraft(group)
|
||||||
|
saveComment.value = ''
|
||||||
|
actionError.value = ''
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- 保存(前端 JSON 预检 → PUT 整组;类型校验后端兜底) ----------------------------
|
||||||
|
|
||||||
|
async function onSave(): Promise<void> {
|
||||||
|
if (saving.value) return
|
||||||
|
actionError.value = ''
|
||||||
|
const parsed = parseJsonGroupDraft(draft.value)
|
||||||
|
if (!parsed.ok) {
|
||||||
|
actionError.value = parsed.error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
const res = await putConfigGroup(
|
||||||
|
props.slug,
|
||||||
|
props.groupKey,
|
||||||
|
parsed.value,
|
||||||
|
saveComment.value.trim() || undefined,
|
||||||
|
{ showErrorMessage: false }
|
||||||
|
)
|
||||||
|
saveComment.value = ''
|
||||||
|
ElMessage.success(
|
||||||
|
`${RESET_GROUP_LABELS[props.groupKey]}已保存(版本 v${res.version},热生效)`
|
||||||
|
)
|
||||||
|
emit('saved', { version: res.version, group: props.groupKey, config: res.config })
|
||||||
|
} catch (error) {
|
||||||
|
// 400 校验 detail(带字段路径,如 "engine.ws_conn_limit: 须为正整数")
|
||||||
|
actionError.value = error instanceof Error ? error.message : '保存失败'
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 重置出厂(确认框 → POST config/{group}/reset → emit saved) --------------------
|
||||||
|
|
||||||
|
async function onReset(): Promise<void> {
|
||||||
|
if (resetting.value) return
|
||||||
|
resetting.value = true
|
||||||
|
try {
|
||||||
|
const res = await confirmAndResetGroup(props.slug, props.groupKey)
|
||||||
|
if (!res) return // 用户取消确认
|
||||||
|
ElMessage.success(
|
||||||
|
`${RESET_GROUP_LABELS[props.groupKey]}已重置出厂(版本 v${res.version},热生效)`
|
||||||
|
)
|
||||||
|
emit('saved', {
|
||||||
|
version: res.version,
|
||||||
|
group: props.groupKey,
|
||||||
|
config: res.config as Record<string, unknown>
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
actionError.value = error instanceof Error ? error.message : '重置失败'
|
||||||
|
} finally {
|
||||||
|
resetting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.json-pane {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-side {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.card-sub {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--art-gray-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-error {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-docs {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
padding: 10px 12px 10px 30px;
|
||||||
|
list-style: disc;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--el-fill-color-lighter);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.9;
|
||||||
|
|
||||||
|
.field-key {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-desc {
|
||||||
|
color: var(--art-gray-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-editor {
|
||||||
|
:deep(textarea) {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.comment-input {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mono {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -38,11 +38,14 @@ import {
|
|||||||
formatKB,
|
formatKB,
|
||||||
formatVersionTime,
|
formatVersionTime,
|
||||||
initWallEdit,
|
initWallEdit,
|
||||||
|
JSON_GROUP_FIELDS,
|
||||||
|
jsonGroupDraft,
|
||||||
normalizeAiCredsGroup,
|
normalizeAiCredsGroup,
|
||||||
normalizeEmailCredsGroup,
|
normalizeEmailCredsGroup,
|
||||||
normalizePromptsGroup,
|
normalizePromptsGroup,
|
||||||
normalizePushCredsGroup,
|
normalizePushCredsGroup,
|
||||||
parsePatternEntry,
|
parsePatternEntry,
|
||||||
|
parseJsonGroupDraft,
|
||||||
promptByteInfo,
|
promptByteInfo,
|
||||||
promptsOverCap,
|
promptsOverCap,
|
||||||
rowToEntry,
|
rowToEntry,
|
||||||
@@ -705,3 +708,40 @@ describe('scopedConfigJson / snapshotGroupKeys(两版对比的范围视图)'
|
|||||||
expect(snapshotGroupKeys(null)).toEqual([])
|
expect(snapshotGroupKeys(null)).toEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('通用 JSON 组编辑(终审 I-1:contact/engine/misc)', () => {
|
||||||
|
it('jsonGroupDraft:组值 → 2 空格缩进等宽稿;null/undefined → 空对象', () => {
|
||||||
|
expect(jsonGroupDraft({ cc_completion: false })).toBe(
|
||||||
|
'{\n "cc_completion": false\n}'
|
||||||
|
)
|
||||||
|
expect(jsonGroupDraft(null)).toBe('{}')
|
||||||
|
expect(jsonGroupDraft(undefined)).toBe('{}')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parseJsonGroupDraft:语法错误 / 非对象顶层 → 带原因拒绝;对象通过', () => {
|
||||||
|
const bad = parseJsonGroupDraft('{ "a": ')
|
||||||
|
expect(bad.ok).toBe(false)
|
||||||
|
if (!bad.ok) expect(bad.error).toContain('JSON 语法错误')
|
||||||
|
const notObject = parseJsonGroupDraft('[1, 2]')
|
||||||
|
expect(notObject.ok).toBe(false)
|
||||||
|
if (!notObject.ok) expect(notObject.error).toContain('顶层须为 JSON 对象')
|
||||||
|
const ok = parseJsonGroupDraft('{"cc_completion": false}')
|
||||||
|
expect(ok.ok).toBe(true)
|
||||||
|
if (ok.ok) expect(ok.value).toEqual({ cc_completion: false })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('JSON_GROUP_FIELDS:三组字段说明齐备(键与 DEFAULT_CONFIG 同名)', () => {
|
||||||
|
expect(Object.keys(JSON_GROUP_FIELDS).sort()).toEqual(['contact', 'engine', 'misc'])
|
||||||
|
expect(JSON_GROUP_FIELDS.contact.map((f) => f.key)).toEqual([
|
||||||
|
'cc_completion',
|
||||||
|
'email_light_ask',
|
||||||
|
'template_languages'
|
||||||
|
])
|
||||||
|
expect(JSON_GROUP_FIELDS.engine.map((f) => f.key)).toEqual([
|
||||||
|
'rate_limit_per_hour',
|
||||||
|
'ws_conn_limit',
|
||||||
|
'context_seed_keys'
|
||||||
|
])
|
||||||
|
expect(JSON_GROUP_FIELDS.misc.map((f) => f.key)).toEqual(['name_to_region_extras'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -443,6 +443,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 通用 JSON 组 pane(终审 I-1:contact / engine / misc) -->
|
||||||
|
<template v-else-if="JSON_GROUPS.has(activeGroup)">
|
||||||
|
<div v-loading="configLoading" class="group-pane">
|
||||||
|
<ElAlert
|
||||||
|
v-if="configError"
|
||||||
|
type="error"
|
||||||
|
:closable="false"
|
||||||
|
:title="configError"
|
||||||
|
class="pane-error"
|
||||||
|
>
|
||||||
|
<ElButton size="small" :loading="configLoading" @click="reloadConfig">重试</ElButton>
|
||||||
|
</ElAlert>
|
||||||
|
<JsonGroupPane
|
||||||
|
v-else-if="activeSlug && configLoaded"
|
||||||
|
:slug="activeSlug"
|
||||||
|
:group-key="activeJsonGroup"
|
||||||
|
:group="snapshot?.[activeJsonGroup]"
|
||||||
|
@saved="onPaneSaved"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 版本 pane(Task 7d) -->
|
<!-- 版本 pane(Task 7d) -->
|
||||||
<template v-else-if="activeGroup === 'versions'">
|
<template v-else-if="activeGroup === 'versions'">
|
||||||
<div class="group-pane">
|
<div class="group-pane">
|
||||||
@@ -477,7 +499,9 @@
|
|||||||
import PromptsPane from './PromptsPane.vue'
|
import PromptsPane from './PromptsPane.vue'
|
||||||
import WallPane from './WallPane.vue'
|
import WallPane from './WallPane.vue'
|
||||||
import CredsPane from './CredsPane.vue'
|
import CredsPane from './CredsPane.vue'
|
||||||
|
import JsonGroupPane from './JsonGroupPane.vue'
|
||||||
import VersionsPane from './VersionsPane.vue'
|
import VersionsPane from './VersionsPane.vue'
|
||||||
|
import type { JsonGroupKey } from './model'
|
||||||
import { confirmAndResetGroup } from './reset'
|
import { confirmAndResetGroup } from './reset'
|
||||||
|
|
||||||
defineOptions({ name: 'ConfigCenter' })
|
defineOptions({ name: 'ConfigCenter' })
|
||||||
@@ -495,7 +519,10 @@
|
|||||||
{ key: 'brand', label: '品牌' },
|
{ key: 'brand', label: '品牌' },
|
||||||
{ key: 'prompts', label: '提示词' },
|
{ key: 'prompts', label: '提示词' },
|
||||||
{ key: 'wall', label: '安全词表' },
|
{ key: 'wall', label: '安全词表' },
|
||||||
|
{ key: 'contact', label: '联系方式' },
|
||||||
{ key: 'creds', label: '凭据' },
|
{ key: 'creds', label: '凭据' },
|
||||||
|
{ key: 'engine', label: '引擎' },
|
||||||
|
{ key: 'misc', label: '杂项' },
|
||||||
{ key: 'versions', label: '版本' }
|
{ key: 'versions', label: '版本' }
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
@@ -504,10 +531,23 @@
|
|||||||
'brand',
|
'brand',
|
||||||
'prompts',
|
'prompts',
|
||||||
'wall',
|
'wall',
|
||||||
|
'contact',
|
||||||
'creds',
|
'creds',
|
||||||
|
'engine',
|
||||||
|
'misc',
|
||||||
'versions'
|
'versions'
|
||||||
])
|
])
|
||||||
|
|
||||||
|
/** 通用 JSON 组(终审 I-1:contact/engine/misc 走 JsonGroupPane 一份实现) */
|
||||||
|
const JSON_GROUPS = new Set<GroupKey>(['contact', 'engine', 'misc'])
|
||||||
|
|
||||||
|
/** 模板绑定用的收窄视图(JSON 组分支下恒为该三键之一) */
|
||||||
|
const activeJsonGroup = computed<JsonGroupKey>(() =>
|
||||||
|
JSON_GROUPS.has(activeGroup.value)
|
||||||
|
? (activeGroup.value as JsonGroupKey)
|
||||||
|
: 'contact'
|
||||||
|
)
|
||||||
|
|
||||||
type GroupKey = (typeof GROUP_NAV)[number]['key']
|
type GroupKey = (typeof GROUP_NAV)[number]['key']
|
||||||
|
|
||||||
const activeGroup = ref<GroupKey>('brand')
|
const activeGroup = ref<GroupKey>('brand')
|
||||||
|
|||||||
@@ -737,3 +737,65 @@ export function scopedConfigJson(
|
|||||||
scope === COMPARE_SCOPE_ALL ? config : { [scope]: config[scope] }
|
scope === COMPARE_SCOPE_ALL ? config : { [scope]: config[scope] }
|
||||||
return JSON.stringify(view, null, 2)
|
return JSON.stringify(view, null, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 通用 JSON 组编辑(Phase 3 终审 I-1:contact / engine / misc) -------------------
|
||||||
|
|
||||||
|
/** 走通用 JSON pane 的组(无密钥字段、结构低频变更——表单化收益低) */
|
||||||
|
export type JsonGroupKey = 'contact' | 'engine' | 'misc'
|
||||||
|
|
||||||
|
/** 组取值 → 编辑器初稿(null 组 = 空对象;2 空格缩进等宽展示) */
|
||||||
|
export function jsonGroupDraft(
|
||||||
|
group: Record<string, unknown> | null | undefined
|
||||||
|
): string {
|
||||||
|
return JSON.stringify(group ?? {}, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑器解析结果:ok=false 时 error 带可直接展示的中文原因 */
|
||||||
|
export type JsonDraftParse =
|
||||||
|
| { ok: true; value: Record<string, unknown> }
|
||||||
|
| { ok: false; error: string }
|
||||||
|
|
||||||
|
/** 编辑稿预检:JSON 语法 + 顶层须为对象(后端 _check_* 类型校验兜底) */
|
||||||
|
export function parseJsonGroupDraft(text: string): JsonDraftParse {
|
||||||
|
let value: unknown
|
||||||
|
try {
|
||||||
|
value = JSON.parse(text)
|
||||||
|
} catch (error) {
|
||||||
|
const detail = error instanceof Error ? error.message : String(error)
|
||||||
|
return { ok: false, error: `JSON 语法错误——${detail}` }
|
||||||
|
}
|
||||||
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||||
|
return { ok: false, error: '顶层须为 JSON 对象({ ... })——整组替换语义' }
|
||||||
|
}
|
||||||
|
// typeof 收窄只到 object(无索引签名)——JSON.parse 产物必为普通对象,断言收形
|
||||||
|
return { ok: true, value: value as Record<string, unknown> }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 三组的字段说明(pane 顶部展示;事实源 app/platform/defaults.py) */
|
||||||
|
export const JSON_GROUP_FIELDS: Record<
|
||||||
|
JsonGroupKey,
|
||||||
|
Array<{ key: string; desc: string }>
|
||||||
|
> = {
|
||||||
|
contact: [
|
||||||
|
{ key: 'cc_completion', desc: 'bool——cc 区号补全判定树开关' },
|
||||||
|
{ key: 'email_light_ask', desc: 'bool——dual-contact 分步双要的邮箱轻补' },
|
||||||
|
{
|
||||||
|
key: 'template_languages',
|
||||||
|
desc: 'string[]——邮件模板语言清单(运行期接线归 Phase 4,清理单 #8)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
engine: [
|
||||||
|
{ key: 'rate_limit_per_hour', desc: '正整数——聊天限流(次/小时/IP)' },
|
||||||
|
{ key: 'ws_conn_limit', desc: '正整数——每 IP WebSocket 连接上限' },
|
||||||
|
{
|
||||||
|
key: 'context_seed_keys',
|
||||||
|
desc: '映射——产品字段 → 会话上下文种子键(如 product_name → app_product_name)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
misc: [
|
||||||
|
{
|
||||||
|
key: 'name_to_region_extras',
|
||||||
|
desc: '映射——国名 → ISO 区域码扩展(resolve_region_code 消费)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
两端点不落库不 bump 版本,成败如实返回(400 只用于"未配置"预检);
|
两端点不落库不 bump 版本,成败如实返回(400 只用于"未配置"预检);
|
||||||
- GET/PUT /api/platform/settings:PlatformSetting CRUD,仅超管。本阶段只落
|
- GET/PUT /api/platform/settings:PlatformSetting CRUD,仅超管。本阶段只落
|
||||||
存储与接口(value 以 JSON Text 直存,读回还原类型);resolver 的平台默认
|
存储与接口(value 以 JSON Text 直存,读回还原类型);resolver 的平台默认
|
||||||
回退链接入归 Phase 3。
|
回退链接线归 Phase 4(当前仅存储,admin 平台设置页页头有注记)。
|
||||||
|
|
||||||
鉴权矩阵(spec §3):config 系 = require_tenant(slug)(租户全开——本租户
|
鉴权矩阵(spec §3):config 系 = require_tenant(slug)(租户全开——本租户
|
||||||
管理员可自助改配置);settings = require_super。409 门经 platform_user 连锁。
|
管理员可自助改配置);settings = require_super。409 门经 platform_user 连锁。
|
||||||
@@ -602,7 +602,7 @@ async def test_push(slug: str, request: Request,
|
|||||||
return {"slug": slug, **await _probe_push(cfg)}
|
return {"slug": slug, **await _probe_push(cfg)}
|
||||||
|
|
||||||
|
|
||||||
# ---- 平台设置(仅超管;resolver 回退链接入归 Phase 3) -----------------------------
|
# ---- 平台设置(仅超管;resolver 回退链接线归 Phase 4——当前仅存储) ------------------
|
||||||
|
|
||||||
|
|
||||||
def _decode_setting(raw: str):
|
def _decode_setting(raw: str):
|
||||||
|
|||||||
Reference in New Issue
Block a user