diff --git a/.agents/skills/to-prd/SKILL.md b/.agents/skills/to-prd/SKILL.md
new file mode 100644
index 0000000..e5f1141
--- /dev/null
+++ b/.agents/skills/to-prd/SKILL.md
@@ -0,0 +1,75 @@
+---
+name: to-prd
+description: Turn the current conversation into a PRD and publish it to the project issue tracker — no interview, just synthesis of what you've already discussed.
+disable-model-invocation: true
+---
+
+This skill takes the current conversation context and codebase understanding and produces a PRD. Do NOT interview the user — just synthesize what you already know.
+
+The issue tracker and triage label vocabulary should have been provided to you — run `/setup-matt-pocock-skills` if not.
+
+## Process
+
+1. Explore the repo to understand the current state of the codebase, if you haven't already. Use the project's domain glossary vocabulary throughout the PRD, and respect any ADRs in the area you're touching.
+
+2. Sketch out the seams at which you're going to test the feature. Existing seams should be preferred to new ones. Use the highest seam possible. If new seams are needed, propose them at the highest point you can. The fewer seams across the codebase, the better - the ideal number is one.
+
+Check with the user that these seams match their expectations.
+
+3. Write the PRD using the template below, then publish it to the project issue tracker. Apply the `ready-for-agent` triage label - no need for additional triage.
+
+
+
+## Problem Statement
+
+The problem that the user is facing, from the user's perspective.
+
+## Solution
+
+The solution to the problem, from the user's perspective.
+
+## User Stories
+
+A LONG, numbered list of user stories. Each user story should be in the format of:
+
+1. As an , I want a , so that
+
+
+1. As a mobile bank customer, I want to see balance on my accounts, so that I can make better informed decisions about my spending
+
+
+This list of user stories should be extremely extensive and cover all aspects of the feature.
+
+## Implementation Decisions
+
+A list of implementation decisions that were made. This can include:
+
+- The modules that will be built/modified
+- The interfaces of those modules that will be modified
+- Technical clarifications from the developer
+- Architectural decisions
+- Schema changes
+- API contracts
+- Specific interactions
+
+Do NOT include specific file paths or code snippets. They may end up being outdated very quickly.
+
+Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it within the relevant decision and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits.
+
+## Testing Decisions
+
+A list of testing decisions that were made. Include:
+
+- A description of what makes a good test (only test external behavior, not implementation details)
+- Which modules will be tested
+- Prior art for the tests (i.e. similar types of tests in the codebase)
+
+## Out of Scope
+
+A description of the things that are out of scope for this PRD.
+
+## Further Notes
+
+Any further notes about the feature.
+
+
diff --git a/.axhub/make/axhub.config.json b/.axhub/make/axhub.config.json
index 35830c1..952115b 100644
--- a/.axhub/make/axhub.config.json
+++ b/.axhub/make/axhub.config.json
@@ -28,8 +28,8 @@
"productionBranch": "main"
},
"s3": {
- "accessKeyId": "",
- "secretAccessKey": "",
+ "accessKeyId": "LTAI5t9EV2UByZDBcNSgmm2q",
+ "secretAccessKey": "pozhgZ58J0XJ2pqKyM3pY1tZLClata",
"region": "cn-hangzhou",
"bucket": "model-lnoneos",
"prefix": "",
@@ -39,10 +39,15 @@
"githubPages": {
"repository": "",
"branch": "gh-pages",
- "sourceDirectory": "/"
+ "sourceDirectory": "/",
+ "pathPrefix": ""
},
"publishSettings": {
- "includeSource": true
+ "includeSource": true,
+ "visibleTargets": [
+ "axhub",
+ "s3"
+ ]
}
}
-}
+}
\ No newline at end of file
diff --git a/.axhub/make/sidebar-tree.json b/.axhub/make/sidebar-tree.json
index d1bd8cc..9c9df69 100644
--- a/.axhub/make/sidebar-tree.json
+++ b/.axhub/make/sidebar-tree.json
@@ -1,6 +1,6 @@
{
"version": 1,
- "updatedAt": "2026-07-06T05:10:19.174Z",
+ "updatedAt": "2026-07-07T09:35:00.000Z",
"prototypes": [
{
"id": "folder-1782874576229-r8atbu",
@@ -118,6 +118,12 @@
"kind": "item",
"title": "车辆氢费明细",
"itemKey": "prototypes/vehicle-h2-fee-ledger"
+ },
+ {
+ "id": "item:prototypes:vehicle-maintenance-ledger",
+ "kind": "item",
+ "title": "维修保养台账",
+ "itemKey": "prototypes/vehicle-maintenance-ledger"
}
]
},
@@ -131,6 +137,12 @@
"kind": "item",
"title": "收款记录",
"itemKey": "prototypes/payment-records"
+ },
+ {
+ "id": "item:prototypes:vehicle-return-settlement",
+ "kind": "item",
+ "title": "还车应结款",
+ "itemKey": "prototypes/vehicle-return-settlement"
}
]
}
diff --git a/.local/extract-annotation-source/annotation-nodes.json b/.local/extract-annotation-source/annotation-nodes.json
index eaa9a4b..e413de1 100644
--- a/.local/extract-annotation-source/annotation-nodes.json
+++ b/.local/extract-annotation-source/annotation-nodes.json
@@ -1,287 +1,821 @@
[
{
- "id": "ct-list-filter",
+ "id": "lbl-filter",
"index": 1,
"title": "筛选条件",
"pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-filter\"]"
+ ],
+ "fingerprint": "lbl-filter",
+ "path": []
+ },
+ "aiPrompt": "年份、月份、部门、业务员等筛选。",
+ "annotationText": "## 2. 筛选区\n\n默认展示 4 项(一行),点击「更多筛选」展开全部 8 项。\n\n| 筛选项 | 说明 |\n|---|---|\n| 年份 / 月份 | 账单所属年月 |\n| 业务部门 / 业务员 | 精确匹配 |\n| 车牌号码 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 明细状态 | 草稿 / 已确认 / 部分收款 / 已收款 |\n| 付款日期 | 日期区间 |\n\n---",
"hasMarkdown": true,
- "annotationText": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 文本输入 | 支持回车触发查询 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 适用品牌 | 下拉 | 选项来自车型主数据品牌列表 |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认",
- "aiPrompt": "列表页筛选区字段与查询逻辑。",
"color": "#2563eb",
"images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-list-filter\"]"
- ],
- "fingerprint": "ct-list-filter",
- "path": []
- }
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000,
+ "controls": []
},
{
- "id": "ct-list-toolbar",
+ "id": "lbl-kpi",
"index": 2,
- "title": "新增模板",
+ "title": "KPI 统计",
"pageId": "list",
- "hasMarkdown": false,
- "annotationText": "进入新增页,默认从「合同主体」章节开始编辑。",
- "aiPrompt": "列表工具栏新增入口。",
- "color": "#32a06e",
- "images": [],
- "controls": [],
"locator": {
"selectors": [
- "[data-annotation-id=\"ct-list-toolbar\"]"
+ "[data-annotation-id=\"lbl-kpi\"]"
],
- "fingerprint": "ct-list-toolbar",
+ "fingerprint": "lbl-kpi",
"path": []
- }
+ },
+ "aiPrompt": "六张统计卡片口径。",
+ "annotationText": "## 3. KPI 统计卡片(6 张)\n\n台账条数、应收总计、实收总计、未收总计、成本总计、盈亏总计。\n\n- **台账条数**:员工仅统计本人录入;主管统计全组。点击切换列表为显示全部。\n- **未收总计**:点击筛选下方列表为未收款数据。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000,
+ "controls": []
},
{
- "id": "ct-list-table",
+ "id": "lbl-kpi-count",
"index": 3,
- "title": "Word 模板列表",
+ "title": "台账条数",
"pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-kpi-count\"]"
+ ],
+ "fingerprint": "lbl-kpi-count",
+ "path": []
+ },
+ "aiPrompt": "台账条数 KPI 点击切换列表。",
+ "annotationText": "## 台账条数 KPI\n\n业务服务组员工只能看到自己录入的数据条数以及金额;业务服务组主管可以查看所有业务服务组人员录入的数据总和。点击可切换列表显示全部数据。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-kpi-unreceived",
+ "index": 4,
+ "title": "未收总计",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-kpi-unreceived\"]"
+ ],
+ "fingerprint": "lbl-kpi-unreceived",
+ "path": []
+ },
+ "aiPrompt": "未收总计 KPI 筛选未收款。",
+ "annotationText": "## 未收总计 KPI\n\n点击未收总计卡片,下方列表仅展示未收款数据(状态不等于已收款)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-toolbar",
+ "index": 5,
+ "title": "列表工具栏",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-toolbar\"]"
+ ],
+ "fingerprint": "lbl-toolbar",
+ "path": []
+ },
+ "aiPrompt": "导入、导出、标记确认、提交审批。",
+ "annotationText": "## 4. 列表工具栏\n\n左侧「仅显示未收款数据」勾选后,列表仅显示状态不等于已收款的记录。\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板并上传 Excel(原型 Toast) |\n| 导出 | 导出当前筛选结果 |\n| 标记为已确认 | 勾选草稿行后二次确认 |\n| 提交收款审批 | 勾选行提交审批 |\n\n添加行已移至表格底部;列表采用下拉加载更多,不做分页。\n\n---",
"hasMarkdown": true,
- "annotationText": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同类型 | `contractType` | 展示用户自定义的合同类型文案;已发布模板在类型后以 Tag 显示「启用」或「停用」 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名 |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 切换模板是否可供租赁合同创建选用 |\n| 编辑 | 未启用或草稿 | 进入编辑页 |\n| 更多 → 版本日志 | 已发布 | 查看发布归档快照 |\n| 更多 → 删除 | 未处于启用锁定 | 弹窗确认后删除 |\n\n**多套模板并存**:同一合同类型可维护多套模板;租赁合同创建时先选合同类型,再自动套用该类型下已启用的标准模板(`isDefault` 优先)。\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要。",
- "aiPrompt": "主表与子表嵌套列表结构。",
"color": "#0f766e",
"images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-list-table\"]"
- ],
- "fingerprint": "ct-list-table",
- "path": []
- }
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000,
+ "controls": []
},
{
- "id": "ct-editor-topbar",
- "index": 4,
- "title": "编辑页顶栏",
- "pageId": "editor",
- "hasMarkdown": true,
- "annotationText": "### 8.2 顶栏\n\n| 元素 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布 |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**降为草稿**(清空版本号) |\n| **保存并发布** | 新建已发布记录 + 版本号 | 原记录变已发布 + 版本号 | **新建一条已发布记录**(新版本),原版本保留 |\n\n**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空,且须已填写合同类型(见 §8.4)。",
- "aiPrompt": "返回、保存草稿、保存并发布。",
- "color": "#64748b",
- "images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-editor-topbar\"]"
- ],
- "fingerprint": "ct-editor-topbar",
- "path": []
- }
- },
- {
- "id": "ct-section-tabs",
- "index": 5,
- "title": "章节 Tab",
- "pageId": "editor",
- "hasMarkdown": true,
- "annotationText": "### 8.3 章节 Tab\n\n- 共 9 个章节,**全部在横向滚动条中展示**(带序号 1–9),当前章节自动滚入视野。\n- 右上角 **章节列表** 可搜索并跳转(适合长标题章节)。\n- 当前章节全称在 Tab 下方展示,避免标题被截断后找不到位置。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**;其他 Tab 仅展示该章节切片。",
- "aiPrompt": "9 个合同章节切换。",
- "color": "#2563eb",
- "images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-section-tabs\"]"
- ],
- "fingerprint": "ct-section-tabs",
- "path": []
- }
- },
- {
- "id": "ct-contract-type",
+ "id": "lbl-btn-mark-confirmed",
"index": 6,
- "title": "合同类型",
- "pageId": "editor",
- "hasMarkdown": true,
- "annotationText": "### 8.4 合同类型(仅合同主体 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 合同类型输入框 | **必填**;用户自行填写,如「正式合同」「现代18吨试用合同」 |\n| 展示位置 | 章节 Tab 下方配置卡片,与附件章节的「适用车型」卡片交互一致 |\n| 用途 | 同步至列表页「合同类型」列;租赁合同创建时按类型匹配标准模板 |\n\n**规则**:\n\n- 仅当选中 **合同主体** 章节 Tab 时显示本卡片。\n- 未填写时无法保存或发布,并提示「请输入合同类型」。\n- 切换至租赁订单等附件章节时,显示「适用车型」卡片,本卡片隐藏。\n- 保存/发布时写入模板 `contractType` 字段。",
- "aiPrompt": "合同主体章节下的合同类型配置。",
- "color": "#7c3aed",
- "images": [],
- "controls": [],
+ "title": "标记为已确认",
+ "pageId": "list",
"locator": {
"selectors": [
- "[data-annotation-id=\"ct-contract-type\"]"
+ "[data-annotation-id=\"lbl-btn-mark-confirmed\"]"
],
- "fingerprint": "ct-contract-type",
+ "fingerprint": "lbl-btn-mark-confirmed",
"path": []
- }
+ },
+ "aiPrompt": "草稿批量确认与权限收口。",
+ "annotationText": "## 标记为已确认\n\n勾选草稿记录后点击,弹出二次确认:「确认后的数据只有主管才能继续进行修改,是否确认数据准确无误?」确认后状态变更为已确认,业务服务组人员不可再删改。\n\n---",
+ "hasMarkdown": true,
+ "color": "#0f766e",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
},
{
- "id": "ct-vehicle-binding",
+ "id": "lbl-btn-add-row",
"index": 7,
- "title": "适用车型绑定",
- "pageId": "editor",
- "hasMarkdown": true,
- "annotationText": "### 8.5 适用车型绑定(附件章节 Tab:租赁订单~安全责任协议)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌型号选择器 | 点击输入框弹出卡片;支持搜索筛选 |\n| 已选区 | 弹层内展示已选「品牌 · 型号」,可逐项移除 |\n| 品牌列 | 左侧列表切换品牌 |\n| 型号区 | 右侧多选型号(点击切换选中) |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定。\n- 未绑定任何车型时表示全部车型适用。",
- "aiPrompt": "附件章节适用车型配置。",
- "color": "#059669",
- "images": [],
- "controls": [],
+ "title": "添加行",
+ "pageId": "list",
"locator": {
"selectors": [
- "[data-annotation-id=\"ct-vehicle-binding\"]"
+ "[data-annotation-id=\"lbl-btn-add-row\"]"
],
- "fingerprint": "ct-vehicle-binding",
+ "fingerprint": "lbl-btn-add-row",
"path": []
- }
+ },
+ "aiPrompt": "表格底部新增行。",
+ "annotationText": "## 添加行\n\n位于表格底部;列表不做分页,采用下拉加载更多,避免多选受分页限制。\n\n---",
+ "hasMarkdown": true,
+ "color": "#0f766e",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
},
{
- "id": "ct-editor-panel",
+ "id": "lbl-table",
"index": 8,
- "title": "编辑区",
- "pageId": "editor",
+ "title": "租赁台账宽表",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-table\"]"
+ ],
+ "fingerprint": "lbl-table",
+ "path": []
+ },
+ "aiPrompt": "运营明细台账列表与行操作。",
+ "annotationText": "## 5. 列表字段\n\n对齐 Excel「租赁车辆收入明细表」全量字段:年份、月份、业务部门(含业务员)、付款日期、车牌(含车型)、客户(含项目)、增值服务、享免政策、提车/合同/起止日期、平均天数、押金、合同标的租金、应收与收入拆分、减免、实收/未收、开票与付款信息、成本拆分、盈亏、GPS里程、减免明细、资产归属、签约公司、备注。\n\n宽表支持横向滚动;序号与操作列固定。",
"hasMarkdown": true,
- "annotationText": "### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 富文本工具栏(节选)\n\n| 按钮 | 说明 |\n|---|---|\n| **红线** | 在章节中选中内容后标记;新增租赁合同时**修改红线内文字**将自动变为非标合同,**不修改**则仍为标准流程 |\n| **取消**(红线旁) | 选中已标红线内容后点击,取消红线;取消后修改该部分不再触发非标流程 |\n| **条件** | 在**合同主体及租赁订单~安全责任协议**各章节圈定具体内容,使**部分条款**仅对指定车型生效(创建合同时按订单车辆命中显隐) |\n| **取消**(条件旁) | 选中已设条件的条款后点击,解除该部分的车型限定 |\n| **锁定** | 选中章节内内容后标记;新增租赁合同时该部分**无法被篡改** |\n| **解除**(锁定旁) | 选中已锁定内容后点击,解除锁定 |\n\n**叠加规则**:红线、条件与锁定可作用于**同一段内容**,互不排斥。\n\n**与章节「适用车型」的区别**:\n\n- **适用车型**(附件章节 Tab 上方卡片):控制整章是否在创建合同时对某车型**可见/不可见**。\n- **条件**(工具栏):仅作用于章节内的**部分段落**,不影响整章显隐。\n\n#### 8.6.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 选中段落或文字;\n2. 点击「条件」并绑定「品牌 · 型号」;\n3. 创建合同时:订单车辆任意一辆命中 → 段落纳入正文,否则隐藏。",
- "aiPrompt": "Word 版式分页编辑区。",
- "color": "#7c3aed",
- "images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-editor-panel\"]"
- ],
- "fingerprint": "ct-editor-panel",
- "path": []
- }
- },
- {
- "id": "ct-rte-risk-redline",
- "index": 9,
- "title": "红线",
- "pageId": "editor",
- "hasMarkdown": false,
- "annotationText": "红线功能主要用于在章节中选择特定内容,点击红线将其设置为篡改后自动变成非标合同。不修改红线内内容不会导致流程变为非标。",
- "aiPrompt": "风控红线条款标记。",
- "color": "#dc2626",
- "images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-rte-risk-redline\"]"
- ],
- "fingerprint": "ct-rte-risk-redline",
- "path": []
- }
- },
- {
- "id": "ct-rte-unrisk-redline",
- "index": 10,
- "title": "取消红线",
- "pageId": "editor",
- "hasMarkdown": false,
- "annotationText": "取消功能主要用于取消被选中的部分的红线内容。取消后,在新增租赁合同时修改该部分内容,不会导致流程变为非标准流程。",
- "aiPrompt": "取消风控红线标记。",
- "color": "#94a3b8",
- "images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-rte-unrisk-redline\"]"
- ],
- "fingerprint": "ct-rte-unrisk-redline",
- "path": []
- }
- },
- {
- "id": "ct-rte-vehicle-clause",
- "index": 11,
- "title": "条件",
- "pageId": "editor",
- "hasMarkdown": false,
- "annotationText": "条件可在合同主体及租赁订单~安全责任协议全部章节使用:圈定章节内具体内容,使部分条款仅对指定车型生效。与各章节「适用车型」(控制整章是否可见)不同,条件只作用于章节内部分段落。",
- "aiPrompt": "条件条款(段落级车型显隐)。",
"color": "#2563eb",
"images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-rte-vehicle-clause\"]"
- ],
- "fingerprint": "ct-rte-vehicle-clause",
- "path": []
- }
+ "createdAt": 1740787200000,
+ "updatedAt": 1782700000000,
+ "controls": []
},
{
- "id": "ct-rte-unvehicle-clause",
- "index": 12,
- "title": "取消条件",
- "pageId": "editor",
- "hasMarkdown": false,
- "annotationText": "当条款被设置条件后,可以通过选中特定条款内容,点击取消来取消该部分内容的车型限定。",
- "aiPrompt": "取消条件条款标记。",
+ "id": "lbl-col-status",
+ "index": 9,
+ "title": "状态流转",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-status\"]"
+ ],
+ "fingerprint": "lbl-col-status",
+ "path": []
+ },
+ "aiPrompt": "状态流转字段说明。",
+ "annotationText": "## 状态流转\n\n1. 新增/导入 → **草稿**(业务服务组可删改)\n2. 多选 + 「标记为已确认」→ **已确认**(仅主管可改)\n3. 已确认行可「关联收款记录」;到账合计 < 应收 → **部分收款**;≥ 应收 → **已收款**\n\n---",
+ "hasMarkdown": true,
"color": "#64748b",
"images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-rte-unvehicle-clause\"]"
- ],
- "fingerprint": "ct-rte-unvehicle-clause",
- "path": []
- }
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
},
{
- "id": "ct-rte-inline-lock",
- "index": 13,
- "title": "锁定",
- "pageId": "editor",
- "hasMarkdown": false,
- "annotationText": "锁定主要用于选中章节内的内容;锁定之后该部分内容无法在新增租赁合同时被篡改。红线、条件以及锁定可以在同一段内容同时生效。",
- "aiPrompt": "内容锁定区标记。",
- "color": "#b45309",
- "images": [],
- "controls": [],
+ "id": "lbl-col-dept",
+ "index": 10,
+ "title": "业务部门",
+ "pageId": "list",
"locator": {
"selectors": [
- "[data-annotation-id=\"ct-rte-inline-lock\"]"
+ "[data-annotation-id=\"lbl-col-dept\"]"
],
- "fingerprint": "ct-rte-inline-lock",
+ "fingerprint": "lbl-col-dept",
"path": []
- }
- },
- {
- "id": "ct-rte-uninline-lock",
- "index": 14,
- "title": "解除锁定",
- "pageId": "editor",
- "hasMarkdown": false,
- "annotationText": "选中已锁定内容后点击解除;解除后新增租赁合同时可再次编辑该部分。",
- "aiPrompt": "解除内容锁定标记。",
- "color": "#94a3b8",
- "images": [],
- "controls": [],
- "locator": {
- "selectors": [
- "[data-annotation-id=\"ct-rte-uninline-lock\"]"
- ],
- "fingerprint": "ct-rte-uninline-lock",
- "path": []
- }
- },
- {
- "id": "ct-preview-panel",
- "index": 15,
- "title": "实时预览",
- "pageId": "editor",
+ },
+ "aiPrompt": "业务部门字段说明。",
+ "annotationText": "## 业务部门列\n\n业务部门与业务员合并为一列:上方业务部门,下方业务员。\n\n---",
"hasMarkdown": true,
- "annotationText": "### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充",
- "aiPrompt": "A4 分页预览与模拟车型。",
- "color": "#0ea5e9",
+ "color": "#64748b",
"images": [],
- "controls": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-plate",
+ "index": 11,
+ "title": "车牌号码",
+ "pageId": "list",
"locator": {
"selectors": [
- "[data-annotation-id=\"ct-preview-panel\"]"
+ "[data-annotation-id=\"lbl-col-plate\"]"
],
- "fingerprint": "ct-preview-panel",
+ "fingerprint": "lbl-col-plate",
"path": []
- }
+ },
+ "aiPrompt": "车牌号码字段说明。",
+ "annotationText": "## 车牌号码列\n\n车牌与车型合并为一列:上方车牌号码,下方品牌与型号;导入/新增时按车牌在系统中自动匹配。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-customer",
+ "index": 12,
+ "title": "客户名称",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-customer\"]"
+ ],
+ "fingerprint": "lbl-col-customer",
+ "path": []
+ },
+ "aiPrompt": "客户名称字段说明。",
+ "annotationText": "## 客户名称列\n\n客户名称与项目名称合并为一列:上方客户名称,下方项目名称;按账单日期与车牌号自动匹配。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-pickup-date",
+ "index": 13,
+ "title": "提车日期",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-pickup-date\"]"
+ ],
+ "fingerprint": "lbl-col-pickup-date",
+ "path": []
+ },
+ "aiPrompt": "提车日期字段说明。",
+ "annotationText": "## 提车日期\n\n等于交车管理中该车辆运维 E 签宝签字完成的时间。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-deposit",
+ "index": 14,
+ "title": "保证金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-deposit\"]"
+ ],
+ "fingerprint": "lbl-col-deposit",
+ "path": []
+ },
+ "aiPrompt": "保证金字段说明。",
+ "annotationText": "## 保证金\n\n与创建合同时的保证金、提车应收款中的保证金等同;提车应收款有保证金时显示。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-contract-rent",
+ "index": 15,
+ "title": "合同标的租金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-contract-rent\"]"
+ ],
+ "fingerprint": "lbl-col-contract-rent",
+ "path": []
+ },
+ "aiPrompt": "合同标的租金字段说明。",
+ "annotationText": "## 合同标的租金\n\n等于该车辆在签订该合同时合同中的租金(月租金标准)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-receivable-rent",
+ "index": 16,
+ "title": "应收租金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-receivable-rent\"]"
+ ],
+ "fingerprint": "lbl-col-receivable-rent",
+ "path": []
+ },
+ "aiPrompt": "应收租金字段说明。",
+ "annotationText": "## 应收租金\n\n按合同付款周期(1/3/6/12 个月)计算的单期账单租金总金额 = 合同标的租金 × 付款周期月数。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-monthly-income",
+ "index": 17,
+ "title": "月度收入",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-monthly-income\"]"
+ ],
+ "fingerprint": "lbl-col-monthly-income",
+ "path": []
+ },
+ "aiPrompt": "月度收入字段说明。",
+ "annotationText": "## 月度收入\n\n租金 + 服务费 − 里程减免金额 − 其他减免金额(后两项为减项)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-monthly-rent",
+ "index": 18,
+ "title": "月度租金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-monthly-rent\"]"
+ ],
+ "fingerprint": "lbl-col-monthly-rent",
+ "path": []
+ },
+ "aiPrompt": "月度租金字段说明。",
+ "annotationText": "## 月度租金\n\n根据客户付款周期间隔,计算每个月需收取的租金,等于合同标的租金。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-maintenance-income",
+ "index": 19,
+ "title": "维保包干收入",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-maintenance-income\"]"
+ ],
+ "fingerprint": "lbl-col-maintenance-income",
+ "path": []
+ },
+ "aiPrompt": "维保包干收入字段说明。",
+ "annotationText": "## 维保包干收入\n\n合同勾选维保包干时:单价(元/km)× 里程数,必填;未勾选时默认 0 且不可编辑。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-insurance-surcharge",
+ "index": 20,
+ "title": "保险上浮费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-insurance-surcharge\"]"
+ ],
+ "fingerprint": "lbl-col-insurance-surcharge",
+ "path": []
+ },
+ "aiPrompt": "保险上浮费字段说明。",
+ "annotationText": "## 保险上浮费\n\n事故中有保险上浮费时,最新账单显示警示标志;关联事故单后自动汇总上浮费,支持多选。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-ops-income",
+ "index": 21,
+ "title": "运维费(收入侧)",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-ops-income\"]"
+ ],
+ "fingerprint": "lbl-col-ops-income",
+ "path": []
+ },
+ "aiPrompt": "运维费(收入侧)字段说明。",
+ "annotationText": "## 运维费(收入侧)\n\n维修/保养台账为「客户承担」时,最新一期账单显示警示图标,提醒关联维修/保养记录;关联后自动汇总运维费,支持多选。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-other-income",
+ "index": 22,
+ "title": "其他收入",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-other-income\"]"
+ ],
+ "fingerprint": "lbl-col-other-income",
+ "path": []
+ },
+ "aiPrompt": "其他收入字段说明。",
+ "annotationText": "## 其他收入\n\n手工填写,预留作为特殊情况记录使用。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-received-amount",
+ "index": 23,
+ "title": "实收金额",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-received-amount\"]"
+ ],
+ "fingerprint": "lbl-col-received-amount",
+ "path": []
+ },
+ "aiPrompt": "实收金额字段说明。",
+ "annotationText": "## 实收金额\n\n根据财务导入收款明细归集;支持关联多条并分配计入金额;超出应收部分在单据下自行分配。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-unreceived",
+ "index": 24,
+ "title": "未收金额",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-unreceived\"]"
+ ],
+ "fingerprint": "lbl-col-unreceived",
+ "path": []
+ },
+ "aiPrompt": "未收金额字段说明。",
+ "annotationText": "## 未收金额\n\n未收金额 = 应收合计 − 实收金额,系统自动计算,不可手工录入。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-invoice-date",
+ "index": 25,
+ "title": "开票日期",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-invoice-date\"]"
+ ],
+ "fingerprint": "lbl-col-invoice-date",
+ "path": []
+ },
+ "aiPrompt": "开票日期字段说明。",
+ "annotationText": "## 开票日期\n\n账单开票后由财务维护,自动反写,只读。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-actual-payment",
+ "index": 26,
+ "title": "实际付款日期",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-actual-payment\"]"
+ ],
+ "fingerprint": "lbl-col-actual-payment",
+ "path": []
+ },
+ "aiPrompt": "实际付款日期字段说明。",
+ "annotationText": "## 实际付款日期\n\n关联收款记录时取对应到账记录的付款日期;未关联时为空。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-payment-method",
+ "index": 27,
+ "title": "付款方式",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-payment-method\"]"
+ ],
+ "fingerprint": "lbl-col-payment-method",
+ "path": []
+ },
+ "aiPrompt": "付款方式字段说明。",
+ "annotationText": "## 付款方式\n\n由合同付款周期决定:1 个月→月度预付,3 个月→季度预付,6 个月→半年预付,12 个月→年度预付。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-vehicle-standard-cost",
+ "index": 28,
+ "title": "车辆标准成本",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-vehicle-standard-cost\"]"
+ ],
+ "fingerprint": "lbl-col-vehicle-standard-cost",
+ "path": []
+ },
+ "aiPrompt": "车辆标准成本字段说明。",
+ "annotationText": "## 车辆标准成本\n\n固定成本,按车型明细维护,只读。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-vehicle-actual-cost",
+ "index": 29,
+ "title": "车辆实际成本",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-vehicle-actual-cost\"]"
+ ],
+ "fingerprint": "lbl-col-vehicle-actual-cost",
+ "path": []
+ },
+ "aiPrompt": "车辆实际成本字段说明。",
+ "annotationText": "## 车辆实际成本\n\n车辆标准成本 ×(实际天数 / 30),自动计算。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-insurance-fee",
+ "index": 30,
+ "title": "保险费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-insurance-fee\"]"
+ ],
+ "fingerprint": "lbl-col-insurance-fee",
+ "path": []
+ },
+ "aiPrompt": "保险费字段说明。",
+ "annotationText": "## 保险费\n\n暂保留,不可编辑和导入;后续可能自动计算。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-hydrogen-fee",
+ "index": 31,
+ "title": "氢费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-hydrogen-fee\"]"
+ ],
+ "fingerprint": "lbl-col-hydrogen-fee",
+ "path": []
+ },
+ "aiPrompt": "氢费字段说明。",
+ "annotationText": "## 氢费\n\n表头提示:仅显示合同氢费承担方式为「我司承担」时,该账单期内氢费总和(含未对账/已对账)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-ops-cost",
+ "index": 32,
+ "title": "运维费(成本侧)",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-ops-cost\"]"
+ ],
+ "fingerprint": "lbl-col-ops-cost",
+ "path": []
+ },
+ "aiPrompt": "运维费(成本侧)字段说明。",
+ "annotationText": "## 运维费(成本侧)\n\n与收入侧运维费平进平出,自动同步。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-brokerage-fee",
+ "index": 33,
+ "title": "居间费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-brokerage-fee\"]"
+ ],
+ "fingerprint": "lbl-col-brokerage-fee",
+ "path": []
+ },
+ "aiPrompt": "居间费字段说明。",
+ "annotationText": "## 居间费\n\n手动输入。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-other-cost",
+ "index": 34,
+ "title": "其他(成本侧)",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-other-cost\"]"
+ ],
+ "fingerprint": "lbl-col-other-cost",
+ "path": []
+ },
+ "aiPrompt": "其他(成本侧)字段说明。",
+ "annotationText": "## 其他(成本侧)\n\n前方其他收入有填写时,此处为必填项;与收入侧其他费用区分。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-gps-mileage",
+ "index": 35,
+ "title": "GPS里程",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-gps-mileage\"]"
+ ],
+ "fingerprint": "lbl-col-gps-mileage",
+ "path": []
+ },
+ "aiPrompt": "GPS里程字段说明。",
+ "annotationText": "## GPS里程\n\n从 BI 里程取账单时间段的数据,自动展示。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-deduction-detail",
+ "index": 36,
+ "title": "其他减免金额明细内容",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-deduction-detail\"]"
+ ],
+ "fingerprint": "lbl-col-deduction-detail",
+ "path": []
+ },
+ "aiPrompt": "其他减免金额明细内容字段说明。",
+ "annotationText": "## 其他减免金额明细内容\n\n手动输入,备注明细内容。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-asset-ownership",
+ "index": 37,
+ "title": "资产归属",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-asset-ownership\"]"
+ ],
+ "fingerprint": "lbl-col-asset-ownership",
+ "path": []
+ },
+ "aiPrompt": "资产归属字段说明。",
+ "annotationText": "## 资产归属\n\n显示车辆实际归属公司名称,按车辆档案自动获取。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-signing-company",
+ "index": 38,
+ "title": "签约公司",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-signing-company\"]"
+ ],
+ "fingerprint": "lbl-col-signing-company",
+ "path": []
+ },
+ "aiPrompt": "签约公司字段说明。",
+ "annotationText": "## 签约公司\n\n租赁合同甲方,签订合同时自动获取;用于租赁总账利润归集。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-remark",
+ "index": 39,
+ "title": "备注",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-remark\"]"
+ ],
+ "fingerprint": "lbl-col-remark",
+ "path": []
+ },
+ "aiPrompt": "备注字段说明。",
+ "annotationText": "## 备注\n\n手动输入。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
}
]
\ No newline at end of file
diff --git a/.local/extract-annotation-source/directory-tree.txt b/.local/extract-annotation-source/directory-tree.txt
new file mode 100644
index 0000000..56be96c
--- /dev/null
+++ b/.local/extract-annotation-source/directory-tree.txt
@@ -0,0 +1,71 @@
+[folder] ONE-OS 原型导航
+ [link] 合同模板管理 → /prototypes/contract-template-management
+ [link] 保险采购 → /prototypes/insurance-procurement
+ [link] 车辆管理 → /prototypes/vehicle-management
+ [link] 租赁合同管理 → /prototypes/lease-contract-management
+ [link] 客户管理 → /prototypes/customer-management
+ [link] 租赁业务台账(当前) → /prototypes/lease-business-ledger
+ [link] 车辆氢费明细 → /prototypes/vehicle-h2-fee-ledger
+ [link] 自营业务台账 → /prototypes/self-operated-business-ledger
+ [folder] ONE-OS Web 端
+ [link] 工作台 → /prototypes/oneos-web-workbench
+ [link] 登录 → /prototypes/oneos-web-login
+ [link] 车辆管理 → /prototypes/oneos-web-vehicle-asset
+ [link] 合同模板管理 → /prototypes/oneos-web-contract-template
+ [link] 帮助中心 → /prototypes/oneos-web-help-center
+ [link] 财务管理 → /prototypes/oneos-web-finance
+ [link] 采购管理 → /prototypes/oneos-web-procurement
+ [link] 车辆租赁合同 → /prototypes/oneos-web-lease-contract
+ [link] 加氢站管理 → /prototypes/oneos-web-h2-station
+ [link] 数据分析 → /prototypes/oneos-web-data-analysis
+ [link] 台账数据 → /prototypes/oneos-web-ledger-data
+ [link] 业务管理 → /prototypes/oneos-web-business
+ [link] 运维管理 → /prototypes/oneos-web-ops
+ [link] 需求说明 → /prototypes/oneos-web-requirements
+[folder] 租赁业务台账说明
+ [markdown] 模块总览
+ [link] 租赁业务台账原型 → ./index.html
+ [markdown] PRD 全文 (src/prototypes/lease-business-ledger/.spec/requirements-prd.md)
+ [folder] 页面标注
+ [markdown] 筛选条件
+ [folder] KPI 统计
+ [markdown] KPI 统计
+ [markdown] 台账条数
+ [markdown] 未收总计
+ [folder] 列表工具栏
+ [markdown] 列表工具栏
+ [markdown] 标记为已确认
+ [markdown] 添加行
+ [folder] 租赁台账宽表
+ [markdown] 租赁台账宽表
+ [markdown] 状态流转
+ [markdown] 业务部门
+ [markdown] 车牌号码
+ [markdown] 客户名称
+ [markdown] 提车日期
+ [markdown] 保证金
+ [markdown] 合同标的租金
+ [markdown] 应收租金
+ [markdown] 月度收入
+ [markdown] 月度租金
+ [markdown] 维保包干收入
+ [markdown] 保险上浮费
+ [markdown] 运维费(收入侧)
+ [markdown] 其他收入
+ [markdown] 实收金额
+ [markdown] 未收金额
+ [markdown] 开票日期
+ [markdown] 实际付款日期
+ [markdown] 付款方式
+ [markdown] 车辆标准成本
+ [markdown] 车辆实际成本
+ [markdown] 保险费
+ [markdown] 氢费
+ [markdown] 运维费(成本侧)
+ [markdown] 居间费
+ [markdown] 其他(成本侧)
+ [markdown] GPS里程
+ [markdown] 其他减免金额明细内容
+ [markdown] 资产归属
+ [markdown] 签约公司
+ [markdown] 备注
\ No newline at end of file
diff --git a/.local/extract-annotation-source/markdown-nodes.json b/.local/extract-annotation-source/markdown-nodes.json
index 855bd89..5f8e120 100644
--- a/.local/extract-annotation-source/markdown-nodes.json
+++ b/.local/extract-annotation-source/markdown-nodes.json
@@ -1,57 +1,248 @@
[
{
- "id": "ct-doc-overview",
+ "id": "lbl-doc-overview",
"title": "模块总览",
- "markdown": "## 模块定位\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 合同模板管理 |\n| 目标用户 | 法务人员 |\n| 核心任务 | 维护 Word 版商用车租赁合同模板,按章节编辑,发布版本并设标准模板 |\n| 数据模型 | 一份 Word 文件 = 一条记录,内含 9 个章节内容 |\n| PRD 文档 | 见目录「PRD 全文 → PRD 完整文档」 |\n| 资源文件 | `src/resources/contract-template-management/PRD.md` |\n---\n### 产品目标\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本 |\n| 标准模板唯一 | 同一合同分类同时仅一套标准模板生效 |"
+ "markdownPath": null,
+ "preview": "## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 台账数据 — 租赁业务台账 |\n| 目标用户 | 租赁业务客服、业务服务组主管 |\n| 核心任务 | 维护租赁运营明细、汇总应收实收与盈亏、标记确认与关联收"
},
{
- "id": "ct-doc-prd-full",
- "title": "PRD 完整文档",
- "markdown": "# 合同模板管理 · 产品需求文档(PRD)\n\n| 项 | 内容 |\n|---|---|\n| 文档版本 | v1.0 |\n| 模块名称 | 合同模板管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 目标读者 | 前端 / 后端 / 测试 / 法务产品对接人 |\n| 交互原型 | `/prototypes/contract-template-management` |\n| 关联原型 | [车辆管理](/prototypes/vehicle-management)(车型主数据口径参考) |\n| 文档状态 | 已对齐原型,待研发排期 |\n\n---\n\n## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁合同以 Word 文档形式维护,内容长、结构复杂,包含合同主体与 8 个附件章节。不同车型可能需要不同的附件条款组合。法务人员需要:\n\n- 以 **Word 版式保真** 的方式维护整份合同;\n- 按 **章节** 管理内容,并配置 **适用车型**;\n- 发布 **版本**,并指定 **标准模板** 供客服草拟新合同时默认使用;\n- 在合同主体及租赁订单~安全责任协议各章节中配置 **条件条款**(按订单车辆命中显隐)。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本 |\n| 标准模板唯一 | 同一合同分类同时仅一套标准模板生效 |\n\n### 1.3 非目标(本期不做)\n\n- 采购合同、服务合同等其他分类的完整业务闭环(数据结构预留 `category`,当前仅启用 `lease`)\n- 在线多人协同编辑、审批流配置界面\n- Word 导出回写(本期以 HTML 结构化存储 + 预览为主)\n- 列表页顶部章节 Tab(**仅编辑页**保留章节 Tab)\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、设标准模板 |\n| 客服人员(间接用户) | 草拟新合同时默认引用标准模板(下游模块消费) |\n| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |\n\n### 2.2 核心用户故事\n\n1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。\n2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。\n3. **作为法务**,我希望在合同主体及租赁订单~安全责任协议各章节为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。\n4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。\n5. **作为法务**,我希望将某已发布版本设为「标准合同」,且系统自动禁用旧标准,以便客服始终使用唯一标准。\n6. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| Word 文档 / 模板记录 | 列表主表一行,对应一份完整合同文件 |\n| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |\n| 草稿 | `status = draft`,无版本号 |\n| 已发布 | `status = published`,有版本号 `vX.XX` |\n| 标准模板 | `isDefault = true` 且已发布的模板;同分类唯一 |\n| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock=\"1\"` |\n| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline=\"1\"` |\n| 条件条款 | 仅合同主体可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause=\"1\"` |\n| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |\n| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |\n\n---\n\n## 4. 信息架构\n\n```text\n合同模板管理\n├── 列表页\n│ ├── 筛选区\n│ ├── 工具栏(新增)\n│ └── 嵌套表格\n│ ├── 主表:Word 文件\n│ └── 子表:9 个章节(展开行)\n└── 编辑 / 新增页\n ├── 顶栏(返回、保存、保存并发布)\n ├── 章节 Tab(仅本页展示)\n ├── 条件条款说明(各章节 Tab 均可使用工具栏「条件」)\n ├── 适用车型绑定(附件章节 Tab 时显示)\n └── 工作区\n ├── 左侧:Word 版式编辑区\n └── 右侧:A4 实时预览\n```\n\n---\n\n## 5. 数据模型\n\n### 5.1 模板记录(Word 文档级)\n\n一条记录代表 **一份 Word 合同文件**,不按章节拆成多行。\n\n```typescript\ninterface ContractTemplateDocument {\n id: string; // 唯一 ID\n category: 'lease'; // 合同分类,当前固定商用车租赁\n fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」\n versionName: string; // 展示用版本名称\n versionNo: string | null; // 已发布:v1.00;草稿:null\n status: 'draft' | 'published';\n isDefault: boolean; // 是否标准模板\n sectionContents: Record; // 各章节 HTML\n sectionVehicleBindings: Record; // 各章节车型绑定\n creator: string;\n createTime: string; // YYYY-MM-DD HH:mm\n updater: string;\n updateTime: string;\n remark?: string; // 内部备注(列表不展示)\n}\n\ninterface VehicleBinding {\n brand: string; // 品牌,如「现代」\n model: string; // 型号,如「18吨氢燃料电池车」\n}\n```\n\n### 5.2 章节枚举(SectionKey)\n\n| key | 章节名称 | 车型绑定 | 条件条款 |\n|---|---|:---:|:---:|\n| `main` | 合同主体 | 否 | 是 |\n| `lease_order` | 租赁订单 | 是 | 是 |\n| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |\n| `safety_notice` | 安全责任告知书 | 是 | 是 |\n| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |\n| `safety_commitment` | 安全责任承诺书 | 是 | 是 |\n| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |\n| `authorization` | 授权委托书 | 是 | 是 |\n| `safety_agreement` | 安全责任协议 | 是 | 是 |\n\n### 5.3 章节内容 HTML 标记规范\n\n研发需保证存储、渲染、统计口径一致:\n\n| 类型 | 选择器 / 属性 | 统计函数 |\n|---|---|---|\n| 锁定区 | `.ct-inline-lock[data-inline-lock=\"1\"]` | `countLockRegions` |\n| 风控红线 | `.ct-risk-redline[data-risk-redline=\"1\"]` | `countRiskRedlineRegions` |\n| 条件条款 | `.ct-vehicle-clause[data-vehicle-clause=\"1\"]` | `countVehicleClauseRegions` |\n| 条件条款车型 | `data-vehicle-bindings`(JSON 数组) | 预览命中逻辑 |\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 版本号规则\n\n- 格式:`v{major}.{minor}`,如 `v1.00`、`v1.01`\n- **仅发布时生成**;保存草稿时 `versionNo = null`\n- 同分类下递增:取当前已发布版本最大 minor +1;minor ≥ 99 时 major +1、minor 归 0\n- 首版默认 `v1.00`\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布 |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**降为草稿**(清空版本号) |\n| **保存并发布** | 新建已发布记录 + 版本号 | 原记录变已发布 + 版本号 | **新建一条已发布记录**(新版本),原版本保留 |\n\n**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。\n\n### 6.3 标准模板规则\n\n1. 同一 `category` **同时仅允许一条** `isDefault = true` 且 `status = published` 的记录。\n2. 仅 **已发布且非当前标准** 的记录可执行「设为标准模板」。\n3. 切换标准时:**先禁用**原标准(`isDefault → false`),再启用新标准(`isDefault → true`)。\n4. **标准模板不可删除**;操作列不展示「设为标准模板」「删除」。\n5. 下游「客服草拟新合同」默认引用当前标准模板(本期由其他模块消费)。\n\n### 6.4 删除规则\n\n- 仅 **非标准模板** 可删除。\n- 删除前二次确认;删除后不可恢复。\n\n### 6.5 列表排序\n\n1. 标准模板置顶;\n2. 其余按 `updateTime` 降序。\n\n### 6.6 筛选匹配规则\n\n| 字段 | 匹配逻辑 |\n|---|---|\n| 合同名称 | 模糊匹配 `fileName`、`versionName`、`versionNo`、`creator`、`updater` |\n| 状态 | 精确匹配 `draft` / `published` |\n| 创建人 | 精确匹配 `creator` |\n| 适用品牌 | 文档内 **任一附件章节** 的车型绑定命中该品牌;无绑定的文档在筛品牌时视为「全部车型」可通过 |\n\n> **已移除**:筛选项「适用型号」、主表列「备注」(产品确认不再展示)。\n\n---\n\n## 7. 列表页需求\n\n### 7.1 页面结构\n\n筛选区 + 工具栏 + 嵌套表格。**列表页不展示章节 Tab。**\n\n### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 文本输入 | 支持回车触发查询 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 适用品牌 | 下拉 | 选项来自车型主数据品牌列表 |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认\n\n### 7.3 工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增 | 进入新增页,默认章节为「合同主体」,模式为 create |\n\n### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `fileName` | 超长省略;标准模板显示金色标签「标准合同」 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 编辑 | 始终 | 进入编辑页,加载整份 `sectionContents` |\n| 设为标准模板 | 已发布 && 非标准 | 弹窗确认后切换标准 |\n| 删除 | 非标准 | 弹窗确认后删除 |\n\n**设为标准模板弹窗文案要点**:\n\n- 说明:同一分类同时只能有一套标准模板;客服草拟新合同时将默认使用新标准。\n- 若存在旧标准:明示将先禁用「{旧标准文件名}」,再设置「{新标准文件名}」。\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n| 内容状态 | 有内容:「已配置」;无内容:「未配置」 |\n\n---\n\n## 8. 编辑 / 新增页需求\n\n### 8.1 页面结构\n\n顶栏 → 章节 Tab → 条件说明 / 车型绑定(按章节显隐)→ 左右分栏工作区。\n\n### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n### 8.3 章节 Tab\n\n- 共 9 个章节;**前 4 个直显**,其余收入「更多」下拉。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。\n- **其他 Tab**:仅展示该章节切片内容。\n\n章节顺序:\n\n1. 合同主体 \n2. 租赁订单 \n3. 车辆产生的费用 \n4. 安全责任告知书 \n5. 重型普通货运车辆租赁动态监管责任告知与确认书 \n6. 安全责任承诺书 \n7. 车辆使用规范和驾驶员要求 \n8. 授权委托书 \n9. 安全责任协议 \n\n### 8.4 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n### 8.5 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。\n\n### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.6.3 条件条款交互\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充\n\n### 8.8 空状态\n\n编辑区无内容时,预览区展示空状态引导;新增模式下突出「导入 Word 模板」按钮。\n\n---\n\n## 9. Word 导入与章节拆分\n\n### 9.1 导入范围\n\n- 一次导入 **整份合同**;\n- 导入后写入各 `sectionContents[key]`;\n- 默认进入「合同主体」章节编辑。\n\n### 9.2 拆分策略\n\n1. 对导入 HTML 做 Word 导出清洗(`sanitizeWordExportHtml`);\n2. 从附件章节起,按 **章节标记字符串** 定位切分点;\n3. 切分点优先落在 `
`、``、`` 之后,避免打断标签;\n4. `main` = 第一个切分点之前的全文;\n5. 各附件章节 = 相邻切分点之间的片段。\n\n### 9.3 章节标记(节选)\n\n研发需维护可扩展的 `markers` 列表,示例:\n\n| 章节 | 标记示例 |\n|---|---|\n| 租赁订单 | `附件 1:租赁订单`、`附件1:租赁订单` |\n| 车辆产生的费用 | `附件2:车辆产生的费用` |\n| 安全责任告知书 | `附件3:安全责任告知书` |\n| … | 完整列表见原型 `DOCUMENT_SECTION_TABS` |\n\n### 9.4 误匹配过滤\n\n以下情况 **不应** 作为切分点:\n\n- 标记出现在「详见附件…」「以附件…」等引用语境;\n- 标记过短且不在 `` 标题语境内;\n- 标记前为「(」「、」等不完整标题上下文。\n\n---\n\n## 10. 模板占位符\n\n### 10.1 变量分组\n\n**甲方签约主体**(创建合同时按所选签约公司自动替换):\n\n| key | 标签 |\n|---|---|\n| `lessorName` | 甲方全称 |\n| `lessorAddress` | 甲方地址 |\n| `lessorContact` | 甲方联系人 |\n| `lessorPhone` | 甲方电话 |\n| `lessorEmail` | 甲方邮箱 |\n| `lessorAccountName` | 收款户名 |\n| `lessorBankName` | 开户行 |\n| `lessorBankAccount` | 银行账号 |\n\n**合同通用**(创建合同时由系统或客服填写):\n\n| key | 标签 |\n|---|---|\n| `contractCode` | 合同编号 |\n| `customerName` | 乙方公司全称 |\n| `creditCode` | 乙方统一社会信用代码 |\n| `customerAddress` | 乙方地址 |\n| `customerContact` | 乙方联系人 |\n| `customerPhone` | 乙方联系电话 |\n| `signDateYear` | 签订年份 |\n| `signDateMonth` | 签订月份 |\n| `signDateDay` | 签订日期 |\n\n### 10.2 插入规则\n\n- 编辑区光标处插入 `{{key}}`;\n- 存储为 HTML 文本,不做运行时强校验;\n- 缺失变量创建合同时按空字符串或业务默认值处理(由下游定义)。\n\n---\n\n## 11. 车型主数据\n\n品牌 · 型号下拉数据与车辆管理模块保持一致,当前原型内置示例:\n\n| 品牌 | 型号示例 |\n|---|---|\n| 现代 | 18吨氢燃料电池车、帕力安牌4.5吨冷链车 |\n| 苏龙 | 9.6米氢燃料电池车 |\n| 柳汽乘龙 | 9.6米氢燃料电池车 |\n| 飞驰 | 集卡头、半挂车 |\n| 跃进 | 4.2米冷链车 |\n| 宇通 | 氢燃料电池客车 |\n\n> 正式环境应由车辆主数据服务提供;绑定存储仅保存品牌 + 型号字符串。\n\n---\n\n## 12. 与其他模块的关系\n\n```mermaid\nflowchart LR\n A[合同模板管理] -->|标准模板 + 章节内容| B[租赁合同创建]\n A -->|车型绑定口径| C[车辆管理]\n A -->|占位符变量定义| B\n B -->|订单车辆列表| A\n```\n\n| 模块 | 关系 |\n|---|---|\n| 租赁合同创建 | 读取标准模板或指定版本;按订单车辆过滤条件条款与附件章节 |\n| 车辆管理 | 车型品牌/型号枚举口径一致 |\n| 非标准合同审批 | 合同正文出现 `data-nonstd-trigger` 时触发(标记规范已预留) |\n\n---\n\n## 13. 权限与审计(建议)\n\n| 能力 | 建议权限 |\n|---|---|\n| 查看列表 | 法务、客服(只读) |\n| 新增 / 编辑 / 发布 | 法务 |\n| 设为标准模板 | 法务负责人 |\n| 删除 | 法务负责人 |\n\n**审计字段**:`creator`、`createTime`、`updater`、`updateTime` 必须在每次保存时更新。\n\n---\n\n## 14. 接口需求(建议)\n\n### 14.1 列表查询\n\n```\nGET /api/contract-templates?category=lease&keyword=&status=&creator=&vehicleBrand=&page=1&pageSize=10\n```\n\n响应需包含主表字段;章节摘要可列表内嵌或展开时按需加载。\n\n### 14.2 详情\n\n```\nGET /api/contract-templates/{id}\n```\n\n返回完整 `sectionContents`、`sectionVehicleBindings`。\n\n### 14.3 创建 / 更新\n\n```\nPOST /api/contract-templates\nPUT /api/contract-templates/{id}\n```\n\n请求体含章节 HTML 与绑定;`publish: boolean` 控制是否发布。\n\n### 14.4 设为标准\n\n```\nPOST /api/contract-templates/{id}/set-default\n```\n\n服务端事务:同 category 旧标准 `isDefault=false`,新标准 `isDefault=true`。\n\n### 14.5 删除\n\n```\nDELETE /api/contract-templates/{id}\n```\n\n拒绝删除 `isDefault=true` 的记录。\n\n### 14.6 Word 导入\n\n```\nPOST /api/contract-templates/import-word\nContent-Type: multipart/form-data\n```\n\n返回拆分后的 `sectionContents` 供前端预览确认(或后端直接落库,由研发评估)。\n\n---\n\n## 15. 非功能需求\n\n| 类别 | 要求 |\n|---|---|\n| 性能 | 单份合同 HTML ≤ 2MB 时,章节切换 < 500ms(前端缓存) |\n| 兼容性 | Chrome / Edge 最新两个大版本;编辑区最低宽度 1280px 推荐 |\n| 可访问性 | 表单控件有 `aria-label`;操作按钮具备可读文案 |\n| 安全 | 导入 Word 需做 XSS 清洗;仅白名单标签/属性入库 |\n| 可追溯 | 已发布版本不可原地覆盖内容,只能新发版 |\n\n---\n\n## 16. 验收标准\n\n### 16.1 列表页\n\n- [ ] 筛选项包含:合同名称、状态、创建人、适用品牌;**不含**适用型号、备注列\n- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表\n- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数、内容状态\n- [ ] 标准模板金色标签置顶,且不可删除\n- [ ] 切换标准模板时旧标准自动禁用\n\n### 16.2 编辑页\n\n- [ ] 章节 Tab 仅在编辑页展示;前 4 个直显 + 更多\n- [ ] 导入 Word 后 9 章节正确拆分\n- [ ] 附件章节可绑定车型;各章节均可设条件条款\n- [ ] 预览支持连续/单页切换;各章节可模拟车型验证条件条款\n- [ ] 保存草稿无版本号;发布生成版本号\n- [ ] 编辑已发布并发布时产生新版本,旧版本保留\n\n### 16.3 数据一致性\n\n- [ ] 列表子表「风控条款数」与编辑区统计一致\n- [ ] 列表子表「条件条款数」与各章节实际标记数一致\n- [ ] 筛选「适用品牌」与子表车型绑定逻辑一致\n\n### 16.4 标注与文档(原型)\n\n- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录路由可切换列表页 / 新增页\n\n---\n\n## 17. 原型实现映射(供研发对照)\n\n| 能力 | 原型文件 / 符号 |\n|---|---|\n| 主逻辑 | `src/prototypes/contract-template-management/ContractTemplate.jsx` |\n| 入口与标注 | `src/prototypes/contract-template-management/index.tsx` |\n| 标注数据 | `src/prototypes/contract-template-management/annotation-source.json` |\n| 占位符与出租方 | `src/prototypes/contract-template-management/contract-template-vars.js` |\n| 样式 | `src/prototypes/contract-template-management/styles/` |\n\n---\n\n## 18. 附录:标准模板确认弹窗(原文案)\n\n**标题**:确认设为标准模板\n\n**正文**:\n\n> 同一分类同时只能有一套标准模板生效。确认后,客服草拟新合同时将默认使用新标准模板。\n>\n> (若存在旧标准)将先禁用当前标准模板「{旧标准名称}」,再设置「{新标准名称}」为新的标准模板。\n\n**按钮**:取消 / 确认设置\n\n---\n\n## 19. 修订记录\n\n| 版本 | 日期 | 作者 | 说明 |\n|---|---|---|---|\n| v1.0 | 2026-06-25 | 产品 | 基于原型与需求讨论首版;明确 Word 文档级模型、嵌套列表、标准模板唯一、移除适用型号筛选与备注列 |"
+ "id": "lbl-doc-prd",
+ "title": "PRD 全文",
+ "markdownPath": "src/prototypes/lease-business-ledger/.spec/requirements-prd.md",
+ "preview": "# 租赁业务台账 · 产品需求说明(PRD)\n\n> 对齐《租赁业务运营明细台账模版》;业务服务组维护各自台账,标记已确认与收款关联为原型演示。\n\n---\n\n## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | "
},
{
- "id": "ct-doc-prd-overview",
- "title": "背景 · 数据模型 · 业务规则",
- "markdown": "# 合同模板管理 · 全局说明\n\n## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁合同以 Word 文档形式维护,内容长、结构复杂,包含合同主体与 8 个附件章节。不同车型可能需要不同的附件条款组合。法务人员需要:\n\n- 以 **Word 版式保真** 的方式维护整份合同;\n- 按 **章节** 管理内容,并配置 **适用车型**;\n- 发布 **版本**,并指定 **标准模板** 供客服草拟新合同时默认使用;\n- 在合同主体及租赁订单~安全责任协议各章节中配置 **条件条款**(按订单车辆命中显隐)。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本 |\n| 标准模板唯一 | 同一合同分类同时仅一套标准模板生效 |\n\n### 1.3 非目标(本期不做)\n\n- 采购合同、服务合同等其他分类的完整业务闭环(数据结构预留 `category`,当前仅启用 `lease`)\n- 在线多人协同编辑、审批流配置界面\n- Word 导出回写(本期以 HTML 结构化存储 + 预览为主)\n- 列表页顶部章节 Tab(**仅编辑页**保留章节 Tab)\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、设标准模板 |\n| 客服人员(间接用户) | 草拟新合同时默认引用标准模板(下游模块消费) |\n| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |\n\n### 2.2 核心用户故事\n\n1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。\n2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。\n3. **作为法务**,我希望在合同主体及租赁订单~安全责任协议各章节为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。\n4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。\n5. **作为法务**,我希望将某已发布版本设为「标准合同」,且系统自动禁用旧标准,以便客服始终使用唯一标准。\n6. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| Word 文档 / 模板记录 | 列表主表一行,对应一份完整合同文件 |\n| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |\n| 草稿 | `status = draft`,无版本号 |\n| 已发布 | `status = published`,有版本号 `vX.XX` |\n| 标准模板 | `isDefault = true` 且已发布的模板;同分类唯一 |\n| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock=\"1\"` |\n| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline=\"1\"` |\n| 条件条款 | 仅合同主体可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause=\"1\"` |\n| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |\n| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |\n\n---\n\n## 4. 信息架构\n\n```text\n合同模板管理\n├── 列表页\n│ ├── 筛选区\n│ ├── 工具栏(新增)\n│ └── 嵌套表格\n│ ├── 主表:Word 文件\n│ └── 子表:9 个章节(展开行)\n└── 编辑 / 新增页\n ├── 顶栏(返回、保存、保存并发布)\n ├── 章节 Tab(仅本页展示)\n ├── 条件条款说明(各章节 Tab 均可使用工具栏「条件」)\n ├── 适用车型绑定(附件章节 Tab 时显示)\n └── 工作区\n ├── 左侧:Word 版式编辑区\n └── 右侧:A4 实时预览\n```\n\n---\n\n## 5. 数据模型\n\n### 5.1 模板记录(Word 文档级)\n\n一条记录代表 **一份 Word 合同文件**,不按章节拆成多行。\n\n```typescript\ninterface ContractTemplateDocument {\n id: string; // 唯一 ID\n category: 'lease'; // 合同分类,当前固定商用车租赁\n fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」\n versionName: string; // 展示用版本名称\n versionNo: string | null; // 已发布:v1.00;草稿:null\n status: 'draft' | 'published';\n isDefault: boolean; // 是否标准模板\n sectionContents: Record; // 各章节 HTML\n sectionVehicleBindings: Record; // 各章节车型绑定\n creator: string;\n createTime: string; // YYYY-MM-DD HH:mm\n updater: string;\n updateTime: string;\n remark?: string; // 内部备注(列表不展示)\n}\n\ninterface VehicleBinding {\n brand: string; // 品牌,如「现代」\n model: string; // 型号,如「18吨氢燃料电池车」\n}\n```\n\n### 5.2 章节枚举(SectionKey)\n\n| key | 章节名称 | 车型绑定 | 条件条款 |\n|---|---|:---:|:---:|\n| `main` | 合同主体 | 否 | 是 |\n| `lease_order` | 租赁订单 | 是 | 是 |\n| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |\n| `safety_notice` | 安全责任告知书 | 是 | 是 |\n| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |\n| `safety_commitment` | 安全责任承诺书 | 是 | 是 |\n| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |\n| `authorization` | 授权委托书 | 是 | 是 |\n| `safety_agreement` | 安全责任协议 | 是 | 是 |\n\n### 5.3 章节内容 HTML 标记规范\n\n研发需保证存储、渲染、统计口径一致:\n\n| 类型 | 选择器 / 属性 | 统计函数 |\n|---|---|---|\n| 锁定区 | `.ct-inline-lock[data-inline-lock=\"1\"]` | `countLockRegions` |\n| 风控红线 | `.ct-risk-redline[data-risk-redline=\"1\"]` | `countRiskRedlineRegions` |\n| 条件条款 | `.ct-vehicle-clause[data-vehicle-clause=\"1\"]` | `countVehicleClauseRegions` |\n| 条件条款车型 | `data-vehicle-bindings`(JSON 数组) | 预览命中逻辑 |\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 版本号规则\n\n- 格式:`v{major}.{minor}`,如 `v1.00`、`v1.01`\n- **仅发布时生成**;保存草稿时 `versionNo = null`\n- 同分类下递增:取当前已发布版本最大 minor +1;minor ≥ 99 时 major +1、minor 归 0\n- 首版默认 `v1.00`\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布 |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**降为草稿**(清空版本号) |\n| **保存并发布** | 新建已发布记录 + 版本号 | 原记录变已发布 + 版本号 | **新建一条已发布记录**(新版本),原版本保留 |\n\n**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。\n\n### 6.3 标准模板规则\n\n1. 同一 `category` **同时仅允许一条** `isDefault = true` 且 `status = published` 的记录。\n2. 仅 **已发布且非当前标准** 的记录可执行「设为标准模板」。\n3. 切换标准时:**先禁用**原标准(`isDefault → false`),再启用新标准(`isDefault → true`)。\n4. **标准模板不可删除**;操作列不展示「设为标准模板」「删除」。\n5. 下游「客服草拟新合同」默认引用当前标准模板(本期由其他模块消费)。\n\n### 6.4 删除规则\n\n- 仅 **非标准模板** 可删除。\n- 删除前二次确认;删除后不可恢复。\n\n### 6.5 列表排序\n\n1. 标准模板置顶;\n2. 其余按 `updateTime` 降序。\n\n### 6.6 筛选匹配规则\n\n| 字段 | 匹配逻辑 |\n|---|---|\n| 合同名称 | 模糊匹配 `fileName`、`versionName`、`versionNo`、`creator`、`updater` |\n| 状态 | 精确匹配 `draft` / `published` |\n| 创建人 | 精确匹配 `creator` |\n| 适用品牌 | 文档内 **任一附件章节** 的车型绑定命中该品牌;无绑定的文档在筛品牌时视为「全部车型」可通过 |\n\n> **已移除**:筛选项「适用型号」、主表列「备注」(产品确认不再展示)。\n\n---\n\n---\n\n## 5. 数据模型\n\n### 5.1 模板记录(Word 文档级)\n\n一条记录代表 **一份 Word 合同文件**,不按章节拆成多行。\n\n```typescript\ninterface ContractTemplateDocument {\n id: string; // 唯一 ID\n category: 'lease'; // 合同分类,当前固定商用车租赁\n fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」\n versionName: string; // 展示用版本名称\n versionNo: string | null; // 已发布:v1.00;草稿:null\n status: 'draft' | 'published';\n isDefault: boolean; // 是否标准模板\n sectionContents: Record; // 各章节 HTML\n sectionVehicleBindings: Record; // 各章节车型绑定\n creator: string;\n createTime: string; // YYYY-MM-DD HH:mm\n updater: string;\n updateTime: string;\n remark?: string; // 内部备注(列表不展示)\n}\n\ninterface VehicleBinding {\n brand: string; // 品牌,如「现代」\n model: string; // 型号,如「18吨氢燃料电池车」\n}\n```\n\n### 5.2 章节枚举(SectionKey)\n\n| key | 章节名称 | 车型绑定 | 条件条款 |\n|---|---|:---:|:---:|\n| `main` | 合同主体 | 否 | 是 |\n| `lease_order` | 租赁订单 | 是 | 是 |\n| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |\n| `safety_notice` | 安全责任告知书 | 是 | 是 |\n| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |\n| `safety_commitment` | 安全责任承诺书 | 是 | 是 |\n| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |\n| `authorization` | 授权委托书 | 是 | 是 |\n| `safety_agreement` | 安全责任协议 | 是 | 是 |\n\n### 5.3 章节内容 HTML 标记规范\n\n研发需保证存储、渲染、统计口径一致:\n\n| 类型 | 选择器 / 属性 | 统计函数 |\n|---|---|---|\n| 锁定区 | `.ct-inline-lock[data-inline-lock=\"1\"]` | `countLockRegions` |\n| 风控红线 | `.ct-risk-redline[data-risk-redline=\"1\"]` | `countRiskRedlineRegions` |\n| 条件条款 | `.ct-vehicle-clause[data-vehicle-clause=\"1\"]` | `countVehicleClauseRegions` |\n| 条件条款车型 | `data-vehicle-bindings`(JSON 数组) | 预览命中逻辑 |\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 版本号规则\n\n- 格式:`v{major}.{minor}`,如 `v1.00`、`v1.01`\n- **仅发布时生成**;保存草稿时 `versionNo = null`\n- 同分类下递增:取当前已发布版本最大 minor +1;minor ≥ 99 时 major +1、minor 归 0\n- 首版默认 `v1.00`\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布 |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**降为草稿**(清空版本号) |\n| **保存并发布** | 新建已发布记录 + 版本号 | 原记录变已发布 + 版本号 | **新建一条已发布记录**(新版本),原版本保留 |\n\n**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。\n\n### 6.3 标准模板规则\n\n1. 同一 `category` **同时仅允许一条** `isDefault = true` 且 `status = published` 的记录。\n2. 仅 **已发布且非当前标准** 的记录可执行「设为标准模板」。\n3. 切换标准时:**先禁用**原标准(`isDefault → false`),再启用新标准(`isDefault → true`)。\n4. **标准模板不可删除**;操作列不展示「设为标准模板」「删除」。\n5. 下游「客服草拟新合同」默认引用当前标准模板(本期由其他模块消费)。\n\n### 6.4 删除规则\n\n- 仅 **非标准模板** 可删除。\n- 删除前二次确认;删除后不可恢复。\n\n### 6.5 列表排序\n\n1. 标准模板置顶;\n2. 其余按 `updateTime` 降序。\n\n### 6.6 筛选匹配规则\n\n| 字段 | 匹配逻辑 |\n|---|---|\n| 合同名称 | 模糊匹配 `fileName`、`versionName`、`versionNo`、`creator`、`updater` |\n| 状态 | 精确匹配 `draft` / `published` |\n| 创建人 | 精确匹配 `creator` |\n| 适用品牌 | 文档内 **任一附件章节** 的车型绑定命中该品牌;无绑定的文档在筛品牌时视为「全部车型」可通过 |\n\n> **已移除**:筛选项「适用型号」、主表列「备注」(产品确认不再展示)。\n\n---"
- },
- {
- "id": "ct-doc-prd-list",
- "title": "列表页 PRD",
- "markdown": "# 合同模板管理 · 列表页 PRD\n\n## 7. 列表页需求\n\n### 7.1 页面结构\n\n筛选区 + 工具栏 + 嵌套表格。**列表页不展示章节 Tab。**\n\n### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 文本输入 | 支持回车触发查询 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 适用品牌 | 下拉 | 选项来自车型主数据品牌列表 |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认\n\n### 7.3 工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增 | 进入新增页,默认章节为「合同主体」,模式为 create |\n\n### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `fileName` | 超长省略;标准模板显示金色标签「标准合同」 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 编辑 | 始终 | 进入编辑页,加载整份 `sectionContents` |\n| 设为标准模板 | 已发布 && 非标准 | 弹窗确认后切换标准 |\n| 删除 | 非标准 | 弹窗确认后删除 |\n\n**设为标准模板弹窗文案要点**:\n\n- 说明:同一分类同时只能有一套标准模板;客服草拟新合同时将默认使用新标准。\n- 若存在旧标准:明示将先禁用「{旧标准文件名}」,再设置「{新标准文件名}」。\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n| 内容状态 | 有内容:「已配置」;无内容:「未配置」 |\n\n---"
- },
- {
- "id": "ct-doc-prd-editor",
- "title": "编辑页 PRD",
- "markdown": "# 合同模板管理 · 编辑/新增页 PRD\n\n## 8. 编辑 / 新增页需求\n\n### 8.1 页面结构\n\n顶栏 → 章节 Tab → 条件说明 / 车型绑定(按章节显隐)→ 左右分栏工作区。\n\n### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n### 8.3 章节 Tab\n\n- 共 9 个章节;**前 4 个直显**,其余收入「更多」下拉。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。\n- **其他 Tab**:仅展示该章节切片内容。\n\n章节顺序:\n\n1. 合同主体 \n2. 租赁订单 \n3. 车辆产生的费用 \n4. 安全责任告知书 \n5. 重型普通货运车辆租赁动态监管责任告知与确认书 \n6. 安全责任承诺书 \n7. 车辆使用规范和驾驶员要求 \n8. 授权委托书 \n9. 安全责任协议 \n\n### 8.4 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n### 8.5 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。\n\n### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.6.3 条件条款交互\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充\n\n### 8.8 空状态\n\n编辑区无内容时,预览区展示空状态引导;新增模式下突出「导入 Word 模板」按钮。\n\n---\n\n---\n\n## 9. Word 导入与章节拆分\n\n### 9.1 导入范围\n\n- 一次导入 **整份合同**;\n- 导入后写入各 `sectionContents[key]`;\n- 默认进入「合同主体」章节编辑。\n\n### 9.2 拆分策略\n\n1. 对导入 HTML 做 Word 导出清洗(`sanitizeWordExportHtml`);\n2. 从附件章节起,按 **章节标记字符串** 定位切分点;\n3. 切分点优先落在 ``、``、`` 之后,避免打断标签;\n4. `main` = 第一个切分点之前的全文;\n5. 各附件章节 = 相邻切分点之间的片段。\n\n### 9.3 章节标记(节选)\n\n研发需维护可扩展的 `markers` 列表,示例:\n\n| 章节 | 标记示例 |\n|---|---|\n| 租赁订单 | `附件 1:租赁订单`、`附件1:租赁订单` |\n| 车辆产生的费用 | `附件2:车辆产生的费用` |\n| 安全责任告知书 | `附件3:安全责任告知书` |\n| … | 完整列表见原型 `DOCUMENT_SECTION_TABS` |\n\n### 9.4 误匹配过滤\n\n以下情况 **不应** 作为切分点:\n\n- 标记出现在「详见附件…」「以附件…」等引用语境;\n- 标记过短且不在 `` 标题语境内;\n- 标记前为「(」「、」等不完整标题上下文。\n\n---\n\n---\n\n## 10. 模板占位符\n\n### 10.1 变量分组\n\n**甲方签约主体**(创建合同时按所选签约公司自动替换):\n\n| key | 标签 |\n|---|---|\n| `lessorName` | 甲方全称 |\n| `lessorAddress` | 甲方地址 |\n| `lessorContact` | 甲方联系人 |\n| `lessorPhone` | 甲方电话 |\n| `lessorEmail` | 甲方邮箱 |\n| `lessorAccountName` | 收款户名 |\n| `lessorBankName` | 开户行 |\n| `lessorBankAccount` | 银行账号 |\n\n**合同通用**(创建合同时由系统或客服填写):\n\n| key | 标签 |\n|---|---|\n| `contractCode` | 合同编号 |\n| `customerName` | 乙方公司全称 |\n| `creditCode` | 乙方统一社会信用代码 |\n| `customerAddress` | 乙方地址 |\n| `customerContact` | 乙方联系人 |\n| `customerPhone` | 乙方联系电话 |\n| `signDateYear` | 签订年份 |\n| `signDateMonth` | 签订月份 |\n| `signDateDay` | 签订日期 |\n\n### 10.2 插入规则\n\n- 编辑区光标处插入 `{{key}}`;\n- 存储为 HTML 文本,不做运行时强校验;\n- 缺失变量创建合同时按空字符串或业务默认值处理(由下游定义)。\n\n---"
- },
- {
- "id": "ct-doc-prd-acceptance",
- "title": "验收标准",
- "markdown": "## 16. 验收标准\n\n### 16.1 列表页\n\n- [ ] 筛选项包含:合同名称、状态、创建人、适用品牌;**不含**适用型号、备注列\n- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表\n- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数、内容状态\n- [ ] 标准模板金色标签置顶,且不可删除\n- [ ] 切换标准模板时旧标准自动禁用\n\n### 16.2 编辑页\n\n- [ ] 章节 Tab 仅在编辑页展示;前 4 个直显 + 更多\n- [ ] 导入 Word 后 9 章节正确拆分\n- [ ] 附件章节可绑定车型;各章节均可设条件条款\n- [ ] 预览支持连续/单页切换;各章节可模拟车型验证条件条款\n- [ ] 保存草稿无版本号;发布生成版本号\n- [ ] 编辑已发布并发布时产生新版本,旧版本保留\n\n### 16.3 数据一致性\n\n- [ ] 列表子表「风控条款数」与编辑区统计一致\n- [ ] 列表子表「条件条款数」与各章节实际标记数一致\n- [ ] 筛选「适用品牌」与子表车型绑定逻辑一致\n\n### 16.4 标注与文档(原型)\n\n- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录路由可切换列表页 / 新增页\n\n---"
- },
- {
- "id": "ct-doc-node-filter",
+ "id": "lbl-doc-lbl-filter",
"title": "筛选条件",
- "markdown": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 文本输入 | 支持回车触发查询 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 适用品牌 | 下拉 | 选项来自车型主数据品牌列表 |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认"
+ "markdownPath": null,
+ "preview": "## 2. 筛选区\n\n默认展示 4 项(一行),点击「更多筛选」展开全部 8 项。\n\n| 筛选项 | 说明 |\n|---|---|\n| 年份 / 月份 | 账单所属年月 |\n| 业务部门 / 业务员 | 精确匹配 |\n| 车牌号码 | 精确"
},
{
- "id": "ct-doc-node-table",
- "title": "嵌套列表",
- "markdown": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `fileName` | 超长省略;标准模板显示金色标签「标准合同」 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 编辑 | 始终 | 进入编辑页,加载整份 `sectionContents` |\n| 设为标准模板 | 已发布 && 非标准 | 弹窗确认后切换标准 |\n| 删除 | 非标准 | 弹窗确认后删除 |\n\n**设为标准模板弹窗文案要点**:\n\n- 说明:同一分类同时只能有一套标准模板;客服草拟新合同时将默认使用新标准。\n- 若存在旧标准:明示将先禁用「{旧标准文件名}」,再设置「{新标准文件名}」。\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n| 内容状态 | 有内容:「已配置」;无内容:「未配置」 |\n\n### 6.3 标准模板规则\n\n1. 同一 `category` **同时仅允许一条** `isDefault = true` 且 `status = published` 的记录。\n2. 仅 **已发布且非当前标准** 的记录可执行「设为标准模板」。\n3. 切换标准时:**先禁用**原标准(`isDefault → false`),再启用新标准(`isDefault → true`)。\n4. **标准模板不可删除**;操作列不展示「设为标准模板」「删除」。\n5. 下游「客服草拟新合同」默认引用当前标准模板(本期由其他模块消费)。"
+ "id": "lbl-doc-lbl-kpi",
+ "title": "KPI 统计",
+ "markdownPath": null,
+ "preview": "## 3. KPI 统计卡片(6 张)\n\n台账条数、应收总计、实收总计、未收总计、成本总计、盈亏总计。\n\n- **台账条数**:员工仅统计本人录入;主管统计全组。点击切换列表为显示全部。\n- **未收总计**:点击筛选下方列表为未收款数据。"
},
{
- "id": "ct-doc-node-tabs",
- "title": "章节 Tab",
- "markdown": "### 8.3 章节 Tab\n\n- 共 9 个章节;**前 4 个直显**,其余收入「更多」下拉。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。\n- **其他 Tab**:仅展示该章节切片内容。\n\n章节顺序:\n\n1. 合同主体 \n2. 租赁订单 \n3. 车辆产生的费用 \n4. 安全责任告知书 \n5. 重型普通货运车辆租赁动态监管责任告知与确认书 \n6. 安全责任承诺书 \n7. 车辆使用规范和驾驶员要求 \n8. 授权委托书 \n9. 安全责任协议"
+ "id": "lbl-doc-lbl-kpi-count",
+ "title": "台账条数",
+ "markdownPath": null,
+ "preview": "## 台账条数 KPI\n\n业务服务组员工只能看到自己录入的数据条数以及金额;业务服务组主管可以查看所有业务服务组人员录入的数据总和。点击可切换列表显示全部数据。\n\n---"
},
{
- "id": "ct-doc-node-workspace",
- "title": "编辑与预览",
- "markdown": "## 编辑与预览\n\n### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.6.3 条件条款交互\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n---\n\n### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充"
+ "id": "lbl-doc-lbl-kpi-unreceived",
+ "title": "未收总计",
+ "markdownPath": null,
+ "preview": "## 未收总计 KPI\n\n点击未收总计卡片,下方列表仅展示未收款数据(状态不等于已收款)。\n\n---"
},
{
- "id": "ct-doc-annotation-hint",
- "title": "标注查看提示",
- "markdown": "## 标注查看提示\n\n右侧标注工具栏可浏览目录、切换主题与颜色筛选。列表页与编辑页标注按当前页面自动显隐。\n\n**PRD 同步**:目录「PRD 全文」已与 `src/resources/contract-template-management/PRD.md` 对齐;修改 PRD 后需重新同步至 `annotation-source.json`。"
+ "id": "lbl-doc-lbl-toolbar",
+ "title": "列表工具栏",
+ "markdownPath": null,
+ "preview": "## 4. 列表工具栏\n\n左侧「仅显示未收款数据」勾选后,列表仅显示状态不等于已收款的记录。\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板并上传 Excel(原型 Toast) |\n| 导出 | 导出当前筛选结果"
+ },
+ {
+ "id": "lbl-doc-lbl-btn-mark-confirmed",
+ "title": "标记为已确认",
+ "markdownPath": null,
+ "preview": "## 标记为已确认\n\n勾选草稿记录后点击,弹出二次确认:「确认后的数据只有主管才能继续进行修改,是否确认数据准确无误?」确认后状态变更为已确认,业务服务组人员不可再删改。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-btn-add-row",
+ "title": "添加行",
+ "markdownPath": null,
+ "preview": "## 添加行\n\n位于表格底部;列表不做分页,采用下拉加载更多,避免多选受分页限制。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-table",
+ "title": "租赁台账宽表",
+ "markdownPath": null,
+ "preview": "## 5. 列表字段\n\n对齐 Excel「租赁车辆收入明细表」全量字段:年份、月份、业务部门(含业务员)、付款日期、车牌(含车型)、客户(含项目)、增值服务、享免政策、提车/合同/起止日期、平均天数、押金、合同标的租金、应收与收入拆分、减免"
+ },
+ {
+ "id": "lbl-doc-lbl-col-status",
+ "title": "状态流转",
+ "markdownPath": null,
+ "preview": "## 状态流转\n\n1. 新增/导入 → **草稿**(业务服务组可删改)\n2. 多选 + 「标记为已确认」→ **已确认**(仅主管可改)\n3. 已确认行可「关联收款记录」;到账合计 < 应收 → **部分收款**;≥ 应收 → **已收款"
+ },
+ {
+ "id": "lbl-doc-lbl-col-dept",
+ "title": "业务部门",
+ "markdownPath": null,
+ "preview": "## 业务部门列\n\n业务部门与业务员合并为一列:上方业务部门,下方业务员。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-plate",
+ "title": "车牌号码",
+ "markdownPath": null,
+ "preview": "## 车牌号码列\n\n车牌与车型合并为一列:上方车牌号码,下方品牌与型号;导入/新增时按车牌在系统中自动匹配。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-customer",
+ "title": "客户名称",
+ "markdownPath": null,
+ "preview": "## 客户名称列\n\n客户名称与项目名称合并为一列:上方客户名称,下方项目名称;按账单日期与车牌号自动匹配。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-pickup-date",
+ "title": "提车日期",
+ "markdownPath": null,
+ "preview": "## 提车日期\n\n等于交车管理中该车辆运维 E 签宝签字完成的时间。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-deposit",
+ "title": "保证金",
+ "markdownPath": null,
+ "preview": "## 保证金\n\n与创建合同时的保证金、提车应收款中的保证金等同;提车应收款有保证金时显示。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-contract-rent",
+ "title": "合同标的租金",
+ "markdownPath": null,
+ "preview": "## 合同标的租金\n\n等于该车辆在签订该合同时合同中的租金(月租金标准)。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-receivable-rent",
+ "title": "应收租金",
+ "markdownPath": null,
+ "preview": "## 应收租金\n\n按合同付款周期(1/3/6/12 个月)计算的单期账单租金总金额 = 合同标的租金 × 付款周期月数。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-monthly-income",
+ "title": "月度收入",
+ "markdownPath": null,
+ "preview": "## 月度收入\n\n租金 + 服务费 − 里程减免金额 − 其他减免金额(后两项为减项)。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-monthly-rent",
+ "title": "月度租金",
+ "markdownPath": null,
+ "preview": "## 月度租金\n\n根据客户付款周期间隔,计算每个月需收取的租金,等于合同标的租金。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-maintenance-income",
+ "title": "维保包干收入",
+ "markdownPath": null,
+ "preview": "## 维保包干收入\n\n合同勾选维保包干时:单价(元/km)× 里程数,必填;未勾选时默认 0 且不可编辑。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-insurance-surcharge",
+ "title": "保险上浮费",
+ "markdownPath": null,
+ "preview": "## 保险上浮费\n\n事故中有保险上浮费时,最新账单显示警示标志;关联事故单后自动汇总上浮费,支持多选。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-ops-income",
+ "title": "运维费(收入侧)",
+ "markdownPath": null,
+ "preview": "## 运维费(收入侧)\n\n维修/保养台账为「客户承担」时,最新一期账单显示警示图标,提醒关联维修/保养记录;关联后自动汇总运维费,支持多选。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-other-income",
+ "title": "其他收入",
+ "markdownPath": null,
+ "preview": "## 其他收入\n\n手工填写,预留作为特殊情况记录使用。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-received-amount",
+ "title": "实收金额",
+ "markdownPath": null,
+ "preview": "## 实收金额\n\n根据财务导入收款明细归集;支持关联多条并分配计入金额;超出应收部分在单据下自行分配。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-unreceived",
+ "title": "未收金额",
+ "markdownPath": null,
+ "preview": "## 未收金额\n\n未收金额 = 应收合计 − 实收金额,系统自动计算,不可手工录入。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-invoice-date",
+ "title": "开票日期",
+ "markdownPath": null,
+ "preview": "## 开票日期\n\n账单开票后由财务维护,自动反写,只读。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-actual-payment",
+ "title": "实际付款日期",
+ "markdownPath": null,
+ "preview": "## 实际付款日期\n\n关联收款记录时取对应到账记录的付款日期;未关联时为空。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-payment-method",
+ "title": "付款方式",
+ "markdownPath": null,
+ "preview": "## 付款方式\n\n由合同付款周期决定:1 个月→月度预付,3 个月→季度预付,6 个月→半年预付,12 个月→年度预付。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-vehicle-standard-cost",
+ "title": "车辆标准成本",
+ "markdownPath": null,
+ "preview": "## 车辆标准成本\n\n固定成本,按车型明细维护,只读。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-vehicle-actual-cost",
+ "title": "车辆实际成本",
+ "markdownPath": null,
+ "preview": "## 车辆实际成本\n\n车辆标准成本 ×(实际天数 / 30),自动计算。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-insurance-fee",
+ "title": "保险费",
+ "markdownPath": null,
+ "preview": "## 保险费\n\n暂保留,不可编辑和导入;后续可能自动计算。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-hydrogen-fee",
+ "title": "氢费",
+ "markdownPath": null,
+ "preview": "## 氢费\n\n表头提示:仅显示合同氢费承担方式为「我司承担」时,该账单期内氢费总和(含未对账/已对账)。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-ops-cost",
+ "title": "运维费(成本侧)",
+ "markdownPath": null,
+ "preview": "## 运维费(成本侧)\n\n与收入侧运维费平进平出,自动同步。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-brokerage-fee",
+ "title": "居间费",
+ "markdownPath": null,
+ "preview": "## 居间费\n\n手动输入。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-other-cost",
+ "title": "其他(成本侧)",
+ "markdownPath": null,
+ "preview": "## 其他(成本侧)\n\n前方其他收入有填写时,此处为必填项;与收入侧其他费用区分。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-gps-mileage",
+ "title": "GPS里程",
+ "markdownPath": null,
+ "preview": "## GPS里程\n\n从 BI 里程取账单时间段的数据,自动展示。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-deduction-detail",
+ "title": "其他减免金额明细内容",
+ "markdownPath": null,
+ "preview": "## 其他减免金额明细内容\n\n手动输入,备注明细内容。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-asset-ownership",
+ "title": "资产归属",
+ "markdownPath": null,
+ "preview": "## 资产归属\n\n显示车辆实际归属公司名称,按车辆档案自动获取。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-signing-company",
+ "title": "签约公司",
+ "markdownPath": null,
+ "preview": "## 签约公司\n\n租赁合同甲方,签订合同时自动获取;用于租赁总账利润归集。\n\n---"
+ },
+ {
+ "id": "lbl-doc-lbl-col-remark",
+ "title": "备注",
+ "markdownPath": null,
+ "preview": "## 备注\n\n手动输入。\n\n---"
}
]
\ No newline at end of file
diff --git a/.local/extract-annotation-source/output.json b/.local/extract-annotation-source/output.json
index 00c4b7d..5901731 100644
--- a/.local/extract-annotation-source/output.json
+++ b/.local/extract-annotation-source/output.json
@@ -1,8 +1,8 @@
{
- "title": "站点信息 - 原型预览(开发)",
+ "title": "租赁业务台账 - 原型预览(开发)",
"errors": [
- "Warning: [antd: Tag] `bordered={false}` is deprecated. Please use `variant=\"filled\"` instead.",
- "Warning: [antd: Modal] `destroyOnClose` is deprecated. Please use `destroyOnHidden` instead."
+ "Warning: Encountered two children with the same key, `%s`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.%s closed \n at LeaseBusinessLedgerApp (http://localhost:51720/prototypes/lease-business-ledger/index.tsx?t=1782980649549:55:33)\n at AxhubPreviewErrorBoundary (http://localhost:51720/prototypes/lease-business-ledger/__axhub-preview-loader.js:8:5)",
+ "Warning: Encountered two children with the same key, `%s`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.%s closed \n at LeaseBusinessLedgerApp (http://localhost:51720/prototypes/lease-business-ledger/index.tsx?t=1782980649549:55:33)\n at AxhubPreviewErrorBoundary (http://localhost:51720/prototypes/lease-business-ledger/__axhub-preview-loader.js:8:5)"
],
"source": {
"directory": {
@@ -13,10 +13,45 @@
"title": "ONE-OS 原型导航",
"defaultExpanded": true,
"children": [
+ {
+ "type": "link",
+ "id": "nav-link-contract-template-management",
+ "title": "合同模板管理",
+ "href": "/prototypes/contract-template-management",
+ "target": "self"
+ },
+ {
+ "type": "link",
+ "id": "nav-link-insurance-procurement",
+ "title": "保险采购",
+ "href": "/prototypes/insurance-procurement",
+ "target": "self"
+ },
+ {
+ "type": "link",
+ "id": "nav-link-vehicle-management",
+ "title": "车辆管理",
+ "href": "/prototypes/vehicle-management",
+ "target": "self"
+ },
+ {
+ "type": "link",
+ "id": "nav-link-lease-contract-management",
+ "title": "租赁合同管理",
+ "href": "/prototypes/lease-contract-management",
+ "target": "self"
+ },
+ {
+ "type": "link",
+ "id": "nav-link-customer-management",
+ "title": "客户管理",
+ "href": "/prototypes/customer-management",
+ "target": "self"
+ },
{
"type": "link",
"id": "nav-link-lease-business-ledger",
- "title": "租赁业务台账",
+ "title": "租赁业务台账(当前)",
"href": "/prototypes/lease-business-ledger",
"target": "self"
},
@@ -144,101 +179,293 @@
},
{
"type": "folder",
- "id": "h2-site-doc-root",
- "title": "站点信息说明",
+ "id": "lbl-doc-root",
+ "title": "租赁业务台账说明",
"defaultExpanded": true,
"children": [
{
"type": "markdown",
- "id": "h2-site-doc-overview",
+ "id": "lbl-doc-overview",
"title": "模块总览",
- "markdown": "## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块 | 加氢站管理 → 站点信息 |\n| 用户 | 运营、财务结算、管理员 |\n| 核心任务 | 站点主数据、运营配置、氢费对账结算 |\n| 页面结构 | 筛选 + KPI + 工具栏 + 列表 + 分页 |\n\n## 2. 关联模块\n\n- **车辆氢费明细**:氢费数据来源与对账回写目标\n- **加氢站合包**:加氢订单、加氢记录仍在合包入口\n\n## 3. 验收重点\n\n1. KPI 高频/低频/无加氢与氢费明细一致\n2. 对账单两阶段流程与跨页回写\n3. 列表布局与租赁合同 vm 规范一致"
+ "markdown": "## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 台账数据 — 租赁业务台账 |\n| 目标用户 | 租赁业务客服、业务服务组主管 |\n| 核心任务 | 维护租赁运营明细、汇总应收实收与盈亏、标记确认与关联收款 |\n| 页面结构 | 筛选区 + KPI 统计 + 工具栏 + 宽表 + 下拉加载更多(不做分页) |\n\n---"
},
{
"type": "link",
- "id": "h2-site-link-vh2",
- "title": "车辆氢费明细",
- "href": "../vehicle-h2-fee-ledger/index.html",
+ "id": "lbl-link-self",
+ "title": "租赁业务台账原型",
+ "href": "./index.html",
"target": "self"
},
{
- "type": "link",
- "id": "h2-site-link-bundle",
- "title": "加氢站管理(合包)",
- "href": "../oneos-web-h2-station/index.html",
- "target": "self"
- },
- {
- "type": "folder",
- "id": "h2-site-doc-prd",
+ "type": "markdown",
+ "id": "lbl-doc-prd",
"title": "PRD 全文",
- "defaultExpanded": true,
- "children": [
- {
- "type": "markdown",
- "id": "h2-site-doc-prd-full",
- "title": "PRD 完整文档",
- "markdown": "# 加氢站管理 · 站点信息 — 产品需求文档(PRD)\n\n| 项 | 内容 |\n|---|---|\n| 文档版本 | v1.0 |\n| 模块名称 | 加氢站管理 → 站点信息 |\n| 所属系统 | ONE-OS Web 端 |\n| 目标读者 | 前端 / 后端 / 测试 / 业务产品对接人 |\n| 交互原型 | `/prototypes/oneos-web-h2-station-site` |\n| 关联原型 | [车辆氢费明细](/prototypes/vehicle-h2-fee-ledger)、[加氢站合包](/prototypes/oneos-web-h2-station) |\n| 文档状态 | 已对齐原型 |\n\n---\n\n## 1. 背景与目标\n\n### 1.1 业务背景\n\n加氢站是氢费成本结算的关键节点。运营侧需要维护站点主数据(签约、营业、成本价、预付余额),并在财务周期内按站点与加氢站完成对账、收票与付款闭环。\n\n### 1.2 本期目标\n\n- 站点台账的查询、新建、编辑、查看、删除与批量导入\n- 营业状态、成本价格、预付余额等运营配置\n- **按站点生成氢费对账单 → 填写收票信息 → 提交结算**,并与「车辆氢费明细」联动回写\n\n### 1.3 用户角色\n\n| 角色 | 典型诉求 |\n|---|---|\n| 加氢站运营 | 维护站点资料、营业与价格;查看加氢量与余额 |\n| 财务/结算 | 按周期生成对账单、登记收票、完成站点付款闭环 |\n| 管理员 | 批量导入站点、删除无效站点、配置系统账号 |\n\n### 1.4 非目标(本期不做)\n\n- 对账单审批流、ERP 自动过账\n- 发票 OCR 识别与验真\n- 加氢订单、加氢记录(仍在合包 `oneos-web-h2-station`)\n- 后端接口联调(原型使用 Mock + 共享 Store)\n\n---\n\n## 2. 信息架构\n\n```text\n站点信息\n├── 列表页\n│ ├── 筛选区(4 项)\n│ ├── KPI 卡片(6 张)\n│ ├── 工具栏\n│ └── 站点台账表 + 分页\n├── 新建 / 编辑(同页内嵌视图)\n│ ├── 基本信息\n│ ├── 系统账号绑定\n│ └── 供应商 / 付款信息\n└── 弹窗 / 抽屉\n ├── 查看详情\n ├── 营业状态 / 价格配置\n ├── 加氢量下钻 / 预付余额下钻\n ├── 生成对账单(两阶段)\n └── 查看对账记录\n```\n\n---\n\n## 3. 列表与 KPI\n\n> 详见 `.spec/requirements-prd-list.md`\n\n- 筛选:名称、签约、地区、营业状态\n- KPI:全部 / 签约·普通 / 高频≥3 / 低频1–2 / 无加氢0\n- 列表字段:名称地址、合作时间、营业、成本价、加氢次数/量、预付余额、联系方式、操作\n- 设计规范:与租赁合同管理一致的 vm 布局与 Inter 字体\n\n---\n\n## 4. 站点主数据\n\n### 4.1 新建站点\n\n同页内嵌视图:基本信息、系统账号绑定(可选)、供应商信息(新建或关联)。\n\n### 4.2 编辑 / 查看\n\n| 模式 | 交互 |\n|---|---|\n| 编辑 | 整页表单;系统账号仅 admin 可改 |\n| 查看 | 弹窗分块展示站点、账号、供应商、付款信息 |\n\n### 4.3 批量导入\n\n下载 CSV 模板 → 上传 → 解析预览 → 确认导入;站点名称不可重复。\n\n---\n\n## 5. 运营配置\n\n### 5.1 营业状态\n\n卡片式弹窗:营业状态、营业时间按钮组;非全天须配置时段;展示状态变更记录。\n\n### 5.2 价格配置\n\n配置成本价格(元/kg)与生效时间;到达生效时间后刷新列表「当前成本价格」;展示价格调整记录。\n\n### 5.3 下钻分析\n\n- **加氢量下钻**:统计卡 + 明细表 + 导出,字段对齐车辆氢费明细\n- **预付余额下钻**:充值/加氢流水 + 趋势图 + 导出\n\n---\n\n## 6. 对账与结算(重点)\n\n> 详见 `.spec/requirements-prd-statement.md`\n\n与「车辆氢费明细」配合完成站点侧氢费结算闭环:\n\n1. 拉取已对账且未结算的记录\n2. 两阶段:选期生成 → 填收票提交\n3. 提交后更新预付余额、写对账历史、回写台账(对账日期、收票日期、已付款)\n\n---\n\n## 7. 数据与集成\n\n| 能力 | 实现 |\n|---|---|\n| 氢费明细 | `src/common/h2VehicleLedgerBridge.js` |\n| 对账回写 | `H2_STATION_STATEMENT_LEDGER_UPDATES` |\n| 页面实现 | `src/prototypes/oneos-web-h2-station/pages/03-站点信息.jsx` |\n| 独立入口 | `src/prototypes/oneos-web-h2-station-site/index.tsx` |\n\n---\n\n## 8. 验收标准\n\n### 8.1 列表页\n\n- [ ] 筛选与 KPI 六卡片联动正确\n- [ ] 高频/低频/无加氢口径与车辆氢费明细一致\n- [ ] 加氢量、预付余额下钻数据来自共享 Store\n- [ ] 布局与租赁合同 vm 规范一致\n\n### 8.2 对账流程\n\n- [ ] 仅已对账未结算记录可进入对账单\n- [ ] 两阶段交互与必填校验完整\n- [ ] 提交后回写车辆氢费明细可跨页验收\n\n### 8.3 标注与文档\n\n- [ ] `@axhub/annotation` 已接入,关键区域有 `data-annotation-id`\n- [ ] 右侧工具栏目录可阅 PRD 全文与页面标注\n\n---\n\n## 9. 修订记录\n\n| 版本 | 日期 | 说明 |\n|---|---|---|\n| v1.0 | 2026-07-01 | 独立入口、vm 规范对齐、标注目录与 PRD 接入 |\n",
- "markdownPath": "src/prototypes/oneos-web-h2-station-site/.spec/requirements-prd.md"
- },
- {
- "type": "markdown",
- "id": "h2-site-doc-prd-list",
- "title": "列表页 PRD",
- "markdown": "# 加氢站管理 · 站点信息 — 列表页 PRD\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 加氢站管理 → 站点信息 |\n| 所属系统 | ONE-OS Web 端 |\n| 目标用户 | 加氢站运营、财务结算、平台管理员 |\n| 核心任务 | 查询站点台账、维护主数据、查看运营指标、发起对账与充值 |\n| 交互原型 | `/prototypes/oneos-web-h2-station-site` |\n| 设计规范 | 与租赁合同管理一致的 vm 布局(筛选 / KPI / 表格 / 分页) |\n\n---\n\n## 1. 页面结构\n\n用户进入「加氢站管理 → 站点信息」后,列表页自上而下为:\n\n1. **筛选区** — 名称、签约、地区、营业状态\n2. **KPI 分类** — 全部 / 签约·普通 / 高频 / 低频 / 无加氢\n3. **工具栏** — 全站余额提醒、批量导入、发起充值单、新建站点\n4. **站点列表** — 主数据 + 运营指标 + 操作列\n5. **分页栏** — 每页 5/10/20/50 条\n\n---\n\n## 2. 筛选区\n\n与租赁合同等模块一致的 **vm 筛选卡片** 布局:标题「筛选条件」+ 4 列栅格(标签在上、控件在下);底部分隔线 + 右对齐「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 加氢站名称 | 输入框 | 模糊匹配,支持回车查询 |\n| 是否签约 | 下拉 | 签约站点 / 普通站点 |\n| 地区(省-市) | 级联 | 省、市两级,可只选省 |\n| 营业状态 | 下拉 | 营业中 / 暂停营业 / 停止营业 |\n\n**交互**:\n\n- **查询**:写入「已应用筛选」,列表与 KPI 统计联动,回到第 1 页\n- **重置**:清空筛选条件并刷新列表\n\n---\n\n## 3. KPI 统计卡片\n\n与租赁合同一致的 **vm-kpi-card**:左侧图标 + 标题 + 数值,右上角 `?` 悬停说明,选中态绿色描边。\n\n| 卡片 | 口径 |\n|---|---|\n| 全部加氢站 | 列表全部站点 |\n| 签约站点 | 已签约,点击快捷筛选 |\n| 普通站点 | 未签约,点击快捷筛选 |\n| 高频加氢站 | 该站加氢次数 ≥ 3 |\n| 低频加氢站 | 有加氢但次数 < 3 |\n| 无加氢站点 | 加氢次数 = 0 |\n\n加氢次数来自「车辆氢费明细」共享 Store,按站点名称聚合。KPI 与筛选区签约条件互斥切换(点 KPI 分类会清除签约筛选,点签约卡会重置 KPI 为全部)。\n\n---\n\n## 4. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 全站余额提醒设置 | 配置全局预付余额预警阈值 |\n| 批量导入 | 下载模板 → 上传 CSV/Excel → 预览确认导入 |\n| 发起充值单 | 为站点发起预付充值流程(原型演示) |\n| 新建站点 | 进入同页内嵌新建视图 |\n\n---\n\n## 5. 列表字段说明\n\n| 字段 | 说明 |\n|---|---|\n| 加氢站名称 | 含签约标签;下一行展示省市区 + 详细地址 |\n| 合作时间 | 未签约可空 |\n| 营业状态 / 营业时间 | 列表只读;在「更多 → 营业状态」维护 |\n| 当前成本价格 | 元/kg;受价格配置生效时间驱动;多价时展示警示 |\n| 加氢次数 / 加氢量 | 加氢量可点击下钻;表头支持排序 |\n| 预付余额 | 可点击下钻流水;负值标红并显示「已欠费」 |\n| 联系方式 | 联系人 + 电话 |\n| 操作 | 查看 + 更多菜单 |\n\n**更多菜单**:编辑、删除、营业状态、价格配置、生成对账单、查看对账记录等(按状态与权限显隐)。\n\n**分页**:默认 10 条,可选 5/10/20/50。\n\n---\n\n## 6. 数据来源\n\n- 氢费/加氢明细:统一来自 `src/common/h2VehicleLedgerBridge.js`,与「车辆氢费明细」共享 Store\n- 先打开「车辆氢费明细」时台账全量同步;仅打开加氢站模块时使用 canonical seed\n- 对账单提交经 `H2_STATION_STATEMENT_LEDGER_UPDATES` 回写车辆氢费明细\n\n---\n\n## 7. 双入口说明\n\n| 入口 | 路径 |\n|---|---|\n| 独立原型(本页) | OneOS → 加氢站管理 → 站点信息 |\n| 合包 | ONE-OS Web 端 → 加氢站管理 → 加氢订单 / 加氢记录 |\n",
- "markdownPath": "src/prototypes/oneos-web-h2-station-site/.spec/requirements-prd-list.md"
- },
- {
- "type": "markdown",
- "id": "h2-site-doc-prd-statement",
- "title": "对账与结算 PRD",
- "markdown": "# 加氢站管理 · 站点信息 — 对账与结算 PRD\n\n> 本模块与「车辆氢费明细」配合完成**站点侧氢费结算闭环**。\n\n---\n\n## 1. 数据来源\n\n对账单明细取自 **车辆氢费明细** 中同时满足以下条件的记录:\n\n1. 对账状态 = **已对账**(台账已完成「完成对账」)\n2. **尚未**被本站点历史对账单结算过(未绑定 `statementRecordId`)\n3. 加氢时间落在用户选择的 **[账单开始日期, 账单结束日期]** 内\n4. 加氢站名称与当前操作站点一致\n\n> 对账单明细**仅展示成本字段**(加氢量、成本单价、成本总价等),不展示加氢单价、加氢总价。\n\n---\n\n## 2. 两阶段交互\n\n**阶段一 · 选期生成**\n\n- 日期区下方展示:**上次对账单结束时间**(无历史则「暂无」)\n- 默认开始日期建议 = 上次结束日 + 1 天\n- 点击「生成对账单」后进入阶段二,日期锁定不可改\n- 无符合条件记录时提示调整日期\n\n**阶段二 · 结算提交**\n\n统计卡(明细表上方):\n\n| 指标 | 说明 |\n|---|---|\n| 加氢次数 | 本账单包含的已对账记录笔数 |\n| 加氢总量 | kg 合计 |\n| 成本总金额 | 成本总价合计 |\n\n明细表下方**必填**结算项:\n\n| 字段 | 规则 |\n|---|---|\n| 结算后加氢站预付款余额 | **只读**,= 当前预付余额 − 成本总金额;不足为负并标「已欠费」 |\n| 收票日期 | 必填 |\n| 收票金额 | 必填,默认填入成本总金额 |\n| 发票附件 | 必填,支持上传 |\n\n---\n\n## 3. 提交后的系统行为\n\n提交成功后需**原子性**完成:\n\n1. 写入对账历史(供「查看对账记录」查询)\n2. 扣减站点预付余额\n3. 标记台账记录已结算(绑定 `statementRecordId`)\n4. **回写车辆氢费明细**:\n\n| 车辆氢费明细字段 | 回写值 |\n|---|---|\n| 对账日期 | 操作完成时间 |\n| 收票日期 | 操作完成日期 |\n| 加氢站付款状态 | 已付款 |\n\n原型通过 `H2_STATION_STATEMENT_LEDGER_UPDATES` / `h2VehicleLedgerBridge` 模拟跨页回写。\n\n---\n\n## 4. 查看对账记录\n\n入口:列表操作列 → 更多 → **查看对账记录**\n\n历史列表:对账日期、对账人、账单区间、加氢次数、加氢金额、结算后余额、查看明细。\n\n查看明细:账单区间、统计三卡、收票信息、发票附件、成本明细表。\n\n---\n\n## 5. 校验与异常\n\n| 场景 | 处理 |\n|---|---|\n| 日期未填 / 开始晚于结束 | 阻止生成 |\n| 区间内无已对账未结算记录 | 阻止生成,提示调整日期 |\n| 收票日期/金额/附件未填 | 阻止提交 |\n| 收票金额 ≤ 0 | 阻止提交 |\n| 预付余额结算后为负 | 允许提交,标「已欠费」 |\n\n---\n\n## 6. 研发要点\n\n1. **对账单幂等**:同一笔已对账记录只能进入一次成功提交的对账单\n2. **上次结束时间**:取该站点已成功提交对账单的最大 `endDate`\n3. **余额计算**:`balanceAfter = prepaidBalance - sum(costTotal)`\n4. **跨模块同步**:生产对接统一台账;原型使用 Bridge Store\n5. **明细字段范围**:对账弹窗不展示客户侧加氢单价/总价\n",
- "markdownPath": "src/prototypes/oneos-web-h2-station-site/.spec/requirements-prd-statement.md"
- },
- {
- "type": "markdown",
- "id": "h2-site-doc-scope",
- "title": "范围与决策",
- "markdown": "# 站点信息独立原型 — 范围与决策\n\n## 背景\n\n「站点信息」从 ONE-OS Web 合包 `oneos-web-h2-station` 拆出,作为 OneOS → 加氢站管理下的独立 Axhub 入口,便于与正式菜单路径对齐。\n\n## 范围\n\n| 包含 | 不包含 |\n|------|--------|\n| 站点列表、KPI、新建/编辑/查看/删除 | 加氢订单、加氢记录(仍在合包) |\n| 营业状态、价格配置、余额下钻 | 后端接口联调 |\n| 两阶段对账单、充值单、批量导入 | |\n\n## 数据来源\n\n- **氢费/加氢明细**:统一来自 `src/common/h2VehicleLedgerBridge.js`,与「车辆氢费明细」原型共享 Store。\n- 先打开「车辆氢费明细」时,台账全量行会同步至 Store;仅打开加氢站模块时使用 canonical seed(10 条演示数据)。\n- 对账单提交 patch 经 `window.H2_STATION_STATEMENT_LEDGER_UPDATES` 回写车辆氢费明细。\n\n## KPI 口径(已确认)\n\n全部 / 签约·普通 / **高频(≥3 次)** / **低频(1–2 次)** / **无加氢(0 次)**,与《站点管理使用说明书》及页面内嵌 PRD 一致。\n\n## 角色演示\n\n页面右上角提供「平台管理员 / 加氢站账号」切换,影响编辑页系统账号字段可编辑性。\n\n## 双入口说明\n\n- **本原型**:OneOS → 加氢站管理 → 站点信息\n- **合包**:ONE-OS Web 端 → 加氢站管理 → 加氢订单 / 加氢记录\n",
- "markdownPath": "src/prototypes/oneos-web-h2-station-site/.spec/requirements.md"
- },
- {
- "type": "markdown",
- "id": "h2-site-doc-acceptance",
- "title": "验收标准",
- "markdown": "## 8. 验收标准\n\n### 8.1 列表页\n\n- [ ] 筛选与 KPI 六卡片联动正确\n- [ ] 高频/低频/无加氢口径与车辆氢费明细一致\n- [ ] 加氢量、预付余额下钻数据来自共享 Store\n- [ ] 布局与租赁合同 vm 规范一致\n\n### 8.2 对账流程\n\n- [ ] 仅已对账未结算记录可进入对账单\n- [ ] 两阶段交互与必填校验完整\n- [ ] 提交后回写车辆氢费明细可跨页验收\n\n### 8.3 标注与文档\n\n- [ ] `@axhub/annotation` 已接入,关键区域有 `data-annotation-id`\n- [ ] 右侧工具栏目录可阅 PRD 全文与页面标注\n\n---"
- }
- ]
+ "markdown": "# 租赁业务台账 · 产品需求说明(PRD)\n\n> 对齐《租赁业务运营明细台账模版》;业务服务组维护各自台账,标记已确认与收款关联为原型演示。\n\n---\n\n## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 台账数据 — 租赁业务台账 |\n| 目标用户 | 租赁业务客服、业务服务组主管 |\n| 核心任务 | 维护租赁运营明细、汇总应收实收与盈亏、标记确认与关联收款 |\n| 页面结构 | 筛选区 + KPI 统计 + 工具栏 + 宽表 + 下拉加载更多(不做分页) |\n\n---\n\n## 2. 筛选区\n\n默认展示 4 项(一行),点击「更多筛选」展开全部 8 项。\n\n| 筛选项 | 说明 |\n|---|---|\n| 年份 / 月份 | 账单所属年月 |\n| 业务部门 / 业务员 | 精确匹配 |\n| 车牌号码 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 明细状态 | 草稿 / 已确认 / 部分收款 / 已收款 |\n| 付款日期 | 日期区间 |\n\n---\n\n## 3. KPI 统计卡片(6 张)\n\n台账条数、应收总计、实收总计、未收总计、成本总计、盈亏总计。\n\n- **台账条数**:员工仅统计本人录入;主管统计全组。点击切换列表为显示全部。\n- **未收总计**:点击筛选下方列表为未收款数据。\n\n### 台账条数 KPI\n\n业务服务组员工只能看到自己录入的数据条数以及金额;业务服务组主管可以查看所有业务服务组人员录入的数据总和。点击可切换列表显示全部数据。\n\n### 未收总计 KPI\n\n点击未收总计卡片,下方列表仅展示未收款数据(状态不等于已收款)。\n\n---\n\n## 4. 列表工具栏\n\n左侧「仅显示未收款数据」勾选后,列表仅显示状态不等于已收款的记录。\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板并上传 Excel(原型 Toast) |\n| 导出 | 导出当前筛选结果 |\n| 标记为已确认 | 勾选草稿行后二次确认 |\n| 提交收款审批 | 勾选行提交审批 |\n\n添加行已移至表格底部;列表采用下拉加载更多,不做分页。\n\n### 标记为已确认\n\n勾选草稿记录后点击,弹出二次确认:「确认后的数据只有主管才能继续进行修改,是否确认数据准确无误?」确认后状态变更为已确认,业务服务组人员不可再删改。\n\n### 添加行\n\n位于表格底部;列表不做分页,采用下拉加载更多,避免多选受分页限制。\n\n---\n\n## 5. 列表字段\n\n对齐 Excel「租赁车辆收入明细表」全量字段:年份、月份、业务部门(含业务员)、付款日期、车牌(含车型)、客户(含项目)、增值服务、享免政策、提车/合同/起止日期、平均天数、押金、合同标的租金、应收与收入拆分、减免、实收/未收、开票与付款信息、成本拆分、盈亏、GPS里程、减免明细、资产归属、签约公司、备注。\n\n宽表支持横向滚动;序号与操作列固定。\n\n---\n\n## 6. 状态流转\n\n1. 新增/导入 → **草稿**(业务服务组可删改)\n2. 多选 + 「标记为已确认」→ **已确认**(仅主管可改)\n3. 已确认行可「关联收款记录」;到账合计 < 应收 → **部分收款**;≥ 应收 → **已收款**\n\n---\n\n## 7. 字段说明\n\n### 业务部门列\n\n业务部门与业务员合并为一列:上方业务部门,下方业务员。\n\n### 车牌号码列\n\n车牌与车型合并为一列:上方车牌号码,下方品牌与型号;导入/新增时按车牌在系统中自动匹配。\n\n### 客户名称列\n\n客户名称与项目名称合并为一列:上方客户名称,下方项目名称;按账单日期与车牌号自动匹配。\n\n### 提车日期\n\n等于交车管理中该车辆运维 E 签宝签字完成的时间。\n\n### 保证金\n\n与创建合同时的保证金、提车应收款中的保证金等同;提车应收款有保证金时显示。\n\n### 合同标的租金\n\n等于该车辆在签订该合同时合同中的租金(月租金标准)。\n\n### 应收租金\n\n按合同付款周期(1/3/6/12 个月)计算的单期账单租金总金额 = 合同标的租金 × 付款周期月数。\n\n### 月度收入\n\n租金 + 服务费 − 里程减免金额 − 其他减免金额(后两项为减项)。\n\n### 月度租金\n\n根据客户付款周期间隔,计算每个月需收取的租金,等于合同标的租金。\n\n### 维保包干收入\n\n合同勾选维保包干时:单价(元/km)× 里程数,必填;未勾选时默认 0 且不可编辑。\n\n### 保险上浮费\n\n事故中有保险上浮费时,最新账单显示警示标志;关联事故单后自动汇总上浮费,支持多选。\n\n### 运维费(收入侧)\n\n维修/保养台账为「客户承担」时,最新一期账单显示警示图标,提醒关联维修/保养记录;关联后自动汇总运维费,支持多选。\n\n### 其他收入\n\n手工填写,预留作为特殊情况记录使用。\n\n### 实收金额\n\n根据财务导入收款明细归集;支持关联多条并分配计入金额;超出应收部分在单据下自行分配。\n\n### 未收金额\n\n未收金额 = 应收合计 − 实收金额,系统自动计算,不可手工录入。\n\n### 开票日期\n\n账单开票后由财务维护,自动反写,只读。\n\n### 实际付款日期\n\n关联收款记录时取对应到账记录的付款日期;未关联时为空。\n\n### 付款方式\n\n由合同付款周期决定:1 个月→月度预付,3 个月→季度预付,6 个月→半年预付,12 个月→年度预付。\n\n### 车辆标准成本\n\n固定成本,按车型明细维护,只读。\n\n### 车辆实际成本\n\n车辆标准成本 ×(实际天数 / 30),自动计算。\n\n### 保险费\n\n暂保留,不可编辑和导入;后续可能自动计算。\n\n### 氢费\n\n表头提示:仅显示合同氢费承担方式为「我司承担」时,该账单期内氢费总和(含未对账/已对账)。\n\n### 运维费(成本侧)\n\n与收入侧运维费平进平出,自动同步。\n\n### 居间费\n\n手动输入。\n\n### 其他(成本侧)\n\n前方其他收入有填写时,此处为必填项;与收入侧其他费用区分。\n\n### GPS里程\n\n从 BI 里程取账单时间段的数据,自动展示。\n\n### 其他减免金额明细内容\n\n手动输入,备注明细内容。\n\n### 资产归属\n\n显示车辆实际归属公司名称,按车辆档案自动获取。\n\n### 签约公司\n\n租赁合同甲方,签订合同时自动获取;用于租赁总账利润归集。\n\n### 备注\n\n手动输入。",
+ "markdownPath": "src/prototypes/lease-business-ledger/.spec/requirements-prd.md"
},
{
"type": "folder",
- "id": "h2-site-doc-page",
+ "id": "lbl-doc-page-annotations",
"title": "页面标注",
"defaultExpanded": true,
"children": [
{
"type": "markdown",
- "id": "h2-site-doc-node-h2-site-filter",
+ "id": "lbl-doc-lbl-filter",
"title": "筛选条件",
- "markdown": "## 2. 筛选区\n\n与租赁合同等模块一致的 **vm 筛选卡片** 布局:标题「筛选条件」+ 4 列栅格(标签在上、控件在下);底部分隔线 + 右对齐「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 加氢站名称 | 输入框 | 模糊匹配,支持回车查询 |\n| 是否签约 | 下拉 | 签约站点 / 普通站点 |\n| 地区(省-市) | 级联 | 省、市两级,可只选省 |\n| 营业状态 | 下拉 | 营业中 / 暂停营业 / 停止营业 |\n\n**交互**:\n\n- **查询**:写入「已应用筛选」,列表与 KPI 统计联动,回到第 1 页\n- **重置**:清空筛选条件并刷新列表\n\n---"
+ "markdown": "## 2. 筛选区\n\n默认展示 4 项(一行),点击「更多筛选」展开全部 8 项。\n\n| 筛选项 | 说明 |\n|---|---|\n| 年份 / 月份 | 账单所属年月 |\n| 业务部门 / 业务员 | 精确匹配 |\n| 车牌号码 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 明细状态 | 草稿 / 已确认 / 部分收款 / 已收款 |\n| 付款日期 | 日期区间 |\n\n---"
},
{
- "type": "markdown",
- "id": "h2-site-doc-node-h2-site-kpi",
- "title": "KPI 快捷筛选",
- "markdown": "## 3. KPI 统计卡片\n\n与租赁合同一致的 **vm-kpi-card**:左侧图标 + 标题 + 数值,右上角 `?` 悬停说明,选中态绿色描边。\n\n| 卡片 | 口径 |\n|---|---|\n| 全部加氢站 | 列表全部站点 |\n| 签约站点 | 已签约,点击快捷筛选 |\n| 普通站点 | 未签约,点击快捷筛选 |\n| 高频加氢站 | 该站加氢次数 ≥ 3 |\n| 低频加氢站 | 有加氢但次数 < 3 |\n| 无加氢站点 | 加氢次数 = 0 |\n\n加氢次数来自「车辆氢费明细」共享 Store,按站点名称聚合。KPI 与筛选区签约条件互斥切换(点 KPI 分类会清除签约筛选,点签约卡会重置 KPI 为全部)。\n\n---"
+ "type": "folder",
+ "id": "lbl-doc-folder-kpi",
+ "title": "KPI 统计",
+ "defaultExpanded": false,
+ "children": [
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-kpi",
+ "title": "KPI 统计",
+ "markdown": "## 3. KPI 统计卡片(6 张)\n\n台账条数、应收总计、实收总计、未收总计、成本总计、盈亏总计。\n\n- **台账条数**:员工仅统计本人录入;主管统计全组。点击切换列表为显示全部。\n- **未收总计**:点击筛选下方列表为未收款数据。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-kpi-count",
+ "title": "台账条数",
+ "markdown": "## 台账条数 KPI\n\n业务服务组员工只能看到自己录入的数据条数以及金额;业务服务组主管可以查看所有业务服务组人员录入的数据总和。点击可切换列表显示全部数据。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-kpi-unreceived",
+ "title": "未收总计",
+ "markdown": "## 未收总计 KPI\n\n点击未收总计卡片,下方列表仅展示未收款数据(状态不等于已收款)。\n\n---"
+ }
+ ]
},
{
- "type": "markdown",
- "id": "h2-site-doc-node-h2-site-statement",
- "title": "工具栏与对账",
- "markdown": "## 4. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 全站余额提醒设置 | 配置全局预付余额预警阈值 |\n| 批量导入 | 下载模板 → 上传 CSV/Excel → 预览确认导入 |\n| 发起充值单 | 为站点发起预付充值流程(原型演示) |\n| 新建站点 | 进入同页内嵌新建视图 |\n\n---\n\n---\n\n## 6. 数据来源\n\n- 氢费/加氢明细:统一来自 `src/common/h2VehicleLedgerBridge.js`,与「车辆氢费明细」共享 Store\n- 先打开「车辆氢费明细」时台账全量同步;仅打开加氢站模块时使用 canonical seed\n- 对账单提交经 `H2_STATION_STATEMENT_LEDGER_UPDATES` 回写车辆氢费明细\n\n---"
+ "type": "folder",
+ "id": "lbl-doc-folder-toolbar",
+ "title": "列表工具栏",
+ "defaultExpanded": false,
+ "children": [
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-toolbar",
+ "title": "列表工具栏",
+ "markdown": "## 4. 列表工具栏\n\n左侧「仅显示未收款数据」勾选后,列表仅显示状态不等于已收款的记录。\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板并上传 Excel(原型 Toast) |\n| 导出 | 导出当前筛选结果 |\n| 标记为已确认 | 勾选草稿行后二次确认 |\n| 提交收款审批 | 勾选行提交审批 |\n\n添加行已移至表格底部;列表采用下拉加载更多,不做分页。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-btn-mark-confirmed",
+ "title": "标记为已确认",
+ "markdown": "## 标记为已确认\n\n勾选草稿记录后点击,弹出二次确认:「确认后的数据只有主管才能继续进行修改,是否确认数据准确无误?」确认后状态变更为已确认,业务服务组人员不可再删改。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-btn-add-row",
+ "title": "添加行",
+ "markdown": "## 添加行\n\n位于表格底部;列表不做分页,采用下拉加载更多,避免多选受分页限制。\n\n---"
+ }
+ ]
},
{
- "type": "markdown",
- "id": "h2-site-doc-node-h2-site-table",
- "title": "站点列表",
- "markdown": "## 5. 列表字段说明\n\n| 字段 | 说明 |\n|---|---|\n| 加氢站名称 | 含签约标签;下一行展示省市区 + 详细地址 |\n| 合作时间 | 未签约可空 |\n| 营业状态 / 营业时间 | 列表只读;在「更多 → 营业状态」维护 |\n| 当前成本价格 | 元/kg;受价格配置生效时间驱动;多价时展示警示 |\n| 加氢次数 / 加氢量 | 加氢量可点击下钻;表头支持排序 |\n| 预付余额 | 可点击下钻流水;负值标红并显示「已欠费」 |\n| 联系方式 | 联系人 + 电话 |\n| 操作 | 查看 + 更多菜单 |\n\n**更多菜单**:编辑、删除、营业状态、价格配置、生成对账单、查看对账记录等(按状态与权限显隐)。\n\n**分页**:默认 10 条,可选 5/10/20/50。\n\n---"
+ "type": "folder",
+ "id": "lbl-doc-folder-table",
+ "title": "租赁台账宽表",
+ "defaultExpanded": true,
+ "children": [
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-table",
+ "title": "租赁台账宽表",
+ "markdown": "## 5. 列表字段\n\n对齐 Excel「租赁车辆收入明细表」全量字段:年份、月份、业务部门(含业务员)、付款日期、车牌(含车型)、客户(含项目)、增值服务、享免政策、提车/合同/起止日期、平均天数、押金、合同标的租金、应收与收入拆分、减免、实收/未收、开票与付款信息、成本拆分、盈亏、GPS里程、减免明细、资产归属、签约公司、备注。\n\n宽表支持横向滚动;序号与操作列固定。"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-status",
+ "title": "状态流转",
+ "markdown": "## 状态流转\n\n1. 新增/导入 → **草稿**(业务服务组可删改)\n2. 多选 + 「标记为已确认」→ **已确认**(仅主管可改)\n3. 已确认行可「关联收款记录」;到账合计 < 应收 → **部分收款**;≥ 应收 → **已收款**\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-dept",
+ "title": "业务部门",
+ "markdown": "## 业务部门列\n\n业务部门与业务员合并为一列:上方业务部门,下方业务员。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-plate",
+ "title": "车牌号码",
+ "markdown": "## 车牌号码列\n\n车牌与车型合并为一列:上方车牌号码,下方品牌与型号;导入/新增时按车牌在系统中自动匹配。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-customer",
+ "title": "客户名称",
+ "markdown": "## 客户名称列\n\n客户名称与项目名称合并为一列:上方客户名称,下方项目名称;按账单日期与车牌号自动匹配。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-pickup-date",
+ "title": "提车日期",
+ "markdown": "## 提车日期\n\n等于交车管理中该车辆运维 E 签宝签字完成的时间。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-deposit",
+ "title": "保证金",
+ "markdown": "## 保证金\n\n与创建合同时的保证金、提车应收款中的保证金等同;提车应收款有保证金时显示。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-contract-rent",
+ "title": "合同标的租金",
+ "markdown": "## 合同标的租金\n\n等于该车辆在签订该合同时合同中的租金(月租金标准)。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-receivable-rent",
+ "title": "应收租金",
+ "markdown": "## 应收租金\n\n按合同付款周期(1/3/6/12 个月)计算的单期账单租金总金额 = 合同标的租金 × 付款周期月数。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-monthly-income",
+ "title": "月度收入",
+ "markdown": "## 月度收入\n\n租金 + 服务费 − 里程减免金额 − 其他减免金额(后两项为减项)。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-monthly-rent",
+ "title": "月度租金",
+ "markdown": "## 月度租金\n\n根据客户付款周期间隔,计算每个月需收取的租金,等于合同标的租金。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-maintenance-income",
+ "title": "维保包干收入",
+ "markdown": "## 维保包干收入\n\n合同勾选维保包干时:单价(元/km)× 里程数,必填;未勾选时默认 0 且不可编辑。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-insurance-surcharge",
+ "title": "保险上浮费",
+ "markdown": "## 保险上浮费\n\n事故中有保险上浮费时,最新账单显示警示标志;关联事故单后自动汇总上浮费,支持多选。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-ops-income",
+ "title": "运维费(收入侧)",
+ "markdown": "## 运维费(收入侧)\n\n维修/保养台账为「客户承担」时,最新一期账单显示警示图标,提醒关联维修/保养记录;关联后自动汇总运维费,支持多选。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-other-income",
+ "title": "其他收入",
+ "markdown": "## 其他收入\n\n手工填写,预留作为特殊情况记录使用。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-received-amount",
+ "title": "实收金额",
+ "markdown": "## 实收金额\n\n根据财务导入收款明细归集;支持关联多条并分配计入金额;超出应收部分在单据下自行分配。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-unreceived",
+ "title": "未收金额",
+ "markdown": "## 未收金额\n\n未收金额 = 应收合计 − 实收金额,系统自动计算,不可手工录入。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-invoice-date",
+ "title": "开票日期",
+ "markdown": "## 开票日期\n\n账单开票后由财务维护,自动反写,只读。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-actual-payment",
+ "title": "实际付款日期",
+ "markdown": "## 实际付款日期\n\n关联收款记录时取对应到账记录的付款日期;未关联时为空。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-payment-method",
+ "title": "付款方式",
+ "markdown": "## 付款方式\n\n由合同付款周期决定:1 个月→月度预付,3 个月→季度预付,6 个月→半年预付,12 个月→年度预付。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-vehicle-standard-cost",
+ "title": "车辆标准成本",
+ "markdown": "## 车辆标准成本\n\n固定成本,按车型明细维护,只读。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-vehicle-actual-cost",
+ "title": "车辆实际成本",
+ "markdown": "## 车辆实际成本\n\n车辆标准成本 ×(实际天数 / 30),自动计算。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-insurance-fee",
+ "title": "保险费",
+ "markdown": "## 保险费\n\n暂保留,不可编辑和导入;后续可能自动计算。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-hydrogen-fee",
+ "title": "氢费",
+ "markdown": "## 氢费\n\n表头提示:仅显示合同氢费承担方式为「我司承担」时,该账单期内氢费总和(含未对账/已对账)。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-ops-cost",
+ "title": "运维费(成本侧)",
+ "markdown": "## 运维费(成本侧)\n\n与收入侧运维费平进平出,自动同步。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-brokerage-fee",
+ "title": "居间费",
+ "markdown": "## 居间费\n\n手动输入。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-other-cost",
+ "title": "其他(成本侧)",
+ "markdown": "## 其他(成本侧)\n\n前方其他收入有填写时,此处为必填项;与收入侧其他费用区分。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-gps-mileage",
+ "title": "GPS里程",
+ "markdown": "## GPS里程\n\n从 BI 里程取账单时间段的数据,自动展示。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-deduction-detail",
+ "title": "其他减免金额明细内容",
+ "markdown": "## 其他减免金额明细内容\n\n手动输入,备注明细内容。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-asset-ownership",
+ "title": "资产归属",
+ "markdown": "## 资产归属\n\n显示车辆实际归属公司名称,按车辆档案自动获取。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-signing-company",
+ "title": "签约公司",
+ "markdown": "## 签约公司\n\n租赁合同甲方,签订合同时自动获取;用于租赁总账利润归集。\n\n---"
+ },
+ {
+ "type": "markdown",
+ "id": "lbl-doc-lbl-col-remark",
+ "title": "备注",
+ "markdown": "## 备注\n\n手动输入。\n\n---"
+ }
+ ]
}
]
}
@@ -248,91 +475,822 @@
},
"nodes": [
{
- "id": "h2-site-filter",
+ "id": "lbl-filter",
"index": 1,
"title": "筛选条件",
- "pageId": "site-info",
+ "pageId": "list",
"locator": {
"selectors": [
- ".h2-station-page .vm-filter-card",
- "[data-annotation-id=\"h2-site-filter\"]"
+ "[data-annotation-id=\"lbl-filter\"]"
],
- "fingerprint": "filter|site-info",
+ "fingerprint": "lbl-filter",
"path": []
},
- "aiPrompt": "筛选字段与查询/重置行为。",
- "annotationText": "## 2. 筛选区\n\n与租赁合同等模块一致的 **vm 筛选卡片** 布局:标题「筛选条件」+ 4 列栅格(标签在上、控件在下);底部分隔线 + 右对齐「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 加氢站名称 | 输入框 | 模糊匹配,支持回车查询 |\n| 是否签约 | 下拉 | 签约站点 / 普通站点 |\n| 地区(省-市) | 级联 | 省、市两级,可只选省 |\n| 营业状态 | 下拉 | 营业中 / 暂停营业 / 停止营业 |\n\n**交互**:\n\n- **查询**:写入「已应用筛选」,列表与 KPI 统计联动,回到第 1 页\n- **重置**:清空筛选条件并刷新列表\n\n---",
+ "aiPrompt": "年份、月份、部门、业务员等筛选。",
+ "annotationText": "## 2. 筛选区\n\n默认展示 4 项(一行),点击「更多筛选」展开全部 8 项。\n\n| 筛选项 | 说明 |\n|---|---|\n| 年份 / 月份 | 账单所属年月 |\n| 业务部门 / 业务员 | 精确匹配 |\n| 车牌号码 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 明细状态 | 草稿 / 已确认 / 部分收款 / 已收款 |\n| 付款日期 | 日期区间 |\n\n---",
"hasMarkdown": true,
"color": "#2563eb",
"images": [],
- "createdAt": 1782912426507,
- "updatedAt": 1782912426507,
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000,
"controls": []
},
{
- "id": "h2-site-kpi",
+ "id": "lbl-kpi",
"index": 2,
- "title": "KPI 快捷筛选",
- "pageId": "site-info",
+ "title": "KPI 统计",
+ "pageId": "list",
"locator": {
"selectors": [
- ".h2-station-page .vm-kpi-row",
- "[data-annotation-id=\"h2-site-kpi\"]"
+ "[data-annotation-id=\"lbl-kpi\"]"
],
- "fingerprint": "kpi|site-info",
+ "fingerprint": "lbl-kpi",
"path": []
},
- "aiPrompt": "六张 KPI 卡片口径与点击筛选。",
- "annotationText": "## 3. KPI 统计卡片\n\n与租赁合同一致的 **vm-kpi-card**:左侧图标 + 标题 + 数值,右上角 `?` 悬停说明,选中态绿色描边。\n\n| 卡片 | 口径 |\n|---|---|\n| 全部加氢站 | 列表全部站点 |\n| 签约站点 | 已签约,点击快捷筛选 |\n| 普通站点 | 未签约,点击快捷筛选 |\n| 高频加氢站 | 该站加氢次数 ≥ 3 |\n| 低频加氢站 | 有加氢但次数 < 3 |\n| 无加氢站点 | 加氢次数 = 0 |\n\n加氢次数来自「车辆氢费明细」共享 Store,按站点名称聚合。KPI 与筛选区签约条件互斥切换(点 KPI 分类会清除签约筛选,点签约卡会重置 KPI 为全部)。\n\n---",
+ "aiPrompt": "六张统计卡片口径。",
+ "annotationText": "## 3. KPI 统计卡片(6 张)\n\n台账条数、应收总计、实收总计、未收总计、成本总计、盈亏总计。\n\n- **台账条数**:员工仅统计本人录入;主管统计全组。点击切换列表为显示全部。\n- **未收总计**:点击筛选下方列表为未收款数据。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000,
+ "controls": []
+ },
+ {
+ "id": "lbl-kpi-count",
+ "index": 3,
+ "title": "台账条数",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-kpi-count\"]"
+ ],
+ "fingerprint": "lbl-kpi-count",
+ "path": []
+ },
+ "aiPrompt": "台账条数 KPI 点击切换列表。",
+ "annotationText": "## 台账条数 KPI\n\n业务服务组员工只能看到自己录入的数据条数以及金额;业务服务组主管可以查看所有业务服务组人员录入的数据总和。点击可切换列表显示全部数据。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-kpi-unreceived",
+ "index": 4,
+ "title": "未收总计",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-kpi-unreceived\"]"
+ ],
+ "fingerprint": "lbl-kpi-unreceived",
+ "path": []
+ },
+ "aiPrompt": "未收总计 KPI 筛选未收款。",
+ "annotationText": "## 未收总计 KPI\n\n点击未收总计卡片,下方列表仅展示未收款数据(状态不等于已收款)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-toolbar",
+ "index": 5,
+ "title": "列表工具栏",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-toolbar\"]"
+ ],
+ "fingerprint": "lbl-toolbar",
+ "path": []
+ },
+ "aiPrompt": "导入、导出、标记确认、提交审批。",
+ "annotationText": "## 4. 列表工具栏\n\n左侧「仅显示未收款数据」勾选后,列表仅显示状态不等于已收款的记录。\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板并上传 Excel(原型 Toast) |\n| 导出 | 导出当前筛选结果 |\n| 标记为已确认 | 勾选草稿行后二次确认 |\n| 提交收款审批 | 勾选行提交审批 |\n\n添加行已移至表格底部;列表采用下拉加载更多,不做分页。\n\n---",
+ "hasMarkdown": true,
+ "color": "#0f766e",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1740787200000,
+ "controls": []
+ },
+ {
+ "id": "lbl-btn-mark-confirmed",
+ "index": 6,
+ "title": "标记为已确认",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-btn-mark-confirmed\"]"
+ ],
+ "fingerprint": "lbl-btn-mark-confirmed",
+ "path": []
+ },
+ "aiPrompt": "草稿批量确认与权限收口。",
+ "annotationText": "## 标记为已确认\n\n勾选草稿记录后点击,弹出二次确认:「确认后的数据只有主管才能继续进行修改,是否确认数据准确无误?」确认后状态变更为已确认,业务服务组人员不可再删改。\n\n---",
+ "hasMarkdown": true,
+ "color": "#0f766e",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-btn-add-row",
+ "index": 7,
+ "title": "添加行",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-btn-add-row\"]"
+ ],
+ "fingerprint": "lbl-btn-add-row",
+ "path": []
+ },
+ "aiPrompt": "表格底部新增行。",
+ "annotationText": "## 添加行\n\n位于表格底部;列表不做分页,采用下拉加载更多,避免多选受分页限制。\n\n---",
+ "hasMarkdown": true,
+ "color": "#0f766e",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-table",
+ "index": 8,
+ "title": "租赁台账宽表",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-table\"]"
+ ],
+ "fingerprint": "lbl-table",
+ "path": []
+ },
+ "aiPrompt": "运营明细台账列表与行操作。",
+ "annotationText": "## 5. 列表字段\n\n对齐 Excel「租赁车辆收入明细表」全量字段:年份、月份、业务部门(含业务员)、付款日期、车牌(含车型)、客户(含项目)、增值服务、享免政策、提车/合同/起止日期、平均天数、押金、合同标的租金、应收与收入拆分、减免、实收/未收、开票与付款信息、成本拆分、盈亏、GPS里程、减免明细、资产归属、签约公司、备注。\n\n宽表支持横向滚动;序号与操作列固定。",
"hasMarkdown": true,
"color": "#2563eb",
"images": [],
- "createdAt": 1782912426507,
- "updatedAt": 1782912426507,
+ "createdAt": 1740787200000,
+ "updatedAt": 1782700000000,
"controls": []
},
{
- "id": "h2-site-statement",
- "index": 3,
- "title": "工具栏与对账",
- "pageId": "site-info",
+ "id": "lbl-col-status",
+ "index": 9,
+ "title": "状态流转",
+ "pageId": "list",
"locator": {
"selectors": [
- ".h2-station-page .vm-table-toolbar",
- "[data-annotation-id=\"h2-site-toolbar\"]"
+ "[data-annotation-id=\"lbl-col-status\"]"
],
- "fingerprint": "toolbar|statement",
+ "fingerprint": "lbl-col-status",
"path": []
},
- "aiPrompt": "工具栏操作与对账单氢费联动。",
- "annotationText": "## 4. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 全站余额提醒设置 | 配置全局预付余额预警阈值 |\n| 批量导入 | 下载模板 → 上传 CSV/Excel → 预览确认导入 |\n| 发起充值单 | 为站点发起预付充值流程(原型演示) |\n| 新建站点 | 进入同页内嵌新建视图 |\n\n---\n\n---\n\n## 6. 数据来源\n\n- 氢费/加氢明细:统一来自 `src/common/h2VehicleLedgerBridge.js`,与「车辆氢费明细」共享 Store\n- 先打开「车辆氢费明细」时台账全量同步;仅打开加氢站模块时使用 canonical seed\n- 对账单提交经 `H2_STATION_STATEMENT_LEDGER_UPDATES` 回写车辆氢费明细\n\n---",
+ "aiPrompt": "状态流转字段说明。",
+ "annotationText": "## 状态流转\n\n1. 新增/导入 → **草稿**(业务服务组可删改)\n2. 多选 + 「标记为已确认」→ **已确认**(仅主管可改)\n3. 已确认行可「关联收款记录」;到账合计 < 应收 → **部分收款**;≥ 应收 → **已收款**\n\n---",
"hasMarkdown": true,
- "color": "#d97706",
+ "color": "#64748b",
"images": [],
- "createdAt": 1782912426507,
- "updatedAt": 1782912426507,
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
"controls": []
},
{
- "id": "h2-site-table",
- "index": 4,
- "title": "站点列表",
- "pageId": "site-info",
+ "id": "lbl-col-dept",
+ "index": 10,
+ "title": "业务部门",
+ "pageId": "list",
"locator": {
"selectors": [
- ".h2-station-page .vm-table-card",
- "[data-annotation-id=\"h2-site-table\"]"
+ "[data-annotation-id=\"lbl-col-dept\"]"
],
- "fingerprint": "table|site-info",
+ "fingerprint": "lbl-col-dept",
"path": []
},
- "aiPrompt": "台账表格列、下钻与行内操作。",
- "annotationText": "## 5. 列表字段说明\n\n| 字段 | 说明 |\n|---|---|\n| 加氢站名称 | 含签约标签;下一行展示省市区 + 详细地址 |\n| 合作时间 | 未签约可空 |\n| 营业状态 / 营业时间 | 列表只读;在「更多 → 营业状态」维护 |\n| 当前成本价格 | 元/kg;受价格配置生效时间驱动;多价时展示警示 |\n| 加氢次数 / 加氢量 | 加氢量可点击下钻;表头支持排序 |\n| 预付余额 | 可点击下钻流水;负值标红并显示「已欠费」 |\n| 联系方式 | 联系人 + 电话 |\n| 操作 | 查看 + 更多菜单 |\n\n**更多菜单**:编辑、删除、营业状态、价格配置、生成对账单、查看对账记录等(按状态与权限显隐)。\n\n**分页**:默认 10 条,可选 5/10/20/50。\n\n---",
+ "aiPrompt": "业务部门字段说明。",
+ "annotationText": "## 业务部门列\n\n业务部门与业务员合并为一列:上方业务部门,下方业务员。\n\n---",
"hasMarkdown": true,
- "color": "#32a06e",
+ "color": "#64748b",
"images": [],
- "createdAt": 1782912426507,
- "updatedAt": 1782912426507,
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-plate",
+ "index": 11,
+ "title": "车牌号码",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-plate\"]"
+ ],
+ "fingerprint": "lbl-col-plate",
+ "path": []
+ },
+ "aiPrompt": "车牌号码字段说明。",
+ "annotationText": "## 车牌号码列\n\n车牌与车型合并为一列:上方车牌号码,下方品牌与型号;导入/新增时按车牌在系统中自动匹配。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-customer",
+ "index": 12,
+ "title": "客户名称",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-customer\"]"
+ ],
+ "fingerprint": "lbl-col-customer",
+ "path": []
+ },
+ "aiPrompt": "客户名称字段说明。",
+ "annotationText": "## 客户名称列\n\n客户名称与项目名称合并为一列:上方客户名称,下方项目名称;按账单日期与车牌号自动匹配。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-pickup-date",
+ "index": 13,
+ "title": "提车日期",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-pickup-date\"]"
+ ],
+ "fingerprint": "lbl-col-pickup-date",
+ "path": []
+ },
+ "aiPrompt": "提车日期字段说明。",
+ "annotationText": "## 提车日期\n\n等于交车管理中该车辆运维 E 签宝签字完成的时间。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-deposit",
+ "index": 14,
+ "title": "保证金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-deposit\"]"
+ ],
+ "fingerprint": "lbl-col-deposit",
+ "path": []
+ },
+ "aiPrompt": "保证金字段说明。",
+ "annotationText": "## 保证金\n\n与创建合同时的保证金、提车应收款中的保证金等同;提车应收款有保证金时显示。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-contract-rent",
+ "index": 15,
+ "title": "合同标的租金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-contract-rent\"]"
+ ],
+ "fingerprint": "lbl-col-contract-rent",
+ "path": []
+ },
+ "aiPrompt": "合同标的租金字段说明。",
+ "annotationText": "## 合同标的租金\n\n等于该车辆在签订该合同时合同中的租金(月租金标准)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-receivable-rent",
+ "index": 16,
+ "title": "应收租金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-receivable-rent\"]"
+ ],
+ "fingerprint": "lbl-col-receivable-rent",
+ "path": []
+ },
+ "aiPrompt": "应收租金字段说明。",
+ "annotationText": "## 应收租金\n\n按合同付款周期(1/3/6/12 个月)计算的单期账单租金总金额 = 合同标的租金 × 付款周期月数。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-monthly-income",
+ "index": 17,
+ "title": "月度收入",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-monthly-income\"]"
+ ],
+ "fingerprint": "lbl-col-monthly-income",
+ "path": []
+ },
+ "aiPrompt": "月度收入字段说明。",
+ "annotationText": "## 月度收入\n\n租金 + 服务费 − 里程减免金额 − 其他减免金额(后两项为减项)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-monthly-rent",
+ "index": 18,
+ "title": "月度租金",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-monthly-rent\"]"
+ ],
+ "fingerprint": "lbl-col-monthly-rent",
+ "path": []
+ },
+ "aiPrompt": "月度租金字段说明。",
+ "annotationText": "## 月度租金\n\n根据客户付款周期间隔,计算每个月需收取的租金,等于合同标的租金。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-maintenance-income",
+ "index": 19,
+ "title": "维保包干收入",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-maintenance-income\"]"
+ ],
+ "fingerprint": "lbl-col-maintenance-income",
+ "path": []
+ },
+ "aiPrompt": "维保包干收入字段说明。",
+ "annotationText": "## 维保包干收入\n\n合同勾选维保包干时:单价(元/km)× 里程数,必填;未勾选时默认 0 且不可编辑。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-insurance-surcharge",
+ "index": 20,
+ "title": "保险上浮费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-insurance-surcharge\"]"
+ ],
+ "fingerprint": "lbl-col-insurance-surcharge",
+ "path": []
+ },
+ "aiPrompt": "保险上浮费字段说明。",
+ "annotationText": "## 保险上浮费\n\n事故中有保险上浮费时,最新账单显示警示标志;关联事故单后自动汇总上浮费,支持多选。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-ops-income",
+ "index": 21,
+ "title": "运维费(收入侧)",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-ops-income\"]"
+ ],
+ "fingerprint": "lbl-col-ops-income",
+ "path": []
+ },
+ "aiPrompt": "运维费(收入侧)字段说明。",
+ "annotationText": "## 运维费(收入侧)\n\n维修/保养台账为「客户承担」时,最新一期账单显示警示图标,提醒关联维修/保养记录;关联后自动汇总运维费,支持多选。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-other-income",
+ "index": 22,
+ "title": "其他收入",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-other-income\"]"
+ ],
+ "fingerprint": "lbl-col-other-income",
+ "path": []
+ },
+ "aiPrompt": "其他收入字段说明。",
+ "annotationText": "## 其他收入\n\n手工填写,预留作为特殊情况记录使用。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-received-amount",
+ "index": 23,
+ "title": "实收金额",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-received-amount\"]"
+ ],
+ "fingerprint": "lbl-col-received-amount",
+ "path": []
+ },
+ "aiPrompt": "实收金额字段说明。",
+ "annotationText": "## 实收金额\n\n根据财务导入收款明细归集;支持关联多条并分配计入金额;超出应收部分在单据下自行分配。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-unreceived",
+ "index": 24,
+ "title": "未收金额",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-unreceived\"]"
+ ],
+ "fingerprint": "lbl-col-unreceived",
+ "path": []
+ },
+ "aiPrompt": "未收金额字段说明。",
+ "annotationText": "## 未收金额\n\n未收金额 = 应收合计 − 实收金额,系统自动计算,不可手工录入。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1782700000000,
+ "updatedAt": 1782700000000,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-invoice-date",
+ "index": 25,
+ "title": "开票日期",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-invoice-date\"]"
+ ],
+ "fingerprint": "lbl-col-invoice-date",
+ "path": []
+ },
+ "aiPrompt": "开票日期字段说明。",
+ "annotationText": "## 开票日期\n\n账单开票后由财务维护,自动反写,只读。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-actual-payment",
+ "index": 26,
+ "title": "实际付款日期",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-actual-payment\"]"
+ ],
+ "fingerprint": "lbl-col-actual-payment",
+ "path": []
+ },
+ "aiPrompt": "实际付款日期字段说明。",
+ "annotationText": "## 实际付款日期\n\n关联收款记录时取对应到账记录的付款日期;未关联时为空。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-payment-method",
+ "index": 27,
+ "title": "付款方式",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-payment-method\"]"
+ ],
+ "fingerprint": "lbl-col-payment-method",
+ "path": []
+ },
+ "aiPrompt": "付款方式字段说明。",
+ "annotationText": "## 付款方式\n\n由合同付款周期决定:1 个月→月度预付,3 个月→季度预付,6 个月→半年预付,12 个月→年度预付。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-vehicle-standard-cost",
+ "index": 28,
+ "title": "车辆标准成本",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-vehicle-standard-cost\"]"
+ ],
+ "fingerprint": "lbl-col-vehicle-standard-cost",
+ "path": []
+ },
+ "aiPrompt": "车辆标准成本字段说明。",
+ "annotationText": "## 车辆标准成本\n\n固定成本,按车型明细维护,只读。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-vehicle-actual-cost",
+ "index": 29,
+ "title": "车辆实际成本",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-vehicle-actual-cost\"]"
+ ],
+ "fingerprint": "lbl-col-vehicle-actual-cost",
+ "path": []
+ },
+ "aiPrompt": "车辆实际成本字段说明。",
+ "annotationText": "## 车辆实际成本\n\n车辆标准成本 ×(实际天数 / 30),自动计算。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-insurance-fee",
+ "index": 30,
+ "title": "保险费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-insurance-fee\"]"
+ ],
+ "fingerprint": "lbl-col-insurance-fee",
+ "path": []
+ },
+ "aiPrompt": "保险费字段说明。",
+ "annotationText": "## 保险费\n\n暂保留,不可编辑和导入;后续可能自动计算。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-hydrogen-fee",
+ "index": 31,
+ "title": "氢费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-hydrogen-fee\"]"
+ ],
+ "fingerprint": "lbl-col-hydrogen-fee",
+ "path": []
+ },
+ "aiPrompt": "氢费字段说明。",
+ "annotationText": "## 氢费\n\n表头提示:仅显示合同氢费承担方式为「我司承担」时,该账单期内氢费总和(含未对账/已对账)。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-ops-cost",
+ "index": 32,
+ "title": "运维费(成本侧)",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-ops-cost\"]"
+ ],
+ "fingerprint": "lbl-col-ops-cost",
+ "path": []
+ },
+ "aiPrompt": "运维费(成本侧)字段说明。",
+ "annotationText": "## 运维费(成本侧)\n\n与收入侧运维费平进平出,自动同步。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-brokerage-fee",
+ "index": 33,
+ "title": "居间费",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-brokerage-fee\"]"
+ ],
+ "fingerprint": "lbl-col-brokerage-fee",
+ "path": []
+ },
+ "aiPrompt": "居间费字段说明。",
+ "annotationText": "## 居间费\n\n手动输入。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-other-cost",
+ "index": 34,
+ "title": "其他(成本侧)",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-other-cost\"]"
+ ],
+ "fingerprint": "lbl-col-other-cost",
+ "path": []
+ },
+ "aiPrompt": "其他(成本侧)字段说明。",
+ "annotationText": "## 其他(成本侧)\n\n前方其他收入有填写时,此处为必填项;与收入侧其他费用区分。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-gps-mileage",
+ "index": 35,
+ "title": "GPS里程",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-gps-mileage\"]"
+ ],
+ "fingerprint": "lbl-col-gps-mileage",
+ "path": []
+ },
+ "aiPrompt": "GPS里程字段说明。",
+ "annotationText": "## GPS里程\n\n从 BI 里程取账单时间段的数据,自动展示。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-deduction-detail",
+ "index": 36,
+ "title": "其他减免金额明细内容",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-deduction-detail\"]"
+ ],
+ "fingerprint": "lbl-col-deduction-detail",
+ "path": []
+ },
+ "aiPrompt": "其他减免金额明细内容字段说明。",
+ "annotationText": "## 其他减免金额明细内容\n\n手动输入,备注明细内容。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-asset-ownership",
+ "index": 37,
+ "title": "资产归属",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-asset-ownership\"]"
+ ],
+ "fingerprint": "lbl-col-asset-ownership",
+ "path": []
+ },
+ "aiPrompt": "资产归属字段说明。",
+ "annotationText": "## 资产归属\n\n显示车辆实际归属公司名称,按车辆档案自动获取。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-signing-company",
+ "index": 38,
+ "title": "签约公司",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-signing-company\"]"
+ ],
+ "fingerprint": "lbl-col-signing-company",
+ "path": []
+ },
+ "aiPrompt": "签约公司字段说明。",
+ "annotationText": "## 签约公司\n\n租赁合同甲方,签订合同时自动获取;用于租赁总账利润归集。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
+ "controls": []
+ },
+ {
+ "id": "lbl-col-remark",
+ "index": 39,
+ "title": "备注",
+ "pageId": "list",
+ "locator": {
+ "selectors": [
+ "[data-annotation-id=\"lbl-col-remark\"]"
+ ],
+ "fingerprint": "lbl-col-remark",
+ "path": []
+ },
+ "aiPrompt": "备注字段说明。",
+ "annotationText": "## 备注\n\n手动输入。\n\n---",
+ "hasMarkdown": true,
+ "color": "#64748b",
+ "images": [],
+ "createdAt": 1740787200000,
+ "updatedAt": 1782711877197,
"controls": []
}
]
diff --git a/package-lock.json b/package-lock.json
index 6bf1048..4bb4e90 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,9 @@
"version": "0.1.11",
"dependencies": {
"@axhub/annotation": "^1.0.10",
+ "docx-preview": "^0.3.7",
"lucide-react": "^0.562.0",
+ "mammoth": "^1.12.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
@@ -2585,6 +2587,15 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.13",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
+ "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
@@ -2693,6 +2704,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.10.38",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
@@ -2706,6 +2737,12 @@
"node": ">=6.0.0"
}
},
+ "node_modules/bluebird": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
+ "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==",
+ "license": "MIT"
+ },
"node_modules/browserslist": {
"version": "4.28.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
@@ -2825,6 +2862,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "license": "MIT"
+ },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -2877,6 +2920,30 @@
"node": ">=8"
}
},
+ "node_modules/dingbat-to-unicode": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz",
+ "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/docx-preview": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.7.tgz",
+ "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jszip": ">=3.0.0"
+ }
+ },
+ "node_modules/duck": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
+ "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==",
+ "license": "BSD",
+ "dependencies": {
+ "underscore": "^1.13.1"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.378",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz",
@@ -3183,12 +3250,30 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
"node_modules/is-mobile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
"integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
"license": "MIT"
},
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@@ -3294,6 +3379,27 @@
"node": ">=6"
}
},
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "license": "(MIT OR GPL-3.0-or-later)",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -3586,6 +3692,17 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lop": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz",
+ "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "duck": "^0.1.12",
+ "option": "~0.2.1",
+ "underscore": "^1.13.1"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3656,6 +3773,39 @@
"node": ">=10"
}
},
+ "node_modules/mammoth": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.12.0.tgz",
+ "integrity": "sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.6",
+ "argparse": "~1.0.3",
+ "base64-js": "^1.5.1",
+ "bluebird": "~3.4.0",
+ "dingbat-to-unicode": "^1.0.1",
+ "jszip": "^3.7.1",
+ "lop": "^0.4.2",
+ "path-is-absolute": "^1.0.0",
+ "underscore": "^1.13.1",
+ "xmlbuilder": "^10.0.0"
+ },
+ "bin": {
+ "mammoth": "bin/mammoth"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/mammoth/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
"node_modules/motion": {
"version": "12.41.0",
"resolved": "https://registry.npmjs.org/motion/-/motion-12.41.0.tgz",
@@ -3767,6 +3917,12 @@
"wrappy": "1"
}
},
+ "node_modules/option": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz",
+ "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -3787,9 +3943,17 @@
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
- "dev": true,
"license": "(MIT AND Zlib)"
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -3853,6 +4017,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "license": "MIT"
+ },
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
@@ -3905,6 +4075,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
"node_modules/rollup": {
"version": "4.62.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
@@ -3950,6 +4135,12 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -3985,6 +4176,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+ "license": "MIT"
+ },
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@@ -4017,6 +4214,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
@@ -4043,6 +4246,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/string-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
@@ -4185,6 +4397,12 @@
"node": ">=14.17"
}
},
+ "node_modules/underscore": {
+ "version": "1.13.8",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
+ "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
+ "license": "MIT"
+ },
"node_modules/undici-types": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
@@ -4224,6 +4442,12 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -5006,6 +5230,15 @@
"node": ">=0.8"
}
},
+ "node_modules/xmlbuilder": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",
+ "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index d7e59d6..f926554 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,9 @@
},
"dependencies": {
"@axhub/annotation": "^1.0.10",
+ "docx-preview": "^0.3.7",
"lucide-react": "^0.562.0",
+ "mammoth": "^1.12.0",
"xlsx": "^0.18.5"
}
}
diff --git a/scripts/sync-oneos-web-prototypes.mjs b/scripts/sync-oneos-web-prototypes.mjs
index 7052e1d..6bbe39b 100644
--- a/scripts/sync-oneos-web-prototypes.mjs
+++ b/scripts/sync-oneos-web-prototypes.mjs
@@ -23,7 +23,9 @@ const XLL_MODULES = [
/** 从 ONE-OS Web 合包拆出、由项目内手写 index 维护的独立原型 */
const STANDALONE_MODULES = [
{ slug: 'business-dept-ledger', title: '业务部台账', skipCodegen: true },
+ { slug: 'vehicle-maintenance-ledger', title: '车辆维修明细', skipCodegen: true },
{ slug: 'oneos-web-h2-station-site', title: '站点信息', skipCodegen: true },
+ { slug: 'vehicle-return-settlement', title: '还车应结款', skipCodegen: true },
];
const MODULES = [
@@ -32,12 +34,18 @@ const MODULES = [
{ slug: 'oneos-web-vehicle-asset', title: '车辆管理', files: ['车辆管理.jsx', '车辆管理-查看.jsx'] },
{ slug: 'oneos-web-contract-template', title: '合同模板管理', files: ['合同模板管理.jsx'] },
{ slug: 'oneos-web-help-center', title: '帮助中心', dir: '帮助中心' },
- { slug: 'oneos-web-finance', title: '财务管理', dir: '财务管理', excludeDirs: ['文档'] },
+ {
+ slug: 'oneos-web-finance',
+ title: '财务管理',
+ dir: '财务管理',
+ excludeDirs: ['文档'],
+ excludeFiles: ['还车应结款.jsx', '还车应结款-查看.jsx', '还车应结款-费用明细.jsx'],
+ },
{ slug: 'oneos-web-procurement', title: '采购管理', dir: '采购管理' },
{ slug: 'oneos-web-lease-contract', title: '车辆租赁合同', dir: '车辆租赁合同' },
{ slug: 'oneos-web-h2-station', title: '加氢站管理', dir: '加氢站管理', excludeDirs: ['export-tools', 'AI-加氢站站点信息-complete'], excludeFiles: ['站点信息.jsx'] },
{ slug: 'oneos-web-data-analysis', title: '数据分析', dir: '数据分析', excludeFiles: ['业务部台账.jsx'] },
- { slug: 'oneos-web-ledger-data', title: '台账数据', dir: '台账数据', excludeDirs: ['docs'] },
+ { slug: 'oneos-web-ledger-data', title: '台账数据', dir: '台账数据', excludeDirs: ['docs'], excludeFiles: ['车辆维修明细.jsx'] },
{ slug: 'oneos-web-business', title: '业务管理', dir: '业务管理', excludeDirs: ['文档', 'export-tools', 'AI-保险采购-complete'] },
{ slug: 'oneos-web-ops', title: '运维管理', dir: '运维管理' },
{ slug: 'oneos-web-requirements', title: '需求说明', dir: '需求说明', docsOnly: true },
diff --git a/skills-lock.json b/skills-lock.json
index ff044d1..45635e1 100644
--- a/skills-lock.json
+++ b/skills-lock.json
@@ -1,4 +1,11 @@
{
"version": 1,
- "skills": {}
+ "skills": {
+ "to-prd": {
+ "source": "mattpocock/skills",
+ "sourceType": "github",
+ "skillPath": "skills/engineering/to-prd/SKILL.md",
+ "computedHash": "5a733e19d825f22e83de52e68b8840c9fd2fa7501282277a122c1aa28a922059"
+ }
+ }
}
diff --git a/src/common/TablePagination.tsx b/src/common/TablePagination.tsx
index 39eb635..25bf188 100644
--- a/src/common/TablePagination.tsx
+++ b/src/common/TablePagination.tsx
@@ -7,12 +7,17 @@ export const DEFAULT_PAGE_SIZE = 20;
/** 全局可选每页条数 */
export const PAGE_SIZE_OPTIONS = [10, 20, 50, 100] as const;
+/** 加氢站等场景的紧凑每页条数选项 */
+export const COMPACT_PAGE_SIZE_OPTIONS = [5, 10, 20, 50] as const;
+
export interface TablePaginationProps {
page: number;
pageSize: number;
total: number;
onPageChange: (page: number) => void;
onPageSizeChange: (size: number) => void;
+ /** 每页条数下拉选项,默认 PAGE_SIZE_OPTIONS */
+ pageSizeOptions?: readonly number[];
}
function buildPageItems(current: number, totalPages: number): Array {
@@ -40,66 +45,74 @@ export function TablePagination({
total,
onPageChange,
onPageSizeChange,
+ pageSizeOptions = PAGE_SIZE_OPTIONS,
}: TablePaginationProps) {
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const pageItems = useMemo(() => buildPageItems(page, totalPages), [page, totalPages]);
-
- if (total === 0) return null;
+ const sizeOptions = pageSizeOptions.length > 0 ? pageSizeOptions : PAGE_SIZE_OPTIONS;
return (
-
+
+ 共 {total} 条
+
-
-
-
- {pageItems.map((item, index) => (
- item === 'ellipsis' ? (
-
…
- ) : (
+ {total > 0 && (
+ <>
+
- )
- ))}
-
-
+ {pageItems.map((item, index) => (
+ item === 'ellipsis' ? (
+
…
+ ) : (
+
+ )
+ ))}
+
+
+
+
+
+ >
+ )}
);
}
diff --git a/src/common/modal-detail-table.css b/src/common/modal-detail-table.css
index 21c4b85..3309a5f 100644
--- a/src/common/modal-detail-table.css
+++ b/src/common/modal-detail-table.css
@@ -76,9 +76,16 @@
.ln-modal-detail-table .ant-table-thead > tr > th.ln-modal-detail-table__money,
.ln-modal-detail-table .ant-table-tbody > tr > td.ln-modal-detail-table__money {
+ text-align: left;
font-variant-numeric: tabular-nums;
}
+.ln-modal-detail-table .ant-table-thead > tr > th.ant-table-cell-align-right,
+.ln-modal-detail-table .ant-table-tbody > tr > td.ant-table-cell-align-right,
+.ln-modal-detail-table .ant-table-summary .ant-table-cell-align-right {
+ text-align: left !important;
+}
+
/* 左侧冻结列(收款时间等) */
.ln-modal-detail-table .ant-table-cell-fix-left,
.ln-modal-detail-table .ant-table-cell-fix-start {
diff --git a/src/common/oneosWebLegacy/OneosWebLegacyShell.tsx b/src/common/oneosWebLegacy/OneosWebLegacyShell.tsx
index 7195802..51caac8 100644
--- a/src/common/oneosWebLegacy/OneosWebLegacyShell.tsx
+++ b/src/common/oneosWebLegacy/OneosWebLegacyShell.tsx
@@ -1,6 +1,6 @@
-import React, { useMemo } from 'react';
+import React, { useEffect, useMemo } from 'react';
import { ConfigProvider, Layout, Menu, Typography } from 'antd';
-import { defineHashPageRoute, useHashPage } from '../useHashPage';
+import { clearHostPrototypeRouteInfo, defineHashPageRoute, useHashPage } from '../useHashPage';
import { ensureOneosWebLegacyGlobals } from './legacyGlobals';
import '../../resources/oneos-web-legacy/styles/oneos-app.css';
@@ -28,13 +28,79 @@ const oneosTheme = {
},
};
-export function OneosWebLegacyShell({
+function OneosWebLegacyPageLayout({
+ children,
+ moduleTitle,
+ pages,
+ activePageId,
+ onSelectPage,
+}: {
+ children: React.ReactNode;
+ moduleTitle: string;
+ pages: OneosWebLegacyPage[];
+ activePageId?: string;
+ onSelectPage?: (pageId: string) => void;
+}) {
+ const showMenu = pages.length > 1 && activePageId && onSelectPage;
+
+ return (
+
+
+ {showMenu ? (
+
+ {moduleTitle}
+
+ ) : null}
+
+ {children}
+
+
+
+ );
+}
+
+function OneosWebLegacySinglePageShell({ pages }: OneosWebLegacyShellProps) {
+ const active = pages[0];
+
+ useEffect(() => {
+ clearHostPrototypeRouteInfo();
+ }, []);
+
+ if (!active) {
+ return (
+
+ 未配置可预览页面
+
+ );
+ }
+
+ const ActivePage = active.component;
+
+ return (
+
+
+
+ );
+}
+
+function OneosWebLegacyMultiPageShell({
moduleTitle,
pages,
defaultPageId,
}: OneosWebLegacyShellProps) {
- ensureOneosWebLegacyGlobals();
-
const route = useMemo(
() => defineHashPageRoute(
pages.map((page) => ({ id: page.id, title: page.title })),
@@ -54,33 +120,25 @@ export function OneosWebLegacyShell({
}
const ActivePage = active.component;
- const showMenu = pages.length > 1;
return (
-
-
- {showMenu ? (
-
- {moduleTitle}
-
- ) : null}
-
-
-
-
-
+
+
+
);
}
+
+export function OneosWebLegacyShell(props: OneosWebLegacyShellProps) {
+ ensureOneosWebLegacyGlobals();
+
+ if (props.pages.length <= 1) {
+ return ;
+ }
+
+ return ;
+}
diff --git a/src/common/vehicle-brand-model-catalog.js b/src/common/vehicle-brand-model-catalog.js
new file mode 100644
index 0000000..5edde92
--- /dev/null
+++ b/src/common/vehicle-brand-model-catalog.js
@@ -0,0 +1,67 @@
+/**
+ * 品牌·型号主数据:优先来自车辆管理种子数据,并与租赁合同演示枚举合并。
+ */
+import vehicles from '../prototypes/vehicle-management/data/vehicles.json';
+
+/** 租赁合同/条件条款演示用补充车型(车辆台账中可能尚未收录) */
+var LEASE_DEMO_EXTRA_CATALOG = [
+ { brand: '现代', models: ['18吨氢燃料电池车', '9.6米厢式货车', '帕力安牌4.5吨冷链车'] },
+ { brand: '苏龙', models: ['9.6米氢燃料电池车', '海格牌18吨双飞翼货车'] },
+ { brand: '飞驰', models: ['集卡头', '半挂车'] },
+ { brand: '跃进', models: ['4.2米冷链车', '4.5吨冷链车'] },
+ { brand: '宇通', models: ['氢燃料电池客车'] },
+];
+
+function mergeCatalogs() {
+ var map = new Map();
+ var sources = arguments;
+ for (var s = 0; s < sources.length; s++) {
+ var list = sources[s] || [];
+ list.forEach(function (item) {
+ if (!item || !item.brand) return;
+ if (!map.has(item.brand)) map.set(item.brand, new Set());
+ var models = item.models || [];
+ models.forEach(function (model) {
+ if (model) map.get(item.brand).add(model);
+ });
+ });
+ }
+ return Array.from(map.entries())
+ .map(function (entry) {
+ return {
+ brand: entry[0],
+ models: Array.from(entry[1]).sort(function (a, b) {
+ return String(a).localeCompare(String(b), 'zh-CN');
+ }),
+ };
+ })
+ .sort(function (a, b) {
+ return a.brand.localeCompare(b.brand, 'zh-CN');
+ });
+}
+
+function buildFromVehicleManagement() {
+ var map = new Map();
+ (vehicles || []).forEach(function (row) {
+ if (!row || !row.brand || !row.model) return;
+ if (!map.has(row.brand)) map.set(row.brand, new Set());
+ map.get(row.brand).add(row.model);
+ });
+ return Array.from(map.entries()).map(function (entry) {
+ return {
+ brand: entry[0],
+ models: Array.from(entry[1]).sort(function (a, b) {
+ return String(a).localeCompare(String(b), 'zh-CN');
+ }),
+ };
+ });
+}
+
+var cachedCatalog = null;
+
+export function getVehicleBrandModelCatalog() {
+ if (!cachedCatalog) {
+ cachedCatalog = mergeCatalogs(buildFromVehicleManagement(), LEASE_DEMO_EXTRA_CATALOG);
+ }
+ return cachedCatalog;
+}
diff --git a/src/common/vm-checkbox.css b/src/common/vm-checkbox.css
new file mode 100644
index 0000000..22fe1f4
--- /dev/null
+++ b/src/common/vm-checkbox.css
@@ -0,0 +1,118 @@
+/**
+ * ONE-OS vm-page 多选框 — 全局样式
+ * 规范:src/prototypes/vm-shared/DESIGN.md
+ */
+
+.vm-checkbox,
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"] {
+ appearance: none;
+ -webkit-appearance: none;
+ display: inline-grid;
+ place-content: center;
+ width: 18px;
+ height: 18px;
+ margin: 0;
+ flex-shrink: 0;
+ border: 1.5px solid var(--ln-hairline-strong);
+ border-radius: 6px;
+ background-color: var(--ln-surface-card);
+ background-repeat: no-repeat;
+ background-position: center;
+ box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.05);
+ cursor: pointer;
+ vertical-align: middle;
+ transition:
+ border-color 0.18s ease,
+ background-color 0.18s ease,
+ box-shadow 0.18s ease,
+ transform 0.14s cubic-bezier(0.34, 1.3, 0.64, 1);
+}
+
+.vm-checkbox:hover:not(:disabled),
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:hover:not(:disabled) {
+ border-color: color-mix(in srgb, var(--ln-primary) 55%, var(--ln-hairline-strong));
+ box-shadow:
+ inset 0 1px 2px rgba(15, 23, 42, 0.04),
+ 0 0 0 3px color-mix(in srgb, var(--ln-primary) 14%, transparent);
+}
+
+.vm-checkbox:active:not(:disabled),
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:active:not(:disabled) {
+ transform: scale(0.92);
+}
+
+.vm-checkbox:checked,
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:checked {
+ border-color: var(--ln-primary-focus);
+ background-color: var(--ln-primary);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.75 8.25 6.5 11 12.25 5'/%3E%3C/svg%3E");
+ background-size: 13px 13px;
+ box-shadow:
+ 0 1px 2px color-mix(in srgb, var(--ln-primary) 28%, transparent),
+ inset 0 1px 0 rgba(255, 255, 255, 0.18);
+}
+
+.vm-checkbox:indeterminate,
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:indeterminate {
+ border-color: var(--ln-primary-focus);
+ background-color: var(--ln-primary);
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-width='2.25' stroke-linecap='round'%3E%3Cpath d='M4.25 8h7.5'/%3E%3C/svg%3E");
+ background-size: 13px 13px;
+ box-shadow:
+ 0 1px 2px color-mix(in srgb, var(--ln-primary) 28%, transparent),
+ inset 0 1px 0 rgba(255, 255, 255, 0.18);
+}
+
+.vm-checkbox:focus-visible,
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:focus-visible {
+ outline: none;
+ box-shadow:
+ inset 0 1px 2px rgba(15, 23, 42, 0.04),
+ 0 0 0 3px color-mix(in srgb, var(--ln-primary) 28%, transparent);
+}
+
+.vm-checkbox:checked:focus-visible,
+.vm-checkbox:indeterminate:focus-visible,
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:checked:focus-visible,
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:indeterminate:focus-visible {
+ box-shadow:
+ 0 1px 2px color-mix(in srgb, var(--ln-primary) 28%, transparent),
+ 0 0 0 3px color-mix(in srgb, var(--ln-primary) 28%, transparent);
+}
+
+.vm-checkbox:disabled,
+.vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
+/* 表格勾选列 */
+.vm-col-check,
+.ldb-col-check,
+.cm-col-check {
+ width: 48px;
+ text-align: center;
+ vertical-align: middle;
+}
+
+.vm-col-check .vm-checkbox,
+.ldb-col-check .vm-checkbox,
+.cm-col-check .vm-checkbox,
+.vm-col-check > input[type="checkbox"],
+.ldb-col-check > input[type="checkbox"],
+.cm-col-check > input[type="checkbox"] {
+ display: inline-grid;
+}
+
+
+@media (prefers-reduced-motion: reduce) {
+ .vm-checkbox,
+ .vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"] {
+ transition: none;
+ }
+
+ .vm-checkbox:active:not(:disabled),
+ .vm-page :is(.vm-col-check, .ldb-col-check, .cm-col-check) > input[type="checkbox"]:active:not(:disabled) {
+ transform: none;
+ }
+}
diff --git a/src/common/vm-filter-panel.ts b/src/common/vm-filter-panel.ts
new file mode 100644
index 0000000..979b35f
--- /dev/null
+++ b/src/common/vm-filter-panel.ts
@@ -0,0 +1,29 @@
+/**
+ * 列表页筛选区全局规则(与 vehicle-management/style.css 中 .vm-filter-grid 4 列布局一致)
+ *
+ * 规则:筛选项超过 1 行(默认 4 项)时,其余收入折叠区,通过「更多筛选」展开。
+ */
+export const VM_FILTER_COLUMNS_PER_ROW = 4;
+
+/** 默认首行展示的筛选项数量(1 行) */
+export const VM_FILTER_PRIMARY_VISIBLE_COUNT = VM_FILTER_COLUMNS_PER_ROW;
+
+export function splitFilterFields(
+ fields: T[],
+ primaryCount: number = VM_FILTER_PRIMARY_VISIBLE_COUNT,
+): { primary: T[]; extra: T[] } {
+ if (fields.length <= primaryCount) {
+ return { primary: fields, extra: [] };
+ }
+ return {
+ primary: fields.slice(0, primaryCount),
+ extra: fields.slice(primaryCount),
+ };
+}
+
+export function shouldShowFilterExpand(
+ fieldCount: number,
+ primaryCount: number = VM_FILTER_PRIMARY_VISIBLE_COUNT,
+): boolean {
+ return fieldCount > primaryCount;
+}
diff --git a/src/common/vm-pagination.css b/src/common/vm-pagination.css
index 244d809..35bca65 100644
--- a/src/common/vm-pagination.css
+++ b/src/common/vm-pagination.css
@@ -2,6 +2,7 @@
* ONE-OS vm-page 列表分页 — 全局样式
* 规范:src/prototypes/vm-shared/DESIGN.md
* 组件:src/common/TablePagination.tsx
+ * 元素顺序:共 x 条 → 页码 → 每页 N 条
*/
.vm-pagination {
@@ -10,7 +11,9 @@
align-items: center;
flex-wrap: wrap;
gap: 12px 16px;
- width: 100%;
+ width: auto;
+ flex: 0 1 auto;
+ margin-left: auto;
padding: 0;
}
@@ -23,6 +26,18 @@
flex-shrink: 0;
}
+.vm-pagination-total {
+ font-size: 0.875rem;
+ color: var(--ln-muted);
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.vm-pagination-total-num {
+ color: var(--ln-ink);
+ font-weight: 600;
+}
+
.vm-pagination-size-label {
white-space: nowrap;
}
@@ -65,31 +80,41 @@
color: var(--ln-ink);
font-size: 0.875rem;
font-weight: 500;
+ font-variant-numeric: tabular-nums;
cursor: pointer;
- transition: background 0.2s, border-color 0.2s, color 0.2s;
+ transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease, transform 0.12s ease;
}
.vm-pagination-nav:disabled {
- opacity: 0.45;
+ opacity: 0.4;
cursor: not-allowed;
+ background: var(--ln-canvas-soft);
}
.vm-pagination-nav:hover:not(:disabled),
.vm-pagination-page:hover:not(.active) {
background: var(--ln-canvas-soft);
- border-color: var(--ln-hairline-strong);
+ border-color: var(--ln-primary);
+ color: var(--ln-primary-focus);
+}
+
+.vm-pagination-nav:active:not(:disabled),
+.vm-pagination-page:active:not(.active) {
+ transform: scale(0.96);
}
.vm-pagination-page.active {
background: var(--ln-primary);
border-color: var(--ln-primary);
color: var(--ln-on-primary);
+ box-shadow: 0 1px 3px rgba(37, 99, 235, 0.28);
}
.vm-pagination-nav:focus-visible,
.vm-pagination-page:focus-visible {
outline: none;
border-color: var(--vm-focus-border-color);
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.18);
}
.vm-pagination-ellipsis {
@@ -100,6 +125,20 @@
height: var(--vm-control-height);
color: var(--ln-muted);
font-size: 0.875rem;
+ letter-spacing: 0.08em;
+ user-select: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .vm-pagination-nav,
+ .vm-pagination-page {
+ transition: none;
+ }
+
+ .vm-pagination-nav:active:not(:disabled),
+ .vm-pagination-page:active:not(.active) {
+ transform: none;
+ }
}
/* 详情 Tab 等紧凑场景:vm-table-footer--split vm-table-footer--compact */
@@ -136,3 +175,7 @@
height: 28px;
font-size: 0.8125rem;
}
+
+.vm-table-footer--compact .vm-pagination-total {
+ font-size: 0.8125rem;
+}
diff --git a/src/prototypes/contract-template-management/.spec/prototype-review.md b/src/prototypes/contract-template-management/.spec/prototype-review.md
index e857e72..0717768 100644
--- a/src/prototypes/contract-template-management/.spec/prototype-review.md
+++ b/src/prototypes/contract-template-management/.spec/prototype-review.md
@@ -2,53 +2,107 @@
- 审查目标:`src/prototypes/contract-template-management`
- 用户资料/参考资料:
- - 本轮用户任务:处理评审结论中 P1–P3 共 5 条问题
+ - 本轮用户任务:对「合同模板管理」原型执行 Prototype Review / 需求评审
- `src/resources/contract-template-management/PRD.md`(v1.2)
- - `src/prototypes/contract-template-management/annotation-source.json`
- - `contract-template-seed.js`、`contract-template-section-html.js`、`contract-template-catalog.js`、`contract-template-store.js`
- - 关联下游:`lease-contract-preview-build.js`、`LeaseContractCreate.jsx`
-- 生成时间:2026-06-29(复审)
+ - `src/prototypes/contract-template-management/index.tsx`、`ContractTemplate.jsx`
+ - `contract-template-store.js`、`contract-template-catalog.js`、`contract-template-section-html.js`、`contract-template-section-filter.js`、`contract-template-seed.js`
+ - 下游消费:`lease-contract-management/lease-contract-preview-build.js`、`LeaseContractCreate.jsx`
+ - `annotation-source.json`、`.spec/prototype-comments.json`
+- 生成时间:2026-07-07
## 总体点评
-上一轮评审中的 **5 条优先级问题均已落地**。合同模板管理原型现已具备:
+合同模板管理原型在**主业务链路上已基本成立**:列表筛选与嵌套章节子表、草稿/发布/启用停用、版本日志与快照查看、编辑页 9 章节 Tab、锁定/风控/条件条款工具、离开未保存确认,以及经 `contract-template-store` 向租赁合同创建贯通预览,均能在同浏览器 `localStorage` 会话内端到端演示。这与 PRD v1.2 对「Word 文档级模型 + 启用态控制 + 跨模块消费」的定位一致。
-- 租赁创建预览优先消费 store 中 `sectionContents`(静态 HTML 仅兜底)
-- 冷启动种子:直接进入租赁创建即可选用已启用模板
-- PRD / 标注与实现对齐(`contractType`、列表筛选、子表列、章节 Tab 交互)
-- `localStorage` 持久化(兼写 `sessionStorage`)
-- 移除 `isDefault` /「标准模板」残留逻辑
+当前最影响「按真实法务作业验收」的缺口集中在 **Word 导入仍为文件名驱动的 Mock**,无法证明 PRD §9 对任意 `.doc/.docx` 的拆分策略;其次是 **标注/内嵌 PRD 与 v1.2 字段口径仍有残留不一致**,以及租赁预览侧 **条件条款存在模板 HTML 与硬编码注入双轨**。权限、存储失败、正式 API 与异常分支属原型已知边界,应在研发阶段补齐,但不阻断当前演示型原型价值。
-原型可作为研发与测试的**端到端演示基线**;正式环境仍需 API 持久化、权限与异常分支。
+## P0-P3 优先级问题
-## P0-P3 优先级问题(复审)
+### P1 - Word 导入未实现真实解析与章节拆分
-| 级别 | 问题 | 状态 |
-|---|---|---|
-| P1 | 租赁预览未消费 store 章节正文 | **已修复** — `getContractTemplateBaseHtml` 优先 `mergeMonolithicHtml(doc.sectionContents)` |
-| P1 | PRD / 标注与实现字段口径不一致 | **已修复** — PRD v1.2 + `build-annotation-source.mjs` 同步 |
-| P1 | 冷启动租赁创建无模板 | **已修复** — `catalog.js` 调用 `ensureContractTemplateStore(buildInitialWordTemplates)` |
-| P2 | 仅 sessionStorage 持久化 | **已缓解** — store 读写 `localStorage` 优先 |
-| P3 | `isDefault` / 标准模板残留 | **已修复** — 种子、store 默认逻辑、编辑区文案已清理 |
+- 证据:`ContractTemplate.jsx` 中 `handleImportWord` 仅调用 `mockWordBlocks(file.name, categoryFilter)`,按文件名关键字返回内置 `v266LeaseBlocks` / 试用合同块或通用占位块,未读取上传文件内容,也未调用 PRD §9 所述 `sanitizeWordExportHtml` + 标记切分流程。
+- 影响:法务无法在本原型中验证「上传任意 Word → 自动拆成 9 章」这一核心维护路径;验收标准 §16.2「导入 Word 后 9 章节正确拆分」仅对少数种子文件名成立,与 PRD 表述的业务成立条件存在明显差距。
+- 修复方向:原型层至少接入 `.docx → HTML` 解析(或明确标注为演示 Mock 并下调 PRD 验收项);研发按 §9.2–§9.4 实现服务端/前端拆分,并在导入失败时给出可恢复提示(重试、保留原稿、查看错误原因)。
+
+### P2 - 标注与内嵌 PRD 仍使用「合同名称」,与 v1.2「合同类型」冲突
+
+- 证据:`annotation-source.json` 列表筛选节点 `ct-list-filter` 的 `annotationText` 与 `markdownMap.ct-list-filter` 仍写「合同名称 / 选项来自模板名称」;而 `PRD.md` v1.2 §7.2、§7.4 与实现中筛选字段 `keyword`、列表列 `contractType`、编辑区「合同类型」均已统一为 **合同类型**。
+- 影响:评审、测试与业务方按标注操作时会对筛选对象产生误解,造成需求口径分裂;与 PRD §16.4「标注与 PRD 一致」未完全满足。
+- 修复方向:运行或更新 `scripts/build-annotation-source.mjs`,将筛选说明改为「合同类型(可搜索下拉,精确匹配 `contractType`)」;同步目录内嵌 PRD 片段版本号至 v1.2。
+
+### P2 - 租赁预览条件条款存在「模板 HTML」与硬编码注入双轨
+
+- 证据:`lease-contract-preview-build.js` 在读取 store 合并 HTML 后仍调用 `injectPrototypeVehicleClauses`;该函数在 `lease-contract-vehicle-clause.js` 中按 `PROTOTYPE_VEHICLE_CLAUSE_DEFS` 与固定 `paragraphMarker` 包裹段落,注释写明「正式环境由模板管理产出」。模板管理侧条件条款则通过编辑区「条件」写入 `data-vehicle-clause` / `data-vehicle-bindings`。
+- 影响:法务在模板管理中新设或调整条件条款后,租赁预览对标准 V26.6 类合同可能仍部分依赖硬编码注入点;与 PRD「创建合同时按订单车辆过滤条件条款」的单一数据源目标不完全一致,增加联调与回归风险。
+- 修复方向:租赁预览改为**仅**解析模板 HTML 中的条件条款标记;硬编码注入降级为种子数据初始化脚本或 feature flag,并在跨模块验收用例中覆盖「仅改模板、不改租赁代码」场景。
+
+### P2 - 车型主数据未与车辆管理模块打通
+
+- 证据:PRD §11 要求品牌·型号与车辆管理一致;实现中 `ContractTemplate.jsx` / `VehicleBrandModelPicker.jsx` 使用 `LEASE_VEHICLE_BRAND_MODEL_CATALOG`(`lease-order-vars.js` 内置枚举),未读取 `vehicle-management` 主数据或接口。
+- 影响:章节「适用车型」绑定与预览模拟车型的选项集可能与正式车辆台账漂移;绑定存的是字符串,正式环境若车型更名会导致历史模板命中失败。
+- 修复方向:原型可保留内置枚举但需在 PRD/标注标明;研发对接车辆主数据 API,并约定品牌型号唯一键与变更策略。
+
+### P3 - 权限边界与存储异常未在原型中表达
+
+- 证据:PRD §13 建议法务可编辑/发布、客服只读等角色能力;原型无角色切换或操作拦截。`contract-template-store.js` 的 `writeToStorage` 在 `localStorage` 失败时静默忽略,无用户提示或降级策略。
+- 影响:无法演示「启用中不可编辑」之外的组织级权限;极端环境下用户以为已保存但实际未持久化,造成跨页面数据丢失误解。
+- 修复方向:原型可增加「法务 / 客服只读」演示开关;存储失败时 `message.error` 并阻止离开编辑页;正式环境对接权限服务与后端持久化。
## 完整性与项目对齐
-| 维度 | 评估 |
+| 维度 | 结论 |
|---|---|
-| 跨模块正文 | 模板管理编辑 ↔ 租赁创建预览:**已贯通**(同会话 / localStorage 内) |
-| 冷启动 | 无需先打开模板管理:**已支持** |
-| 资料一致性 | PRD、标注、列表筛选/列、子表、§8.3 Tab:**已对齐** |
-| 正式环境差距 | API 持久化、角色权限、Word 解析失败等异常分支仍待研发 |
+| 核心用户 | 法务为主、客服/风控间接用户 — 与 PRD §2 一致 |
+| 主流程 | 列表 → 新增/编辑 → 保存/发布 → 启用/停用 → 租赁创建选用 — **可演示** |
+| 入口/出口 | `index.tsx` + `ContractTemplate.jsx` 列表/编辑双视图;`navigationRef` 与标注目录可切换 — 对齐 |
+| 范围边界 | 仅 `lease` 分类业务闭环;`purchase`/`service` 代码预留未暴露 — 符合非目标 |
+| 跨模块 | `contract-template-catalog.js` 冷启动种子、`getContractTemplateBaseHtml` 优先 store — 与 PRD §12、§16.3 对齐 |
+| 资料冲突 | **待确认**:`annotation-source.json` 筛选字段仍称「合同名称」,与 PRD v1.2 / 源码「合同类型」冲突(见 P2) |
+| 项目 metadata | 路由 `/prototypes/contract-template-management`;与 `lease-contract-management` PRD 关联描述一致 |
-## 验收建议(请用户在预览中确认)
+**已覆盖且与 PRD 一致的主要能力(摘要)**:嵌套列表 9 章节子表及风控/条件条款计数;启用中锁定编辑删除、停用二次确认;发布递增版本号、已发布再发布写 `versionLogs` 且默认停用;合同主体非空与合同类型校验;未保存离开确认;版本快照只读查看;附件章节车型绑定与租赁侧 `applyAttachmentSectionVehicleFilter` 联动。
-1. **租赁预览贯通**:在模板管理编辑某章节并保存 → 打开租赁创建 → 选用同一模板 → 预览正文应反映修改。
-2. **冷启动**:清空站点 localStorage 后**直接**打开租赁创建 → 应能选择已启用模板(如「正式合同」「试用合同」)。
-3. **列表筛选**:列表页「合同类型」筛选与主表「合同类型」列一致。
-4. **持久化**:刷新浏览器后模板停用/发布状态应保留(localStorage)。
+## 业务逻辑连贯性
-## 修订记录
+- **状态迁移**:草稿(无版本号)→ 发布(有版本号、默认启用)→ 停用(可编辑)→ 再发布(版本递增、写快照、默认停用)— 与 PRD §6.2–§6.3 一致,源码 `handleSaveTemplate` / `toggleTemplateEnabled` 可印证。
+- **启用与下游**:`getPublishedEnabledLeaseOptions` 过滤 `published && enabled`;停用后 `LeaseContractCreate` 订阅 store 更新选项 — 连贯。
+- **版本号生成**:`generateNextVersionNo` 按 `category === lease` 下全部已发布模板取最大 minor 递增 — 符合 PRD §6.1「同分类下递增」字面规则;若业务期望「每份 Word 文件独立版本序列」,需产品另行确认(当前无冲突证据,记为待确认项)。
+- **条件条款命中**:模板编辑预览与租赁预览均遵循「订单车辆任意命中则展示」;模板管理预览未选模拟车型时隐藏条件段落,与 PRD §8.7.3 一致。
+- **数据生命周期**:`creator/updater/createTime/updateTime` 在保存时更新;删除仅草稿或已发布且停用 — 符合 §6.5。
-| 日期 | 说明 |
+## 状态、异常、边界与恢复
+
+| 类别 | 审查结论 |
|---|---|
-| 2026-06-29 | 复审:5 条评审问题全部处理完毕 |
+| 输入校验 | 保存/发布校验合同主体非空、合同类型必填;条件条款弹窗要求至少一个车型 — 已覆盖 |
+| 空状态 | 列表无数据、筛选无结果、版本日志为空、编辑区无内容预览引导 — 已覆盖 |
+| 操作确认 | 停用、删除、未保存离开 — 有 Modal 确认 |
+| 权限失败 | 未实现角色权限 — 见 P3 |
+| 导入失败 | 非真实 Word 解析,无格式错误/拆分失败分支 — 见 P1 |
+| 存储失败 | `localStorage` 写入失败静默 — 见 P3 |
+| 并发/重复点击 | 保存无 loading 锁,快速连点可能重复写入(原型风险低)— P3 |
+| 跨页面恢复 | 刷新后 `localStorage` 恢复模板;清空存储后 `ensureContractTemplateStore` 重新种子 — 可恢复 |
+| 下游一致性 | 停用模板后租赁侧选项实时收缩;预览读 store `sectionContents` — 已检查通过 |
+
+## 证据与评估说明
+
+- **用户资料优先级**:本轮无额外用户附件;以 PRD v1.2 为业务基线,与 `annotation-source.json` 冲突处已标「待确认」。
+- **读取范围**:
+ - 规范:`rules/prototype-review-guide.md`
+ - 资料:`src/resources/contract-template-management/PRD.md`
+ - 源码:`index.tsx`、`ContractTemplate.jsx`(列表/编辑/保存发布/导入/版本日志)、`contract-template-store.js`、`contract-template-catalog.js`、`contract-template-section-filter.js`
+ - 下游:`lease-contract-preview-build.js`、`lease-contract-vehicle-clause.js`、`LeaseContractCreate.jsx`
+ - 标注:`annotation-source.json`;未展开阅读 `canvas.excalidraw` 全量
+- **独立评估**:`degraded`(未使用子代理分拆「业务基线」与「原型证据」两轮独立评估,在同一审查中顺序完成)
+
+---
+
+## 修复记录(2026-07-07 后续)
+
+| 优先级 | 问题 | 状态 | 说明 |
+|---|---|---|---|
+| P1 | Word 导入 Mock | **已修复** | `contract-template-word-import.js` + mammoth 解析 `.docx`;失败回退演示模板并提示 |
+| P2 | 标注「合同名称」口径 | **已修复** | `build-annotation-source.mjs` 同步 `annotationText` / `annotationsByElementId` / `markdownMap` |
+| P2 | 租赁预览条件条款双轨 | **已修复** | `injectPrototypeVehicleClauses` 在 HTML 已有条件标记时跳过 |
+| P2 | 车型主数据未打通 | **已修复(原型)** | `src/common/vehicle-brand-model-catalog.js` 读取车辆管理种子并合并演示车型 |
+| P3 | 权限与存储异常 | **已修复(原型)** | `?role=cs` 只读演示;存储失败 `message.error` 且阻止离开编辑页 |
diff --git a/src/prototypes/contract-template-management/ContractTemplate.jsx b/src/prototypes/contract-template-management/ContractTemplate.jsx
index 03920b7..82d2af6 100644
--- a/src/prototypes/contract-template-management/ContractTemplate.jsx
+++ b/src/prototypes/contract-template-management/ContractTemplate.jsx
@@ -6,21 +6,48 @@ import {
getContractTemplates,
setContractTemplates,
subscribeContractTemplates,
+ wasLastContractTemplateWriteOk,
} from './contract-template-store.js';
import { buildInitialWordTemplates } from './contract-template-seed.js';
import {
- getContractTypeLabel,
- normalizeContractType,
- resolveContractType,
+ convertDocxArrayBufferToHtml,
+ convertLegacyDocFileToHtml,
+ isDocxArrayBuffer,
+ isImportedWordDocHtml,
+ prepareImportedWordHtml,
+ sanitizeImportedWordHtml,
+} from './contract-template-word-import.js';
+import { renderDocxPreview } from './contract-template-docx-fidelity.js';
+import {
+ getContractNameLabel,
+ getDocumentContractTypeLabel,
+ getDocumentContractTypeSelectOptions,
+ isDocumentContractTypeEnum,
+ normalizeContractName,
+ normalizeDocumentContractType,
+ resolveContractName,
+ resolveDocumentContractType,
} from './contract-template-types.js';
import { LEASE_VEHICLE_BRAND_MODEL_CATALOG } from '../lease-contract-management/lease-order-vars.js';
-import VehicleBrandModelPicker from './VehicleBrandModelPicker.jsx';
+import {
+ extractSectionTabsFromHtml,
+ getSectionTabLabel as getDynamicSectionTabLabel,
+ mergeHtmlBySectionTabs,
+ resolveSectionTabs,
+ splitHtmlBySectionTabs,
+ emptySectionMapForTabs,
+} from './contract-template-section-headings.js';
import {
vehicleBindingKey,
parseVehicleBindingKey,
vehicleMatchesBindings,
formatVehicleBindings,
} from './vehicle-binding-utils.js';
+import {
+ shouldShowFilterExpand,
+ splitFilterFields,
+ VM_FILTER_PRIMARY_VISIBLE_COUNT,
+} from '../../common/vm-filter-panel.ts';
var DEFAULT_LESSOR_COMPANIES = [
@@ -528,6 +555,9 @@ const Component = function (props) {
function prepareWordHtml(html) {
var content = String(html || '');
if (!content) return '';
+ if (isImportedWordDocHtml(content)) {
+ return prepareImportedWordHtml(content);
+ }
if (isWordFormattedDoc(content)) {
content = sanitizeWordExportHtml(content);
content = ensureV266WordStyles(content);
@@ -559,6 +589,9 @@ const Component = function (props) {
function prepareEditorHtml(html) {
var content = String(html || '');
if (!content) return '';
+ if (isImportedWordDocHtml(content)) {
+ return prepareImportedWordHtml(content);
+ }
if (isWordFormattedDoc(content)) {
content = sanitizeWordExportHtml(content);
content = ensureV266WordStyles(content);
@@ -567,8 +600,19 @@ const Component = function (props) {
return content;
}
+ function resolveWordDocShellClass(html) {
+ if (isImportedWordDocHtml(html)) return 'ct-word-doc ct-word-doc--imported';
+ if (/ct-word-doc--v266/.test(String(html || ''))) return 'ct-word-doc ct-word-doc--v266';
+ return 'ct-word-doc';
+ }
+
+ function resolveWordPageBodyClass(html, extra) {
+ return 'ct-word-a4-page__body ' + resolveWordDocShellClass(html) + ' ct-rich-editor ct-rich-editor--word' + (extra || '');
+ }
+
function ensureV266WordStyles(html) {
var content = String(html || '');
+ if (isImportedWordDocHtml(content)) return content;
if (!isWordFormattedDoc(content) || typeof document === 'undefined') return content;
var cssText = '';
@@ -600,7 +644,7 @@ const Component = function (props) {
var source = String(html || '');
if (!isWordFormattedDoc(source)) return false;
var sections = splitHtmlIntoSections(source);
- return DOCUMENT_SECTION_TABS.slice(1).some(function (tab) {
+ return getSectionTabs().slice(1).some(function (tab) {
return !!(sections[tab.key] && String(sections[tab.key]).trim());
});
}
@@ -672,8 +716,9 @@ const Component = function (props) {
var LIST_EXPAND_COL_WIDTH = 48;
function getActiveSectionTabIndex() {
- for (var i = 0; i < DOCUMENT_SECTION_TABS.length; i++) {
- if (DOCUMENT_SECTION_TABS[i].key === activeSectionKey) return i;
+ var tabs = getSectionTabs();
+ for (var i = 0; i < tabs.length; i++) {
+ if (tabs[i].key === activeSectionKey) return i;
}
return 0;
}
@@ -716,9 +761,7 @@ const Component = function (props) {
}
function emptySectionMap() {
- var map = {};
- DOCUMENT_SECTION_TABS.forEach(function (tab) { map[tab.key] = ''; });
- return map;
+ return emptySectionMapForTabs(getSectionTabs());
}
function isWordBlockNode(node) {
@@ -829,6 +872,9 @@ const Component = function (props) {
function splitHtmlIntoSections(html) {
var source = String(html || '');
if (!source.trim()) return emptySectionMap();
+ if (editorSectionTabs && editorSectionTabs.length > 1) {
+ return splitHtmlBySectionTabs(source, editorSectionTabs);
+ }
if (typeof document === 'undefined') return splitMonolithicHtml(source);
var div = document.createElement('div');
div.innerHTML = source;
@@ -847,17 +893,11 @@ const Component = function (props) {
}
function mergeMonolithicHtml(sectionMap) {
- var map = sectionMap || {};
- var out = String(map.main || '');
- DOCUMENT_SECTION_TABS.slice(1).forEach(function (tab) {
- var chunk = map[tab.key];
- if (chunk) out += String(chunk);
- });
- return out;
+ return mergeHtmlBySectionTabs(sectionMap, getSectionTabs());
}
function mergeSectionsToDocument(sectionMap) {
- return DOCUMENT_SECTION_TABS.map(function (tab) {
+ return getSectionTabs().map(function (tab) {
var content = (sectionMap && sectionMap[tab.key]) ? String(sectionMap[tab.key]) : '';
if (!content.trim()) return '';
return '';
@@ -865,8 +905,7 @@ const Component = function (props) {
}
function getSectionTabLabel(key) {
- var tab = DOCUMENT_SECTION_TABS.find(function (item) { return item.key === key; });
- return tab ? tab.label : key;
+ return getDynamicSectionTabLabel(getSectionTabs(), key);
}
var VEHICLE_BRAND_MODEL_CATALOG = LEASE_VEHICLE_BRAND_MODEL_CATALOG;
@@ -920,12 +959,31 @@ const Component = function (props) {
}
function createEmptyListFilter() {
- return { keyword: '', status: '', creator: '', enabled: '' };
+ return { keyword: '', contractType: '', status: '', creator: '', enabled: '' };
+ }
+
+ function hasExtraListFiltersActive(filters) {
+ return !!(filters && filters.enabled);
+ }
+
+ function renderFilterChevronIcon(open) {
+ return React.createElement('svg', {
+ width: 14,
+ height: 14,
+ viewBox: '0 0 24 24',
+ fill: 'none',
+ stroke: 'currentColor',
+ strokeWidth: 2,
+ strokeLinecap: 'round',
+ strokeLinejoin: 'round',
+ 'aria-hidden': true,
+ className: 'vm-filter-toggle-icon' + (open ? ' is-open' : ''),
+ }, React.createElement('path', { d: 'M6 9l6 6 6-6' }));
}
function hasActiveListFilters(filters) {
if (!filters) return false;
- return !!(filters.keyword || filters.status || filters.creator || filters.enabled);
+ return !!(filters.keyword || filters.contractType || filters.status || filters.creator || filters.enabled);
}
function filterContractNameOption(input, option) {
@@ -1008,7 +1066,8 @@ const Component = function (props) {
function createWordDocument(params) {
return Object.assign({
category: 'lease',
- contractType: '',
+ contractName: '',
+ contractType: '合同',
fileName: '未命名合同.docx',
versionName: '未命名合同',
versionNo: null,
@@ -1039,7 +1098,8 @@ const Component = function (props) {
}
function buildDocumentChapterRows(doc) {
- return DOCUMENT_SECTION_TABS.map(function (tab) {
+ var tabs = resolveSectionTabs(doc);
+ return tabs.map(function (tab) {
var html = (doc.sectionContents && doc.sectionContents[tab.key]) || '';
var bindings = getDocumentSectionBindings(doc, tab.key);
var hasContent = !!(String(html).trim());
@@ -1058,7 +1118,7 @@ const Component = function (props) {
function documentHasVehicleBindingMatch(doc, brand, model) {
if (!brand && !model) return true;
var hasBindings = false;
- var matched = DOCUMENT_SECTION_TABS.some(function (tab) {
+ var matched = resolveSectionTabs(doc).some(function (tab) {
if (!sectionAllowsVehicleBinding(tab.key)) return false;
var bindings = getDocumentSectionBindings(doc, tab.key);
if (!bindings.length) return false;
@@ -1073,8 +1133,12 @@ const Component = function (props) {
if (!filters) return true;
var keyword = String(filters.keyword || '').trim();
if (keyword) {
- var contractType = String(resolveContractType(doc) || '');
- if (contractType !== keyword) return false;
+ var contractName = String(resolveContractName(doc) || '');
+ if (contractName !== keyword) return false;
+ }
+ if (filters.contractType) {
+ var docType = resolveDocumentContractType(doc);
+ if (docType !== filters.contractType) return false;
}
if (filters.status && doc.status !== filters.status) return false;
if (filters.creator && doc.creator !== filters.creator) return false;
@@ -1195,6 +1259,11 @@ const Component = function (props) {
}, []);
function setTemplates(updater) {
setContractTemplates(updater);
+ var storageOk = wasLastContractTemplateWriteOk();
+ storageWriteFailedState[1](!storageOk);
+ if (!storageOk) {
+ message.error('本地存储失败,修改可能无法持久化,请清理浏览器缓存后重试');
+ }
}
var categoryFilterState = useState('lease');
@@ -1233,11 +1302,32 @@ const Component = function (props) {
var editingRecord = editingRecordState[0];
var setEditingRecord = editingRecordState[1];
- var editorContractTypeState = useState('');
- var editorContractType = editorContractTypeState[0];
- var setEditorContractType = editorContractTypeState[1];
+ var editorContractNameState = useState('');
+ var editorContractName = editorContractNameState[0];
+ var setEditorContractName = editorContractNameState[1];
+
+ var editorDocumentContractTypeState = useState('合同');
+ var editorDocumentContractType = editorDocumentContractTypeState[0];
+ var setEditorDocumentContractType = editorDocumentContractTypeState[1];
+
+ var editorSectionTabsState = useState(null);
+ var editorSectionTabs = editorSectionTabsState[0];
+ var setEditorSectionTabs = editorSectionTabsState[1];
+
+ function hasEditorWordImported() {
+ if (editorMode === 'edit' && editingRecord) return true;
+ return !!importChangedInSessionState[0];
+ }
+
+ function getSectionTabs() {
+ if (editorSectionTabs && editorSectionTabs.length) return editorSectionTabs;
+ if (!hasEditorWordImported()) return [];
+ if (!String(formDocumentHtmlState[0] || '').trim()) return [];
+ return DOCUMENT_SECTION_TABS;
+ }
var lastImportFileNameState = useState('');
+ var importedDocxBufferState = useState(null);
var importChangedInSessionState = useState(false);
var formBlocksState = useState([]);
var formDocumentHtmlState = useState('');
@@ -1250,6 +1340,7 @@ const Component = function (props) {
var versionLogTargetState = useState(null);
var snapshotViewState = useState(null);
var editorDirtyState = useState(false);
+ var storageWriteFailedState = useState(false);
var leaveEditorModalState = useState(false);
var editorSyncKeyState = useState(0);
var editorInsertHandlerRef = useRef(null);
@@ -1285,6 +1376,9 @@ const Component = function (props) {
var listFilterAppliedState = useState(createEmptyListFilter);
var listFilterApplied = listFilterAppliedState[0];
var setListFilterApplied = listFilterAppliedState[1];
+ var listFilterExpandedState = useState(function () { return false; });
+ var listFilterExpanded = listFilterExpandedState[0];
+ var setListFilterExpanded = listFilterExpandedState[1];
var editorScrollElRef = useRef(null);
var previewScrollElRef = useRef(null);
var scrollSyncLockRef = useRef(false);
@@ -1293,6 +1387,12 @@ const Component = function (props) {
var workspaceZoom = workspaceZoomState[0];
var setWorkspaceZoom = workspaceZoomState[1];
+ useEffect(function () {
+ if (hasExtraListFiltersActive(listFilterDraft) || hasExtraListFiltersActive(listFilterApplied)) {
+ setListFilterExpanded(true);
+ }
+ }, [listFilterDraft.enabled, listFilterApplied.enabled]);
+
function attachWorkspaceScrollSync(el) {
if (!el || el.dataset.ctScrollSyncBound === '1') return;
el.dataset.ctScrollSyncBound = '1';
@@ -1325,6 +1425,14 @@ const Component = function (props) {
var CURRENT_USER = '法务-当前用户';
+ var IS_READ_ONLY_ROLE = typeof window !== 'undefined' && (function () {
+ var role = new URLSearchParams(window.location.search).get('role');
+ return role === 'cs' || role === 'readonly';
+ })();
+
+ function isPrototypeReadOnly() {
+ return IS_READ_ONLY_ROLE;
+ }
var pageStyle = ONEOS_ANT_TABLE_GLOBAL_FIX.concat([
'.vm-page.ct-page,.ct-page{color:var(--ln-body)}' +
@@ -1413,6 +1521,8 @@ const Component = function (props) {
'.ct-rich-editor--word .ct-word-doc--v266 style{display:none!important}' +
'.ct-rich-editor:focus{border-color:var(--ln-primary);box-shadow:inset 0 0 0 1px var(--ln-primary)}' +
'.ct-rich-editor.is-readonly{background:#f8fafc;color:#475569;cursor:default}' +
+ '.ct-readonly-banner{padding:10px 16px;margin:0 0 12px;background:#fff7ed;border:1px solid #fed7aa;border-radius:8px;color:#9a3412;font-size:13px;line-height:1.5}' +
+ '.ct-readonly-hint{font-size:13px;color:#64748b}' +
'.ct-rich-editor img{max-width:100%;height:auto;margin:8px 0;border-radius:4px;display:block}' +
'.ct-rich-wrap.is-fullscreen{position:fixed;inset:0;z-index:1100;background:#fff;padding:12px;display:flex;flex-direction:column}' +
'.ct-rich-wrap.is-fullscreen .ct-rich-editor{max-height:none;min-height:0;flex:1}' +
@@ -1423,7 +1533,7 @@ const Component = function (props) {
'.ct-preview-doc .ct-page-break{border-top-color:#94A3B8}' +
'.ct-preview-doc.ct-word-doc,.ct-preview-doc .ct-word-doc{font-family:FangSong,STFangsong,"仿宋",SimSun,"宋体",serif;font-size:10.5pt;line-height:1.5;color:#000;letter-spacing:0}' +
'.ct-preview-doc.ct-word-doc{padding:48px 56px 56px}' +
- '.ct-word-doc:not(.ct-word-doc--v266) p{margin:0 0 4px}' +
+ '.ct-word-doc:not(.ct-word-doc--v266):not(.ct-word-doc--imported) p{margin:0 0 4px}' +
'.ct-word-title{text-align:center;font-size:18pt;font-weight:700;margin:0 0 10px!important;line-height:1.4}' +
'.ct-word-contract-no{text-align:right;margin:0 0 14px!important;padding-right:1em;font-size:10.5pt}' +
'.ct-word-party-line{margin:0!important;line-height:1.8;font-size:10.5pt}' +
@@ -1476,10 +1586,11 @@ const Component = function (props) {
'.ct-preview-pages{display:flex;flex-direction:column;align-items:center;gap:20px;width:100%;box-sizing:border-box;padding:4px 5px 12px}' +
'.ct-preview-page{width:210mm;max-width:calc(100% - 10px);min-height:297mm;padding:25.4mm 31.8mm;box-sizing:border-box;background:#fff;border:1px solid #d9d9d9;box-shadow:0 2px 12px rgba(0,0,0,0.12);position:relative;flex-shrink:0;margin-inline:0}' +
'.ct-preview-page__body{width:100%;overflow:hidden;word-break:break-word}' +
+ '.ct-preview-page__body.ct-word-doc--imported{overflow:visible;word-break:normal}' +
'.ct-preview-page__footer{position:absolute;bottom:10mm;left:0;right:0;text-align:center;font-size:9pt;color:#999;font-family:FangSong,STFangsong,"仿宋",SimSun,"宋体",serif}' +
'.ct-preview-page.is-loading{min-height:120px;display:flex;align-items:center;justify-content:center;color:#94A3B8;font-size:13px;width:210mm}' +
'.ct-preview-panel--word{background:#b8b8b8;padding:0}' +
- '.ct-rich-editor .ct-word-doc:not(.ct-word-doc--v266),.ct-rich-editor .ct-word-doc:not(.ct-word-doc--v266){font-family:FangSong,STFangsong,"仿宋",SimSun,"宋体",serif;font-size:10.5pt;line-height:1.5;color:#000}' +
+ '.ct-rich-editor .ct-word-doc:not(.ct-word-doc--v266):not(.ct-word-doc--imported),.ct-rich-editor .ct-word-doc:not(.ct-word-doc--v266):not(.ct-word-doc--imported){font-family:FangSong,STFangsong,"仿宋",SimSun,"宋体",serif;font-size:10.5pt;line-height:1.5;color:#000}' +
'.ct-rich-editor .ct-word-title{text-align:center;font-size:18pt;font-weight:700}' +
'.ct-rich-editor .ct-word-contract-no{text-align:right;padding-right:1em}' +
'.ct-rich-editor .ct-word-clause-indent,.ct-rich-editor .ct-word-clause-indent2{text-indent:2em;text-align:justify}' +
@@ -1539,7 +1650,11 @@ const Component = function (props) {
fileName: record.fileName,
versionName: record.versionName,
versionNo: record.versionNo,
- contractType: resolveContractType(record),
+ contractName: resolveContractName(record),
+ contractType: resolveDocumentContractType(record),
+ sectionTabs: resolveSectionTabs(record).map(function (tab) {
+ return { key: tab.key, label: tab.label };
+ }),
sectionContents: cloneJson(record.sectionContents),
sectionVehicleBindings: cloneJson(record.sectionVehicleBindings),
remark: record.remark || '',
@@ -1701,10 +1816,13 @@ const Component = function (props) {
return '合同 ' + versionNo;
}
- function buildWordDocumentItem(publish, sectionContents, sectionVehicleBindings, importName, editing, contractType) {
+ function buildWordDocumentItem(publish, sectionContents, sectionVehicleBindings, importName, editing, contractName, documentContractType) {
var now = formatNowTime();
- var resolvedContractType = normalizeContractType(
- contractType || (editing && resolveContractType(editing)) || ''
+ var resolvedContractName = normalizeContractName(
+ contractName || (editing && resolveContractName(editing)) || ''
+ );
+ var resolvedDocumentContractType = normalizeDocumentContractType(
+ documentContractType || (editing && resolveDocumentContractType(editing)) || ''
);
var fileName = importName
? (/\./.test(importName) ? importName : importName + '.docx')
@@ -1712,12 +1830,17 @@ const Component = function (props) {
var versionName = stripFileExtension(fileName) || (editing && editing.versionName) || '未命名合同';
var bindingsMap = JSON.parse(JSON.stringify(sectionVehicleBindings || {}));
var contentsMap = Object.assign(emptySectionMap(), sectionContents || {});
+ var sectionTabsSnapshot = getSectionTabs().map(function (tab) {
+ return { key: tab.key, label: tab.label };
+ });
if (publish) {
var versionNo = generateNextVersionNo();
return {
id: 'doc-' + Date.now(),
category: categoryFilter,
- contractType: resolvedContractType,
+ contractName: resolvedContractName,
+ contractType: resolvedDocumentContractType,
+ sectionTabs: sectionTabsSnapshot,
fileName: fileName,
versionNo: versionNo,
versionName: buildAutoVersionName(versionNo, importName, versionName),
@@ -1736,7 +1859,9 @@ const Component = function (props) {
return {
id: 'doc-' + Date.now(),
category: categoryFilter,
- contractType: resolvedContractType,
+ contractName: resolvedContractName,
+ contractType: resolvedDocumentContractType,
+ sectionTabs: sectionTabsSnapshot,
fileName: fileName,
versionNo: null,
versionName: buildAutoVersionName(null, importName, versionName),
@@ -1786,13 +1911,20 @@ const Component = function (props) {
}
function openCreate() {
+ if (isPrototypeReadOnly()) {
+ message.warning('当前为只读演示角色,无法新增模板');
+ return;
+ }
var sectionKey = 'main';
setActiveSectionKey(sectionKey);
setEditorMode('create');
setEditingRecord(null);
- setEditorContractType('');
+ setEditorContractName('');
+ setEditorDocumentContractType('合同');
+ setEditorSectionTabs(null);
setEditorSectionKey(sectionKey);
lastImportFileNameState[1]('');
+ importedDocxBufferState[1](null);
importChangedInSessionState[1](false);
formDocumentHtmlState[1]('');
formBlocksState[1]([]);
@@ -1811,10 +1943,13 @@ const Component = function (props) {
var sectionKey = 'main';
setEditorMode('edit');
setEditingRecord(record);
- setEditorContractType(resolveContractType(record));
+ setEditorContractName(resolveContractName(record));
+ setEditorDocumentContractType(resolveDocumentContractType(record));
+ setEditorSectionTabs(record.sectionTabs && record.sectionTabs.length ? record.sectionTabs : null);
setEditorSectionKey(sectionKey);
setActiveSectionKey(sectionKey);
lastImportFileNameState[1](record.fileName || '');
+ importedDocxBufferState[1](null);
importChangedInSessionState[1](false);
var sections = Object.assign(emptySectionMap(), record.sectionContents || {});
var html = mergeMonolithicHtml(sections);
@@ -1830,19 +1965,62 @@ const Component = function (props) {
setPageView('editor');
}
- function handleImportWord(file) {
- var name = file && file.name ? file.name : 'Word';
+ function applyImportedWordHtml(fullHtml, name) {
var sectionKey = getCurrentSectionKey();
lastImportFileNameState[1](name);
importChangedInSessionState[1](true);
- var blocks = mockWordBlocks(name, categoryFilter);
- var fullHtml = mergeBlocksToDocument(blocks);
- formDocumentHtmlState[1](fullHtml);
- formBlocksState[1](documentToBlocks(fullHtml));
- setSectionContents(splitHtmlIntoSections(fullHtml));
+ var prepared = prepareWordHtml(fullHtml);
+ var tabs = extractSectionTabsFromHtml(prepared);
+ if (tabs && tabs.length > 1) {
+ setEditorSectionTabs(tabs);
+ formDocumentHtmlState[1](prepared);
+ formBlocksState[1](documentToBlocks(prepared));
+ setSectionContents(splitHtmlBySectionTabs(prepared, tabs));
+ } else {
+ setEditorSectionTabs(null);
+ formDocumentHtmlState[1](prepared);
+ formBlocksState[1](documentToBlocks(prepared));
+ setSectionContents(splitHtmlIntoSections(prepared));
+ }
setActiveSectionKey(sectionKey);
editorSyncKeyState[1](function (v) { return v + 1; });
markEditorDirty();
+ }
+
+ function handleImportWord(file) {
+ var name = file && file.name ? file.name : 'Word';
+ if (isPrototypeReadOnly()) {
+ message.warning('当前为只读演示角色,无法导入 Word');
+ return false;
+ }
+ if (/\.docx?$/i.test(name)) {
+ var hideLoading = message.loading('正在解析 Word 文件…', 0);
+ file.arrayBuffer().then(function (arrayBuffer) {
+ if (isDocxArrayBuffer(arrayBuffer)) {
+ importedDocxBufferState[1](arrayBuffer);
+ return convertDocxArrayBufferToHtml(arrayBuffer);
+ }
+ importedDocxBufferState[1](null);
+ return convertLegacyDocFileToHtml(file);
+ }).then(function (rawHtml) {
+ applyImportedWordHtml(rawHtml, name);
+ message.success('已导入 Word 合同全文,章节 Tab 已按文档标题自动生成');
+ }).catch(function () {
+ importedDocxBufferState[1](null);
+ var blocks = mockWordBlocks(name, categoryFilter);
+ if (blocks && blocks.length) {
+ applyImportedWordHtml(mergeBlocksToDocument(blocks), name);
+ message.warning('未能完整解析该 Word 文件,已载入与文件名匹配的演示模板');
+ } else {
+ message.error('Word 导入失败,请确认文件为有效的 .doc 或 .docx 格式');
+ }
+ }).finally(function () {
+ if (typeof hideLoading === 'function') hideLoading();
+ });
+ return false;
+ }
+ importedDocxBufferState[1](null);
+ applyImportedWordHtml(mergeBlocksToDocument(mockWordBlocks(name, categoryFilter)), name);
message.success('已导入 Word 合同全文,编辑区将按原版式分页展示全部章节');
return false;
}
@@ -1872,23 +2050,37 @@ const Component = function (props) {
}
function handleSaveTemplate(publish) {
+ if (isPrototypeReadOnly()) {
+ message.warning('当前为只读演示角色,无法保存或发布模板');
+ return;
+ }
var sections = collectEditorSectionContents();
var mainHtml = sections.main || '';
if (!mainHtml.trim()) {
message.warning('请先编辑合同内容,或导入 Word 模板');
return;
}
- if (!normalizeContractType(editorContractType)) {
- message.warning('请输入合同类型');
+ if (!normalizeContractName(editorContractName)) {
+ message.warning('请输入合同名称');
+ return;
+ }
+ if (!normalizeDocumentContractType(editorDocumentContractType)) {
+ message.warning('请选择类型');
return;
}
var now = formatNowTime();
var importName = lastImportFileNameState[0] || '';
var bindingsMap = collectEditorSectionVehicleBindings();
- var savedContractType = normalizeContractType(editorContractType);
+ var savedContractName = normalizeContractName(editorContractName);
+ var savedDocumentContractType = normalizeDocumentContractType(editorDocumentContractType);
+ var savedSectionTabs = getSectionTabs().map(function (tab) {
+ return { key: tab.key, label: tab.label };
+ });
if (editorMode === 'create') {
- var created = buildWordDocumentItem(publish, sections, bindingsMap, importName, null, savedContractType);
+ var created = buildWordDocumentItem(
+ publish, sections, bindingsMap, importName, null, savedContractName, savedDocumentContractType
+ );
setTemplates(function (prev) { return prev.concat([created]); });
message.success(publish ? ('已发布,版本号 ' + created.versionNo) : '草稿已保存(未生成版本号)');
} else if (editingRecord) {
@@ -1914,7 +2106,9 @@ const Component = function (props) {
var logs = (t.versionLogs || []).concat([logEntry]);
return Object.assign({}, t, {
fileName: fileName,
- contractType: savedContractType,
+ contractName: savedContractName,
+ contractType: savedDocumentContractType,
+ sectionTabs: savedSectionTabs,
versionNo: nextVersionNo,
versionName: buildAutoVersionName(nextVersionNo, importName, stripFileExtension(fileName)),
sectionContents: sections,
@@ -1937,7 +2131,9 @@ const Component = function (props) {
: t.fileName;
return Object.assign({}, t, {
fileName: fileName,
- contractType: savedContractType,
+ contractName: savedContractName,
+ contractType: savedDocumentContractType,
+ sectionTabs: savedSectionTabs,
versionNo: publishVersionNo,
versionName: buildAutoVersionName(publishVersionNo, importName, stripFileExtension(fileName)),
status: 'published',
@@ -1962,7 +2158,9 @@ const Component = function (props) {
: t.fileName;
return Object.assign({}, t, {
fileName: fileName,
- contractType: savedContractType,
+ contractName: savedContractName,
+ contractType: savedDocumentContractType,
+ sectionTabs: savedSectionTabs,
sectionContents: sections,
sectionVehicleBindings: bindingsMap,
updater: CURRENT_USER,
@@ -1980,7 +2178,9 @@ const Component = function (props) {
: t.fileName;
var patch = {
fileName: fileName,
- contractType: savedContractType,
+ contractName: savedContractName,
+ contractType: savedDocumentContractType,
+ sectionTabs: savedSectionTabs,
sectionContents: sections,
sectionVehicleBindings: bindingsMap,
updater: CURRENT_USER,
@@ -2005,10 +2205,15 @@ const Component = function (props) {
setEditorSectionKey(null);
placeholderPopoverOpenState[1](false);
editorInsertHandlerRef.current = null;
+ storageWriteFailedState[1](false);
clearEditorDirty();
}
function requestBackToList() {
+ if (storageWriteFailedState[0]) {
+ message.warning('本地存储未成功,请重试保存后再离开,否则修改可能丢失');
+ return;
+ }
if (pageView === 'editor' && editorDirtyState[0]) {
leaveEditorModalState[1](true);
return;
@@ -2017,6 +2222,10 @@ const Component = function (props) {
}
function confirmLeaveEditor() {
+ if (storageWriteFailedState[0]) {
+ message.warning('本地存储未成功,请重试保存后再离开,否则修改可能丢失');
+ return;
+ }
leaveEditorModalState[1](false);
backToList();
}
@@ -2120,6 +2329,10 @@ const Component = function (props) {
function mergePaginatedEditorHtml(chunks, sourceHtml) {
var inner = chunks.filter(function (c) { return c != null; }).join('');
var source = String(sourceHtml || '');
+ if (isImportedWordDocHtml(source) || isImportedWordDocHtml(inner)) {
+ if (inner.indexOf('ct-word-doc--imported') >= 0) return inner;
+ return '' + inner + '
';
+ }
if (source.indexOf('ct-word-doc--v266') >= 0 || inner.indexOf('ct-word-doc--v266') >= 0) {
if (inner.indexOf('ct-word-doc--v266') >= 0) return inner;
return '' + inner + '
';
@@ -2164,7 +2377,7 @@ const Component = function (props) {
return nodes;
}
- function createWordMeasureContext() {
+ function createWordMeasureContext(sourceHtml) {
var layout = getWordPageLayout();
var sandbox = document.createElement('div');
sandbox.className = 'vm-page ct-page ct-word-measure-host';
@@ -2173,7 +2386,7 @@ const Component = function (props) {
var shell = document.createElement('div');
shell.className = 'ct-word-a4-page';
var body = document.createElement('div');
- body.className = 'ct-word-a4-page__body ct-word-doc ct-word-doc--v266 ct-rich-editor ct-rich-editor--word';
+ body.className = resolveWordPageBodyClass(sourceHtml || '');
shell.appendChild(body);
sandbox.appendChild(shell);
return { sandbox: sandbox, body: body, inner: body, maxHeight: layout.contentHeightPx };
@@ -2320,13 +2533,13 @@ const Component = function (props) {
function paginateWordHtml(html) {
if (typeof document === 'undefined') return [String(html || '')];
var fixed = prepareEditorHtml(html);
- if (isWordFormattedDoc(html)) ensureV266WordStyles(html);
+ if (isWordFormattedDoc(html) && !isImportedWordDocHtml(html)) ensureV266WordStyles(html);
var nodes = collectWordDocNodes(fixed);
if (!nodes.length) return [fixed || ''];
var sourceLen = getWordNodesPlainLength(nodes);
var attempts = [1, 0.94, 0.88];
for (var a = 0; a < attempts.length; a++) {
- var ctx = createWordMeasureContext();
+ var ctx = createWordMeasureContext(html);
try {
var scale = attempts[a];
ctx.maxHeight = Math.max(120, Math.floor(ctx.maxHeight * scale));
@@ -2346,6 +2559,7 @@ const Component = function (props) {
function WordPaginatedPreview(props) {
var html = props.html || '';
+ var docxBuffer = props.docxBuffer || null;
var onScrollContainerMount = props.onScrollContainerMount;
var previewZoom = props.zoom != null ? props.zoom : 100;
var setPreviewZoom = props.onZoomChange || function () {};
@@ -2353,6 +2567,8 @@ const Component = function (props) {
var viewModeState = useState('scroll');
var pagesState = useState([]);
var pageIdxState = useState(0);
+ var docxRootRef = useRef(null);
+ var docxRenderTokenRef = useRef(0);
var viewMode = viewModeState[0];
var setViewMode = viewModeState[1];
var pages = pagesState[0];
@@ -2410,6 +2626,34 @@ const Component = function (props) {
}
useEffect(function () {
+ if (!docxBuffer) return;
+ var token = docxRenderTokenRef.current + 1;
+ docxRenderTokenRef.current = token;
+ var cancelled = false;
+
+ function run() {
+ if (cancelled) return;
+ var container = docxRootRef.current;
+ if (!container) {
+ window.requestAnimationFrame(run);
+ return;
+ }
+ renderDocxPreview(container, docxBuffer).catch(function () {
+ if (!cancelled && docxRenderTokenRef.current === token && container) {
+ container.innerHTML = 'Word 原版式预览加载失败,已回退 HTML 预览
';
+ }
+ });
+ }
+
+ run();
+ return function () {
+ cancelled = true;
+ if (docxRootRef.current) docxRootRef.current.innerHTML = '';
+ };
+ }, [docxBuffer]);
+
+ useEffect(function () {
+ if (docxBuffer) return;
if (!html) {
pagesState[1]([]);
return;
@@ -2419,13 +2663,13 @@ const Component = function (props) {
pageIdxState[1](0);
}, 0);
return function () { window.clearTimeout(timer); };
- }, [html]);
+ }, [html, docxBuffer]);
if (!html || !String(html).trim()) {
return null;
}
- if (!pages.length) {
+ if (!pages.length && !docxBuffer) {
return React.createElement('div', { className: 'ct-preview-pages' },
React.createElement('div', { className: 'ct-preview-page is-loading' }, 'Word 分页排版计算中…')
);
@@ -2507,12 +2751,28 @@ const Component = function (props) {
);
}
+ if (docxBuffer) {
+ return wrapPreviewShell(
+ previewToolbar,
+ React.createElement('div', { className: 'ct-preview-pages ct-preview-pages--docx-fidelity' },
+ React.createElement('div', { className: 'ct-docx-fidelity-banner', role: 'note' },
+ 'Word 原版式预览(与 WPS 排版一致);左侧编辑区保留可编辑结构'
+ ),
+ React.createElement('div', {
+ ref: docxRootRef,
+ className: 'ct-docx-fidelity-root',
+ 'aria-label': 'Word 原版式预览'
+ })
+ )
+ );
+ }
+
if (viewMode === 'single') {
return wrapPreviewShell(
previewToolbar,
React.createElement('div', { className: 'ct-preview-page', key: pageIdx },
React.createElement('div', {
- className: 'ct-preview-page__body ct-word-doc ct-word-doc--v266 ct-word-measure',
+ className: 'ct-preview-page__body ' + resolveWordDocShellClass(html) + ' ct-word-measure',
dangerouslySetInnerHTML: { __html: pages[pageIdx] }
}),
React.createElement('div', { className: 'ct-preview-page__footer' }, '— ', pageIdx + 1, ' —')
@@ -2525,7 +2785,7 @@ const Component = function (props) {
pages.map(function (pageHtml, i) {
return React.createElement('div', { className: 'ct-preview-page', key: 'p-' + i },
React.createElement('div', {
- className: 'ct-preview-page__body ct-word-doc ct-word-doc--v266 ct-word-measure',
+ className: 'ct-preview-page__body ' + resolveWordDocShellClass(html) + ' ct-word-measure',
dangerouslySetInnerHTML: { __html: pageHtml }
}),
React.createElement('div', { className: 'ct-preview-page__footer' }, '— ', i + 1, ' —')
@@ -2544,6 +2804,7 @@ const Component = function (props) {
return React.createElement('div', { className: 'ct-preview-doc ct-word-doc ct-word-paginated' },
React.createElement(WordPaginatedPreview, {
html: displayHtml,
+ docxBuffer: isImportedWordDocHtml(raw) ? importedDocxBufferState[0] : null,
zoom: workspaceZoom,
onZoomChange: setWorkspaceZoom,
onScrollContainerMount: registerPreviewScrollContainer
@@ -2568,6 +2829,7 @@ const Component = function (props) {
var onScrollContainerMount = props.onScrollContainerMount;
var workspaceZoom = props.workspaceZoom != null ? props.workspaceZoom : 100;
var enableVehicleClause = !!props.enableVehicleClause;
+ var readOnly = !!props.readOnly;
var editorRef = useRef(null);
var sourceTextareaRef = useRef(null);
var lockIdRef = useRef(1);
@@ -3964,14 +4226,16 @@ const Component = function (props) {
)
);
- return React.createElement('div', { className: 'ct-rich-wrap' + (isFullscreen ? ' is-fullscreen' : '') + (isWordEditor ? ' ct-rich-wrap--word-a4' : '') },
- React.createElement('div', { className: 'ct-rich-toolbar' }, row1, row2),
+ return React.createElement('div', { className: 'ct-rich-wrap' + (isFullscreen ? ' is-fullscreen' : '') + (isWordEditor ? ' ct-rich-wrap--word-a4' : '') + (readOnly ? ' is-readonly' : '') },
+ readOnly ? null : React.createElement('div', { className: 'ct-rich-toolbar' }, row1, row2),
isSourceMode
? React.createElement('textarea', {
ref: sourceTextareaRef,
className: 'ct-rich-source',
value: sourceDraftState[0],
+ readOnly: readOnly,
onChange: function (e) {
+ if (readOnly) return;
sourceDraftState[1](e.target.value);
onChange(e.target.value);
}
@@ -3997,8 +4261,8 @@ const Component = function (props) {
ref: function (el) { pageShellRefs.current[i] = el; }
},
React.createElement('div', {
- className: 'ct-word-a4-page__body ct-word-doc ct-word-doc--v266 ct-rich-editor ct-rich-editor--word',
- contentEditable: true,
+ className: resolveWordPageBodyClass(html, readOnly ? ' is-readonly' : ''),
+ contentEditable: !readOnly,
suppressContentEditableWarning: true,
onInput: syncHtml,
onBlur: handlePageBodyBlur,
@@ -4020,8 +4284,8 @@ const Component = function (props) {
editorRef.current = el;
if (onScrollContainerMount) onScrollContainerMount(el);
},
- className: 'ct-rich-editor',
- contentEditable: true,
+ className: 'ct-rich-editor' + (readOnly ? ' is-readonly' : ''),
+ contentEditable: !readOnly,
suppressContentEditableWarning: true,
onInput: syncHtml,
onBlur: handlePageBodyBlur
@@ -4071,7 +4335,8 @@ const Component = function (props) {
onRegisterInsert: registerEditorInsertRef.current,
onScrollContainerMount: registerEditorScrollContainer,
workspaceZoom: workspaceZoom,
- enableVehicleClause: sectionAllowsMainVehicleClauses(sectionKey)
+ enableVehicleClause: sectionAllowsMainVehicleClauses(sectionKey),
+ readOnly: isPrototypeReadOnly()
});
}
@@ -4183,8 +4448,9 @@ const Component = function (props) {
}
function renderSectionTabsMorePanel() {
+ var sectionTabs = getSectionTabs();
var query = String(sectionMoreSearchState[0] || '').trim().toLowerCase();
- var filtered = DOCUMENT_SECTION_TABS.filter(function (tab, index) {
+ var filtered = sectionTabs.filter(function (tab, index) {
if (!query) return true;
var label = String(tab.label || '').toLowerCase();
var order = String(index + 1);
@@ -4203,7 +4469,7 @@ const Component = function (props) {
React.createElement('div', { className: 'ct-section-tabs-more__list', role: 'tablist', 'aria-label': '全部章节' },
filtered.length
? filtered.map(function (tab) {
- var tabIndex = DOCUMENT_SECTION_TABS.findIndex(function (item) { return item.key === tab.key; });
+ var tabIndex = sectionTabs.findIndex(function (item) { return item.key === tab.key; });
return React.createElement('button', {
key: tab.key,
type: 'button',
@@ -4226,12 +4492,14 @@ const Component = function (props) {
}
function renderSectionTabs() {
+ var sectionTabs = getSectionTabs();
+ if (!sectionTabs.length) return null;
var activeIndex = getActiveSectionTabIndex();
- var activeTab = DOCUMENT_SECTION_TABS[activeIndex] || DOCUMENT_SECTION_TABS[0];
+ var activeTab = sectionTabs[activeIndex] || sectionTabs[0];
return React.createElement('div', { className: 'ct-section-nav' },
React.createElement('div', { className: 'ct-section-tabs-scroll', ref: sectionTabsScrollRef },
React.createElement('div', { className: 'ct-section-tabs', role: 'tablist', 'aria-label': '合同章节' },
- DOCUMENT_SECTION_TABS.map(function (tab, index) {
+ sectionTabs.map(function (tab, index) {
var isActive = activeSectionKey === tab.key;
return React.createElement('button', {
key: tab.key,
@@ -4383,37 +4651,61 @@ const Component = function (props) {
}
function renderContractTypeEditor() {
+ var sectionTabs = getSectionTabs();
+ var hasMainTab = sectionTabs.some(function (tab) { return tab.key === 'main'; });
+ if (!hasMainTab) return null;
var sectionKey = getCurrentSectionKey();
if (sectionKey !== 'main') return null;
return React.createElement('section', {
className: 'ct-contract-type-card ct-vehicle-binding-card vm-filter-card',
- 'aria-label': '合同类型',
+ 'aria-label': '合同主体配置',
'data-annotation-id': 'ct-contract-type',
},
React.createElement('div', { className: 'ct-vehicle-binding-card__head' },
- React.createElement('h3', { className: 'ct-vehicle-binding-card__title' },
- React.createElement('span', { className: 'ct-form-field__required', 'aria-hidden': 'true' }, '*'),
- '合同类型'
- ),
+ React.createElement('h3', { className: 'ct-vehicle-binding-card__title' }, '合同主体配置'),
React.createElement('p', { className: 'ct-vehicle-binding-card__desc' },
- '必填。用于列表展示,并供租赁合同创建时按类型筛选模板;请自行填写,如「正式合同」「现代18吨试用合同」。'
+ '合同名称与类型均为必填,将同步至列表页展示,并供租赁合同创建时按名称筛选模板。'
)
),
React.createElement('div', { className: 'ct-contract-type-card__form' },
- React.createElement(Input, {
- className: 'ct-editor-contract-type-input',
- value: editorContractType,
- onChange: function (e) {
- setEditorContractType(e.target.value);
- markEditorDirty();
- },
- placeholder: '请输入合同类型,如:正式合同',
- maxLength: 32,
- allowClear: true,
- required: true,
- 'aria-required': 'true',
- 'aria-label': '合同类型',
- })
+ React.createElement('label', { className: 'ct-contract-type-card__field' },
+ React.createElement('span', { className: 'ct-contract-type-card__label' },
+ React.createElement('span', { className: 'ct-form-field__required', 'aria-hidden': 'true' }, '*'),
+ '合同名称'
+ ),
+ React.createElement(Input, {
+ className: 'ct-editor-contract-type-input',
+ value: editorContractName,
+ onChange: function (e) {
+ setEditorContractName(e.target.value);
+ markEditorDirty();
+ },
+ placeholder: '请输入合同名称,如:正式合同',
+ maxLength: 32,
+ allowClear: true,
+ required: true,
+ 'aria-required': 'true',
+ 'aria-label': '合同名称',
+ })
+ ),
+ React.createElement('label', { className: 'ct-contract-type-card__field ct-contract-type-card__field--type' },
+ React.createElement('span', { className: 'ct-contract-type-card__label' },
+ React.createElement('span', { className: 'ct-form-field__required', 'aria-hidden': 'true' }, '*'),
+ '类型'
+ ),
+ React.createElement(Select, {
+ className: 'ct-editor-contract-type-select',
+ value: editorDocumentContractType,
+ options: getDocumentContractTypeSelectOptions(),
+ onChange: function (v) {
+ setEditorDocumentContractType(v);
+ markEditorDirty();
+ },
+ placeholder: '请选择类型',
+ 'aria-label': '类型',
+ 'aria-required': 'true',
+ })
+ )
)
);
}
@@ -4454,6 +4746,14 @@ const Component = function (props) {
}
function renderTemplateRowActions(record) {
+ if (isPrototypeReadOnly()) {
+ return React.createElement('div', { className: 'ct-row-actions' },
+ React.createElement(Button, {
+ type: 'link', size: 'small', className: 'ct-action-btn', key: 'view',
+ onClick: function () { openEdit(record); }
+ }, '查看')
+ );
+ }
var published = isPublishedTemplate(record);
var enabled = isTemplateEnabled(record);
var actionLocked = published && enabled;
@@ -4511,15 +4811,20 @@ const Component = function (props) {
function getListColumns() {
var cols = [
- { title: '合同类型', dataIndex: 'contractType', key: 'contractType', width: 160, ellipsis: true,
+ { title: '合同名称', dataIndex: 'contractName', key: 'contractName', width: 160, ellipsis: true,
render: function (_, r) {
- var typeLabel = getContractTypeLabel(resolveContractType(r));
+ var nameLabel = getContractNameLabel(resolveContractName(r));
return React.createElement('div', { className: 'ct-contract-name-cell' },
- React.createElement('span', { className: 'ct-contract-name-cell__name', title: typeLabel }, typeLabel),
+ React.createElement('span', { className: 'ct-contract-name-cell__name', title: nameLabel }, nameLabel),
renderEnabledTag(r)
);
}
},
+ { title: '类型', dataIndex: 'contractType', key: 'contractType', width: 120, ellipsis: true,
+ render: function (_, r) {
+ return getDocumentContractTypeLabel(resolveDocumentContractType(r));
+ }
+ },
{ title: '版本号', dataIndex: 'versionNo', key: 'versionNo', width: 96, align: 'center',
render: function (v) { return v || '—'; }
},
@@ -4590,15 +4895,15 @@ const Component = function (props) {
});
}
- function getContractTypeFilterOptions() {
+ function getContractNameFilterOptions() {
var seen = {};
var options = [];
templates.forEach(function (doc) {
if (doc.category !== categoryFilter) return;
- var type = resolveContractType(doc);
- if (!type || seen[type]) return;
- seen[type] = true;
- options.push({ value: type, label: type });
+ var name = resolveContractName(doc);
+ if (!name || seen[name]) return;
+ seen[name] = true;
+ options.push({ value: name, label: name });
});
return options.sort(function (a, b) {
return String(a.label).localeCompare(String(b.label), 'zh-CN');
@@ -4608,18 +4913,30 @@ const Component = function (props) {
function renderListFilterPanel() {
var fields = [
React.createElement('label', { key: 'keyword', className: 'vm-filter-field' },
- React.createElement('span', null, '合同类型'),
+ React.createElement('span', null, '合同名称'),
React.createElement(Select, {
className: 'ct-list-filter-select',
value: listFilterDraft.keyword || undefined,
- placeholder: '请选择或搜索合同类型',
+ placeholder: '请选择或搜索合同名称',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
filterOption: filterContractNameOption,
onChange: function (v) { updateListFilterDraft('keyword', v || ''); },
- options: getContractTypeFilterOptions(),
- 'aria-label': '合同类型'
+ options: getContractNameFilterOptions(),
+ 'aria-label': '合同名称'
+ })
+ ),
+ React.createElement('label', { key: 'contractType', className: 'vm-filter-field' },
+ React.createElement('span', null, '类型'),
+ React.createElement(Select, {
+ className: 'ct-list-filter-select',
+ value: listFilterDraft.contractType || undefined,
+ placeholder: '全部',
+ allowClear: true,
+ onChange: function (v) { updateListFilterDraft('contractType', v || ''); },
+ options: getDocumentContractTypeSelectOptions(),
+ 'aria-label': '类型'
})
),
React.createElement('label', { key: 'status', className: 'vm-filter-field' },
@@ -4662,12 +4979,55 @@ const Component = function (props) {
})
)
];
+ var split = splitFilterFields(fields, VM_FILTER_PRIMARY_VISIBLE_COUNT);
+ var primaryFields = split.primary;
+ var extraFields = split.extra;
+ var showExpand = shouldShowFilterExpand(fields.length);
+ var extraActiveCount = listFilterDraft.enabled ? 1 : 0;
+ var expandPanelId = 'ct-list-filter-expand-panel';
return React.createElement('section', { className: 'vm-filter-card', 'aria-label': '筛选条件', 'data-annotation-id': 'ct-list-filter' },
React.createElement('header', { className: 'vm-filter-header' },
React.createElement('h2', { className: 'vm-filter-title' }, '筛选条件')
),
- React.createElement('div', { className: 'vm-filter-grid' }, fields),
+ React.createElement('div', { className: 'vm-filter-body' },
+ React.createElement('div', {
+ className: 'vm-filter-grid vm-filter-grid--primary',
+ 'data-filter-tier': 'primary'
+ }, primaryFields),
+ showExpand
+ ? React.createElement('div', {
+ className: 'vm-filter-expand' + (listFilterExpanded ? ' is-expanded' : ''),
+ 'aria-hidden': !listFilterExpanded,
+ id: expandPanelId
+ },
+ React.createElement('div', { className: 'vm-filter-expand-inner' },
+ React.createElement('div', {
+ className: 'vm-filter-grid vm-filter-expand-grid',
+ 'data-filter-tier': 'extra'
+ }, listFilterExpanded ? extraFields : null)
+ )
+ )
+ : null
+ ),
React.createElement('div', { className: 'vm-filter-actions' },
+ showExpand
+ ? React.createElement('button', {
+ type: 'button',
+ className: 'vm-btn vm-btn-link vm-filter-toggle',
+ onClick: function () { setListFilterExpanded(!listFilterExpanded); },
+ 'aria-expanded': listFilterExpanded,
+ 'aria-controls': expandPanelId
+ },
+ listFilterExpanded ? '收起' : '更多筛选',
+ extraActiveCount > 0 && !listFilterExpanded
+ ? React.createElement('span', {
+ className: 'vm-filter-toggle-badge',
+ 'aria-label': '已设置 ' + extraActiveCount + ' 项扩展筛选'
+ }, String(extraActiveCount))
+ : null,
+ renderFilterChevronIcon(listFilterExpanded)
+ )
+ : null,
React.createElement(Button, { className: 'vm-btn vm-btn-ghost', onClick: resetListFilters }, '重置'),
React.createElement(Button, { type: 'primary', className: 'ct-btn-cta', onClick: applyListFilters }, '查询')
)
@@ -4698,34 +5058,35 @@ const Component = function (props) {
React.createElement('div', { className: 'ct-panel-head__title' },
React.createElement('span', null, '编辑区',
React.createElement('span', { className: 'ct-panel-head__badge' }, getSectionTabLabel(sectionKey))
- ),
- hasContent && isWordFormattedDoc(activeSectionHtml)
- ? React.createElement('span', { className: 'ct-editor-layout-hint' }, 'A4 版式较宽时可横向滑动查看')
- : null
+ )
),
React.createElement('div', { className: 'ct-panel-head__actions' },
- React.createElement(Popover, {
- content: renderTemplatePlaceholderCard(),
- title: null,
- trigger: 'click',
- placement: 'bottomRight',
- open: placeholderPopoverOpenState[0],
- onOpenChange: placeholderPopoverOpenState[1],
- overlayInnerStyle: { padding: '12px 14px' }
- },
- React.createElement(Button, {
- className: 'ct-panel-tool-btn ct-panel-tool-btn--ghost' + (placeholderPopoverOpenState[0] ? ' is-active' : ''),
- type: 'default'
- }, renderPlaceholderToolIcon(), '模板占位符')
- ),
- React.createElement(Upload, {
- accept: '.doc,.docx',
- showUploadList: false,
- beforeUpload: handleImportWord
- }, React.createElement(Button, {
- className: 'ct-panel-tool-btn' + (!hasContent && editorMode === 'create' ? ' ct-panel-tool-btn--accent' : ' ct-panel-tool-btn--ghost'),
- type: 'default'
- }, renderImportWordToolIcon(), '导入 Word 模板'))
+ isPrototypeReadOnly()
+ ? React.createElement('span', { className: 'ct-readonly-hint' }, '只读查看')
+ : React.createElement(React.Fragment, null,
+ React.createElement(Popover, {
+ content: renderTemplatePlaceholderCard(),
+ title: null,
+ trigger: 'click',
+ placement: 'bottomRight',
+ open: placeholderPopoverOpenState[0],
+ onOpenChange: placeholderPopoverOpenState[1],
+ overlayInnerStyle: { padding: '12px 14px' }
+ },
+ React.createElement(Button, {
+ className: 'ct-panel-tool-btn ct-panel-tool-btn--ghost' + (placeholderPopoverOpenState[0] ? ' is-active' : ''),
+ type: 'default'
+ }, renderPlaceholderToolIcon(), '模板占位符')
+ ),
+ React.createElement(Upload, {
+ accept: '.doc,.docx',
+ showUploadList: false,
+ beforeUpload: handleImportWord
+ }, React.createElement(Button, {
+ className: 'ct-panel-tool-btn' + (!hasContent && editorMode === 'create' ? ' ct-panel-tool-btn--accent' : ' ct-panel-tool-btn--ghost'),
+ type: 'default'
+ }, renderImportWordToolIcon(), '导入 Word 模板'))
+ )
)
),
React.createElement('div', { key: 'editor-panel-' + editorSyncKeyState[0] + '-' + getCurrentSectionKey(), className: 'ct-editor-panel' }, renderUnifiedEditor())
@@ -4770,24 +5131,34 @@ const Component = function (props) {
}
function renderEditorPage() {
+ var readOnly = isPrototypeReadOnly();
return React.createElement('div', { className: 'ct-editor-page' },
+ readOnly
+ ? React.createElement('div', { className: 'ct-readonly-banner', role: 'status' },
+ '当前为客服只读演示(URL 参数 ?role=cs 或 ?role=readonly),不可保存或修改模板内容。'
+ )
+ : null,
React.createElement('div', { className: 'ct-editor-page__topbar vm-detail-topbar', 'data-annotation-id': 'ct-editor-topbar' },
React.createElement(Button, { className: 'ct-editor-page__back-btn vm-btn vm-btn-back', onClick: requestBackToList }, '← 返回列表'),
- React.createElement('div', { className: 'ct-editor-page__topbar-actions' },
- React.createElement(Button, {
- className: 'vm-btn vm-btn-secondary',
- onClick: function () { handleSaveTemplate(false); }
- }, '保存'),
- React.createElement(Button, {
- type: 'primary',
- className: 'vm-btn vm-btn-primary ct-btn-cta',
- onClick: function () { handleSaveTemplate(true); }
- }, '保存并发布')
+ readOnly
+ ? null
+ : React.createElement('div', { className: 'ct-editor-page__topbar-actions' },
+ React.createElement(Button, {
+ className: 'vm-btn vm-btn-secondary',
+ onClick: function () { handleSaveTemplate(false); }
+ }, '保存'),
+ React.createElement(Button, {
+ type: 'primary',
+ className: 'vm-btn vm-btn-primary ct-btn-cta',
+ onClick: function () { handleSaveTemplate(true); }
+ }, '保存并发布')
+ )
+ ),
+ getSectionTabs().length
+ ? React.createElement('nav', { className: 'ct-editor-page__sections', 'aria-label': '合同章节导航', 'data-annotation-id': 'ct-section-tabs' },
+ renderSectionTabs()
)
- ),
- React.createElement('nav', { className: 'ct-editor-page__sections', 'aria-label': '合同章节导航', 'data-annotation-id': 'ct-section-tabs' },
- renderSectionTabs()
- ),
+ : null,
renderContractTypeEditor(),
renderVehicleBindingEditor(),
React.createElement('div', { className: 'ct-editor-page__body' }, renderEditorWorkspace())
@@ -4800,8 +5171,10 @@ const Component = function (props) {
React.createElement('section', { className: 'vm-table-section' },
React.createElement('div', { className: 'vm-table-toolbar', 'data-annotation-id': 'ct-list-toolbar' },
React.createElement('div', { className: 'vm-table-actions' },
- React.createElement(Button, { type: 'primary', className: 'ct-btn-cta', onClick: openCreate }, '新增')
- )
+ isPrototypeReadOnly()
+ ? React.createElement('span', { className: 'ct-readonly-hint' }, '只读演示:可查看模板,不可新增或修改(?role=cs)')
+ : React.createElement(Button, { type: 'primary', className: 'ct-btn-cta', onClick: openCreate }, '新增')
+ ),
),
React.createElement('div', { className: 'vm-table-card ct-list-table-card', 'data-annotation-id': 'ct-list-table' },
renderListTable()
@@ -4917,7 +5290,13 @@ const Component = function (props) {
var previewHtml = mergeMonolithicHtml(Object.assign(emptySectionMap(), snap.sectionContents || {}));
return React.createElement('div', null,
React.createElement('div', { className: 'ct-snapshot-meta' },
- React.createElement('div', null, '合同名称:', snap.fileName || snap.versionName || '—'),
+ React.createElement('div', null, 'Word 文件名:', snap.fileName || snap.versionName || '—'),
+ snap.contractName || snap.contractType
+ ? React.createElement('div', null, '合同名称:', snap.contractName || snap.contractType)
+ : null,
+ snap.contractType && isDocumentContractTypeEnum(snap.contractType)
+ ? React.createElement('div', null, '类型:', snap.contractType)
+ : null,
React.createElement('div', null, '版本号:', snap.versionNo || '—'),
React.createElement('div', null, '归档时间:', snap.archivedAt || snapshotViewState[0].time || '—')
),
diff --git a/src/prototypes/contract-template-management/annotation-source.json b/src/prototypes/contract-template-management/annotation-source.json
index 5ad344d..d650203 100644
--- a/src/prototypes/contract-template-management/annotation-source.json
+++ b/src/prototypes/contract-template-management/annotation-source.json
@@ -5,7 +5,7 @@
"version": 2,
"prototypeName": "contract-template-management",
"pageId": "list",
- "updatedAt": 1783328981482,
+ "updatedAt": 1783426129808,
"nodes": [
{
"id": "ct-list-filter",
@@ -20,12 +20,12 @@
"path": []
},
"aiPrompt": "列表页筛选区字段与查询逻辑。",
- "annotationText": "支持按合同名称(可搜索下拉)、状态、创建人、启用状态筛选。",
+ "annotationText": "支持按合同名称(可搜索下拉)、类型、状态、创建人、启用状态筛选;超过 4 项时默认折叠扩展筛选项。",
"hasMarkdown": true,
"color": "#2563eb",
"images": [],
"createdAt": 1740508800000,
- "updatedAt": 1740508800000
+ "updatedAt": 1783426129808
},
{
"id": "ct-list-toolbar",
@@ -40,12 +40,12 @@
"path": []
},
"aiPrompt": "列表工具栏新增入口。",
- "annotationText": "进入新增页,默认从「合同主体」章节开始编辑。",
+ "annotationText": "进入新增页;未导入 Word 前不展示章节 Tab。",
"hasMarkdown": false,
"color": "#32a06e",
"images": [],
"createdAt": 1740508800000,
- "updatedAt": 1740508800000
+ "updatedAt": 1783426129808
},
{
"id": "ct-list-table",
@@ -99,18 +99,18 @@
"fingerprint": "ct-section-tabs",
"path": []
},
- "aiPrompt": "9 个合同章节切换。",
- "annotationText": "横向滚动查看全部 9 章;可通过章节列表搜索并跳转。",
+ "aiPrompt": "由 Word「标题」样式自动生成的章节 Tab。",
+ "annotationText": "导入 Word 后,按文档中标记为「标题」的段落文本自动生成章节 Tab;新增模式未导入前整段隐藏(含合同主体)。",
"hasMarkdown": true,
"color": "#2563eb",
"images": [],
"createdAt": 1740508800000,
- "updatedAt": 1782820000000
+ "updatedAt": 1783426129808
},
{
"id": "ct-contract-type",
"index": 6.5,
- "title": "合同类型",
+ "title": "合同主体配置",
"pageId": "editor",
"locator": {
"selectors": [
@@ -119,13 +119,13 @@
"fingerprint": "ct-contract-type",
"path": []
},
- "aiPrompt": "合同主体章节下的合同类型配置。",
- "annotationText": "仅在「合同主体」章节 Tab 下展示;合同类型为必填,用户自行填写后同步至列表页。",
+ "aiPrompt": "合同主体章节下的合同名称与类型配置。",
+ "annotationText": "仅当存在「合同主体」Tab 且选中时展示;合同名称与类型均为必填,同步至列表页。",
"hasMarkdown": true,
"color": "#7c3aed",
"images": [],
"createdAt": 1740508800000,
- "updatedAt": 1782820000000
+ "updatedAt": 1783426129808
},
{
"id": "ct-vehicle-binding",
@@ -309,19 +309,21 @@
}
],
"annotationsByElementId": {
- "ct-list-filter": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 可搜索下拉 | 选项来自现有模板名称;支持输入模糊搜索 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅已发布) |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认",
- "ct-list-table": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同类型 | `contractType` | 展示用户自定义的合同类型文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名 |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿或已发布且停用 | 启用中禁用 |\n| 删除 | 草稿或已发布且停用 | 启用中禁用 |\n\n### 7.6 子表(展开行)\n\n展示 9 个章节摘要:章节名、适用车型、风控条款数、条件条款数。"
+ "ct-list-filter": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 可搜索下拉 | 选项来自现有模板 `contractName` 去重;支持输入模糊搜索 |\n| 类型 | 下拉 | 合同 / 授权委托书 / 转三方协议 / 其他 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**布局**:默认首行展示 4 项(合同名称、类型、状态、创建人);第 5 项「启用状态」收入「更多筛选」折叠区(与全局 `vm-filter` 规则一致)。扩展筛选项已设置时,折叠区自动展开并显示角标。\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认",
+ "ct-list-table": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `contractName` | 展示用户自定义合同名称;同行展示启用状态 Tag |\n| 类型 | `contractType` | 展示类型枚举文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n---\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n---\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---",
+ "ct-contract-type": "### 8.4 合同主体配置(仅合同主体 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 合同名称输入框 | **必填**;用户自行填写,如「正式合同」 |\n| 类型下拉 | **必填**;选项:合同 / 授权委托书 / 转三方协议 / 其他 |\n| 展示位置 | 章节 Tab 下方配置卡片,与附件章节的「适用车型」卡片交互一致 |\n| 用途 | 同步至列表页「合同名称」「类型」列;租赁合同创建时按合同名称筛选模板 |\n\n**规则**:\n\n- 仅当存在 **合同主体** 章节 Tab 且当前选中该 Tab 时显示本卡片(未导入 Word 时不展示)。\n- 未填写合同名称时无法保存或发布,并提示「请输入合同名称」。\n- 未选择类型时无法保存或发布,并提示「请选择类型」。\n- 切换至租赁订单等附件章节时,显示「适用车型」卡片,本卡片隐藏。\n- 保存/发布时分别写入 `contractName`、`contractType` 字段。",
+ "ct-section-tabs": "### 8.3 章节 Tab\n\n- **新增模式**:未导入 Word 时 **不展示** 章节 Tab 导航(含「合同主体」)及合同主体配置卡片。\n- **编辑模式**:优先按记录已保存的 `sectionTabs` 展示;无自定义 Tabs 时按 §9 规则生成。\n- **Tab 生成逻辑(非固定列表)**:\n - 导入 Word 后,系统识别文档中被标记为 **「标题」** 样式的段落(Word 样式名如 `标题`、`标题1`~`标题3` 等,导入后映射为 `h1`~`h6`);\n - 每个标题段落的纯文本生成一个章节 Tab,Tab 文案与标题一致;\n - 第一个标题之前的内容归入 **「合同主体」** Tab(`main`);该 Tab 下编辑/预览 **整份 Word 合并全文**;\n - 其余 Tab 仅展示对应标题至下一标题之间的切片内容;\n - 若文档中 **未识别到任何标题**,回退为按 §9.2 预置章节标记切分的默认章节 Tab(与演示模板一致)。\n- **展示**:横向滚动 Tab 条展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。"
}
},
"markdownMap": {
- "ct-list-filter": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同类型 | 可搜索下拉 | 选项来自现有模板 `contractType` 去重;支持输入模糊搜索 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认",
- "ct-list-table": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同类型 | `contractType` | 展示用户自定义的合同类型文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n---\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n---\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---",
- "ct-editor-topbar": "### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n---\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布(须先停用) |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**保持已发布与版本号**,仍为停用态 |\n| **保存并发布** | 新建已发布记录 + 版本号,默认启用 | 原记录变已发布 + 版本号,默认启用 | **同记录升级版本号**;旧内容写入 `versionLogs` 快照;发布后默认停用,需手动启用 |\n\n**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。\n\n**离开编辑**:有未保存修改时返回列表需二次确认。",
- "ct-contract-type": "### 8.4 合同类型(仅合同主体 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 合同类型输入框 | **必填**;用户自行填写,如「正式合同」「现代18吨试用合同」 |\n| 展示位置 | 章节 Tab 下方配置卡片,与附件章节的「适用车型」卡片交互一致 |\n| 用途 | 同步至列表页「合同类型」列;租赁合同创建时按类型匹配标准模板 |\n\n**规则**:\n\n- 仅当选中 **合同主体** 章节 Tab 时显示本卡片。\n- 未填写时无法保存或发布,并提示「请输入合同类型」。\n- 切换至租赁订单等附件章节时,显示「适用车型」卡片,本卡片隐藏。\n- 保存/发布时写入模板 `contractType` 字段。",
- "ct-section-tabs": "### 8.3 章节 Tab\n\n- 共 9 个章节;**横向滚动 Tab 条**展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。\n- **其他 Tab**:仅展示该章节切片内容。\n\n章节顺序:\n\n1. 合同主体 \n2. 租赁订单 \n3. 车辆产生的费用 \n4. 安全责任告知书 \n5. 重型普通货运车辆租赁动态监管责任告知与确认书 \n6. 安全责任承诺书 \n7. 车辆使用规范和驾驶员要求 \n8. 授权委托书 \n9. 安全责任协议",
- "ct-vehicle-binding": "### 8.5 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。",
- "ct-editor-panel": "### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.6.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。",
- "ct-preview-panel": "### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充"
+ "ct-list-filter": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 可搜索下拉 | 选项来自现有模板 `contractName` 去重;支持输入模糊搜索 |\n| 类型 | 下拉 | 合同 / 授权委托书 / 转三方协议 / 其他 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**布局**:默认首行展示 4 项(合同名称、类型、状态、创建人);第 5 项「启用状态」收入「更多筛选」折叠区(与全局 `vm-filter` 规则一致)。扩展筛选项已设置时,折叠区自动展开并显示角标。\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认",
+ "ct-list-table": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `contractName` | 展示用户自定义合同名称;同行展示启用状态 Tag |\n| 类型 | `contractType` | 展示类型枚举文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n---\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n---\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---",
+ "ct-editor-topbar": "### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n---\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布(须先停用) |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**保持已发布与版本号**,仍为停用态 |\n| **保存并发布** | 新建已发布记录 + 版本号,默认启用 | 原记录变已发布 + 版本号,默认启用 | **同记录升级版本号**;旧内容写入 `versionLogs` 快照;发布后默认停用,需手动启用 |\n\n**校验**:保存/发布前须满足:\n\n- 合同主体(`sectionContents.main`)不能为空;\n- **合同名称**(`contractName`)已填写;\n- **类型**(`contractType`)已选择。\n\n**离开编辑**:有未保存修改时返回列表需二次确认。",
+ "ct-contract-type": "### 8.4 合同主体配置(仅合同主体 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 合同名称输入框 | **必填**;用户自行填写,如「正式合同」 |\n| 类型下拉 | **必填**;选项:合同 / 授权委托书 / 转三方协议 / 其他 |\n| 展示位置 | 章节 Tab 下方配置卡片,与附件章节的「适用车型」卡片交互一致 |\n| 用途 | 同步至列表页「合同名称」「类型」列;租赁合同创建时按合同名称筛选模板 |\n\n**规则**:\n\n- 仅当存在 **合同主体** 章节 Tab 且当前选中该 Tab 时显示本卡片(未导入 Word 时不展示)。\n- 未填写合同名称时无法保存或发布,并提示「请输入合同名称」。\n- 未选择类型时无法保存或发布,并提示「请选择类型」。\n- 切换至租赁订单等附件章节时,显示「适用车型」卡片,本卡片隐藏。\n- 保存/发布时分别写入 `contractName`、`contractType` 字段。",
+ "ct-section-tabs": "### 8.3 章节 Tab\n\n- **新增模式**:未导入 Word 时 **不展示** 章节 Tab 导航(含「合同主体」)及合同主体配置卡片。\n- **编辑模式**:优先按记录已保存的 `sectionTabs` 展示;无自定义 Tabs 时按 §9 规则生成。\n- **Tab 生成逻辑(非固定列表)**:\n - 导入 Word 后,系统识别文档中被标记为 **「标题」** 样式的段落(Word 样式名如 `标题`、`标题1`~`标题3` 等,导入后映射为 `h1`~`h6`);\n - 每个标题段落的纯文本生成一个章节 Tab,Tab 文案与标题一致;\n - 第一个标题之前的内容归入 **「合同主体」** Tab(`main`);该 Tab 下编辑/预览 **整份 Word 合并全文**;\n - 其余 Tab 仅展示对应标题至下一标题之间的切片内容;\n - 若文档中 **未识别到任何标题**,回退为按 §9.2 预置章节标记切分的默认章节 Tab(与演示模板一致)。\n- **展示**:横向滚动 Tab 条展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。",
+ "ct-vehicle-binding": "### 8.6 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。",
+ "ct-editor-panel": "### 8.7 编辑区(左侧)\n\n#### 8.7.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.7.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.7.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。",
+ "ct-preview-panel": "### 8.8 实时预览(右侧)\n\n#### 8.8.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.8.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.8.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.8.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充"
},
"assetMap": {},
"directory": {
@@ -465,7 +467,7 @@
"type": "markdown",
"id": "ct-doc-overview",
"title": "模块总览",
- "markdown": "## 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 合同模板管理 |\n| 目标用户 | 法务人员 |\n| 核心任务 | 维护 Word 版商用车租赁合同模板,按章节编辑,发布版本并控制启用态 |\n| 数据模型 | 一份 Word 文件 = 一条记录,内含 9 个章节内容 |\n| PRD 文档 | 见目录「PRD 全文 → PRD 完整文档」 |\n| 资源文件 | `src/resources/contract-template-management/PRD.md` |\n\n---\n\n### 产品目标(摘要)\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;发布变更写入版本日志 |\n| 多套模板并存 | 租赁创建时由用户选择已启用模板 |\n\n**同步命令**:`node src/prototypes/contract-template-management/scripts/build-annotation-source.mjs`"
+ "markdown": "## 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 合同模板管理 |\n| 目标用户 | 法务人员 |\n| 核心任务 | 维护 Word 版商用车租赁合同模板,按章节编辑,发布版本并控制启用态 |\n| 数据模型 | 一份 Word 文件 = 一条记录,内含 9 个章节内容 |\n| 车型主数据 | 品牌·型号选项来自 [车辆管理](/prototypes/vehicle-management) 种子数据,并与租赁演示车型合并 |\n| 角色演示 | 法务默认可编辑;URL 加 ?role=cs 或 ?role=readonly 进入客服只读模式 |\n| PRD 文档 | 见目录「PRD 全文 → PRD 完整文档」 |\n| 资源文件 | `src/resources/contract-template-management/PRD.md` |\n\n---\n\n### 产品目标(摘要)\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;发布变更写入版本日志 |\n| 多套模板并存 | 租赁创建时由用户选择已启用模板 |\n\n**同步命令**:`node src/prototypes/contract-template-management/scripts/build-annotation-source.mjs`"
},
{
"type": "folder",
@@ -477,32 +479,32 @@
"type": "markdown",
"id": "ct-doc-prd-full",
"title": "PRD 完整文档",
- "markdown": "# 合同模板管理 · 产品需求文档(PRD)\n\n| 项 | 内容 |\n|---|---|\n| 文档版本 | v1.0 |\n| 模块名称 | 合同模板管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 目标读者 | 前端 / 后端 / 测试 / 法务产品对接人 |\n| 交互原型 | `/prototypes/contract-template-management` |\n| 关联原型 | [车辆管理](/prototypes/vehicle-management)(车型主数据口径参考) |\n| 文档状态 | 已对齐原型(启用态 / 版本日志 / 全章节条件条款) |\n\n---\n\n## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁合同以 Word 文档形式维护,内容长、结构复杂,包含合同主体与 8 个附件章节。不同车型可能需要不同的附件条款组合。法务人员需要:\n\n- 以 **Word 版式保真** 的方式维护整份合同;\n- 按 **章节** 管理内容,并配置 **适用车型**;\n- 发布 **版本**,通过 **启用/停用** 控制模板是否可供租赁合同选用;\n- 在合同主体及租赁订单~安全责任协议各章节中配置 **条件条款**(按订单车辆命中显隐)。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本并写入版本日志 |\n| 多套模板并存 | 正式/试用、不同车型等多套已发布模板可同时存在;租赁创建时由用户选择 |\n\n### 1.3 非目标(本期不做)\n\n- 采购合同、服务合同等其他分类的完整业务闭环(数据结构预留 `category`,当前仅启用 `lease`)\n- 在线多人协同编辑、审批流配置界面\n- Word 导出回写(本期以 HTML 结构化存储 + 预览为主)\n- 列表页顶部章节 Tab(**仅编辑页**保留章节 Tab)\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、启用/停用模板 |\n| 客服人员(间接用户) | 草拟新合同时从已启用模板中选择(下游「租赁合同管理」消费) |\n| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |\n\n### 2.2 核心用户故事\n\n1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。\n2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。\n3. **作为法务**,我希望在合同主体及租赁订单~安全责任协议各章节为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。\n4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。\n5. **作为法务**,我希望对已发布模板执行启用/停用,停用后方可编辑,并在发布变更时自动归档旧版快照到版本日志。\n6. **作为客服**,我希望在创建租赁合同时第一步选择要使用的已启用合同模板。\n7. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| Word 文档 / 模板记录 | 列表主表一行,对应一份完整合同文件 |\n| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |\n| 草稿 | `status = draft`,无版本号 |\n| 已发布 | `status = published`,有版本号 `vX.XX` |\n| 启用 | `enabled = true` 的已发布模板,可供租赁合同创建选用 |\n| 停用 | `enabled = false` 的已发布模板,不可编辑/删除,不可被租赁创建选用 |\n| 版本日志 | `versionLogs[]`,记录发布变更前自动归档的快照 |\n| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock=\"1\"` |\n| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline=\"1\"` |\n| 条件条款 | 各章节可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause=\"1\"` |\n| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |\n| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |\n\n---\n\n## 4. 信息架构\n\n```text\n合同模板管理\n├── 列表页\n│ ├── 筛选区\n│ ├── 工具栏(新增)\n│ └── 嵌套表格\n│ ├── 主表:Word 文件\n│ └── 子表:9 个章节(展开行)\n└── 编辑 / 新增页\n ├── 顶栏(返回、保存、保存并发布)\n ├── 章节 Tab(仅本页展示)\n ├── 条件条款说明(各章节均可使用工具栏「条件」)\n ├── 适用车型绑定(附件章节 Tab 时显示)\n └── 工作区\n ├── 左侧:Word 版式编辑区\n └── 右侧:A4 实时预览\n```\n\n---\n\n## 5. 数据模型\n\n### 5.1 模板记录(Word 文档级)\n\n一条记录代表 **一份 Word 合同文件**,不按章节拆成多行。\n\n```typescript\ninterface ContractTemplateDocument {\n id: string; // 唯一 ID\n category: 'lease'; // 合同分类,当前固定商用车租赁\n contractType: string; // 用户自定义合同类型,如「正式合同」「试用合同」\n fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」\n versionName: string; // 展示用版本名称\n versionNo: string | null; // 已发布:v1.00;草稿:null\n status: 'draft' | 'published';\n enabled: boolean; // 已发布模板是否启用(草稿恒为 false)\n versionLogs: VersionLogEntry[]; // 版本变更日志与快照\n sectionContents: Record; // 各章节 HTML\n sectionVehicleBindings: Record; // 各章节车型绑定\n creator: string;\n createTime: string; // YYYY-MM-DD HH:mm\n updater: string;\n updateTime: string;\n remark?: string; // 内部备注(列表不展示)\n}\n\ninterface VersionLogEntry {\n id: string;\n versionNo: string; // 归档时的版本号\n action: string; // 如「发布变更」\n operator: string;\n time: string;\n remark?: string;\n snapshot: {\n fileName: string;\n versionName: string;\n versionNo: string;\n sectionContents: Record;\n sectionVehicleBindings: Record;\n remark?: string;\n archivedAt: string;\n };\n}\n\ninterface VehicleBinding {\n brand: string; // 品牌,如「现代」\n model: string; // 型号,如「18吨氢燃料电池车」\n}\n```\n\n### 5.2 章节枚举(SectionKey)\n\n| key | 章节名称 | 车型绑定 | 条件条款 |\n|---|---|:---:|:---:|\n| `main` | 合同主体 | 否 | 是 |\n| `lease_order` | 租赁订单 | 是 | 是 |\n| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |\n| `safety_notice` | 安全责任告知书 | 是 | 是 |\n| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |\n| `safety_commitment` | 安全责任承诺书 | 是 | 是 |\n| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |\n| `authorization` | 授权委托书 | 是 | 是 |\n| `safety_agreement` | 安全责任协议 | 是 | 是 |\n\n### 5.3 章节内容 HTML 标记规范\n\n研发需保证存储、渲染、统计口径一致:\n\n| 类型 | 选择器 / 属性 | 统计函数 |\n|---|---|---|\n| 锁定区 | `.ct-inline-lock[data-inline-lock=\"1\"]` | `countLockRegions` |\n| 风控红线 | `.ct-risk-redline[data-risk-redline=\"1\"]` | `countRiskRedlineRegions` |\n| 条件条款 | `.ct-vehicle-clause[data-vehicle-clause=\"1\"]` | `countVehicleClauseRegions` |\n| 条件条款车型 | `data-vehicle-bindings`(JSON 数组) | 预览命中逻辑 |\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 版本号规则\n\n- 格式:`v{major}.{minor}`,如 `v1.00`、`v1.01`\n- **仅发布时生成**;保存草稿时 `versionNo = null`\n- 同分类下递增:取当前已发布版本最大 minor +1;minor ≥ 99 时 major +1、minor 归 0\n- 首版默认 `v1.00`\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布(须先停用) |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**保持已发布与版本号**,仍为停用态 |\n| **保存并发布** | 新建已发布记录 + 版本号,默认启用 | 原记录变已发布 + 版本号,默认启用 | **同记录升级版本号**;旧内容写入 `versionLogs` 快照;发布后默认停用,需手动启用 |\n\n**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。\n\n**离开编辑**:有未保存修改时返回列表需二次确认。\n\n### 6.3 启用 / 停用规则\n\n1. 仅 **已发布** 模板具有启用态;草稿显示「—」。\n2. **启用中**:不可编辑、不可删除;可供租赁合同创建选用。\n3. **停用**:需二次确认(提示可能导致标准合同无法正常使用);停用后可编辑、可删除;租赁创建不可选用。\n4. **启用**:直接生效,无需二次确认。\n5. 编辑已发布模板并发布后,模板自动变为 **停用**,需重新启用后下游才可选。\n\n### 6.4 版本日志规则\n\n1. 对已发布模板执行「保存并发布」前,系统将 **当前内容自动归档** 为快照写入 `versionLogs`。\n2. 列表操作列提供「版本日志」,可查看历史版本号、操作人、时间与快照正文(只读)。\n3. 列表主表 **一行对应一份 Word 文件**,历史版本通过日志追溯,不另增列表行。\n\n### 6.5 删除规则\n\n- **启用中** 的已发布模板不可删除。\n- 其余记录可删除;删除前二次确认;删除后不可恢复。\n\n### 6.6 列表排序\n\n按 `updateTime` 降序。\n\n### 6.7 筛选匹配规则\n\n| 字段 | 匹配逻辑 |\n|---|---|\n| 合同类型 | 下拉选择(支持输入模糊搜索候选项);精确匹配 `contractType` |\n| 状态 | 精确匹配 `draft` / `published` |\n| 创建人 | 精确匹配 `creator` |\n| 启用状态 | `enabled`:仅已发布且启用;`disabled`:仅已发布且停用 |\n\n> **已移除**:筛选项「适用品牌/适用车型」、主表列「备注」、「设为标准模板」能力。\n\n---\n\n## 7. 列表页需求\n\n### 7.1 页面结构\n\n筛选区 + 工具栏 + 嵌套表格。**列表页不展示章节 Tab。**\n\n### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同类型 | 可搜索下拉 | 选项来自现有模板 `contractType` 去重;支持输入模糊搜索 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认\n\n### 7.3 工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增 | 进入新增页,默认章节为「合同主体」,模式为 create |\n\n### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同类型 | `contractType` | 展示用户自定义的合同类型文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---\n\n## 8. 编辑 / 新增页需求\n\n### 8.1 页面结构\n\n顶栏 → 章节 Tab → 条件说明 / 车型绑定(按章节显隐)→ 左右分栏工作区。\n\n### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n### 8.3 章节 Tab\n\n- 共 9 个章节;**横向滚动 Tab 条**展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。\n- **其他 Tab**:仅展示该章节切片内容。\n\n章节顺序:\n\n1. 合同主体 \n2. 租赁订单 \n3. 车辆产生的费用 \n4. 安全责任告知书 \n5. 重型普通货运车辆租赁动态监管责任告知与确认书 \n6. 安全责任承诺书 \n7. 车辆使用规范和驾驶员要求 \n8. 授权委托书 \n9. 安全责任协议 \n\n### 8.4 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。适用于合同主体及全部附件章节。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n### 8.5 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。\n\n### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.6.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充\n\n### 8.8 空状态\n\n编辑区无内容时,预览区展示空状态引导;新增模式下突出「导入 Word 模板」按钮。\n\n---\n\n## 9. Word 导入与章节拆分\n\n### 9.1 导入范围\n\n- 一次导入 **整份合同**;\n- 导入后写入各 `sectionContents[key]`;\n- 默认进入「合同主体」章节编辑。\n\n### 9.2 拆分策略\n\n1. 对导入 HTML 做 Word 导出清洗(`sanitizeWordExportHtml`);\n2. 从附件章节起,按 **章节标记字符串** 定位切分点;\n3. 切分点优先落在 ``、``、`` 之后,避免打断标签;\n4. `main` = 第一个切分点之前的全文;\n5. 各附件章节 = 相邻切分点之间的片段。\n\n### 9.3 章节标记(节选)\n\n研发需维护可扩展的 `markers` 列表,示例:\n\n| 章节 | 标记示例 |\n|---|---|\n| 租赁订单 | `附件 1:租赁订单`、`附件1:租赁订单` |\n| 车辆产生的费用 | `附件2:车辆产生的费用` |\n| 安全责任告知书 | `附件3:安全责任告知书` |\n| … | 完整列表见原型 `DOCUMENT_SECTION_TABS` |\n\n### 9.4 误匹配过滤\n\n以下情况 **不应** 作为切分点:\n\n- 标记出现在「详见附件…」「以附件…」等引用语境;\n- 标记过短且不在 `` 标题语境内;\n- 标记前为「(」「、」等不完整标题上下文。\n\n---\n\n## 10. 模板占位符\n\n### 10.1 变量分组\n\n**甲方签约主体**(创建合同时按所选签约公司自动替换):\n\n| key | 标签 |\n|---|---|\n| `lessorName` | 甲方全称 |\n| `lessorAddress` | 甲方地址 |\n| `lessorContact` | 甲方联系人 |\n| `lessorPhone` | 甲方电话 |\n| `lessorEmail` | 甲方邮箱 |\n| `lessorAccountName` | 收款户名 |\n| `lessorBankName` | 开户行 |\n| `lessorBankAccount` | 银行账号 |\n\n**合同通用**(创建合同时由系统或客服填写):\n\n| key | 标签 |\n|---|---|\n| `contractCode` | 合同编号 |\n| `customerName` | 乙方公司全称 |\n| `creditCode` | 乙方统一社会信用代码 |\n| `customerAddress` | 乙方地址 |\n| `customerContact` | 乙方联系人 |\n| `customerPhone` | 乙方联系电话 |\n| `signDateYear` | 签订年份 |\n| `signDateMonth` | 签订月份 |\n| `signDateDay` | 签订日期 |\n\n### 10.2 插入规则\n\n- 编辑区光标处插入 `{{key}}`;\n- 存储为 HTML 文本,不做运行时强校验;\n- 缺失变量创建合同时按空字符串或业务默认值处理(由下游定义)。\n\n---\n\n## 11. 车型主数据\n\n品牌 · 型号下拉数据与车辆管理模块保持一致,当前原型内置示例:\n\n| 品牌 | 型号示例 |\n|---|---|\n| 现代 | 18吨氢燃料电池车、帕力安牌4.5吨冷链车 |\n| 苏龙 | 9.6米氢燃料电池车 |\n| 柳汽乘龙 | 9.6米氢燃料电池车 |\n| 飞驰 | 集卡头、半挂车 |\n| 跃进 | 4.2米冷链车 |\n| 宇通 | 氢燃料电池客车 |\n\n> 正式环境应由车辆主数据服务提供;绑定存储仅保存品牌 + 型号字符串。\n\n---\n\n## 12. 与其他模块的关系\n\n```mermaid\nflowchart LR\n A[合同模板管理] -->|已启用模板 + 章节内容| B[租赁合同创建]\n A -->|车型绑定口径| C[车辆管理]\n A -->|占位符变量定义| B\n B -->|订单车辆列表| A\n```\n\n| 模块 | 关系 |\n|---|---|\n| 租赁合同创建 | 创建流程第一步选择 **已启用** 的已发布模板;按订单车辆过滤条件条款与附件章节 |\n| 车辆管理 | 车型品牌/型号枚举口径一致 |\n| 非标准合同审批 | 合同正文出现 `data-nonstd-trigger` 时触发(标记规范已预留) |\n\n---\n\n## 13. 权限与审计(建议)\n\n| 能力 | 建议权限 |\n|---|---|\n| 查看列表 | 法务、客服(只读) |\n| 新增 / 编辑 / 发布 | 法务 |\n| 启用 / 停用 | 法务 |\n| 删除 | 法务负责人 |\n\n**审计字段**:`creator`、`createTime`、`updater`、`updateTime` 必须在每次保存时更新;发布变更写入 `versionLogs`。\n\n---\n\n## 14. 接口需求(建议)\n\n### 14.1 列表查询\n\n```\nGET /api/contract-templates?category=lease&fileName=&status=&creator=&enabled=&page=1&pageSize=10\n```\n\n响应需包含主表字段;章节摘要可列表内嵌或展开时按需加载。\n\n### 14.2 详情\n\n```\nGET /api/contract-templates/{id}\n```\n\n返回完整 `sectionContents`、`sectionVehicleBindings`、`versionLogs`。\n\n### 14.3 创建 / 更新\n\n```\nPOST /api/contract-templates\nPUT /api/contract-templates/{id}\n```\n\n请求体含章节 HTML 与绑定;`publish: boolean` 控制是否发布。\n\n### 14.4 启用 / 停用\n\n```\nPOST /api/contract-templates/{id}/enable\nPOST /api/contract-templates/{id}/disable\n```\n\n停用前需二次确认;启用中记录拒绝编辑/删除类写操作(由业务层校验)。\n\n### 14.5 删除\n\n```\nDELETE /api/contract-templates/{id}\n```\n\n拒绝删除 `enabled=true` 的记录。\n\n### 14.6 Word 导入\n\n```\nPOST /api/contract-templates/import-word\nContent-Type: multipart/form-data\n```\n\n返回拆分后的 `sectionContents` 供前端预览确认(或后端直接落库,由研发评估)。\n\n---\n\n## 15. 非功能需求\n\n| 类别 | 要求 |\n|---|---|\n| 性能 | 单份合同 HTML ≤ 2MB 时,章节切换 < 500ms(前端缓存) |\n| 兼容性 | Chrome / Edge 最新两个大版本;编辑区最低宽度 1280px 推荐 |\n| 可访问性 | 表单控件有 `aria-label`;操作按钮具备可读文案 |\n| 安全 | 导入 Word 需做 XSS 清洗;仅白名单标签/属性入库 |\n| 可追溯 | 已发布版本不可原地覆盖内容,只能新发版 |\n\n---\n\n## 16. 验收标准\n\n### 16.1 列表页\n\n- [ ] 筛选项包含:合同类型(可搜索下拉)、状态、创建人、启用状态\n- [ ] 主表含合同类型、启用状态列;操作含启用/停用、版本日志、编辑、删除\n- [ ] 启用中模板编辑/删除按钮禁用;停用需二次确认\n- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表\n- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数\n\n### 16.2 编辑页\n\n- [ ] 章节 Tab 仅在编辑页展示;横向滚动 + 章节列表面板\n- [ ] 导入 Word 后 9 章节正确拆分\n- [ ] 附件章节可绑定车型;各章节均可设条件条款\n- [ ] 预览支持连续/单页切换;各章节可模拟车型验证条件条款\n- [ ] 保存草稿无版本号;发布生成版本号\n- [ ] 编辑已发布(停用后)并发布时版本号递增,旧版写入版本日志\n- [ ] 编辑已发布(停用后)点「保存」不改变发布态与版本号\n- [ ] 有未保存修改时返回列表需确认\n\n### 16.3 跨模块与数据一致性\n\n- [ ] 模板管理停用后,租赁合同创建页不可再选用该模板\n- [ ] 租赁创建预览读取 store 中模板 `sectionContents`(与模板管理编辑一致)\n- [ ] 列表子表「风控条款数」与编辑区统计一致\n- [ ] 列表子表「条件条款数」与各章节实际标记数一致\n\n### 16.4 标注与文档(原型)\n\n- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录路由可切换列表页 / 新增页\n- [ ] `PRD.md` 与 `annotation-source.json` 与当前实现一致\n\n---\n\n## 17. 原型实现映射(供研发对照)\n\n| 能力 | 原型文件 / 符号 |\n|---|---|\n| 主逻辑 | `src/prototypes/contract-template-management/ContractTemplate.jsx` |\n| 跨模块模板状态 | `src/prototypes/contract-template-management/contract-template-store.js` |\n| 章节 HTML 合并 | `src/prototypes/contract-template-management/contract-template-section-html.js` |\n| 冷启动种子 | `src/prototypes/contract-template-management/contract-template-seed.js` |\n| 租赁侧消费目录 | `src/prototypes/contract-template-management/contract-template-catalog.js` |\n| 入口与标注 | `src/prototypes/contract-template-management/index.tsx` |\n| 标注数据 | `src/prototypes/contract-template-management/annotation-source.json` |\n| 下游选用 | `src/prototypes/lease-contract-management/LeaseContractCreate.jsx` |\n| 占位符与出租方 | `src/prototypes/contract-template-management/contract-template-vars.js` |\n| 样式 | `src/prototypes/contract-template-management/styles/` |\n\n> **原型限制**:模板数据经 `localStorage`(兼写 `sessionStorage`)持久化;冷启动由 `contract-template-seed.js` 注入种子,无需先打开模板管理页即可在租赁创建选用已启用模板。\n\n---\n\n## 18. 附录:停用确认弹窗(原文案)\n\n**标题**:确认停用\n\n**正文**:\n\n> 停用后,该模板将不可在租赁合同创建时选用,是否确定?\n\n**按钮**:取消 / 确定停用\n\n---\n\n## 19. 修订记录\n\n| 版本 | 日期 | 作者 | 说明 |\n|---|---|---|---|\n| v1.2 | 2026-06-29 | 产品 | 租赁预览贯通 store;冷启动种子;列表按合同类型筛选;移除 isDefault;localStorage 持久化;PRD 与实现对齐 |\n| v1.1 | 2026-06-29 | 产品 | 移除标准模板模型,改为启用/停用 + 版本日志;列表筛选与操作同步;各章节支持条件条款 |\n| v1.0 | 2026-06-25 | 产品 | 首版:Word 文档级模型、嵌套列表、章节拆分与条件条款 |\n",
+ "markdown": "# 合同模板管理 · 产品需求文档(PRD)\n\n| 项 | 内容 |\n|---|---|\n| 文档版本 | v1.4 |\n| 模块名称 | 合同模板管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 目标读者 | 前端 / 后端 / 测试 / 法务产品对接人 |\n| 交互原型 | `/prototypes/contract-template-management` |\n| 关联原型 | [车辆管理](/prototypes/vehicle-management)(车型主数据口径参考) |\n| 文档状态 | 已对齐原型(v1.4:章节 Tab 由 Word「标题」自动生成;列表筛选类型字段;未导入 Word 不展示章节导航) |\n\n---\n\n## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁合同以 Word 文档形式维护,内容长、结构复杂,包含合同主体与 8 个附件章节。不同车型可能需要不同的附件条款组合。法务人员需要:\n\n- 以 **Word 版式保真** 的方式维护整份合同;\n- 按 **章节** 管理内容,并配置 **适用车型**;\n- 发布 **版本**,通过 **启用/停用** 控制模板是否可供租赁合同选用;\n- 在合同主体及租赁订单~安全责任协议各章节中配置 **条件条款**(按订单车辆命中显隐)。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本并写入版本日志 |\n| 多套模板并存 | 正式/试用、不同车型等多套已发布模板可同时存在;租赁创建时由用户选择 |\n\n### 1.3 非目标(本期不做)\n\n- 采购合同、服务合同等其他分类的完整业务闭环(数据结构预留 `category`,当前仅启用 `lease`)\n- 在线多人协同编辑、审批流配置界面\n- Word 导出回写(本期以 HTML 结构化存储 + 预览为主)\n- 列表页顶部章节 Tab(**仅编辑页**保留章节 Tab)\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、启用/停用模板 |\n| 客服人员(间接用户) | 草拟新合同时从已启用模板中选择(下游「租赁合同管理」消费) |\n| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |\n\n### 2.2 核心用户故事\n\n1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。\n2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。\n3. **作为法务**,我希望在合同主体及租赁订单~安全责任协议各章节为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。\n4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。\n5. **作为法务**,我希望对已发布模板执行启用/停用,停用后方可编辑,并在发布变更时自动归档旧版快照到版本日志。\n6. **作为客服**,我希望在创建租赁合同时第一步选择要使用的已启用合同模板。\n7. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| Word 文档 / 模板记录 | 列表主表一行,对应一份完整合同文件 |\n| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |\n| 草稿 | `status = draft`,无版本号 |\n| 已发布 | `status = published`,有版本号 `vX.XX` |\n| 启用 | `enabled = true` 的已发布模板,可供租赁合同创建选用 |\n| 停用 | `enabled = false` 的已发布模板,不可编辑/删除,不可被租赁创建选用 |\n| 版本日志 | `versionLogs[]`,记录发布变更前自动归档的快照 |\n| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock=\"1\"` |\n| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline=\"1\"` |\n| 条件条款 | 各章节可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause=\"1\"` |\n| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |\n| 合同名称 | `contractName`,用户自定义展示名,如「正式合同」「试用合同」 |\n| 类型 | `contractType` 枚举:合同 / 授权委托书 / 转三方协议 / 其他 |\n| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |\n\n---\n\n## 4. 信息架构\n\n```text\n合同模板管理\n├── 列表页\n│ ├── 筛选区\n│ ├── 工具栏(新增)\n│ └── 嵌套表格\n│ ├── 主表:Word 文件\n│ └── 子表:9 个章节(展开行)\n└── 编辑 / 新增页\n ├── 顶栏(返回、保存、保存并发布)\n ├── 章节 Tab(仅本页展示)\n ├── 条件条款说明(各章节均可使用工具栏「条件」)\n ├── 适用车型绑定(附件章节 Tab 时显示)\n └── 工作区\n ├── 左侧:Word 版式编辑区\n └── 右侧:A4 实时预览\n```\n\n---\n\n## 5. 数据模型\n\n### 5.1 模板记录(Word 文档级)\n\n一条记录代表 **一份 Word 合同文件**,不按章节拆成多行。\n\n```typescript\ninterface ContractTemplateDocument {\n id: string; // 唯一 ID\n category: 'lease'; // 合同分类,当前固定商用车租赁\n contractName: string; // 合同名称,用户自定义,如「正式合同」\n contractType: string; // 类型枚举:合同 | 授权委托书 | 转三方协议 | 其他\n fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」\n versionName: string; // 展示用版本名称\n versionNo: string | null; // 已发布:v1.00;草稿:null\n status: 'draft' | 'published';\n enabled: boolean; // 已发布模板是否启用(草稿恒为 false)\n versionLogs: VersionLogEntry[]; // 版本变更日志与快照\n sectionContents: Record; // 各章节 HTML\n sectionVehicleBindings: Record; // 各章节车型绑定\n creator: string;\n createTime: string; // YYYY-MM-DD HH:mm\n updater: string;\n updateTime: string;\n remark?: string; // 内部备注(列表不展示)\n}\n\ninterface VersionLogEntry {\n id: string;\n versionNo: string; // 归档时的版本号\n action: string; // 如「发布变更」\n operator: string;\n time: string;\n remark?: string;\n snapshot: {\n fileName: string;\n versionName: string;\n versionNo: string;\n sectionContents: Record;\n sectionVehicleBindings: Record;\n remark?: string;\n archivedAt: string;\n };\n}\n\ninterface VehicleBinding {\n brand: string; // 品牌,如「现代」\n model: string; // 型号,如「18吨氢燃料电池车」\n}\n```\n\n### 5.2 章节枚举(SectionKey)\n\n| key | 章节名称 | 车型绑定 | 条件条款 |\n|---|---|:---:|:---:|\n| `main` | 合同主体 | 否 | 是 |\n| `lease_order` | 租赁订单 | 是 | 是 |\n| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |\n| `safety_notice` | 安全责任告知书 | 是 | 是 |\n| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |\n| `safety_commitment` | 安全责任承诺书 | 是 | 是 |\n| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |\n| `authorization` | 授权委托书 | 是 | 是 |\n| `safety_agreement` | 安全责任协议 | 是 | 是 |\n\n### 5.3 章节内容 HTML 标记规范\n\n研发需保证存储、渲染、统计口径一致:\n\n| 类型 | 选择器 / 属性 | 统计函数 |\n|---|---|---|\n| 锁定区 | `.ct-inline-lock[data-inline-lock=\"1\"]` | `countLockRegions` |\n| 风控红线 | `.ct-risk-redline[data-risk-redline=\"1\"]` | `countRiskRedlineRegions` |\n| 条件条款 | `.ct-vehicle-clause[data-vehicle-clause=\"1\"]` | `countVehicleClauseRegions` |\n| 条件条款车型 | `data-vehicle-bindings`(JSON 数组) | 预览命中逻辑 |\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 版本号规则\n\n- 格式:`v{major}.{minor}`,如 `v1.00`、`v1.01`\n- **仅发布时生成**;保存草稿时 `versionNo = null`\n- 同分类下递增:取当前已发布版本最大 minor +1;minor ≥ 99 时 major +1、minor 归 0\n- 首版默认 `v1.00`\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布(须先停用) |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**保持已发布与版本号**,仍为停用态 |\n| **保存并发布** | 新建已发布记录 + 版本号,默认启用 | 原记录变已发布 + 版本号,默认启用 | **同记录升级版本号**;旧内容写入 `versionLogs` 快照;发布后默认停用,需手动启用 |\n\n**校验**:保存/发布前须满足:\n\n- 合同主体(`sectionContents.main`)不能为空;\n- **合同名称**(`contractName`)已填写;\n- **类型**(`contractType`)已选择。\n\n**离开编辑**:有未保存修改时返回列表需二次确认。\n\n### 6.3 启用 / 停用规则\n\n1. 仅 **已发布** 模板具有启用态;草稿显示「—」。\n2. **启用中**:不可编辑、不可删除;可供租赁合同创建选用。\n3. **停用**:需二次确认(提示可能导致标准合同无法正常使用);停用后可编辑、可删除;租赁创建不可选用。\n4. **启用**:直接生效,无需二次确认。\n5. 编辑已发布模板并发布后,模板自动变为 **停用**,需重新启用后下游才可选。\n\n### 6.4 版本日志规则\n\n1. 对已发布模板执行「保存并发布」前,系统将 **当前内容自动归档** 为快照写入 `versionLogs`。\n2. 列表操作列提供「版本日志」,可查看历史版本号、操作人、时间与快照正文(只读)。\n3. 列表主表 **一行对应一份 Word 文件**,历史版本通过日志追溯,不另增列表行。\n\n### 6.5 删除规则\n\n- **启用中** 的已发布模板不可删除。\n- 其余记录可删除;删除前二次确认;删除后不可恢复。\n\n### 6.6 列表排序\n\n按 `updateTime` 降序。\n\n### 6.7 筛选匹配规则\n\n| 字段 | 匹配逻辑 |\n|---|---|\n| 合同名称 | 可搜索下拉;精确匹配 `contractName`(选项来自现有模板去重) |\n| 类型 | 下拉;精确匹配 `contractType` 枚举 |\n| 状态 | 精确匹配 `draft` / `published` |\n| 创建人 | 精确匹配 `creator` |\n| 启用状态 | `enabled`:仅已发布且启用;`disabled`:仅已发布且停用 |\n\n> **已移除**:筛选项「适用品牌/适用车型」、主表列「备注」、「设为标准模板」能力。\n\n---\n\n## 7. 列表页需求\n\n### 7.1 页面结构\n\n筛选区 + 工具栏 + 嵌套表格。**列表页不展示章节 Tab。**\n\n### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 可搜索下拉 | 选项来自现有模板 `contractName` 去重;支持输入模糊搜索 |\n| 类型 | 下拉 | 合同 / 授权委托书 / 转三方协议 / 其他 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**布局**:默认首行展示 4 项(合同名称、类型、状态、创建人);第 5 项「启用状态」收入「更多筛选」折叠区(与全局 `vm-filter` 规则一致)。扩展筛选项已设置时,折叠区自动展开并显示角标。\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认\n\n### 7.3 工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增 | 进入新增页,模式为 create;**未导入 Word 前不展示章节 Tab** |\n\n### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `contractName` | 展示用户自定义合同名称;同行展示启用状态 Tag |\n| 类型 | `contractType` | 展示类型枚举文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---\n\n## 8. 编辑 / 新增页需求\n\n### 8.1 页面结构\n\n顶栏 → 章节 Tab(导入 Word 后展示)→ 合同主体配置 / 车型绑定(按章节显隐)→ 左右分栏工作区。\n\n### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n### 8.3 章节 Tab\n\n- **新增模式**:未导入 Word 时 **不展示** 章节 Tab 导航(含「合同主体」)及合同主体配置卡片。\n- **编辑模式**:优先按记录已保存的 `sectionTabs` 展示;无自定义 Tabs 时按 §9 规则生成。\n- **Tab 生成逻辑(非固定列表)**:\n - 导入 Word 后,系统识别文档中被标记为 **「标题」** 样式的段落(Word 样式名如 `标题`、`标题1`~`标题3` 等,导入后映射为 `h1`~`h6`);\n - 每个标题段落的纯文本生成一个章节 Tab,Tab 文案与标题一致;\n - 第一个标题之前的内容归入 **「合同主体」** Tab(`main`);该 Tab 下编辑/预览 **整份 Word 合并全文**;\n - 其余 Tab 仅展示对应标题至下一标题之间的切片内容;\n - 若文档中 **未识别到任何标题**,回退为按 §9.2 预置章节标记切分的默认章节 Tab(与演示模板一致)。\n- **展示**:横向滚动 Tab 条展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n\n### 8.4 合同主体配置(仅合同主体 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 合同名称输入框 | **必填**;用户自行填写,如「正式合同」 |\n| 类型下拉 | **必填**;选项:合同 / 授权委托书 / 转三方协议 / 其他 |\n| 展示位置 | 章节 Tab 下方配置卡片,与附件章节的「适用车型」卡片交互一致 |\n| 用途 | 同步至列表页「合同名称」「类型」列;租赁合同创建时按合同名称筛选模板 |\n\n**规则**:\n\n- 仅当存在 **合同主体** 章节 Tab 且当前选中该 Tab 时显示本卡片(未导入 Word 时不展示)。\n- 未填写合同名称时无法保存或发布,并提示「请输入合同名称」。\n- 未选择类型时无法保存或发布,并提示「请选择类型」。\n- 切换至租赁订单等附件章节时,显示「适用车型」卡片,本卡片隐藏。\n- 保存/发布时分别写入 `contractName`、`contractType` 字段。\n\n### 8.5 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。适用于合同主体及全部附件章节。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n### 8.6 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。\n\n### 8.7 编辑区(左侧)\n\n#### 8.7.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.7.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.7.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n### 8.8 实时预览(右侧)\n\n#### 8.8.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.8.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.8.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.8.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充\n\n### 8.9 空状态\n\n编辑区无内容时,预览区展示空状态引导;新增模式下突出「导入 Word 模板」按钮。\n\n---\n\n## 9. Word 导入与章节拆分\n\n### 9.1 导入范围\n\n- 一次导入 **整份合同**;\n- 导入后写入各 `sectionContents[key]`,并展示章节 Tab;\n- 导入后默认进入「合同主体」章节编辑。\n\n### 9.2 拆分策略\n\n**优先:按 Word「标题」样式生成章节 Tab**\n\n1. 对导入 HTML 做 Word 导出清洗(`sanitizeWordExportHtml`);\n2. 扫描 `h1`~`h6` 标题节点(由 Word「标题」类样式映射而来),每个标题文本生成一个章节 Tab;\n3. 第一个标题之前的内容写入 `main`(合同主体);各标题至下一标题之间的片段写入对应 Tab;\n4. 将结果写入 `sectionTabs` 与各 `sectionContents[key]`。\n\n**回退:按预置章节标记切分(无标题时)**\n\n1. 从附件章节起,按 **章节标记字符串** 定位切分点;\n2. 切分点优先落在 ``、``、`` 之后,避免打断标签;\n3. `main` = 第一个切分点之前的全文;\n4. 各附件章节 = 相邻切分点之间的片段;\n5. 使用默认 `DOCUMENT_SECTION_TABS` 作为 Tab 列表。\n\n### 9.3 章节标记(节选)\n\n研发需维护可扩展的 `markers` 列表,示例:\n\n| 章节 | 标记示例 |\n|---|---|\n| 租赁订单 | `附件 1:租赁订单`、`附件1:租赁订单` |\n| 车辆产生的费用 | `附件2:车辆产生的费用` |\n| 安全责任告知书 | `附件3:安全责任告知书` |\n| … | 完整列表见原型 `DOCUMENT_SECTION_TABS` |\n\n### 9.4 误匹配过滤\n\n以下情况 **不应** 作为切分点:\n\n- 标记出现在「详见附件…」「以附件…」等引用语境;\n- 标记过短且不在 `` 标题语境内;\n- 标记前为「(」「、」等不完整标题上下文。\n\n---\n\n## 10. 模板占位符\n\n### 10.1 变量分组\n\n**甲方签约主体**(创建合同时按所选签约公司自动替换):\n\n| key | 标签 |\n|---|---|\n| `lessorName` | 甲方全称 |\n| `lessorAddress` | 甲方地址 |\n| `lessorContact` | 甲方联系人 |\n| `lessorPhone` | 甲方电话 |\n| `lessorEmail` | 甲方邮箱 |\n| `lessorAccountName` | 收款户名 |\n| `lessorBankName` | 开户行 |\n| `lessorBankAccount` | 银行账号 |\n\n**合同通用**(创建合同时由系统或客服填写):\n\n| key | 标签 |\n|---|---|\n| `contractCode` | 合同编号 |\n| `customerName` | 乙方公司全称 |\n| `creditCode` | 乙方统一社会信用代码 |\n| `customerAddress` | 乙方地址 |\n| `customerContact` | 乙方联系人 |\n| `customerPhone` | 乙方联系电话 |\n| `signDateYear` | 签订年份 |\n| `signDateMonth` | 签订月份 |\n| `signDateDay` | 签订日期 |\n\n### 10.2 插入规则\n\n- 编辑区光标处插入 `{{key}}`;\n- 存储为 HTML 文本,不做运行时强校验;\n- 缺失变量创建合同时按空字符串或业务默认值处理(由下游定义)。\n\n---\n\n## 11. 车型主数据\n\n品牌 · 型号下拉数据与车辆管理模块保持一致,当前原型内置示例:\n\n| 品牌 | 型号示例 |\n|---|---|\n| 现代 | 18吨氢燃料电池车、帕力安牌4.5吨冷链车 |\n| 苏龙 | 9.6米氢燃料电池车 |\n| 柳汽乘龙 | 9.6米氢燃料电池车 |\n| 飞驰 | 集卡头、半挂车 |\n| 跃进 | 4.2米冷链车 |\n| 宇通 | 氢燃料电池客车 |\n\n> 正式环境应由车辆主数据服务提供;绑定存储仅保存品牌 + 型号字符串。\n\n---\n\n## 12. 与其他模块的关系\n\n```mermaid\nflowchart LR\n A[合同模板管理] -->|已启用模板 + 章节内容| B[租赁合同创建]\n A -->|车型绑定口径| C[车辆管理]\n A -->|占位符变量定义| B\n B -->|订单车辆列表| A\n```\n\n| 模块 | 关系 |\n|---|---|\n| 租赁合同创建 | 创建流程第一步选择 **已启用** 的已发布模板;按订单车辆过滤条件条款与附件章节 |\n| 车辆管理 | 车型品牌/型号枚举口径一致 |\n| 非标准合同审批 | 合同正文出现 `data-nonstd-trigger` 时触发(标记规范已预留) |\n\n---\n\n## 13. 权限与审计(建议)\n\n| 能力 | 建议权限 |\n|---|---|\n| 查看列表 | 法务、客服(只读) |\n| 新增 / 编辑 / 发布 | 法务 |\n| 启用 / 停用 | 法务 |\n| 删除 | 法务负责人 |\n\n**审计字段**:`creator`、`createTime`、`updater`、`updateTime` 必须在每次保存时更新;发布变更写入 `versionLogs`。\n\n---\n\n## 14. 接口需求(建议)\n\n### 14.1 列表查询\n\n```\nGET /api/contract-templates?category=lease&fileName=&status=&creator=&enabled=&page=1&pageSize=10\n```\n\n响应需包含主表字段;章节摘要可列表内嵌或展开时按需加载。\n\n### 14.2 详情\n\n```\nGET /api/contract-templates/{id}\n```\n\n返回完整 `sectionContents`、`sectionVehicleBindings`、`versionLogs`。\n\n### 14.3 创建 / 更新\n\n```\nPOST /api/contract-templates\nPUT /api/contract-templates/{id}\n```\n\n请求体含章节 HTML 与绑定;`publish: boolean` 控制是否发布。\n\n### 14.4 启用 / 停用\n\n```\nPOST /api/contract-templates/{id}/enable\nPOST /api/contract-templates/{id}/disable\n```\n\n停用前需二次确认;启用中记录拒绝编辑/删除类写操作(由业务层校验)。\n\n### 14.5 删除\n\n```\nDELETE /api/contract-templates/{id}\n```\n\n拒绝删除 `enabled=true` 的记录。\n\n### 14.6 Word 导入\n\n```\nPOST /api/contract-templates/import-word\nContent-Type: multipart/form-data\n```\n\n返回拆分后的 `sectionContents` 供前端预览确认(或后端直接落库,由研发评估)。\n\n---\n\n## 15. 非功能需求\n\n| 类别 | 要求 |\n|---|---|\n| 性能 | 单份合同 HTML ≤ 2MB 时,章节切换 < 500ms(前端缓存) |\n| 兼容性 | Chrome / Edge 最新两个大版本;编辑区最低宽度 1280px 推荐 |\n| 可访问性 | 表单控件有 `aria-label`;操作按钮具备可读文案 |\n| 安全 | 导入 Word 需做 XSS 清洗;仅白名单标签/属性入库 |\n| 可追溯 | 已发布版本不可原地覆盖内容,只能新发版 |\n\n---\n\n## 16. 验收标准\n\n### 16.1 列表页\n\n- [ ] 筛选项包含:合同名称(可搜索下拉)、类型、状态、创建人、启用状态;超过 4 项时默认折叠扩展筛选项\n- [ ] 主表含合同名称、类型列;操作含启用/停用、版本日志、编辑、删除\n- [ ] 启用中模板编辑/删除按钮禁用;停用需二次确认\n- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表\n- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数\n\n### 16.2 编辑页\n\n- [ ] 章节 Tab 仅在编辑页展示;横向滚动 + 章节列表面板\n- [ ] 新增页未导入 Word 时不展示章节 Tab 与合同主体配置卡片\n- [ ] 导入 Word 后章节 Tab 按「标题」样式自动生成(无标题时回退默认标记拆分)\n- [ ] 附件章节可绑定车型;各章节均可设条件条款\n- [ ] 预览支持连续/单页切换;各章节可模拟车型验证条件条款\n- [ ] 保存草稿无版本号;发布生成版本号\n- [ ] 编辑已发布(停用后)并发布时版本号递增,旧版写入版本日志\n- [ ] 编辑已发布(停用后)点「保存」不改变发布态与版本号\n- [ ] 有未保存修改时返回列表需确认\n\n### 16.3 跨模块与数据一致性\n\n- [ ] 模板管理停用后,租赁合同创建页不可再选用该模板\n- [ ] 租赁创建预览读取 store 中模板 `sectionContents`(与模板管理编辑一致)\n- [ ] 列表子表「风控条款数」与编辑区统计一致\n- [ ] 列表子表「条件条款数」与各章节实际标记数一致\n\n### 16.4 标注与文档(原型)\n\n- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录路由可切换列表页 / 新增页\n- [ ] `PRD.md` 与 `annotation-source.json` 与当前实现一致\n\n---\n\n## 17. 原型实现映射(供研发对照)\n\n| 能力 | 原型文件 / 符号 |\n|---|---|\n| 主逻辑 | `src/prototypes/contract-template-management/ContractTemplate.jsx` |\n| 跨模块模板状态 | `src/prototypes/contract-template-management/contract-template-store.js` |\n| 章节 HTML 合并 | `src/prototypes/contract-template-management/contract-template-section-html.js` |\n| 冷启动种子 | `src/prototypes/contract-template-management/contract-template-seed.js` |\n| 租赁侧消费目录 | `src/prototypes/contract-template-management/contract-template-catalog.js` |\n| 入口与标注 | `src/prototypes/contract-template-management/index.tsx` |\n| 标注数据 | `src/prototypes/contract-template-management/annotation-source.json` |\n| 下游选用 | `src/prototypes/lease-contract-management/LeaseContractCreate.jsx` |\n| 占位符与出租方 | `src/prototypes/contract-template-management/contract-template-vars.js` |\n| 样式 | `src/prototypes/contract-template-management/styles/` |\n\n> **原型限制**:模板数据经 `localStorage`(兼写 `sessionStorage`)持久化;冷启动由 `contract-template-seed.js` 注入种子,无需先打开模板管理页即可在租赁创建选用已启用模板。\n\n---\n\n## 18. 附录:停用确认弹窗(原文案)\n\n**标题**:确认停用\n\n**正文**:\n\n> 停用后,该模板将不可在租赁合同创建时选用,是否确定?\n\n**按钮**:取消 / 确定停用\n\n---\n\n## 19. 修订记录\n\n| 版本 | 日期 | 作者 | 说明 |\n|---|---|---|---|\n| v1.4 | 2026-07-07 | 产品 | 章节 Tab 改为由 Word「标题」样式自动生成;明确未导入 Word 不展示章节导航;列表筛选与列名统一为「类型」 |\n| v1.3 | 2026-07-07 | 产品 | 合同名称与类型字段拆分;列表筛选增加类型与折叠;新增页未导入 Word 不展示章节 Tab;PRD 与原型对齐 |\n| v1.2 | 2026-06-29 | 产品 | 租赁预览贯通 store;冷启动种子;列表按合同类型筛选;移除 isDefault;localStorage 持久化;PRD 与实现对齐 |\n| v1.1 | 2026-06-29 | 产品 | 移除标准模板模型,改为启用/停用 + 版本日志;列表筛选与操作同步;各章节支持条件条款 |\n| v1.0 | 2026-06-25 | 产品 | 首版:Word 文档级模型、嵌套列表、章节拆分与条件条款 |\n",
"markdownPath": "src/resources/contract-template-management/PRD.md"
},
{
"type": "markdown",
"id": "ct-doc-prd-overview",
"title": "背景 · 数据模型 · 业务规则",
- "markdown": "## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁合同以 Word 文档形式维护,内容长、结构复杂,包含合同主体与 8 个附件章节。不同车型可能需要不同的附件条款组合。法务人员需要:\n\n- 以 **Word 版式保真** 的方式维护整份合同;\n- 按 **章节** 管理内容,并配置 **适用车型**;\n- 发布 **版本**,通过 **启用/停用** 控制模板是否可供租赁合同选用;\n- 在合同主体及租赁订单~安全责任协议各章节中配置 **条件条款**(按订单车辆命中显隐)。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本并写入版本日志 |\n| 多套模板并存 | 正式/试用、不同车型等多套已发布模板可同时存在;租赁创建时由用户选择 |\n\n### 1.3 非目标(本期不做)\n\n- 采购合同、服务合同等其他分类的完整业务闭环(数据结构预留 `category`,当前仅启用 `lease`)\n- 在线多人协同编辑、审批流配置界面\n- Word 导出回写(本期以 HTML 结构化存储 + 预览为主)\n- 列表页顶部章节 Tab(**仅编辑页**保留章节 Tab)\n\n---\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、启用/停用模板 |\n| 客服人员(间接用户) | 草拟新合同时从已启用模板中选择(下游「租赁合同管理」消费) |\n| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |\n\n### 2.2 核心用户故事\n\n1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。\n2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。\n3. **作为法务**,我希望在合同主体及租赁订单~安全责任协议各章节为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。\n4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。\n5. **作为法务**,我希望对已发布模板执行启用/停用,停用后方可编辑,并在发布变更时自动归档旧版快照到版本日志。\n6. **作为客服**,我希望在创建租赁合同时第一步选择要使用的已启用合同模板。\n7. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。\n\n---\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| Word 文档 / 模板记录 | 列表主表一行,对应一份完整合同文件 |\n| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |\n| 草稿 | `status = draft`,无版本号 |\n| 已发布 | `status = published`,有版本号 `vX.XX` |\n| 启用 | `enabled = true` 的已发布模板,可供租赁合同创建选用 |\n| 停用 | `enabled = false` 的已发布模板,不可编辑/删除,不可被租赁创建选用 |\n| 版本日志 | `versionLogs[]`,记录发布变更前自动归档的快照 |\n| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock=\"1\"` |\n| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline=\"1\"` |\n| 条件条款 | 各章节可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause=\"1\"` |\n| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |\n| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |\n\n---\n\n---\n\n## 4. 信息架构\n\n```text\n合同模板管理\n├── 列表页\n│ ├── 筛选区\n│ ├── 工具栏(新增)\n│ └── 嵌套表格\n│ ├── 主表:Word 文件\n│ └── 子表:9 个章节(展开行)\n└── 编辑 / 新增页\n ├── 顶栏(返回、保存、保存并发布)\n ├── 章节 Tab(仅本页展示)\n ├── 条件条款说明(各章节均可使用工具栏「条件」)\n ├── 适用车型绑定(附件章节 Tab 时显示)\n └── 工作区\n ├── 左侧:Word 版式编辑区\n └── 右侧:A4 实时预览\n```\n\n---\n\n---\n\n## 5. 数据模型\n\n### 5.1 模板记录(Word 文档级)\n\n一条记录代表 **一份 Word 合同文件**,不按章节拆成多行。\n\n```typescript\ninterface ContractTemplateDocument {\n id: string; // 唯一 ID\n category: 'lease'; // 合同分类,当前固定商用车租赁\n contractType: string; // 用户自定义合同类型,如「正式合同」「试用合同」\n fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」\n versionName: string; // 展示用版本名称\n versionNo: string | null; // 已发布:v1.00;草稿:null\n status: 'draft' | 'published';\n enabled: boolean; // 已发布模板是否启用(草稿恒为 false)\n versionLogs: VersionLogEntry[]; // 版本变更日志与快照\n sectionContents: Record; // 各章节 HTML\n sectionVehicleBindings: Record; // 各章节车型绑定\n creator: string;\n createTime: string; // YYYY-MM-DD HH:mm\n updater: string;\n updateTime: string;\n remark?: string; // 内部备注(列表不展示)\n}\n\ninterface VersionLogEntry {\n id: string;\n versionNo: string; // 归档时的版本号\n action: string; // 如「发布变更」\n operator: string;\n time: string;\n remark?: string;\n snapshot: {\n fileName: string;\n versionName: string;\n versionNo: string;\n sectionContents: Record;\n sectionVehicleBindings: Record;\n remark?: string;\n archivedAt: string;\n };\n}\n\ninterface VehicleBinding {\n brand: string; // 品牌,如「现代」\n model: string; // 型号,如「18吨氢燃料电池车」\n}\n```\n\n### 5.2 章节枚举(SectionKey)\n\n| key | 章节名称 | 车型绑定 | 条件条款 |\n|---|---|:---:|:---:|\n| `main` | 合同主体 | 否 | 是 |\n| `lease_order` | 租赁订单 | 是 | 是 |\n| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |\n| `safety_notice` | 安全责任告知书 | 是 | 是 |\n| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |\n| `safety_commitment` | 安全责任承诺书 | 是 | 是 |\n| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |\n| `authorization` | 授权委托书 | 是 | 是 |\n| `safety_agreement` | 安全责任协议 | 是 | 是 |\n\n### 5.3 章节内容 HTML 标记规范\n\n研发需保证存储、渲染、统计口径一致:\n\n| 类型 | 选择器 / 属性 | 统计函数 |\n|---|---|---|\n| 锁定区 | `.ct-inline-lock[data-inline-lock=\"1\"]` | `countLockRegions` |\n| 风控红线 | `.ct-risk-redline[data-risk-redline=\"1\"]` | `countRiskRedlineRegions` |\n| 条件条款 | `.ct-vehicle-clause[data-vehicle-clause=\"1\"]` | `countVehicleClauseRegions` |\n| 条件条款车型 | `data-vehicle-bindings`(JSON 数组) | 预览命中逻辑 |\n\n---\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 版本号规则\n\n- 格式:`v{major}.{minor}`,如 `v1.00`、`v1.01`\n- **仅发布时生成**;保存草稿时 `versionNo = null`\n- 同分类下递增:取当前已发布版本最大 minor +1;minor ≥ 99 时 major +1、minor 归 0\n- 首版默认 `v1.00`\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布(须先停用) |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**保持已发布与版本号**,仍为停用态 |\n| **保存并发布** | 新建已发布记录 + 版本号,默认启用 | 原记录变已发布 + 版本号,默认启用 | **同记录升级版本号**;旧内容写入 `versionLogs` 快照;发布后默认停用,需手动启用 |\n\n**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。\n\n**离开编辑**:有未保存修改时返回列表需二次确认。\n\n### 6.3 启用 / 停用规则\n\n1. 仅 **已发布** 模板具有启用态;草稿显示「—」。\n2. **启用中**:不可编辑、不可删除;可供租赁合同创建选用。\n3. **停用**:需二次确认(提示可能导致标准合同无法正常使用);停用后可编辑、可删除;租赁创建不可选用。\n4. **启用**:直接生效,无需二次确认。\n5. 编辑已发布模板并发布后,模板自动变为 **停用**,需重新启用后下游才可选。\n\n### 6.4 版本日志规则\n\n1. 对已发布模板执行「保存并发布」前,系统将 **当前内容自动归档** 为快照写入 `versionLogs`。\n2. 列表操作列提供「版本日志」,可查看历史版本号、操作人、时间与快照正文(只读)。\n3. 列表主表 **一行对应一份 Word 文件**,历史版本通过日志追溯,不另增列表行。\n\n### 6.5 删除规则\n\n- **启用中** 的已发布模板不可删除。\n- 其余记录可删除;删除前二次确认;删除后不可恢复。\n\n### 6.6 列表排序\n\n按 `updateTime` 降序。\n\n### 6.7 筛选匹配规则\n\n| 字段 | 匹配逻辑 |\n|---|---|\n| 合同类型 | 下拉选择(支持输入模糊搜索候选项);精确匹配 `contractType` |\n| 状态 | 精确匹配 `draft` / `published` |\n| 创建人 | 精确匹配 `creator` |\n| 启用状态 | `enabled`:仅已发布且启用;`disabled`:仅已发布且停用 |\n\n> **已移除**:筛选项「适用品牌/适用车型」、主表列「备注」、「设为标准模板」能力。\n\n---"
+ "markdown": "## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁合同以 Word 文档形式维护,内容长、结构复杂,包含合同主体与 8 个附件章节。不同车型可能需要不同的附件条款组合。法务人员需要:\n\n- 以 **Word 版式保真** 的方式维护整份合同;\n- 按 **章节** 管理内容,并配置 **适用车型**;\n- 发布 **版本**,通过 **启用/停用** 控制模板是否可供租赁合同选用;\n- 在合同主体及租赁订单~安全责任协议各章节中配置 **条件条款**(按订单车辆命中显隐)。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |\n| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |\n| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |\n| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本并写入版本日志 |\n| 多套模板并存 | 正式/试用、不同车型等多套已发布模板可同时存在;租赁创建时由用户选择 |\n\n### 1.3 非目标(本期不做)\n\n- 采购合同、服务合同等其他分类的完整业务闭环(数据结构预留 `category`,当前仅启用 `lease`)\n- 在线多人协同编辑、审批流配置界面\n- Word 导出回写(本期以 HTML 结构化存储 + 预览为主)\n- 列表页顶部章节 Tab(**仅编辑页**保留章节 Tab)\n\n---\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、启用/停用模板 |\n| 客服人员(间接用户) | 草拟新合同时从已启用模板中选择(下游「租赁合同管理」消费) |\n| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |\n\n### 2.2 核心用户故事\n\n1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。\n2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。\n3. **作为法务**,我希望在合同主体及租赁订单~安全责任协议各章节为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。\n4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。\n5. **作为法务**,我希望对已发布模板执行启用/停用,停用后方可编辑,并在发布变更时自动归档旧版快照到版本日志。\n6. **作为客服**,我希望在创建租赁合同时第一步选择要使用的已启用合同模板。\n7. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。\n\n---\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| Word 文档 / 模板记录 | 列表主表一行,对应一份完整合同文件 |\n| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |\n| 草稿 | `status = draft`,无版本号 |\n| 已发布 | `status = published`,有版本号 `vX.XX` |\n| 启用 | `enabled = true` 的已发布模板,可供租赁合同创建选用 |\n| 停用 | `enabled = false` 的已发布模板,不可编辑/删除,不可被租赁创建选用 |\n| 版本日志 | `versionLogs[]`,记录发布变更前自动归档的快照 |\n| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock=\"1\"` |\n| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline=\"1\"` |\n| 条件条款 | 各章节可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause=\"1\"` |\n| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |\n| 合同名称 | `contractName`,用户自定义展示名,如「正式合同」「试用合同」 |\n| 类型 | `contractType` 枚举:合同 / 授权委托书 / 转三方协议 / 其他 |\n| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |\n\n---\n\n---\n\n## 4. 信息架构\n\n```text\n合同模板管理\n├── 列表页\n│ ├── 筛选区\n│ ├── 工具栏(新增)\n│ └── 嵌套表格\n│ ├── 主表:Word 文件\n│ └── 子表:9 个章节(展开行)\n└── 编辑 / 新增页\n ├── 顶栏(返回、保存、保存并发布)\n ├── 章节 Tab(仅本页展示)\n ├── 条件条款说明(各章节均可使用工具栏「条件」)\n ├── 适用车型绑定(附件章节 Tab 时显示)\n └── 工作区\n ├── 左侧:Word 版式编辑区\n └── 右侧:A4 实时预览\n```\n\n---\n\n---\n\n## 5. 数据模型\n\n### 5.1 模板记录(Word 文档级)\n\n一条记录代表 **一份 Word 合同文件**,不按章节拆成多行。\n\n```typescript\ninterface ContractTemplateDocument {\n id: string; // 唯一 ID\n category: 'lease'; // 合同分类,当前固定商用车租赁\n contractName: string; // 合同名称,用户自定义,如「正式合同」\n contractType: string; // 类型枚举:合同 | 授权委托书 | 转三方协议 | 其他\n fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」\n versionName: string; // 展示用版本名称\n versionNo: string | null; // 已发布:v1.00;草稿:null\n status: 'draft' | 'published';\n enabled: boolean; // 已发布模板是否启用(草稿恒为 false)\n versionLogs: VersionLogEntry[]; // 版本变更日志与快照\n sectionContents: Record; // 各章节 HTML\n sectionVehicleBindings: Record; // 各章节车型绑定\n creator: string;\n createTime: string; // YYYY-MM-DD HH:mm\n updater: string;\n updateTime: string;\n remark?: string; // 内部备注(列表不展示)\n}\n\ninterface VersionLogEntry {\n id: string;\n versionNo: string; // 归档时的版本号\n action: string; // 如「发布变更」\n operator: string;\n time: string;\n remark?: string;\n snapshot: {\n fileName: string;\n versionName: string;\n versionNo: string;\n sectionContents: Record;\n sectionVehicleBindings: Record;\n remark?: string;\n archivedAt: string;\n };\n}\n\ninterface VehicleBinding {\n brand: string; // 品牌,如「现代」\n model: string; // 型号,如「18吨氢燃料电池车」\n}\n```\n\n### 5.2 章节枚举(SectionKey)\n\n| key | 章节名称 | 车型绑定 | 条件条款 |\n|---|---|:---:|:---:|\n| `main` | 合同主体 | 否 | 是 |\n| `lease_order` | 租赁订单 | 是 | 是 |\n| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |\n| `safety_notice` | 安全责任告知书 | 是 | 是 |\n| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |\n| `safety_commitment` | 安全责任承诺书 | 是 | 是 |\n| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |\n| `authorization` | 授权委托书 | 是 | 是 |\n| `safety_agreement` | 安全责任协议 | 是 | 是 |\n\n### 5.3 章节内容 HTML 标记规范\n\n研发需保证存储、渲染、统计口径一致:\n\n| 类型 | 选择器 / 属性 | 统计函数 |\n|---|---|---|\n| 锁定区 | `.ct-inline-lock[data-inline-lock=\"1\"]` | `countLockRegions` |\n| 风控红线 | `.ct-risk-redline[data-risk-redline=\"1\"]` | `countRiskRedlineRegions` |\n| 条件条款 | `.ct-vehicle-clause[data-vehicle-clause=\"1\"]` | `countVehicleClauseRegions` |\n| 条件条款车型 | `data-vehicle-bindings`(JSON 数组) | 预览命中逻辑 |\n\n---\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 版本号规则\n\n- 格式:`v{major}.{minor}`,如 `v1.00`、`v1.01`\n- **仅发布时生成**;保存草稿时 `versionNo = null`\n- 同分类下递增:取当前已发布版本最大 minor +1;minor ≥ 99 时 major +1、minor 归 0\n- 首版默认 `v1.00`\n\n### 6.2 保存与发布\n\n| 操作 | 新增模式 | 编辑草稿 | 编辑已发布(须先停用) |\n|---|---|---|---|\n| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**保持已发布与版本号**,仍为停用态 |\n| **保存并发布** | 新建已发布记录 + 版本号,默认启用 | 原记录变已发布 + 版本号,默认启用 | **同记录升级版本号**;旧内容写入 `versionLogs` 快照;发布后默认停用,需手动启用 |\n\n**校验**:保存/发布前须满足:\n\n- 合同主体(`sectionContents.main`)不能为空;\n- **合同名称**(`contractName`)已填写;\n- **类型**(`contractType`)已选择。\n\n**离开编辑**:有未保存修改时返回列表需二次确认。\n\n### 6.3 启用 / 停用规则\n\n1. 仅 **已发布** 模板具有启用态;草稿显示「—」。\n2. **启用中**:不可编辑、不可删除;可供租赁合同创建选用。\n3. **停用**:需二次确认(提示可能导致标准合同无法正常使用);停用后可编辑、可删除;租赁创建不可选用。\n4. **启用**:直接生效,无需二次确认。\n5. 编辑已发布模板并发布后,模板自动变为 **停用**,需重新启用后下游才可选。\n\n### 6.4 版本日志规则\n\n1. 对已发布模板执行「保存并发布」前,系统将 **当前内容自动归档** 为快照写入 `versionLogs`。\n2. 列表操作列提供「版本日志」,可查看历史版本号、操作人、时间与快照正文(只读)。\n3. 列表主表 **一行对应一份 Word 文件**,历史版本通过日志追溯,不另增列表行。\n\n### 6.5 删除规则\n\n- **启用中** 的已发布模板不可删除。\n- 其余记录可删除;删除前二次确认;删除后不可恢复。\n\n### 6.6 列表排序\n\n按 `updateTime` 降序。\n\n### 6.7 筛选匹配规则\n\n| 字段 | 匹配逻辑 |\n|---|---|\n| 合同名称 | 可搜索下拉;精确匹配 `contractName`(选项来自现有模板去重) |\n| 类型 | 下拉;精确匹配 `contractType` 枚举 |\n| 状态 | 精确匹配 `draft` / `published` |\n| 创建人 | 精确匹配 `creator` |\n| 启用状态 | `enabled`:仅已发布且启用;`disabled`:仅已发布且停用 |\n\n> **已移除**:筛选项「适用品牌/适用车型」、主表列「备注」、「设为标准模板」能力。\n\n---"
},
{
"type": "markdown",
"id": "ct-doc-prd-list",
"title": "列表页 PRD",
- "markdown": "# 合同模板管理 · 列表页 PRD\n\n## 7. 列表页需求\n\n### 7.1 页面结构\n\n筛选区 + 工具栏 + 嵌套表格。**列表页不展示章节 Tab。**\n\n### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同类型 | 可搜索下拉 | 选项来自现有模板 `contractType` 去重;支持输入模糊搜索 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认\n\n### 7.3 工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增 | 进入新增页,默认章节为「合同主体」,模式为 create |\n\n### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同类型 | `contractType` | 展示用户自定义的合同类型文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---"
+ "markdown": "# 合同模板管理 · 列表页 PRD\n\n## 7. 列表页需求\n\n### 7.1 页面结构\n\n筛选区 + 工具栏 + 嵌套表格。**列表页不展示章节 Tab。**\n\n### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 可搜索下拉 | 选项来自现有模板 `contractName` 去重;支持输入模糊搜索 |\n| 类型 | 下拉 | 合同 / 授权委托书 / 转三方协议 / 其他 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**布局**:默认首行展示 4 项(合同名称、类型、状态、创建人);第 5 项「启用状态」收入「更多筛选」折叠区(与全局 `vm-filter` 规则一致)。扩展筛选项已设置时,折叠区自动展开并显示角标。\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认\n\n### 7.3 工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 新增 | 进入新增页,模式为 create;**未导入 Word 前不展示章节 Tab** |\n\n### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `contractName` | 展示用户自定义合同名称;同行展示启用状态 Tag |\n| 类型 | `contractType` | 展示类型枚举文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---"
},
{
"type": "markdown",
"id": "ct-doc-prd-editor",
"title": "编辑页 PRD",
- "markdown": "# 合同模板管理 · 编辑/新增页 PRD\n\n## 8. 编辑 / 新增页需求\n\n### 8.1 页面结构\n\n顶栏 → 章节 Tab → 条件说明 / 车型绑定(按章节显隐)→ 左右分栏工作区。\n\n### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n### 8.3 章节 Tab\n\n- 共 9 个章节;**横向滚动 Tab 条**展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。\n- **其他 Tab**:仅展示该章节切片内容。\n\n章节顺序:\n\n1. 合同主体 \n2. 租赁订单 \n3. 车辆产生的费用 \n4. 安全责任告知书 \n5. 重型普通货运车辆租赁动态监管责任告知与确认书 \n6. 安全责任承诺书 \n7. 车辆使用规范和驾驶员要求 \n8. 授权委托书 \n9. 安全责任协议 \n\n### 8.4 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。适用于合同主体及全部附件章节。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n### 8.5 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。\n\n### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.6.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充\n\n### 8.8 空状态\n\n编辑区无内容时,预览区展示空状态引导;新增模式下突出「导入 Word 模板」按钮。\n\n---"
+ "markdown": "# 合同模板管理 · 编辑/新增页 PRD\n\n## 8. 编辑 / 新增页需求\n\n### 8.1 页面结构\n\n顶栏 → 章节 Tab(导入 Word 后展示)→ 合同主体配置 / 车型绑定(按章节显隐)→ 左右分栏工作区。\n\n### 8.2 顶栏\n\n| 按钮 | 行为 |\n|---|---|\n| ← 返回列表 | 回到列表页,不自动保存 |\n| 保存 | 见 §6.2 |\n| 保存并发布 | 见 §6.2 |\n\n### 8.3 章节 Tab\n\n- **新增模式**:未导入 Word 时 **不展示** 章节 Tab 导航(含「合同主体」)及合同主体配置卡片。\n- **编辑模式**:优先按记录已保存的 `sectionTabs` 展示;无自定义 Tabs 时按 §9 规则生成。\n- **Tab 生成逻辑(非固定列表)**:\n - 导入 Word 后,系统识别文档中被标记为 **「标题」** 样式的段落(Word 样式名如 `标题`、`标题1`~`标题3` 等,导入后映射为 `h1`~`h6`);\n - 每个标题段落的纯文本生成一个章节 Tab,Tab 文案与标题一致;\n - 第一个标题之前的内容归入 **「合同主体」** Tab(`main`);该 Tab 下编辑/预览 **整份 Word 合并全文**;\n - 其余 Tab 仅展示对应标题至下一标题之间的切片内容;\n - 若文档中 **未识别到任何标题**,回退为按 §9.2 预置章节标记切分的默认章节 Tab(与演示模板一致)。\n- **展示**:横向滚动 Tab 条展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n\n### 8.4 合同主体配置(仅合同主体 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 合同名称输入框 | **必填**;用户自行填写,如「正式合同」 |\n| 类型下拉 | **必填**;选项:合同 / 授权委托书 / 转三方协议 / 其他 |\n| 展示位置 | 章节 Tab 下方配置卡片,与附件章节的「适用车型」卡片交互一致 |\n| 用途 | 同步至列表页「合同名称」「类型」列;租赁合同创建时按合同名称筛选模板 |\n\n**规则**:\n\n- 仅当存在 **合同主体** 章节 Tab 且当前选中该 Tab 时显示本卡片(未导入 Word 时不展示)。\n- 未填写合同名称时无法保存或发布,并提示「请输入合同名称」。\n- 未选择类型时无法保存或发布,并提示「请选择类型」。\n- 切换至租赁订单等附件章节时,显示「适用车型」卡片,本卡片隐藏。\n- 保存/发布时分别写入 `contractName`、`contractType` 字段。\n\n### 8.5 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。适用于合同主体及全部附件章节。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n### 8.6 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。\n\n### 8.7 编辑区(左侧)\n\n#### 8.7.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.7.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.7.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n### 8.8 实时预览(右侧)\n\n#### 8.8.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.8.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.8.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.8.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充\n\n### 8.9 空状态\n\n编辑区无内容时,预览区展示空状态引导;新增模式下突出「导入 Word 模板」按钮。\n\n---"
},
{
"type": "markdown",
"id": "ct-doc-prd-acceptance",
"title": "验收标准",
- "markdown": "## 16. 验收标准\n\n### 16.1 列表页\n\n- [ ] 筛选项包含:合同类型(可搜索下拉)、状态、创建人、启用状态\n- [ ] 主表含合同类型、启用状态列;操作含启用/停用、版本日志、编辑、删除\n- [ ] 启用中模板编辑/删除按钮禁用;停用需二次确认\n- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表\n- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数\n\n### 16.2 编辑页\n\n- [ ] 章节 Tab 仅在编辑页展示;横向滚动 + 章节列表面板\n- [ ] 导入 Word 后 9 章节正确拆分\n- [ ] 附件章节可绑定车型;各章节均可设条件条款\n- [ ] 预览支持连续/单页切换;各章节可模拟车型验证条件条款\n- [ ] 保存草稿无版本号;发布生成版本号\n- [ ] 编辑已发布(停用后)并发布时版本号递增,旧版写入版本日志\n- [ ] 编辑已发布(停用后)点「保存」不改变发布态与版本号\n- [ ] 有未保存修改时返回列表需确认\n\n### 16.3 跨模块与数据一致性\n\n- [ ] 模板管理停用后,租赁合同创建页不可再选用该模板\n- [ ] 租赁创建预览读取 store 中模板 `sectionContents`(与模板管理编辑一致)\n- [ ] 列表子表「风控条款数」与编辑区统计一致\n- [ ] 列表子表「条件条款数」与各章节实际标记数一致\n\n### 16.4 标注与文档(原型)\n\n- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录路由可切换列表页 / 新增页\n- [ ] `PRD.md` 与 `annotation-source.json` 与当前实现一致\n\n---"
+ "markdown": "## 16. 验收标准\n\n### 16.1 列表页\n\n- [ ] 筛选项包含:合同名称(可搜索下拉)、类型、状态、创建人、启用状态;超过 4 项时默认折叠扩展筛选项\n- [ ] 主表含合同名称、类型列;操作含启用/停用、版本日志、编辑、删除\n- [ ] 启用中模板编辑/删除按钮禁用;停用需二次确认\n- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表\n- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数\n\n### 16.2 编辑页\n\n- [ ] 章节 Tab 仅在编辑页展示;横向滚动 + 章节列表面板\n- [ ] 新增页未导入 Word 时不展示章节 Tab 与合同主体配置卡片\n- [ ] 导入 Word 后章节 Tab 按「标题」样式自动生成(无标题时回退默认标记拆分)\n- [ ] 附件章节可绑定车型;各章节均可设条件条款\n- [ ] 预览支持连续/单页切换;各章节可模拟车型验证条件条款\n- [ ] 保存草稿无版本号;发布生成版本号\n- [ ] 编辑已发布(停用后)并发布时版本号递增,旧版写入版本日志\n- [ ] 编辑已发布(停用后)点「保存」不改变发布态与版本号\n- [ ] 有未保存修改时返回列表需确认\n\n### 16.3 跨模块与数据一致性\n\n- [ ] 模板管理停用后,租赁合同创建页不可再选用该模板\n- [ ] 租赁创建预览读取 store 中模板 `sectionContents`(与模板管理编辑一致)\n- [ ] 列表子表「风控条款数」与编辑区统计一致\n- [ ] 列表子表「条件条款数」与各章节实际标记数一致\n\n### 16.4 标注与文档(原型)\n\n- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录路由可切换列表页 / 新增页\n- [ ] `PRD.md` 与 `annotation-source.json` 与当前实现一致\n\n---"
}
]
},
@@ -525,13 +527,13 @@
"type": "markdown",
"id": "ct-doc-node-filter",
"title": "筛选条件",
- "markdown": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同类型 | 可搜索下拉 | 选项来自现有模板 `contractType` 去重;支持输入模糊搜索 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认"
+ "markdown": "### 7.2 筛选区\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同名称 | 可搜索下拉 | 选项来自现有模板 `contractName` 去重;支持输入模糊搜索 |\n| 类型 | 下拉 | 合同 / 授权委托书 / 转三方协议 / 其他 |\n| 状态 | 下拉 | 全部 / 已发布 / 草稿 |\n| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |\n| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |\n\n**布局**:默认首行展示 4 项(合同名称、类型、状态、创建人);第 5 项「启用状态」收入「更多筛选」折叠区(与全局 `vm-filter` 规则一致)。扩展筛选项已设置时,折叠区自动展开并显示角标。\n\n**按钮**:\n\n- **查询**:应用筛选条件\n- **重置**:清空筛选恢复默认"
},
{
"type": "markdown",
"id": "ct-doc-node-table",
"title": "嵌套列表",
- "markdown": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同类型 | `contractType` | 展示用户自定义的合同类型文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n---\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n---\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---"
+ "markdown": "### 7.4 主表字段\n\n| 列名 | 字段 | 展示规则 |\n|---|---|---|\n| (展开列) | — | 宽 48px |\n| 合同名称 | `contractName` | 展示用户自定义合同名称;同行展示启用状态 Tag |\n| 类型 | `contractType` | 展示类型枚举文案 |\n| 版本号 | `versionNo` | 草稿显示「—」 |\n| 状态 | `status` | 已发布 / 草稿 Tag |\n| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |\n| 创建时间 | `createTime` | — |\n| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |\n| 最后更新时间 | `updateTime` | — |\n| 更新人 | `updater` | 同创建人展示规则 |\n| 操作 | — | 见 7.5 |\n\n**分页**:每页 10 条,展示总条数。\n\n---\n\n### 7.5 主表操作\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |\n| 版本日志 | 已发布 | 查看历史快照 |\n| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |\n| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |\n\n---\n\n### 7.6 子表(展开行)\n\n主表每行可展开,展示该 Word 文档下 **9 个章节** 的摘要(原生 HTML 表,非嵌套 Ant Design Table)。\n\n| 列名 | 说明 |\n|---|---|\n| 章节 | 章节名称 |\n| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |\n| 风控条款数 | 该章节 HTML 内风控红线标记数量 |\n| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |\n\n---"
}
]
},
@@ -554,13 +556,13 @@
"type": "markdown",
"id": "ct-doc-node-tabs",
"title": "章节 Tab",
- "markdown": "### 8.3 章节 Tab\n\n- 共 9 个章节;**横向滚动 Tab 条**展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。\n- **其他 Tab**:仅展示该章节切片内容。\n\n章节顺序:\n\n1. 合同主体 \n2. 租赁订单 \n3. 车辆产生的费用 \n4. 安全责任告知书 \n5. 重型普通货运车辆租赁动态监管责任告知与确认书 \n6. 安全责任承诺书 \n7. 车辆使用规范和驾驶员要求 \n8. 授权委托书 \n9. 安全责任协议\n\n---\n\n### 8.4 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。适用于合同主体及全部附件章节。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n---\n\n### 8.5 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。"
+ "markdown": "### 8.3 章节 Tab\n\n- **新增模式**:未导入 Word 时 **不展示** 章节 Tab 导航(含「合同主体」)及合同主体配置卡片。\n- **编辑模式**:优先按记录已保存的 `sectionTabs` 展示;无自定义 Tabs 时按 §9 规则生成。\n- **Tab 生成逻辑(非固定列表)**:\n - 导入 Word 后,系统识别文档中被标记为 **「标题」** 样式的段落(Word 样式名如 `标题`、`标题1`~`标题3` 等,导入后映射为 `h1`~`h6`);\n - 每个标题段落的纯文本生成一个章节 Tab,Tab 文案与标题一致;\n - 第一个标题之前的内容归入 **「合同主体」** Tab(`main`);该 Tab 下编辑/预览 **整份 Word 合并全文**;\n - 其余 Tab 仅展示对应标题至下一标题之间的切片内容;\n - 若文档中 **未识别到任何标题**,回退为按 §9.2 预置章节标记切分的默认章节 Tab(与演示模板一致)。\n- **展示**:横向滚动 Tab 条展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。\n- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。\n\n---\n\n### 8.5 条件条款说明(合同主体及租赁订单~安全责任协议)\n\n展示说明卡片,文案要点:\n\n> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。适用于合同主体及全部附件章节。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。\n\n---\n\n### 8.6 适用车型绑定(仅附件章节 Tab)\n\n| 元素 | 说明 |\n|---|---|\n| 品牌下拉 | 来自车型主数据 |\n| 型号下拉 | 随品牌联动 |\n| 添加车型 | 追加到当前章节绑定列表 |\n| 已绑定 Tag | 可删除单项 |\n| 空状态文案 | 「当前未绑定车型,表示全部车型适用」 |\n\n**规则**:\n\n- 绑定保存在 `sectionVehicleBindings[当前章节 key]`。\n- 切换章节 Tab 时加载对应章节的绑定到编辑表单。\n- 保存时将各章节绑定一并写入文档记录。"
},
{
"type": "markdown",
"id": "ct-doc-node-workspace",
"title": "编辑与预览",
- "markdown": "### 8.6 编辑区(左侧)\n\n#### 8.6.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.6.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.6.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n---\n\n### 8.7 实时预览(右侧)\n\n#### 8.7.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.7.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.7.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.7.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充"
+ "markdown": "### 8.7 编辑区(左侧)\n\n#### 8.7.1 面板头操作\n\n| 按钮 | 说明 |\n|---|---|\n| { } 模板占位符 | 弹层展示变量分组,点击插入光标处 |\n| 导入 Word 模板 | 接受 `.doc` / `.docx`;导入后按 §9 拆分章节 |\n\n#### 8.7.2 编辑能力\n\n- **Word 版式保真**:A4 分页编辑,保留 Word 导出样式类名。\n- **富文本工具栏**(节选):\n - 字号、加粗、对齐等基础格式\n - **锁定**:选中内容设为锁定区\n - **解锁**:解除锁定\n - **风控**:标记风控红线条款\n - **条件**:在各章节选中段落绑定车型条件\n\n#### 8.7.3 条件条款交互\n\n**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。\n\n1. 用户选中段落或文字;\n2. 点击工具栏「条件」;\n3. 为选中内容绑定一个或多个「品牌 · 型号」;\n4. 生成 `.ct-vehicle-clause` 包裹结构,并写入 `data-vehicle-bindings`;\n5. 创建合同时:订单车辆 **任意一辆** 命中绑定车型 → 段落纳入正文;否则隐藏。\n\n---\n\n### 8.8 实时预览(右侧)\n\n#### 8.8.1 预览模式\n\n| 模式 | 说明 |\n|---|---|\n| 连续预览 | 多页连续滚动 |\n| 单页预览 | 按 A4 单页翻页 |\n\n#### 8.8.2 预览顶栏统计\n\n展示当前章节:\n\n`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`\n\n当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`\n\n#### 8.8.3 模拟车型\n\n- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。\n- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。\n- 选中后按命中规则过滤并展开条件条款正文。\n\n#### 8.8.4 占位符预览\n\n预览时替换部分占位符为样例值,如:\n\n- `{{customerName}}` → `【承租方公司名称】`\n- `{{contractCode}}` → `20260001`\n- 甲方相关变量按预览所选出租方公司填充"
}
]
},
diff --git a/src/prototypes/contract-template-management/contract-template-docx-fidelity.js b/src/prototypes/contract-template-management/contract-template-docx-fidelity.js
new file mode 100644
index 0000000..f046c50
--- /dev/null
+++ b/src/prototypes/contract-template-management/contract-template-docx-fidelity.js
@@ -0,0 +1,27 @@
+/**
+ * docx-preview 高保真 Word 渲染(预览区)
+ */
+import { renderAsync } from 'docx-preview';
+
+var DEFAULT_RENDER_OPTIONS = {
+ className: 'ct-docx-preview',
+ inWrapper: true,
+ ignoreWidth: false,
+ ignoreHeight: false,
+ ignoreFonts: false,
+ breakPages: true,
+ ignoreLastRenderedPageBreak: true,
+ trimXmlDeclaration: true,
+ useBase64URL: true,
+};
+
+export async function renderDocxPreview(container, arrayBuffer, options) {
+ if (!container || !arrayBuffer) return;
+ container.innerHTML = '';
+ await renderAsync(
+ arrayBuffer,
+ container,
+ null,
+ Object.assign({}, DEFAULT_RENDER_OPTIONS, options || {})
+ );
+}
diff --git a/src/prototypes/contract-template-management/contract-template-section-headings.js b/src/prototypes/contract-template-management/contract-template-section-headings.js
new file mode 100644
index 0000000..408b524
--- /dev/null
+++ b/src/prototypes/contract-template-management/contract-template-section-headings.js
@@ -0,0 +1,173 @@
+/**
+ * 从 Word HTML 中按「标题」段落提取章节 Tab,并拆分/合并章节内容。
+ */
+import { DOCUMENT_SECTION_TABS } from './contract-template-section-html.js';
+
+var DEFAULT_MAIN_TAB = { key: 'main', label: '合同主体' };
+var HEADING_TAGS = { H1: true, H2: true, H3: true, H4: true, H5: true, H6: true };
+
+export function getDefaultSectionTabs() {
+ return DOCUMENT_SECTION_TABS.slice();
+}
+
+export function emptySectionMapForTabs(tabs) {
+ var map = {};
+ (tabs || []).forEach(function (tab) {
+ map[tab.key] = '';
+ });
+ return map;
+}
+
+function normalizeHeadingText(text) {
+ return String(text || '').replace(/\s+/g, ' ').trim();
+}
+
+function slugifySectionKey(label, index) {
+ var slug = String(label || '')
+ .toLowerCase()
+ .replace(/[^\u4e00-\u9fa5a-z0-9]+/g, '_')
+ .replace(/^_+|_+$/g, '')
+ .slice(0, 24);
+ return slug ? 'section_' + slug + '_' + index : 'section_' + index;
+}
+
+function isSectionTitleElement(node) {
+ if (!node || node.nodeType !== 1) return false;
+ if (!node.classList || !node.classList.contains('ct-word-section-title')) return false;
+ if (node.closest && node.closest('table')) return false;
+ var text = normalizeHeadingText(node.textContent);
+ return text.length > 0 && text.length <= 80;
+}
+
+function isHeadingElement(node) {
+ if (!node || node.nodeType !== 1) return false;
+ if (isSectionTitleElement(node)) return true;
+ if (!HEADING_TAGS[node.nodeName]) return false;
+ if (node.closest && node.closest('table')) return false;
+ var text = normalizeHeadingText(node.textContent);
+ return text.length > 0 && text.length <= 80;
+}
+
+function collectHeadingElements(root) {
+ var headings = [];
+ if (!root) return headings;
+ var seen = new Set();
+ function push(node) {
+ if (!node || seen.has(node)) return;
+ if (!isHeadingElement(node)) return;
+ seen.add(node);
+ headings.push(node);
+ }
+ root.querySelectorAll('.ct-word-section-title,h1,h2,h3,h4,h5,h6').forEach(push);
+ return headings;
+}
+
+/** 从 Word HTML 提取章节 Tab;无标题时返回 null(调用方回退默认 Tab) */
+export function extractSectionTabsFromHtml(html) {
+ if (typeof document === 'undefined') return null;
+ var source = String(html || '').trim();
+ if (!source) return null;
+
+ var div = document.createElement('div');
+ div.innerHTML = source;
+ var headings = collectHeadingElements(div);
+ if (!headings.length) return null;
+
+ var tabs = [Object.assign({}, DEFAULT_MAIN_TAB)];
+ var usedKeys = { main: true };
+
+ headings.forEach(function (heading, index) {
+ var label = normalizeHeadingText(heading.textContent);
+ if (!label) return;
+ var key = slugifySectionKey(label, index + 1);
+ while (usedKeys[key]) {
+ key = key + '_' + index;
+ }
+ usedKeys[key] = true;
+ tabs.push({ key: key, label: label, headingText: label });
+ });
+
+ return tabs.length > 1 ? tabs : null;
+}
+
+function isWordBlockNode(node) {
+ if (!node) return false;
+ if (node.nodeType === 3) return !!String(node.textContent || '').trim();
+ return node.nodeType === 1 && node.nodeName !== 'STYLE';
+}
+
+function detectDynamicSectionStartKey(node, tabs) {
+ if (!node || node.nodeType !== 1 || !tabs || !tabs.length) return null;
+ if (!isHeadingElement(node)) return null;
+ var text = normalizeHeadingText(node.textContent);
+ for (var i = 1; i < tabs.length; i++) {
+ if (tabs[i].label === text || tabs[i].headingText === text) {
+ return tabs[i].key;
+ }
+ }
+ return null;
+}
+
+/** 按章节 Tab 将整份 HTML 拆为 section map */
+export function splitHtmlBySectionTabs(html, tabs) {
+ var sectionTabs = tabs && tabs.length ? tabs : getDefaultSectionTabs();
+ var source = String(html || '');
+ var result = emptySectionMapForTabs(sectionTabs);
+ if (!source.trim()) return result;
+
+ if (typeof document === 'undefined') {
+ return result;
+ }
+
+ var div = document.createElement('div');
+ div.innerHTML = source.replace(/