docs(governance): 文档性质标记规范 — 区分「需求」与「需求的需求」+ 全量打标 (#71)

用户: 「有的是需求,有的是需求的需求,都需要打上不同的标记」

## 核心洞察
三类文档(META/REQ/REF)长期混用同一套审查标准 → 用错标准 = 审查失效。

## 新增规范
docs/standards/文档性质标记规范.md (v1.0)
- 三分类定义 + 判定判据(判据是「约束范围」,不是文件名)
- ★ 三类规则差异表:
  |        | META | REQ | REF |
  |审查标准| 能否约束住别人 | 验收标准达成 | 与代码一致 |
  |变更成本| 高(全仓) | 中(局部) | 低(同步) |
  |架构锁定| ❌ | ✅ | ❌ |
- 三层落地:① 文档头标性质 ② README §六 索引 ③ META 强制类挂 AGENTS

## 全量打标 25/25
META 13 · REQ 5 · REF 7

## ★ 盘点发现(已修复)
- **11 份 META 中曾仅 4 份有强制入口** → 7 份「标记了但约束不住」
- docs/standards/ 目录名误导:18 份混着 4 REF + 3 REQ
- 易错点:名为"XX接入规范"但只约束一个集成 → 是 REQ 不是 META

## 强制链补齐(AGENTS.md 绝对禁止 8~11)
新增 4 条,把"待挂"META 接入强制链:
- 8. error-code.md        响应格式(成功 {code,data,message} / 错误走 HTTPException)
- 9. 数据采集合并去重机制  所有采集通道的去重键
- 10. 投诉渠道口径与对账   渠道口径权威定义(改动影响全部投诉数字)
- 11. 数据源接入与缓存加速 缓存铁律(写入后必须失效 pattern)

## 其它
- docs/README.md 新增 §六 文档性质索引(单一事实来源);修复历史表格粘连缺陷
- AGENTS.md 新增「文档性质纪律(强制)」章
- review-checklist.md 新增「文档性质」检查项(6 条子项)
- .github/instructions/doc-class-rationale.instructions.md(applyTo: docs/**)

---

## 附带修复:前端构建/编辑器报错(真实问题)

### ① Vue 3 模板被误报 compileError(根因:仓库根无 ESLint 配置)
现象:`v-model:preset="..."` 被编辑器标 [vue/no-v-model-argument] compileError
实测澄清:vite build / docker build / 命令行 eslint **全部通过**
根因:VS Code ESLint 扩展以**工作区根**为 cwd 启动;根目录无任何 ESLint 配置
      → 回退内置默认规则集(含 Vue 2 遗留规则 vue/no-v-model-argument)
修复:
- 新增 eslint.config.mjs(根级**桥接**文件,转发 web/eslint.config.js,单一事实来源)
- 新增 web/jsconfig.json(声明 vueCompilerOptions.target=3,Vue 语言服务)
- 新增 .vscode/settings.json(eslint.useFlatConfig=true 等)
- web/eslint.config.js 显式关闭 vue/no-v-model-argument 并注明原因
验证:两个 cwd 均解析出 118 条规则、该规则 = [0](一致)

### ② el-radio-button label → value 迁移(Element Plus 2.14 弃用)
实测告警:[el-radio] label act as value is about to be deprecated (3.0.0 移除)
项目实际装 element-plus 2.14.4(package.json 写 ^2.6.2)→ 告警真实存在
迁移 28 文件 / 158 处(脚本 scripts/migrate_radio_label_to_value.py,可复现)
验证:浏览器实测 el-radio 弃用告警**归零**

## 验证
- 前端 eslint 0 error(5 warning 均为既有 v-html)· vitest 52 例全绿
- vite build 成功 · docker compose build --no-cache 成功
- 文档性质标记覆盖 25/25 · AGENTS 强制链 9 份 META

关联工单: #71
This commit is contained in:
2026-09-11 19:11:56 +08:00
parent 60b45ef325
commit cab6599ee2
59 changed files with 840 additions and 166 deletions

View File

@@ -0,0 +1,47 @@
---
applyTo: 'docs/**/*.md,AGENTS.md'
---
# 文档性质纪律(强制 · 新增/改动文档必读)
> 全文见 [docs/standards/文档性质标记规范.md](../standards/文档性质标记规范.md)。
> 核心铁律:**文档必须先说清自己是「需求」还是「需求的需求」,再谈内容。**
## 为什么
这两类文档**适用完全不同的规则** —— 混用会导致:
- 用"需求"的标准审查"元需求"(要求规范写验收标准,但规范的验收标准是"能否约束住别人")
- 用"元需求"的稳定性要求"需求"(需求本就该随迭代演进)
- 实现者不知道**到底该遵守哪几条**
## 三分类
| 性质 | 定义 | 判定判据 | 审查标准 |
|---|---|---|---|
| **元需求(META)** | 约束**所有**需求怎么做 | 回答"任何新增功能都必须遵守什么?"/定义口径权威含义/规定流程门禁/提供审查判据/是模板 | 能否**约束住别人** |
| **需求(REQ)** | 做一个**具体**功能/集成 | 有功能边界 + 接口契约 + 验收标准 + 归属工单 | 验收标准是否达成 |
| **参考(REF)** | 现状记录,**无约束** | 以代码为准的清单/已实现机制描述/台账 | 是否与代码一致 |
> ★ **判据是「约束范围」,不是文件名**:
> 名为"XX接入规范"的文档若只约束**一个集成** → 它是 **REQ** 而非 META。
## 三层落地(新增/改动约束性文档时)
1. **文档头部** — 标题下紧随 `**性质**:元需求(META) / 需求(REQ) / 参考(REF)`
2. **索引登记** — 在 `docs/README.md` §六「文档性质索引」加一行
3. **强制链** — META 且含强制条款 → **必须同步挂进 `AGENTS.md` 引用链**
> ⚠️ **标记了但不挂强制链 = 只是标签,不构成约束。**(实测:11 份 META 中曾有 7 份无任何强制入口)
## 三类规则差异
| | META | REQ | REF |
|---|---|---|---|
| 变更成本 | 高(全仓影响) | 中(局部) | 低(同步即可) |
| 走架构锁定 | ❌ | ✅ `docs/arch/` | ❌ |
| 关联工单 | 建议 | **必须** | 建议 |
| AI 自动加载 | 强制类必须 | 否 | 否 |
## 存量策略
**不追溯**(不强制给历史文档补标记);**但谁改动、谁补齐**。

View File

@@ -1,7 +1,10 @@
# 全局行为约束(00-global)
**性质**:元需求(META)—— **仓库级约束源**,约束所有在本仓库内执行的 AI 任务
> 本文件对应"AI 协作治理规范" 4.1 铁律与 4.2 `00-global.md`,作为仓库级指令被 AI 助手自动加载。
> 适用范围:所有在本仓库内执行的 AI 代码生成 / 文档 / 审查任务。
> 文档性质索引见 [docs/README.md §六](docs/README.md)。
## 身份
@@ -28,6 +31,16 @@
新增/改造任何前端界面(页面 / 标签页 / 区块),必须先写清「替谁回答什么问题、
为什么这样组织、不做什么及原因」,并按该规范**三层落地**(代码注释 + 页面可见说明 + 审查清单)。
禁止「顺手加一张图 / 多堆一个 tab」式的无意图改动。
8. 禁止绕过后端响应格式规范(见 `docs/standards/error-code.md`)。
成功返回 `{code:0, data, message}`,错误一律走 `HTTPException`,禁止自定义裸 dict。
9. 禁止绕过 `docs/standards/数据采集合并去重机制说明.md` 规定的**去重键**。
该规范约束**所有采集通道**(投诉指纹 / 手动退款复合键 / 各 data_type 唯一键);
新增采集源必须先确认去重键,否则会产生重复入库。
10. 禁止擅自变更 `docs/standards/投诉渠道口径与对账说明.md` 中的**渠道口径权威定义**
(内部 / 商户号 / 外部投诉的归类判据)。口径是统计的根,改动会影响全部投诉数字,
必须走人工审核 + 回归验证。
11. 禁止绕开 `docs/standards/数据源接入与缓存加速规范.md` 的缓存铁律:
新增数据源 / 看板接口时,写入后必须失效对应缓存 pattern,Redis key 必须逐字符对齐。
## 输出风格
@@ -74,3 +87,28 @@
- 确属降级接受的,写 `结论: 以 <理由> 降级验收`,**禁止静默关闭**
4. **禁止无凭证关闭**。凭证缺失的历史工单**不重开**,改为补记
`## 关闭凭证核验(补记 · YYYY-MM-DD)` 并显式标注为事后追溯。
## 文档性质纪律(强制)
> 全文见 `docs/standards/文档性质标记规范.md`。核心铁律:
> **文档必须先说清自己是「需求」还是「需求的需求」,再谈内容。**
1. **两类必须区分**:
- **需求(REQ)** —— 做一个具体功能/集成。有功能边界、接口契约、验收标准、归属工单。
- **元需求(META)** —— 约束**所有**需求怎么做(规范/口径/流程/模板/审查判据)。
`docs/standards/` 与 `AGENTS.md` 属此类。
- **参考(REF)** —— 现状记录(表结构说明 / API 清单 / 台账),**无约束力**,以代码为准。
2. **判据是「约束范围」,不是文件名**:名为"XX接入规范"的文档若只约束**一个集成**,
它是 **REQ** 而非 META。
3. **三层落地**(新增/改动约束性文档时):
- ① 文档头部标 `**性质**:元需求(META) / 需求(REQ) / 参考(REF)`
- ② 在 `docs/README.md` §六「文档性质索引」登记一行
- ③ META 且含强制条款 → **必须同步挂进本文件(AGENTS.md)引用链**
4. **禁止「标记了但约束不住」**:META 不进 AGENTS 引用链 = 只是标签,不构成约束。
5. **三类适用不同规则**(用错标准 = 审查失效):
| | META | REQ | REF |
|---|---|---|---|
| 审查标准 | 能否**约束住别人** | 验收标准是否达成 | 是否与代码一致 |
| 变更成本 | 高(全仓影响) | 中(局部) | 低(同步即可) |
| 走架构锁定 | ❌ | ✅ | ❌ |
6. **存量不追溯,改动即补齐**:未标性质的存量文档不强改;**谁改动、谁补齐**。

View File

@@ -1,5 +1,8 @@
# API 接口文档
**性质**:参考(REF) —— 全量接口清单(~374 端点),**以代码为准**
> 版本:v1.1 · 日期:2026-08-26
> Base URL:`http://<服务器IP>:8000/api/v1`
> 认证:JWT Bearer Token(除 `/auth/login`、`/auth/login-json`、`/wechat/auth/code-login` 外所有接口需 `Authorization: Bearer <token>`)

View File

@@ -1,11 +1,12 @@
# docs 目录组织与命名规范
**性质**:元需求(META)—— 约束"文档放哪、叫什么名"
**生效日期**:2026-09-03
**版本**:v2.0(2026-09-03 升级:加入类型前缀 + 日期时间戳,启动存量归位)
**版本**:v2.1(2026-09-11 加入「文档性质索引」,见 §六)
**适用范围**:`docs/` 下所有文档的命名与放置位置(新增强制,存量按本规范归位)
**性质**:强制约束
**关联**:[standards/文档性质标记规范.md](standards/文档性质标记规范.md)(管"文档**是什么性质**",与本规范正交)
> 背景:docs 根目录历史文档中英文混杂、报告/规范/设计/说明混放、无日期前缀,检索与维护困难。v2.0 起统一为「目录按类型 + 文件按类型前缀 + 带日期时间戳」。
> 背景:docs 根目录历史文档中英文混杂、报告/规范/设计/说明混放、无日期前缀,检索与维护困难。v2.0 起统一为「目录按类型 + 文件按类型前缀 + 带日期时间戳」。v2.1 起增加**性质标记**(元需求/需求/参考),见 §六。
---
@@ -15,7 +16,8 @@
|---|---|---|---|
| `docs/arch/` | 已锁定架构设计 | `A-YYYYMMDD-序号-简述.md` | `A-20260903-001-issue-automation-gates.md` |
| `docs/arch/_pending/` | 待审核设计草案 | 同 arch 编号 | `A-20260903-002-xxx.md`(PENDING) |
| `docs/standards/` | 规范/清单/模板 | 英文 kebab-case | `error-code.md`、`issue-workflow.md` || `docs/reports/` | 审计/审查/审核报告 | `YYYYMMDD-主题-类型.md` | `20260831-看板缓存优化-审计报告.md` |
| `docs/standards/` | 规范/清单/模板 | 英文 kebab-case | `error-code.md`、`issue-workflow.md` |
| `docs/reports/` | 审计/审查/审核报告 | `YYYYMMDD-主题-类型.md` | `20260831-看板缓存优化-审计报告.md` |
| `docs/debug/` | 调试会话/故障归档 | `debug-<主题>-<YYYYMMDD>.md` | `debug-cs-record-gap-20260902.md` |
| `docs/security/` | 安全事件/凭证整改 | kebab-case | `credential-incident-remediation.md` |
| `docs/ops/` | 运维手册/SOP | `<主题>-运维手册.md` | `数据采集-运维手册.md` |
@@ -23,6 +25,9 @@
| `docs/` 根目录 | 全局总纲/索引 | 中文名或 README | `README.md`、`规划设计.md` |
> 规则:**报告进 reports,规范进 standards,手册进 ops,设计进 design(锁定后进 arch),调试进 debug**。
>
> ⚠️ **目录名 ≠ 性质**:`docs/standards/` 里混着 REF(参考)与 REQ(需求)类文档。
> 目录只表示"它是规范性材料";**真正约束力看 §六 的性质索引**。
---
@@ -151,3 +156,82 @@ PY
- **历史遗留链接**:指向已迁移目录(如 `.trae/documents/`)的链接,应改指向 arch 里对应的锁定文档。
- **README 目录树与索引**:移动后必须同步更新 README 的目录树代码块和文档索引表。
---
## 六、文档性质索引(v2.1 新增)
> **单一事实来源**:本表是全部约束性文档的**性质登记处**。
> 判定标准与三类差异见 [standards/文档性质标记规范.md](standards/文档性质标记规范.md)。
>
> **性质**:`META`=元需求(约束所有需求怎么做)· `REQ`=需求(做一个具体功能)· `REF`=参考(现状记录,无约束)
>
> **强制链**:META 中的"强制类"必须挂进 `AGENTS.md`,否则标记只是标签、不构成约束。
### 6.1 元需求(META)—— 11 份
| 文档 | 强制力 | AGENTS 引用 | 一句话职责 |
|---|---|---|---|
| `AGENTS.md`(根级) | 🔴 强 | 自身即约束源 | 仓库级 AI 铁律(禁止 console.log / 裸 SQL 拼接 / 硬编码密钥)+ 工单纪律 + 界面设计纪律 |
| `standards/issue-workflow.md` | 🔴 强 | ✅ | 所有任务先建工单;一单一目标;**禁止无凭证关闭** |
| `standards/review-checklist.md` | 🔴 强 | ✅ | 代码审查清单(安全/正确性/一致性/性能/界面设计);CRITICAL 阻塞合并 |
| `standards/数据时间字段使用规范.md` | 🔴 强 | ✅ | 时区换算元规则(`AT TIME ZONE 'Asia/Shanghai'`)+ `SYS/system` 排除 |
| `standards/界面设计说明规范.md` | 🔴 强 | ✅ | 界面五要素 + 三层落地;"说不清替谁解决问题的界面不许开工" |
| `standards/error-code.md` | 🔴 强 | ⚠️ **待挂** | 全后端响应格式元规则(成功 `{code:0,data,message}`、错误走 HTTPException) |
| `standards/数据采集合并去重机制说明.md` | 🟠 中 | ⚠️ **待挂** | **所有采集通道**的去重键口径(投诉指纹 / 手动退款复合键 / 各 data_type 唯一键) |
| `standards/投诉渠道口径与对账说明.md` | 🟠 中 | ⚠️ **待挂** | 投诉三源归类的**权威定义**(改动影响所有投诉统计)+ 双方缺口对账口径 |
| `standards/数据源接入与缓存加速规范.md` | 🔴 强 | ⚠️ **待挂** | 新增数据源/看板接口的缓存铁律(写入后必须失效 pattern、key 逐字符对齐) |
| `standards/task-work-order.md` | 🟠 中 | — | AI 派工单模板(强制填输入/输出/约束/禁止事项/测试要求) |
| `standards/alembic-migration管理规范.md` | 🟠 中 | — | 表结构变更元规则(单一 head、downgrade 必须可实现) |
| **`standards/文档性质标记规范.md`** | 🟠 中 | — | **本索引的依据**:文档必须标性质;三类的规则差异 |
> ⚠️ **待办(见 §6.4)**:4 份标"待挂"的 META 尚无 `AGENTS.md` 强制入口,
> 目前仅靠约定俗成维系 —— 需补进强制链,否则"标记 ≠ 约束"。
### 6.2 需求(REQ)—— 具体功能/集成
| 文档 | 说明 |
|---|---|
| `standards/workbuddy数据同步接入规范.md` | 投诉推送接口契约(`/sync/complaints/push`)—— 约束**单个集成**,非元需求 |
| `standards/workbuddy手动退款数据同步接入规范.md` | 手动退款推送契约(`/sync/manual-refunds/push` + 复合键幂等) |
| `standards/外部只读数据接入规范.md` | 对外开放只读 API 契约(`/api/v1/external` scope / 限频 / 字段 / 单位) |
| `规划设计.md` | 系统总体设计与需求规划(v0.4) |
| `规划设计-v0.5.md` | 迭代需求方案(7 项需求 + 5 级角色 + 交互规范 + ADR) |
| `arch/A-*.md`(47 份) | 已锁定架构设计,逐份对应一个需求/改造 |
> 📌 **易混点**:名为"XX接入规范"的文档是 **REQ** 而非 META ——
> 它只约束**某一个具体集成**,不是"所有需求怎么做"。
> **判据是约束范围,不是文件名里有没有"规范"二字。**
### 6.3 参考(REF)—— 现状记录,无约束
| 文档 | 说明 |
|---|---|
| `standards/数据库表结构说明.md` | 86 张表清单与时间字段现状(**以代码为准**) |
| `standards/数据同步与加工说明.md` | 采集链路实现细节(BMS / ai-acc / OCR / 时区加工) |
| `standards/投诉数据接入说明.md` | 三种接入方式操作步骤(手册性质) |
| `standards/人均效率算法说明.md` | 人均效率指标算法现状(工作量分 / 6 专项难度系数 / SCD 回退) |
| `standards/跨项目经验迁移指南.md` | 方法论沉淀(自述"非本项目强制约束") |
| `API接口文档.md` | 全量接口清单(~374 端点,**以代码为准**) |
| `审查行动项登记.md` | 历次审查行动项台账(跟踪登记) |
> 📌 REF 的"过期"不是"违反规则",而是"**与代码不一致**" → 直接重写即可,无需走变更流程。
### 6.4 待修复项(本次盘点发现)
| # | 问题 | 处置 |
|---|---|---|
| 1 | **4 份 META 无强制入口**(`error-code` / `数据采集合并去重机制说明` / `投诉渠道口径与对账说明` / `数据源接入与缓存加速规范`) | 补进 `AGENTS.md` 引用链 |
| 2 | `standards/` 目录名误导(混着 REF 与 REQ) | 已在 §一 加警示;长期可考虑按性质分目录 |
| 3 | 存量文档多数未标 `**性质**` 字段 | **不追溯**;谁改动、谁补齐(同 `界面设计说明规范` 策略) |
| 4 | `人均效率算法说明.md` 含业务口径(难度系数)但自述为"说明" | 存疑:若系数变更需全仓评估,应升级为 META |
### 6.5 新增文档时的登记义务
新增任何约束性文档时,**必须**:
1. 在文档头部标 `**性质**:元需求(META) / 需求(REQ) / 参考(REF)`
2. 在**本 §六 对应表格**中登记一行
3. 若为 META 且含强制条款 → **同步挂进 `AGENTS.md` 引用链**
> 未登记的文档视为"未生效" —— `review-checklist` 可据此判 WARNING。

View File

@@ -257,7 +257,64 @@ graph LR
| A7 | **同主体环比异动榜**(谁涨得最凶) | ★★ 主动发现苗头,而非事后复盘 | 中(依赖 2.2 `compare`) | P3 |
| A8 | **投诉→退款转化率**(每 100 投诉烧多少钱) | ★★ 投诉的「代价单价」 | 低(涉诉金额 ÷ 总量) | P3 |
> **P1 只取 A1**(成本低 + 已在契约的结论条内),其余 P2/P3 视用户挑选补入。
> **P1 只取 A1**(成本低 + 已在契约的结论条内)。
### A2 / A5 已实施(2026-09-11 用户追加选定)
**A5 · 投诉登记时段热力** —— 直接服务**排班决策**
实测(近 30 天,北京时间)呈现**清晰双峰**,可直接指导排班:
| 时段 | 09–12 时 | 19–21 时 | 02–05 时(谷底) |
|---|---|---|---|
| 量级 | 350~428 | 397~422 | 12~23 |
→ 结论:**上午班与晚班是刚需,凌晨可大幅减配**。
**A2 · 反复投诉** —— 说明「第一次没解决好」,比总量更接近真相
> ★★ **口径关键(实测差 3 倍,必须记牢)**:
> 按 SN **直接计数** ≥3 次 = **237 台**;
> 按 **投诉事件(SN + 北京日期)去重**后 ≥3 次 = **78 台**。
>
> 差异来自 #55 口径:**同一天多渠道呈现 = 同一笔投诉的 1 个事件,不算反复**。
> 故「反复投诉」判据必须是 **事件数**,不是原始记录数 ——
> 否则会把「一天内多渠道登记」误判为该设备在反复投诉,**方向性错误**。
### 补充接口契约(A2 / A5,追加至 §二.1 的 `/overview` 响应)
```jsonc
{
// ── A5: 投诉登记时段热力(小时 × 星期,北京时间) ──
// 用途:排班决策(高峰期不加人 = 必然积压)
"hour_heatmap": {
// 每个格子 = 某星期某小时的投诉登记量;前端渲染热力图
"cells": [ { "weekday": 1, "hour": 9, "count": 42 } ], // weekday: 1=周一 … 7=周日
"by_hour": [ { "hour": 9, "count": 350 } ], // 边际分布(24 项)
"by_weekday": [ { "weekday": 1, "count": 1200 } ], // 边际分布(7 项)
"peak_hours": [9, 10, 11, 19, 20, 21], // 由后端判定(≥P75)
"valley_hours": [2, 3, 4, 5] // 由后端判定(≤P25)
},
// ── A2: 反复投诉(同一设备多次投诉事件) ──
// ⚠️ 计数单位是**事件**(SN + 北京日期去重),不是原始记录数(见 §四点五 A2)
"repeat": {
"threshold": 3, // 判据:事件数 ≥ 3
"events_total": 20450, // 窗口内有 SN 的投诉事件总数
"units_total": 10766, // 涉及的设备数
"repeat_units": 78, // 反复投诉设备数
"repeat_events": 265, // 这些设备贡献的事件数
"rate": 0.7, // repeat_units / units_total … ×100 后仍为百分数(保留 1 位)
"items": [ // Top N 清单(按事件数降序)
{ "device_sn": "…", "events": 5, "first_day": "2026-07-02", "last_day": "2026-09-08",
"sources": ["内部投诉", "外部投诉"] }
]
}
}
```
> **A2 的「升级」信号**:若某设备的 `sources` 同时含内部与外部 → 既是反复投诉、
> 又已升级到外部渠道,属**最高优先级**跟进对象(前端以红色标识)。
---

View File

@@ -1,5 +1,8 @@
# Alembic Migration 管理规范
**性质**:元需求(META) —— 约束**所有**表结构变更
> 版本:v1.1 · 日期:2026-08-26
> 关联代码:[backend/alembic/](../backend/alembic/)
> 当前 head:`73a7922ec598`(单一 head)

View File

@@ -1,5 +1,8 @@
# 错误码与响应格式规范
**性质**:元需求(META) —— 约束**所有**后端接口的响应格式
**生效日期**:2026-09-01
**适用范围**:后端 `backend/app/api/v1/**` 所有接口
**关联文档**:[API接口文档.md](../API接口文档.md)、AGENTS.md(全局约束)

View File

@@ -1,5 +1,6 @@
# 工单驱动工作流规范(Issue Workflow)
**性质**:元需求(META)—— 约束"所有任务怎么流转"
**生效日期**:2026-09-03
**适用范围**:所有任务(含需求、事故、治理、缺陷、运维动作),不论由人还是 AI 执行
**关联文档**:[AGENTS.md](../../AGENTS.md)(全局约束)、[task-work-order.md](task-work-order.md)(AI 派工单模板)、[arch/README.md](../arch/README.md)(架构锁定流程)、[review-checklist.md](review-checklist.md)(审查清单)

View File

@@ -1,8 +1,9 @@
# 代码审查清单(30-reviewer)
**性质**:元需求(META)—— 提供**所有**代码审查的判据(CRITICAL 阻塞合并)
**生效日期**:2026-09-01
**适用范围**:所有 AI 初审 / 人工终审
**关联文档**:AGENTS.md(全局约束)、[error-code.md](error-code.md)、[数据时间字段使用规范.md](数据时间字段使用规范.md)、[界面设计说明规范.md](界面设计说明规范.md)
**关联文档**:AGENTS.md(全局约束)、[error-code.md](error-code.md)、[数据时间字段使用规范.md](数据时间字段使用规范.md)、[界面设计说明规范.md](界面设计说明规范.md)、[文档性质标记规范.md](文档性质标记规范.md)
> 审查流程:AI 初审(逐项核对本清单)→ 输出分级报告 → 人工终审(业务正确性 + 边界 + 兼容性)。
> AI 初审**只输出报告,不修改代码**;有 CRITICAL 则切回 Developer 阶段修复后重审。
@@ -11,6 +12,17 @@
## 一、AI 初审检查清单(必须逐项核对)
### 文档性质(新增/改动约束性文档时必查)
> 依据:`docs/standards/文档性质标记规范.md`。区分「需求」与「需求的需求」。
- [ ] 新增/改动的约束性文档,头部含 `**性质**:元需求(META)/ 需求(REQ)/ 参考(REF)`
- [ ] 已在 `docs/README.md` §六「文档性质索引」登记
- [ ] 若为 **META 且含强制条款** → 已挂进 `AGENTS.md` 引用链(否则标记≠约束)
- [ ] 若为 **REQ** → 有功能边界 + 接口契约 + 验收标准 + 归属工单
- [ ] 若为 **REF** → 已声明「以代码为准」,不当作约束引用
- [ ] 性质判定用的是「**约束范围**」而非文件名(名为"XX接入规范"但只约束单个集成的 → REQ)
### 安全
- [ ] SQL 注入风险(是否全部参数化 / `text()` 绑定参数,无字符串拼接)

View File

@@ -1,5 +1,8 @@
# 任务派工单模板(Prompts)
**性质**:元需求(META) —— 约束**所有**派工单的产出形态
**生效日期**:2026-09-01
**适用范围**:所有交给 AI 的单项开发任务(对应"AI 协作治理规范" 4.4)
**关联文档**:AGENTS.md(全局约束)、[error-code.md](error-code.md)、[review-checklist.md](review-checklist.md)、[issue-workflow.md](issue-workflow.md)(工单驱动工作流,先建工单再派工)

View File

@@ -1,5 +1,8 @@
# workbuddy 手动退款数据同步接入规范
**性质**:需求(REQ) —— 约束**单个集成**(手动退款推送),非元需求
> 版本: v1.0 · 2026-08-23
> 适用: workbuddy 定时将 `商户号手机退款处理登记.xlsx` 明细推送到客服中心管理系统
> 配套: 投诉数据同步接入见 [workbuddy数据同步接入规范.md](workbuddy数据同步接入规范.md)

View File

@@ -1,5 +1,8 @@
# workbuddy 投诉数据同步接入规范
**性质**:需求(REQ) —— 约束**单个集成**(投诉推送接口),非元需求
> 版本: v1.0 · 2026-08-20
> 适用: workbuddy 定时将 `315投诉处理登记表.xlsx` 等文件内容推送到客服中心管理系统

View File

@@ -1,5 +1,8 @@
# 人均效率算法说明
**性质**:参考(REF) —— 记录效率算法现状;⚠️ 内含业务口径(难度系数),存疑待定
> 版本:v1.0 · 日期:2026-08-17
> 关联代码:[analytics.py](../backend/app/services/analytics.py) `today_staff_efficiency` 函数(约 L4219)
> 关联模型:[person.py](../backend/app/models/person.py) / [specialty.py](../backend/app/models/specialty.py)

View File

@@ -1,5 +1,8 @@
# 外部只读数据接入规范
**性质**:需求(REQ) —— 约束**单个集成**(对外只读 API),非元需求
> 版本:v1.1 · 2026-09-07(v1.0 2026-09-02)
> 适用:workbuddy 等外部工具**拉取**本系统经营数据(接待/退款/投诉/简报),自行组合生成报告。
> 配套:数据**推送**类接入(投诉/手动退款)见 `workbuddy数据同步接入规范.md` / `workbuddy手动退款数据同步接入规范.md`,本规范只覆盖**只读拉取**。

View File

@@ -1,5 +1,8 @@
# 投诉数据接入说明(315 / 卡商)
**性质**:参考(REF) —— 三种接入方式的操作步骤说明
> 更新: 2026-09-01
> 入口: 数据采集 → 「投诉接入」

View File

@@ -1,5 +1,8 @@
# 投诉渠道口径与对账说明
**性质**:元需求(META) —— 定义投诉渠道口径的**权威含义**(改动影响全部投诉数字)
> 版本:v1.0 · 日期:2026-09-11
> 状态:实现说明(业务 / 上游 / 开发者参考)
> 关联:[投诉数据接入说明.md](投诉数据接入说明.md) · [数据采集合并去重机制说明.md](数据采集合并去重机制说明.md)

View File

@@ -1,5 +1,8 @@
# 数据同步与加工说明
**性质**:参考(REF) —— 记录采集链路实现现状,**以代码为准**
> 版本:v0.8 · 日期:2026-09-01
> 状态:实现补充文档(开发者参考)
> 关联:[规划设计.md](../规划设计.md) §十四/§十九 数据同步与加工实现补充

View File

@@ -1,5 +1,8 @@
# 数据库表结构说明
**性质**:参考(REF) —— 记录表结构现状,**以代码为准**
> 版本:v1.1 · 日期:2026-08-26
> 数据库:PostgreSQL 15
> 总表数:86 张

View File

@@ -1,5 +1,6 @@
# 数据时间字段使用规范
**性质**:元需求(META)—— 约束**全系统**的时间统计口径(时区换算 + 系统操作人排除)
**生效日期**: 2026-08-01
**适用范围**: 全系统统计分析(预设视图、自助分析、自定义 SQL、周报/审计输出)
**关联文档**: [数据时间审核报告.md](../reports/20260801-数据时间审核-审计报告.md)(一次性审计快照)、[规划设计.md](../规划设计.md)、[数据同步与加工说明.md](数据同步与加工说明.md)

View File

@@ -1,5 +1,8 @@
# 数据源接入与缓存加速规范
**性质**:元需求(META) —— 约束**所有新增数据源/看板接口**的缓存接入
> 版本:v1.0 · 日期:2026-08-25
> 状态:架构约束文档(开发者必读)
> 关联:[数据同步与加工说明.md](数据同步与加工说明.md) · [workbuddy数据同步接入规范.md](workbuddy数据同步接入规范.md) · [workbuddy手动退款数据同步接入规范.md](workbuddy手动退款数据同步接入规范.md)

View File

@@ -1,5 +1,8 @@
# 数据集采集/合并/去重 机制说明
**性质**:元需求(META) —— 约束**所有采集通道**的去重键
**版本**:v1.0 · 2026-09-08
**适用范围**:系统所有数据采集通道(BMS/SCRM/共享文档/外部推送/文件导入)的数据合并与去重口径
**背景**:共享文档在线采集落地后,需系统梳理"多通道采集同一数据集时如何合并去重",确保一致性、幂等、不产生脏数据。

View File

@@ -0,0 +1,189 @@
# 文档性质标记规范(Doc Class)
**生效日期**:2026-09-11
**版本**:v1.0
**适用范围**:本仓库**全部**约束性文档(`AGENTS.md`、`docs/standards/`、`docs/arch/`、`docs/` 根级)
**性质**:**元需求(META)** —— 本规范约束"其它文档该怎么被分类与标记"
**关联文档**:[AGENTS.md](../../AGENTS.md)、[issue-workflow.md](issue-workflow.md)、[review-checklist.md](review-checklist.md)、[docs/README.md](../README.md)
---
## 〇、一句话铁律
> **文档必须先说清自己是「需求」还是「需求的需求」,再谈内容。
> 两类文档适用完全不同的规则 —— 混用会导致用错标准审查、用错流程维护。**
---
## 一、为什么要有这个区分
### 1.1 问题的起源
用户提出(2026-09-11):
> 「我看这些需求条目,**有的是需求,有的是需求的需求**,都需要打上不同的标记。」
这个区分戳中了一个真实混乱:仓库里 60+ 份文档混在一起,
**描述"做一个功能"的**与**约束"所有功能怎么做"的**没有区分,导致:
| 症状 | 后果 |
|---|---|
| 用"需求"的标准要求"元需求" | 要求规范写"验收标准",但规范的验收标准是"它能否约束住别人" |
| 用"元需求"的稳定性要求"需求" | 需求本就该随迭代演进,被当成"锁定不可改" |
| 找不到"约束源" | 实现者不知道**到底该遵守哪几条**,规范沦为摆设 |
| 目录名误导 | `docs/standards/` 里混着参考说明与接口契约,新人无法判断哪份有强制力 |
### 1.2 实测盘点(2026-09-11)
对本仓库全部约束性文档分类后的真实分布:
| 性质 | 数量 | 典型 |
|---|---|---|
| **元需求(META)** | 11 | `AGENTS.md`、`issue-workflow.md`、`数据时间字段使用规范.md` |
| **需求(REQ)** | 5 + 架构 47 | `workbuddy数据同步接入规范.md`(约束某一具体集成)、`docs/arch/*` |
| **参考(REF)** | 7 | `数据库表结构说明.md`、`API接口文档.md` |
> ⚠️ **发现的严重问题**:11 份元需求中**仅 4 份**被 `AGENTS.md` 显式引用
> (`issue-workflow` / `review-checklist` / `数据时间字段使用规范` / `界面设计说明规范`)。
> 其余 7 份**无任何强制入口** —— 即"标记了但约束不住"。
> 本规范 §五 规定如何修复。
---
## 二、三分类定义
### 2.1 元需求(META)—— 「需求的需求」
**定义**:约束**所有**需求/实现该怎么做,而非描述某一个功能。
**判定标准**(满足**任一**即可):
| # | 判据 | 例子 |
|---|---|---|
| 1 | 回答"**任何**新增功能都必须遵守什么?" | `数据时间字段使用规范`:所有时间统计必须 `AT TIME ZONE 'Asia/Shanghai'` |
| 2 | 定义**口径/术语的权威含义** | `投诉渠道口径与对账说明`:什么叫"内部投诉" |
| 3 | 规定**流程/门禁** | `issue-workflow`:所有任务先建工单 |
| 4 | 提供**审查判据** | `review-checklist`:CRITICAL 阻塞合并 |
| 5 | 是**模板**(约束产出物形态) | `task-work-order`:派工单必须含输入/输出/约束 |
| 6 | 约束"**文档/配置自身**如何组织" | **本规范**:文档必须标性质 |
**维护特征**:**极少变动**;改动需评估"全仓库影响面",通常配合工单 + 人工审核。
### 2.2 需求(REQ)—— 「具体要做什么」
**定义**:描述一个具体功能/模块/集成的设计与实现契约。
**判定标准**:
| # | 判据 | 例子 |
|---|---|---|
| 1 | 有明确的**功能边界**(做什么、不做什么) | `A-20260911-001-投诉分析整体重构` |
| 2 | 有**接口契约**(路径/字段/错误码) | `workbuddy数据同步接入规范`:`/sync/complaints/push` 契约 |
| 3 | 有**验收标准** | 同上,须能逐条核验 |
| 4 | 有**归属工单** | 每份需求文档应关联 issue 号 |
**维护特征**:**随迭代演进**;变更走正常工单流程即可,无需全仓库评估。
> 📌 **易混点**:名为"XX接入规范"的文档通常是 **REQ** 而非 META ——
> 因为它只约束**某一个具体集成**,不是"所有需求怎么做"。
> **判据是"约束范围",不是文件名里有没有'规范'二字。**
### 2.3 参考(REF)—— 「现状记录」
**定义**:记录系统现状/知识/台账,**不构成约束**(或仅有弱约束)。
**判定标准**:
| # | 判据 | 例子 |
|---|---|---|
| 1 | 以"**代码为准**"的镜像清单 | `API接口文档.md`:~374 端点登记 |
| 2 | 描述**已实现的机制** | `数据同步与加工说明.md`:采集链路现状 |
| 3 | **台账/登记**性质 | `审查行动项登记.md` |
| 4 | 明确的**跨项目方法论**(自述"非本项目强制") | `跨项目经验迁移指南.md` |
**维护特征**:**随实现同步更新**;过期即失效(不"违反",只是"不准")。
---
## 三、标记方式(三层落地)
### 3.1 ① 文档头部 —— 性质字段
每份约束性文档的头部**必须**含性质标记,紧随标题:
```markdown
# <文档标题>
**性质**:元需求(META) <!-- 或 需求(REQ) / 参考(REF) -->
**生效日期**:YYYY-MM-DD
**适用范围**:<约束谁>
...
```
> 存量文档**不强制追溯**(避免大批量改动引入风险);
> **但谁改动、谁补齐**(同 `界面设计说明规范` 的存量策略)。
### 3.2 ② 索引登记 —— `docs/README.md`
全部约束性文档**必须在 `docs/README.md` 的「文档性质索引」表中登记**,
按性质分组,标注:性质 / 强制力 / 是否被 AGENTS 引用 / 一句话职责。
**索引是单一事实来源** —— 新增文档必须同步登记,否则 `review-checklist` 判 WARNING。
### 3.3 ③ 强制链 —— AGENTS.md 引用
**元需求(META)中的"强制类"必须挂进 `AGENTS.md` 的引用链**,
否则标记只是标签、不构成约束(见 §1.2 实测问题)。
判定"强制类 META":文中含明确禁止/必须条款 **且** 有具体适用场景。
---
## 四、三类的规则差异(**这是分类的意义所在**)
| 维度 | 元需求 META | 需求 REQ | 参考 REF |
|---|---|---|---|
| **审查标准** | 能否**约束住别人**?有无明确判据? | 验收标准是否达成? | 是否与代码一致? |
| **变更成本** | 高(全仓库影响) | 中(局部) | 低(同步即可) |
| **是否走架构锁定** | ❌ 不走(走规范评审) | ✅ `docs/arch/` 走锁定流程 | ❌ 不走 |
| **是否需关联工单** | 建议 | **必须** | 建议 |
| **是否进 AI 自动加载** | 强制类必须 | 否 | 否 |
| **过期处理** | 修订 | 归档 | 重写 |
---
## 五、存量问题与修复要求
基于 2026-09-11 盘点,以下 META **无强制入口**,需补进 `AGENTS.md`:
| 文档 | 为什么该挂 | 建议位置 |
|---|---|---|
| `error-code.md` | 所有后端接口的响应格式元规则 | 「绝对禁止」或「输出风格」 |
| `数据采集合并去重机制说明.md` | 约束**所有采集通道**的去重键 | 「绝对禁止」 |
| `投诉渠道口径与对账说明.md` | 投诉口径权威定义,改动影响所有投诉统计 | 「绝对禁止」 |
| `数据源接入与缓存加速规范.md` | 新增数据源/接口的缓存铁律 | 「工具使用」或「绝对禁止」 |
> 其余 META(`alembic-migration管理规范` / `task-work-order`)已由其它规范间接约束,
> 可暂不挂 AGENTS,但**必须进索引**。
---
## 六、自检(新增/改动文档前)
- [ ] 这份文档约束的是"**某个**功能"还是"**所有**功能"?
- [ ] 若答"所有" → META:是否已进 `docs/README.md` 索引 + `AGENTS.md` 强制链?
- [ ] 若是 META:文中每条要求是否都有**明确判据**(而非"建议"式表述)?
- [ ] 若是 REQ:是否有**验收标准** + **归属工单**?
- [ ] 若是 REF:是否声明了"**以代码为准**"?
- [ ] 头部是否已标 `**性质**`?
---
## 七、与既有规范的关系
| 规范 | 关系 |
|---|---|
| `docs/README.md` | 管"文档放哪、叫什么名";**本规范管"文档是什么性质"** —— 正交 |
| `issue-workflow.md` | 管"任务怎么流转";本规范管"文档怎么分类" |
| `review-checklist.md` | 已加「文档性质」检查项 |
| `AGENTS.md` | META 强制类的唯一挂载点 |

View File

@@ -1,9 +1,9 @@
# 界面设计说明规范(Design Rationale)
**性质**:元需求(META)—— 约束**所有**前端界面的设计说明义务(强制)
**生效日期**:2026-09-11
**版本**:v1.0
**适用范围**:所有前端界面(页面 / 标签页 / 独立区块组件)的新增与改造
**性质**:**强制约束**(违反者审查不通过)
**关联文档**:[AGENTS.md](../../AGENTS.md)、[review-checklist.md](review-checklist.md)、[issue-workflow.md](issue-workflow.md)、[docs/README.md](../README.md)
---

View File

@@ -1,8 +1,8 @@
# 跨项目经验迁移指南
**性质**:参考(REF)—— 方法论沉淀,**非本项目强制约束**(自述「供其他项目参考」)
**版本**:v1.0
**日期**:2026-09-06
**性质**:方法论沉淀 / 可迁移最佳实践(非本项目强制约束,供其他项目参考)
**来源**:csctalon 项目实战经验(事故 → 根因 → 修复 → 防复发 闭环提炼)
**用法**:把本文档按需拆给其他项目团队;每章末尾的"可勾选清单"可直接作为落地模板。

View File

@@ -1,5 +1,8 @@
# 审查行动项登记台账
**性质**:参考(REF) —— 历次审查行动项台账(跟踪登记)
> 用途:汇总历次审查发现的问题,登记为可跟踪行动项(来源 → 优先级 → 状态),避免结论丢失。
> 维护规则:每完成一项,状态改为「已完成」并填完成日期;新增问题按来源追加编号。
> 最近更新:2026-09-03(来源 E:技术债评审归档,待 bug 修复后处置)

View File

@@ -1,5 +1,8 @@
# 客服中心管理系统 规划设计 v0.5
**性质**:需求(REQ) —— 迭代需求方案(7 项需求 + 5 级角色 + ADR)
> 版本:v0.5(完整评审版) · 日期:2026-08-18
> 状态:**已评审定稿(30 项决策点全部确认,见 §十四 ADR)**
> 前置版本:v0.4(运营看板多粒度 + Group/Specialty 双体系 + SCRM 同步 + 决策看板 6 子菜单)

View File

@@ -1,5 +1,8 @@
# 客服中心管理系统 规划设计
**性质**:需求(REQ) —— 系统总体设计与需求规划(v0.4)
> 版本:v0.4(运营看板多粒度 + Group/Specialty 双体系 + SCRM 同步 + 决策看板) · 日期:2026-08-17
> 状态:数据分析决策系统 + 日常工作流办公支撑系统
> 说明:v0.2 数据/视图/看板三层底座已落地;v0.3 补齐"报告→审计→措施→跟进→回看"管理闭环;v0.3.1 补齐数据同步与加工实现说明;v0.3.2 升级同步架构 + 看板组件化;**v0.4 新增运营看板多粒度切换、Group/Specialty 双体系、SCRM 同步、OCR 自动登录、决策看板 6 大子菜单、人均效率算法、数据采集统一入口(SyncDashboard)、alembic migration 管理**

34
eslint.config.mjs Normal file
View File

@@ -0,0 +1,34 @@
// ═══════════════════════════════════════════════════════════════════════════
// 根级 ESLint 配置 —— **桥接文件**,不是第二套规则
//
// ★ 为什么需要它(2026-09-11 #67 排查结论)
//
// 现象:`web/src/views/dashboards/ComplaintAnalysis.vue` 中 `v-model:preset="..."`
// 被编辑器标为 [vue/no-v-model-argument] 'v-model' directives require no argument.
//
// 实测澄清(三条证据):
// 1. `cd web && npx eslint src/...` → 0 错误,退出码 0
// 2. `vite build` / `docker compose build --no-cache` → 构建成功
// 3. ESLint Node API(cwd=web)解析该规则值为 `[0]`(已关闭)
//
// 根因:**仓库根目录没有 ESLint 配置**。VS Code 的 ESLint 扩展默认以**工作区根**
// 为 cwd 启动 ESLint;在根目录找不到任何配置时,会退回到内置默认规则集,
// 于是把 Vue 3 的具名 v-model 按旧语义判为非法。而 `vue/no-v-model-argument`
// 只在 eslint-plugin-vue 的 `flat/vue2-*` 规则集中启用。
//
// 修复策略(**单一事实来源**):
// 真实规则**只在 `web/eslint.config.js`**,本文件仅做转发 ——
// 避免出现两套配置互相漂移(历史教训:菜单/口径多处硬编码导致不一致)。
//
// ⚠️ 约束:本文件**不得新增任何规则**。要改规则请改 `web/eslint.config.js`。
// ═══════════════════════════════════════════════════════════════════════════
import webConfig from './web/eslint.config.js'
export default [
// 忽略构建产物与依赖(与 web/ 保持一致)
{ ignores: ['**/dist/**', '**/node_modules/**', '**/.venv/**', 'data/**', 'logs/**'] },
// 直接复用 web 侧配置 —— 单一事实来源
...webConfig,
]

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""el-radio-button: label → value 迁移(Element Plus 2.14 弃用修复)。
## 为什么必须迁移
Element Plus 2.6+ 起,`el-radio-button` 的 `label` 作为「值」的用法被标记 deprecate
(3.0.0 移除),控制台会持续告警:
[el-radio] [API] label act as value is about to be deprecated in version 3.0.0,
please use value instead.
项目实际装的 `element-plus` 为 **2.14.4**(package.json 写 ^2.6.2),故告警真实存在。
官方迁移目标:`label` → `value`。
## 安全边界(只改该改的)
1. **只处理 `<el-radio-button>`**,不动 `<el-radio>`(其 `label` 有显示语义)与其它组件。
2. **只替换「值」位置的 label**:
- 自闭合:`<el-radio-button label="x" />` → `value="x"`
- 带子内容:`<el-radio-button label="x">文本</…>` → `value="x"`(文本保留为显示)
- 动态绑定:`:label="expr"` → `:value="expr"`
3. **不改** `<el-radio-button>` 内作为**显示文本**的 `label` —— 本项目经核查不存在该用法
(157 处全部为「值」;显示文本均写在标签内容里)。
## 用法
python3 scripts/migrate_radio_label_to_value.py --dry-run # 预览(默认)
python3 scripts/migrate_radio_label_to_value.py --apply # 写盘
⚠️ 仅做**文本替换**,不做格式化;写完请自行跑 eslint / build 验证。
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "web" / "src"
# 匹配 <el-radio-button ...> 的开标签(含自闭合),跨行安全
TAG_RE = re.compile(r"<el-radio-button\b[^>]*?/?>", re.DOTALL)
def migrate_tag(tag: str) -> tuple[str, bool]:
"""把单个 el-radio-button 标签内的 label 属性改为 value。"""
changed = False
# ① 静态: label="xxx" → value="xxx"
new_tag, n = re.subn(r'\blabel="', 'value="', tag)
changed |= n > 0
# ② 动态: :label="expr" → :value="expr"
# 注意顺序:先处理带冒号的,避免被 ① 误伤(① 的 \blabel 不匹配 :label 的 label?
# 实测 \blabel 会匹配 ":label" 中的 label,故必须**先**处理带冒号的并加负向断言)
new_tag = re.sub(r'(?<!:)\blabel="', 'value="', new_tag)
new_tag = re.sub(r":label=", ":value=", new_tag)
changed |= new_tag != tag
return new_tag, changed
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--apply", action="store_true", help="写盘(默认仅预览)")
args = ap.parse_args()
files = sorted(SRC.rglob("*.vue"))
total_tags = total_files = 0
report: list[tuple[Path, int]] = []
for f in files:
text = f.read_text(encoding="utf-8")
if "el-radio-button" not in text:
continue
hits = 0
def _sub(m: re.Match) -> str:
nonlocal hits
tag = m.group(0)
new, ch = migrate_tag(tag)
if ch:
hits += 1
return new
new_text = TAG_RE.sub(_sub, text)
if hits:
report.append((f, hits))
total_tags += hits
total_files += 1
if args.apply:
f.write_text(new_text, encoding="utf-8")
mode = "已写盘" if args.apply else "预览(未写盘)"
print(f"=== el-radio-button label → value 迁移 [{mode}] ===\n")
for f, n in report:
print(f" {n:>3} 处 {f.relative_to(ROOT)}")
print(f"\n合计: {total_files} 个文件 / {total_tags} 处")
if not args.apply and total_tags:
print("\n→ 确认无误后加 --apply 执行")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -53,6 +53,15 @@ export default [
// Vue 3 合法模式: props.filter 是响应式对象, 子组件改其属性双向绑定分页
// (父组件 filter 对象传递, 多处使用此惯例)。ESLint 保守报错, 关闭。
'vue/no-mutating-props': 'off',
// ★ 2026-09-11(#67) Vue 2 遗留规则, 显式关闭并说明原因:
// `vue/no-v-model-argument` 只在 `flat/vue2-*` 规则集中启用,
// 作用是不允许 Vue 2 使用 `v-model:argument` 具名双向绑定。
// 本项目是 **Vue 3.4**(v-model 参数是 3.0+ 的官方特性),
// 既有代码亦大量在用(如 el-pagination 的 v-model:current-page)。
// 编辑器侧的 Vue 版本探测可能回退到 vue2 规则集 → 误报为 compileError,
// 实际 `vite build` 与命令行 `eslint` 均无此错误(实测)。
// 显式 off: 统一口径, 避免编辑器与 CI 结论不一致.
'vue/no-v-model-argument': 'off',
},
// console.log 豁免: AGENTS.md 允许 `[TODO:]` 前缀的显式 TODO 标记
// (如 RegistryCenter.vue 的 console.log('[TODO:PolicyDetail]', row))

43
web/jsconfig.json Normal file
View File

@@ -0,0 +1,43 @@
{
// ═══════════════════════════════════════════════════════════════════════
// Vue 语言服务配置(Volar / "Vue - Official" 扩展读取)
//
// ★ 为什么需要它(2026-09-11 #67 排查结论)
// 现象:ComplaintAnalysis.vue 的 `v-model:preset="..."` 被编辑器标为
// [vue/no-v-model-argument] 'v-model' directives require no argument.
// 实测:vite build / docker compose build --no-cache / 命令行 eslint
// 全部通过;用 ESLint Node API 复现扩展加载路径,该规则解析为 [0](关闭)。
// → 结论:报错不在 ESLint 侧,而在 **Vue 语言服务回退到 Vue 2 模板语义**。
//
// `vueCompilerOptions` 是 Volar schema 的**唯一顶层键**
// (见扩展 schemas/vue-tsconfig.schema.json),其中 `target` 默认值为 `"auto"`。
// 未显式声明 Vue 版本时,语言服务可能按旧版语义解析模板,
// 把 Vue 3 的具名 v-model 当成非法语法。
//
// ⚠️ 本项目是纯 JavaScript(无 typescript 依赖),故用 jsconfig 而非 tsconfig:
// 仅为编辑器提示服务,**不影响 vite build**(构建走 @vitejs/plugin-vue)。
// ═══════════════════════════════════════════════════════════════════════
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"allowJs": true,
"checkJs": false,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src/**/*.js",
"src/**/*.vue",
"vite.config.js",
"eslint.config.js"
],
"exclude": ["node_modules", "dist"],
"vueCompilerOptions": {
// ★ 明确声明目标 Vue 版本 —— 这是本次误报的修复点(默认 "auto")
"target": 3
}
}

View File

@@ -3,11 +3,11 @@
<!-- 类型筛选 + 关键词搜索 -->
<div class="filter-bar">
<el-radio-group v-model="activeType" size="small" class="filter-types">
<el-radio-button label="">全部 ({{ items.length }})</el-radio-button>
<el-radio-button value="">全部 ({{ items.length }})</el-radio-button>
<el-radio-button
v-for="t in typeList"
:key="t.key"
:label="t.key"
:value="t.key"
>
{{ t.label }} ({{ countOf(t.key) }})
</el-radio-button>

View File

@@ -19,9 +19,9 @@
<el-col :span="12">
<el-form-item label="可见性">
<el-radio-group v-model="form.visibility">
<el-radio-button label="public">全员</el-radio-button>
<el-radio-button label="manager">管理层</el-radio-button>
<el-radio-button label="admin">管理员</el-radio-button>
<el-radio-button value="public">全员</el-radio-button>
<el-radio-button value="manager">管理层</el-radio-button>
<el-radio-button value="admin">管理员</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>

View File

@@ -20,22 +20,22 @@
<div class="time-bar">
<!-- ① 周期(粒度) —— 分析视角 -->
<el-radio-group :model-value="grain" size="small" @update:model-value="$emit('update:grain', $event)">
<el-radio-button label="auto">自动</el-radio-button>
<el-radio-button label="day">日</el-radio-button>
<el-radio-button label="week">周</el-radio-button>
<el-radio-button label="month">月</el-radio-button>
<el-radio-button value="auto">自动</el-radio-button>
<el-radio-button value="day">日</el-radio-button>
<el-radio-button value="week">周</el-radio-button>
<el-radio-button value="month">月</el-radio-button>
</el-radio-group>
<!-- ② 快捷区间 —— 业务自然周期 -->
<el-radio-group :model-value="preset" size="small" @update:model-value="onPreset">
<el-radio-button label="this_week">本周</el-radio-button>
<el-radio-button label="last_week">上周</el-radio-button>
<el-radio-button label="this_month">本月</el-radio-button>
<el-radio-button label="last_month">上月</el-radio-button>
<el-radio-button label="last7">近7天</el-radio-button>
<el-radio-button label="last30">近30天</el-radio-button>
<el-radio-button label="last90">近90天</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="this_week">本周</el-radio-button>
<el-radio-button value="last_week">上周</el-radio-button>
<el-radio-button value="this_month">本月</el-radio-button>
<el-radio-button value="last_month">上月</el-radio-button>
<el-radio-button value="last7">近7天</el-radio-button>
<el-radio-button value="last30">近30天</el-radio-button>
<el-radio-button value="last90">近90天</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
<!-- ③ 自定义区间 -->

View File

@@ -2,7 +2,7 @@
<div class="filter-bar-wrap">
<slot name="left" />
<el-radio-group v-if="quickOptions && quickOptions.length" v-model="innerQuick" size="small" @change="onQuickChange">
<el-radio-button v-for="opt in quickOptions" :key="opt.value" :label="opt.value">
<el-radio-button v-for="opt in quickOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</el-radio-button>
</el-radio-group>
@@ -22,7 +22,7 @@
/>
<el-radio-group v-if="showGrain && grainOptions && grainOptions.length" v-model="innerGrain" size="small" style="margin-left: 8px" @change="onGrainChange">
<el-radio-button v-for="g in grainOptions" :key="g.value" :label="g.value">{{ g.label }}</el-radio-button>
<el-radio-button v-for="g in grainOptions" :key="g.value" :value="g.value">{{ g.label }}</el-radio-button>
</el-radio-group>
<slot name="middle" />

View File

@@ -4,14 +4,14 @@
<div class="page-title" style="margin: 0">现金流分析</div>
<div class="filter-bar">
<el-radio-group v-model="range" size="small" @change="loadAll">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
<el-radio-button label="90">近90天</el-radio-button>
<el-radio-button value="7">近7天</el-radio-button>
<el-radio-button value="30">近30天</el-radio-button>
<el-radio-button value="90">近90天</el-radio-button>
</el-radio-group>
<el-radio-group v-model="grain" size="small" @change="loadAll">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button label="month">按月</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
<el-radio-button value="month">按月</el-radio-button>
</el-radio-group>
</div>
</div>

View File

@@ -4,14 +4,14 @@
<div class="page-title" style="margin: 0">运营分析</div>
<div class="filter-bar">
<el-radio-group v-model="range" size="small" @change="loadAll">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
<el-radio-button label="90">近90天</el-radio-button>
<el-radio-button value="7">近7天</el-radio-button>
<el-radio-button value="30">近30天</el-radio-button>
<el-radio-button value="90">近90天</el-radio-button>
</el-radio-group>
<el-radio-group v-model="grain" size="small" @change="loadAll">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button label="month">按月</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
<el-radio-button value="month">按月</el-radio-button>
</el-radio-group>
</div>
</div>

View File

@@ -3,9 +3,9 @@
<div class="flex-between mb-16">
<div class="page-title" style="margin: 0">人员操作分析</div>
<el-radio-group v-model="range" size="small" @change="loadAll">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
<el-radio-button label="90">近90天</el-radio-button>
<el-radio-button value="7">近7天</el-radio-button>
<el-radio-button value="30">近30天</el-radio-button>
<el-radio-button value="90">近90天</el-radio-button>
</el-radio-group>
</div>

View File

@@ -40,10 +40,10 @@
<div class="flex-between">
<span>视图配置</span>
<el-radio-group v-model="viewConfig.chart_type" size="small" @change="triggerPreview">
<el-radio-button label="line">折线</el-radio-button>
<el-radio-button label="bar">柱状</el-radio-button>
<el-radio-button label="pie">饼图</el-radio-button>
<el-radio-button label="table">表格</el-radio-button>
<el-radio-button value="line">折线</el-radio-button>
<el-radio-button value="bar">柱状</el-radio-button>
<el-radio-button value="pie">饼图</el-radio-button>
<el-radio-button value="table">表格</el-radio-button>
</el-radio-group>
</div>
</template>
@@ -236,10 +236,10 @@
<div class="time-range-bar">
<span class="label">预览时间范围:</span>
<el-radio-group v-model="previewDays" size="small" @change="triggerPreview">
<el-radio-button :label="7">近7天</el-radio-button>
<el-radio-button :label="30">近30天</el-radio-button>
<el-radio-button :label="90">近90天</el-radio-button>
<el-radio-button :label="0">全部</el-radio-button>
<el-radio-button :value="7">近7天</el-radio-button>
<el-radio-button :value="30">近30天</el-radio-button>
<el-radio-button :value="90">近90天</el-radio-button>
<el-radio-button :value="0">全部</el-radio-button>
</el-radio-group>
<el-button type="primary" size="small" :loading="previewing" @click="triggerPreview">刷新预览</el-button>
</div>

View File

@@ -4,9 +4,9 @@
<div class="page-title" style="margin: 0">分析视图</div>
<div>
<el-radio-group v-model="filterPreset" size="small" @change="loadData">
<el-radio-button :label="null">全部</el-radio-button>
<el-radio-button :label="true">预设</el-radio-button>
<el-radio-button :label="false">自定义</el-radio-button>
<el-radio-button :value="null">全部</el-radio-button>
<el-radio-button :value="true">预设</el-radio-button>
<el-radio-button :value="false">自定义</el-radio-button>
</el-radio-group>
<!-- #46: 写操作入口按角色隐藏, 与后端 AdminUser 配对(避免"按钮在但点了 403") -->
<el-button v-if="isAdmin" type="primary" :icon="Plus" style="margin-left: 12px" @click="goBuilder">新建视图</el-button>

View File

@@ -10,15 +10,15 @@
</div>
<div v-if="view" class="filter-bar">
<el-radio-group v-model="rangeDays" size="small" @change="loadData">
<el-radio-button :label="7">近7天</el-radio-button>
<el-radio-button :label="30">近30天</el-radio-button>
<el-radio-button :label="90">近90天</el-radio-button>
<el-radio-button :label="0">全部</el-radio-button>
<el-radio-button :value="7">近7天</el-radio-button>
<el-radio-button :value="30">近30天</el-radio-button>
<el-radio-button :value="90">近90天</el-radio-button>
<el-radio-button :value="0">全部</el-radio-button>
</el-radio-group>
<el-radio-group v-if="hasTimeDim" v-model="grainMode" size="small" @change="loadData">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button label="month">按月</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
<el-radio-button value="month">按月</el-radio-button>
</el-radio-group>
<el-button :icon="Promotion" @click="publishDialogVisible = true">发布为看板</el-button>
<el-button :icon="Refresh" @click="loadData">刷新</el-button>

View File

@@ -430,9 +430,9 @@
</el-form-item>
<el-form-item label="评判">
<el-radio-group v-model="auditForm.judgment">
<el-radio-button label="通过" />
<el-radio-button label="有问题" />
<el-radio-button label="需改进" />
<el-radio-button value="通过" />
<el-radio-button value="有问题" />
<el-radio-button value="需改进" />
</el-radio-group>
</el-form-item>
<el-form-item label="核查结论">
@@ -492,10 +492,10 @@
<div class="filter-bar mb-16">
<span class="filter-label">来源</span>
<el-radio-group v-model="subjectFilter.source_type" size="small" @change="loadSubjectAnalysis">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="merchant">仅商户号</el-radio-button>
<el-radio-button label="external">仅外部</el-radio-button>
<el-radio-button label="both">升级主体</el-radio-button>
<el-radio-button value="all">全部</el-radio-button>
<el-radio-button value="merchant">仅商户号</el-radio-button>
<el-radio-button value="external">仅外部</el-radio-button>
<el-radio-button value="both">升级主体</el-radio-button>
</el-radio-group>
<span class="filter-label">排序</span>
<el-select v-model="subjectFilter.sort_by" size="small" style="width:130px" @change="loadSubjectAnalysis">
@@ -581,9 +581,9 @@
<div class="flex-between">
<div class="chart-title">主体投诉趋势<span class="dim-text"> · 商户号+外部投诉汇聚</span></div>
<el-radio-group v-model="subjectTrendGrain" size="small">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button label="month">按月</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
<el-radio-button value="month">按月</el-radio-button>
</el-radio-group>
</div>
<BaseChart v-if="subjectTrendOption" :option="subjectTrendOption" height="250px" />
@@ -595,9 +595,9 @@
<div class="flex-between">
<div class="chart-title">Top5 被投诉主体趋势<span class="dim-text"> · 汇聚后按主体总量排序</span></div>
<el-radio-group v-model="topSubjectTrendGrain" size="small">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button label="month">按月</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
<el-radio-button value="month">按月</el-radio-button>
</el-radio-group>
</div>
<BaseChart v-if="topSubjectTrendOption" :option="topSubjectTrendOption" height="250px" />
@@ -739,8 +739,8 @@
平铺会让决策者在 8 个 tab 里迷路。 -->
<div v-if="loaded" class="nav-wrap">
<el-radio-group v-model="topTab" size="default">
<el-radio-button label="analysis">分析</el-radio-button>
<el-radio-button label="execute">
<el-radio-button value="analysis">分析</el-radio-button>
<el-radio-button value="execute">
执行跟踪
<el-badge v-if="(b.total ?? 0) > 0" :value="b.total" type="danger" style="margin-left:6px" />
</el-radio-button>
@@ -749,19 +749,19 @@
<template v-if="topTab === 'analysis'">
<span class="nav-sep" />
<el-radio-group v-model="analysisTab" size="small">
<el-radio-button label="overview">决策概览</el-radio-button>
<el-radio-button label="channel">渠道明细</el-radio-button>
<el-radio-button label="subject">主体分析</el-radio-button>
<el-radio-button value="overview">决策概览</el-radio-button>
<el-radio-button value="channel">渠道明细</el-radio-button>
<el-radio-button value="subject">主体分析</el-radio-button>
</el-radio-group>
</template>
<template v-if="topTab === 'analysis' && analysisTab === 'channel'">
<span class="nav-sep" />
<el-radio-group v-model="channelTab" size="small">
<el-radio-button label="internal">内部投诉</el-radio-button>
<el-radio-button label="merchant">商户号投诉</el-radio-button>
<el-radio-button label="external">外部投诉</el-radio-button>
<el-radio-button label="card">(外)卡商</el-radio-button>
<el-radio-button value="internal">内部投诉</el-radio-button>
<el-radio-button value="merchant">商户号投诉</el-radio-button>
<el-radio-button value="external">外部投诉</el-radio-button>
<el-radio-button value="card">(外)卡商</el-radio-button>
</el-radio-group>
</template>
</div>

View File

@@ -28,9 +28,9 @@
</el-form-item>
<el-form-item label="评判">
<el-radio-group v-model="form.judgment">
<el-radio-button label="通过" />
<el-radio-button label="有问题" />
<el-radio-button label="需改进" />
<el-radio-button value="通过" />
<el-radio-button value="有问题" />
<el-radio-button value="需改进" />
</el-radio-group>
</el-form-item>
<el-form-item label="核查结论">

View File

@@ -8,11 +8,11 @@
</div>
<div v-if="dashboard" class="filter-bar">
<el-radio-group v-model="rangeMode" size="small" @change="onRangeChange">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
<el-radio-button label="90">近90天</el-radio-button>
<el-radio-button label="default">默认</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="7">近7天</el-radio-button>
<el-radio-button value="30">近30天</el-radio-button>
<el-radio-button value="90">近90天</el-radio-button>
<el-radio-button value="default">默认</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
<el-date-picker
v-if="rangeMode === 'custom'"
@@ -26,9 +26,9 @@
@change="onRangeChange"
/>
<el-radio-group v-model="grainMode" size="small" @change="onGrainChange">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button label="month">按月</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
<el-radio-button value="month">按月</el-radio-button>
</el-radio-group>
<el-button :icon="Refresh" :loading="batchLoading" @click="loadAllItems">刷新</el-button>
</div>

View File

@@ -68,9 +68,9 @@
<el-col :span="8">
<el-form-item label="可见性">
<el-radio-group v-model="form.visibility">
<el-radio-button label="public">全员</el-radio-button>
<el-radio-button label="manager">管理层</el-radio-button>
<el-radio-button label="admin">管理员</el-radio-button>
<el-radio-button value="public">全员</el-radio-button>
<el-radio-button value="manager">管理层</el-radio-button>
<el-radio-button value="admin">管理员</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>

View File

@@ -124,9 +124,9 @@
<el-option v-for="g in groupsList" :key="g.id" :label="g.name" :value="g.name" />
</el-select>
<el-radio-group v-model="staffFilter.aggregateBy" size="small">
<el-radio-button label="person">按人</el-radio-button>
<el-radio-button label="group">按分组</el-radio-button>
<el-radio-button label="all">全员</el-radio-button>
<el-radio-button value="person">按人</el-radio-button>
<el-radio-button value="group">按分组</el-radio-button>
<el-radio-button value="all">全员</el-radio-button>
</el-radio-group>
<el-checkbox
v-model="showAllStaff"

View File

@@ -4,16 +4,16 @@
<div class="page-title" style="margin: 0">退款分析</div>
<div class="filter-bar">
<el-radio-group v-model="tabMode" size="small" @change="onTabChange">
<el-radio-button label="overview">决策概览</el-radio-button>
<el-radio-button label="withdrawal">提现时效</el-radio-button>
<el-radio-button label="reconcile">对账复核</el-radio-button>
<el-radio-button value="overview">决策概览</el-radio-button>
<el-radio-button value="withdrawal">提现时效</el-radio-button>
<el-radio-button value="reconcile">对账复核</el-radio-button>
</el-radio-group>
<el-radio-group v-if="tabMode === 'overview'" v-model="rangeMode" size="small">
<el-radio-button label="day">单日·{{ dailyLabel }}</el-radio-button>
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
<el-radio-button label="90">近90天</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="day">单日·{{ dailyLabel }}</el-radio-button>
<el-radio-button value="7">近7天</el-radio-button>
<el-radio-button value="30">近30天</el-radio-button>
<el-radio-button value="90">近90天</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
<el-date-picker
v-if="tabMode === 'overview' && rangeMode === 'custom'"
@@ -26,8 +26,8 @@
style="width: 240px"
/>
<el-radio-group v-if="tabMode === 'overview'" v-model="grainMode" size="small">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
</el-radio-group>
<el-button v-if="tabMode !== 'reconcile'" type="primary" :icon="Search" :loading="analyzing" @click="runAnalysis">分析</el-button>
<el-button v-if="tabMode === 'overview' && analyzed" :icon="Download" @click="handleExport">导出</el-button>

View File

@@ -4,16 +4,16 @@
<div class="page-title" style="margin: 0">挽回分析</div>
<div class="filter-bar" :class="{ 'is-mobile': isMobile }">
<el-radio-group v-model="tabMode" size="small" class="tab-mode-group" @change="onTabChange">
<el-radio-button label="today">今日</el-radio-button>
<el-radio-button label="personnel">人员</el-radio-button>
<el-radio-button label="device">设备</el-radio-button>
<el-radio-button label="type">类型</el-radio-button>
<el-radio-button value="today">今日</el-radio-button>
<el-radio-button value="personnel">人员</el-radio-button>
<el-radio-button value="device">设备</el-radio-button>
<el-radio-button value="type">类型</el-radio-button>
</el-radio-group>
<el-radio-group v-if="tabMode !== 'today'" v-model="rangeMode" size="small" class="range-mode-group">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
<el-radio-button label="90">近90天</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="7">近7天</el-radio-button>
<el-radio-button value="30">近30天</el-radio-button>
<el-radio-button value="90">近90天</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
<el-date-picker
v-if="tabMode !== 'today' && rangeMode === 'custom'"

View File

@@ -16,9 +16,9 @@
@change="loadAll"
/>
<el-radio-group v-model="grain" size="small" @change="renderTrend">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button label="month">按月</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
<el-radio-button value="month">按月</el-radio-button>
</el-radio-group>
<el-button type="primary" size="small" :icon="Search" :loading="loading" @click="loadAll">分析</el-button>
</div>

View File

@@ -105,8 +105,8 @@
<div class="chart-toolbar">
<span class="trend-hint">默认: 成功率 Top3 + Bottom3</span>
<el-radio-group v-model="trendGrain" size="small" @change="loadTrend">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
</el-radio-group>
</div>
</div>
@@ -154,9 +154,9 @@
<div class="device-toolbar">
<span class="block-title" style="margin: 0">设备清单</span>
<el-radio-group v-model="deviceTab" size="small" @change="loadPersonDevices">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="success">成功</el-radio-button>
<el-radio-button label="failed">失败</el-radio-button>
<el-radio-button value="all">全部</el-radio-button>
<el-radio-button value="success">成功</el-radio-button>
<el-radio-button value="failed">失败</el-radio-button>
</el-radio-group>
</div>
<el-table :data="personDevices.devices || []" border stripe size="small" max-height="520" style="width: 100%">

View File

@@ -17,11 +17,11 @@
<div>
<span class="win-label">复核时间窗:</span>
<el-radio-group v-model="quickRange" size="small" class="mr-12">
<el-radio-button label="today">今日</el-radio-button>
<el-radio-button label="yesterday">昨日</el-radio-button>
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="today">今日</el-radio-button>
<el-radio-button value="yesterday">昨日</el-radio-button>
<el-radio-button value="7">近7天</el-radio-button>
<el-radio-button value="30">近30天</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
<el-date-picker
v-model="winRange"

View File

@@ -35,12 +35,12 @@
<div class="flex-between mb-12">
<div class="filter-bar">
<el-radio-group v-model="routeFilter" size="small" @change="load">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="A">A线上</el-radio-button>
<el-radio-button label="B">B钱包</el-radio-button>
<el-radio-button label="C">C闪猫</el-radio-button>
<el-radio-button label="D">D手动</el-radio-button>
<el-radio-button label="unmatched">未登记</el-radio-button>
<el-radio-button value="">全部</el-radio-button>
<el-radio-button value="A">A线上</el-radio-button>
<el-radio-button value="B">B钱包</el-radio-button>
<el-radio-button value="C">C闪猫</el-radio-button>
<el-radio-button value="D">D手动</el-radio-button>
<el-radio-button value="unmatched">未登记</el-radio-button>
</el-radio-group>
<el-select v-model="statusFilter" size="small" placeholder="确认状态" clearable style="width:130px;margin-left:8px" @change="load">
<el-option v-for="s in statusOptions" :key="s" :label="s" :value="s" />

View File

@@ -6,8 +6,8 @@
<span class="block-title" style="margin: 0">类型 × 趋势</span>
<span class="trend-hint">粒度:</span>
<el-radio-group v-model="grain" size="small" @change="loadTrend">
<el-radio-button label="day">按日</el-radio-button>
<el-radio-button label="week">按周</el-radio-button>
<el-radio-button value="day">按日</el-radio-button>
<el-radio-button value="week">按周</el-radio-button>
</el-radio-group>
<el-button :icon="Refresh" size="small" type="primary" :loading="loading" @click="loadAll">刷新</el-button>
</div>
@@ -40,9 +40,9 @@
<div class="chart-header">
<span class="block-title" style="margin: 0">日期 × 原因 矩阵热力图 (近30天)</span>
<el-radio-group v-model="matrixMode" size="small">
<el-radio-button label="failed">失败数</el-radio-button>
<el-radio-button label="total">接待数</el-radio-button>
<el-radio-button label="rate">成功率</el-radio-button>
<el-radio-button value="failed">失败数</el-radio-button>
<el-radio-button value="total">接待数</el-radio-button>
<el-radio-button value="rate">成功率</el-radio-button>
</el-radio-group>
</div>
<BaseChart v-if="heatmapHasData" :option="heatmapOption" height="560px" />

View File

@@ -100,11 +100,11 @@
</el-form-item>
<el-form-item label="时间范围" prop="timeRange">
<el-radio-group v-model="form.timeRange" :disabled="busyRunning" @change="onTimeRangeChange">
<el-radio-button label="incremental">增量接续(推荐)</el-radio-button>
<el-radio-button label="today">今天</el-radio-button>
<el-radio-button label="yesterday">昨天</el-radio-button>
<el-radio-button label="week">过去一周</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="incremental">增量接续(推荐)</el-radio-button>
<el-radio-button value="today">今天</el-radio-button>
<el-radio-button value="yesterday">昨天</el-radio-button>
<el-radio-button value="week">过去一周</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- 增量模式:显示每个选中源即将抓取的时间范围 -->

View File

@@ -94,11 +94,11 @@
</el-form-item>
<el-form-item label="时间范围" prop="timeRange">
<el-radio-group v-model="form.timeRange" :disabled="busyRunning" @change="onTimeRangeChange">
<el-radio-button label="incremental">增量接续(推荐)</el-radio-button>
<el-radio-button label="today">今天</el-radio-button>
<el-radio-button label="yesterday">昨天</el-radio-button>
<el-radio-button label="week">过去一周</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="incremental">增量接续(推荐)</el-radio-button>
<el-radio-button value="today">今天</el-radio-button>
<el-radio-button value="yesterday">昨天</el-radio-button>
<el-radio-button value="week">过去一周</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.timeRange === 'incremental'" label="即将抓取范围">

View File

@@ -136,11 +136,11 @@
</el-form-item>
<el-form-item label="时间范围" prop="timeRange">
<el-radio-group v-model="form.timeRange" :disabled="busyRunning" @change="onTimeRangeChange">
<el-radio-button label="incremental">增量接续(推荐)</el-radio-button>
<el-radio-button label="today">今天</el-radio-button>
<el-radio-button label="yesterday">昨天</el-radio-button>
<el-radio-button label="week">过去一周</el-radio-button>
<el-radio-button label="custom">自定义</el-radio-button>
<el-radio-button value="incremental">增量接续(推荐)</el-radio-button>
<el-radio-button value="today">今天</el-radio-button>
<el-radio-button value="yesterday">昨天</el-radio-button>
<el-radio-button value="week">过去一周</el-radio-button>
<el-radio-button value="custom">自定义</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.timeRange === 'incremental'" label="即将抓取范围">
@@ -233,9 +233,9 @@
<div class="panel-inner">
<div class="flex justify-between items-center mb-8">
<el-radio-group v-model="statusGroupFilter" size="small">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="bms">仅 BMS</el-radio-button>
<el-radio-button label="scrm">仅 SCRM</el-radio-button>
<el-radio-button value="all">全部</el-radio-button>
<el-radio-button value="bms">仅 BMS</el-radio-button>
<el-radio-button value="scrm">仅 SCRM</el-radio-button>
</el-radio-group>
</div>
<el-table :data="mergedStatusRows" border size="small">

View File

@@ -123,8 +123,8 @@
<el-form-item label="规则名" required><el-input v-model="ruleForm.name" /></el-form-item>
<el-form-item label="模式">
<el-radio-group v-model="ruleForm.mode">
<el-radio-button label="metric">指标模式(推荐)</el-radio-button>
<el-radio-button label="legacy">旧模式(激增类)</el-radio-button>
<el-radio-button value="metric">指标模式(推荐)</el-radio-button>
<el-radio-button value="legacy">旧模式(激增类)</el-radio-button>
</el-radio-group>
</el-form-item>
<template v-if="ruleForm.mode === 'metric'">

View File

@@ -90,9 +90,9 @@
</el-form-item>
<el-form-item label="严重程度">
<el-radio-group v-model="form.severity">
<el-radio-button label="高" />
<el-radio-button label="中" />
<el-radio-button label="低" />
<el-radio-button value="高" />
<el-radio-button value="中" />
<el-radio-button value="低" />
</el-radio-group>
</el-form-item>
<el-form-item label="明细描述">

View File

@@ -28,8 +28,8 @@
<div class="page-container" style="padding-top: 0">
<div class="filter-bar flex gap-8" style="flex-wrap: wrap; align-items: center">
<el-radio-group v-model="filter.category" @change="loadData">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button v-for="c in categories" :key="c" :label="c">{{ c }}</el-radio-button>
<el-radio-button value="">全部</el-radio-button>
<el-radio-button v-for="c in categories" :key="c" :value="c">{{ c }}</el-radio-button>
</el-radio-group>
<el-input
v-model="filter.keyword"