迭代 ONE-OS 多原型:合同模板重构与租赁合同列表/表单增强
- 合同模板管理:模板数据层、章节编辑与品牌车型选择器 - 租赁合同管理:列表子表、交车安排、里程汇总及新建/编辑表单优化 - 同步各原型 annotation-source 与 PRD;新增项目导航同步脚本 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,5 +14,39 @@
|
|||||||
"remote": {
|
"remote": {
|
||||||
"url": "https://gitea.lnh2e.com/wangmian/OneOS1.2.git"
|
"url": "https://gitea.lnh2e.com/wangmian/OneOS1.2.git"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cloudPublishing": {
|
||||||
|
"vercel": {
|
||||||
|
"token": "",
|
||||||
|
"projectName": "",
|
||||||
|
"teamId": ""
|
||||||
|
},
|
||||||
|
"cloudflarePages": {
|
||||||
|
"apiToken": "",
|
||||||
|
"accountId": "",
|
||||||
|
"projectName": "",
|
||||||
|
"productionBranch": "main"
|
||||||
|
},
|
||||||
|
"s3": {
|
||||||
|
"accessKeyId": "",
|
||||||
|
"secretAccessKey": "",
|
||||||
|
"region": "cn-hangzhou",
|
||||||
|
"bucket": "model-lnoneos",
|
||||||
|
"prefix": "",
|
||||||
|
"baseUrl": "http://prototype.lnoneos.com/",
|
||||||
|
"endpoint": ""
|
||||||
|
},
|
||||||
|
"githubPages": {
|
||||||
|
"repository": "",
|
||||||
|
"branch": "gh-pages",
|
||||||
|
"sourceDirectory": "/"
|
||||||
|
},
|
||||||
|
"publishSettings": {
|
||||||
|
"includeSource": true,
|
||||||
|
"visibleTargets": [
|
||||||
|
"axhub",
|
||||||
|
"s3"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"updatedAt": "2026-06-30T08:53:12.196Z",
|
"updatedAt": "2026-06-30T10:18:56.724Z",
|
||||||
"prototypes": [
|
"prototypes": [
|
||||||
{
|
{
|
||||||
"id": "item:prototypes:insurance-procurement",
|
"id": "item:prototypes:insurance-procurement",
|
||||||
@@ -945,4 +945,4 @@
|
|||||||
"templates": [],
|
"templates": [],
|
||||||
"components": [],
|
"components": [],
|
||||||
"canvas": []
|
"canvas": []
|
||||||
}
|
}
|
||||||
|
|||||||
287
.local/extract-annotation-source/annotation-nodes.json
Normal file
287
.local/extract-annotation-source/annotation-nodes.json
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "ct-list-filter",
|
||||||
|
"index": 1,
|
||||||
|
"title": "筛选条件",
|
||||||
|
"pageId": "list",
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ct-list-toolbar",
|
||||||
|
"index": 2,
|
||||||
|
"title": "新增模板",
|
||||||
|
"pageId": "list",
|
||||||
|
"hasMarkdown": false,
|
||||||
|
"annotationText": "进入新增页,默认从「合同主体」章节开始编辑。",
|
||||||
|
"aiPrompt": "列表工具栏新增入口。",
|
||||||
|
"color": "#32a06e",
|
||||||
|
"images": [],
|
||||||
|
"controls": [],
|
||||||
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ct-list-toolbar\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "ct-list-toolbar",
|
||||||
|
"path": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ct-list-table",
|
||||||
|
"index": 3,
|
||||||
|
"title": "Word 模板列表",
|
||||||
|
"pageId": "list",
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"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": [],
|
||||||
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ct-contract-type\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "ct-contract-type",
|
||||||
|
"path": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ct-vehicle-binding",
|
||||||
|
"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": [],
|
||||||
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ct-vehicle-binding\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "ct-vehicle-binding",
|
||||||
|
"path": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ct-editor-panel",
|
||||||
|
"index": 8,
|
||||||
|
"title": "编辑区",
|
||||||
|
"pageId": "editor",
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ct-rte-unvehicle-clause",
|
||||||
|
"index": 12,
|
||||||
|
"title": "取消条件",
|
||||||
|
"pageId": "editor",
|
||||||
|
"hasMarkdown": false,
|
||||||
|
"annotationText": "当条款被设置条件后,可以通过选中特定条款内容,点击取消来取消该部分内容的车型限定。",
|
||||||
|
"aiPrompt": "取消条件条款标记。",
|
||||||
|
"color": "#64748b",
|
||||||
|
"images": [],
|
||||||
|
"controls": [],
|
||||||
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ct-rte-unvehicle-clause\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "ct-rte-unvehicle-clause",
|
||||||
|
"path": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ct-rte-inline-lock",
|
||||||
|
"index": 13,
|
||||||
|
"title": "锁定",
|
||||||
|
"pageId": "editor",
|
||||||
|
"hasMarkdown": false,
|
||||||
|
"annotationText": "锁定主要用于选中章节内的内容;锁定之后该部分内容无法在新增租赁合同时被篡改。红线、条件以及锁定可以在同一段内容同时生效。",
|
||||||
|
"aiPrompt": "内容锁定区标记。",
|
||||||
|
"color": "#b45309",
|
||||||
|
"images": [],
|
||||||
|
"controls": [],
|
||||||
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ct-rte-inline-lock\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "ct-rte-inline-lock",
|
||||||
|
"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",
|
||||||
|
"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",
|
||||||
|
"images": [],
|
||||||
|
"controls": [],
|
||||||
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ct-preview-panel\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "ct-preview-panel",
|
||||||
|
"path": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
57
.local/extract-annotation-source/markdown-nodes.json
Normal file
57
.local/extract-annotation-source/markdown-nodes.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -9,6 +9,7 @@
|
|||||||
"dev": "node scripts/sync-vendor-if-present.mjs && vite",
|
"dev": "node scripts/sync-vendor-if-present.mjs && vite",
|
||||||
"start": "npm run dev",
|
"start": "npm run dev",
|
||||||
"metadata:sync": "node scripts/sync-project-metadata.mjs",
|
"metadata:sync": "node scripts/sync-project-metadata.mjs",
|
||||||
|
"project-nav:sync": "node scripts/sync-project-prototype-directory.mjs",
|
||||||
"oneos-web:sync": "node scripts/sync-oneos-web-prototypes.mjs && node scripts/scan-entries.js && npm run metadata:sync",
|
"oneos-web:sync": "node scripts/sync-oneos-web-prototypes.mjs && node scripts/scan-entries.js && npm run metadata:sync",
|
||||||
"build": "node scripts/sync-vendor-if-present.mjs && node scripts/scan-entries.js && node scripts/build-all.js",
|
"build": "node scripts/sync-vendor-if-present.mjs && node scripts/scan-entries.js && node scripts/build-all.js",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
|||||||
188
scripts/sync-project-prototype-directory.mjs
Normal file
188
scripts/sync-project-prototype-directory.mjs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* 将 .axhub/make/sidebar-tree.json 左侧菜单同步到各原型 annotation-source.json 的「原型目录」。
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* node scripts/sync-project-prototype-directory.mjs
|
||||||
|
* node scripts/sync-project-prototype-directory.mjs --prototype vehicle-management
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const NAV_FOLDER_ID = 'oneos-project-nav';
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const args = { prototype: '', projectRoot: '' };
|
||||||
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
|
const arg = argv[i];
|
||||||
|
if (arg === '--prototype' && argv[i + 1]) {
|
||||||
|
args.prototype = argv[++i].trim();
|
||||||
|
} else if (arg === '--project-root' && argv[i + 1]) {
|
||||||
|
args.projectRoot = path.resolve(argv[++i].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJson(filePath) {
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeJson(filePath, value) {
|
||||||
|
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function prototypeIdFromItemKey(itemKey) {
|
||||||
|
const normalized = String(itemKey || '').trim().replace(/^\/+/u, '');
|
||||||
|
const match = normalized.match(/^prototypes\/(.+)$/u);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function navLinkId(prototypeId) {
|
||||||
|
return `nav-link-${prototypeId.replace(/[^a-zA-Z0-9]+/gu, '-').replace(/^-+|-+$/gu, '').toLowerCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLinkNode(sidebarItem, currentPrototypeId) {
|
||||||
|
const prototypeId = prototypeIdFromItemKey(sidebarItem.itemKey);
|
||||||
|
if (!prototypeId) return null;
|
||||||
|
const isCurrent = prototypeId === currentPrototypeId;
|
||||||
|
return {
|
||||||
|
type: 'link',
|
||||||
|
id: navLinkId(prototypeId),
|
||||||
|
title: isCurrent ? `${sidebarItem.title}(当前)` : sidebarItem.title,
|
||||||
|
href: `/prototypes/${prototypeId}`,
|
||||||
|
target: 'self',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sidebarItemsToDirectoryChildren(sidebarItems, currentPrototypeId) {
|
||||||
|
const children = [];
|
||||||
|
for (const item of sidebarItems || []) {
|
||||||
|
if (item?.kind === 'item') {
|
||||||
|
const link = createLinkNode(item, currentPrototypeId);
|
||||||
|
if (link) children.push(link);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (item?.kind !== 'folder' || !Array.isArray(item.children)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const folderChildren = item.children
|
||||||
|
.filter((child) => child?.kind === 'item')
|
||||||
|
.map((child) => createLinkNode(child, currentPrototypeId))
|
||||||
|
.filter(Boolean);
|
||||||
|
if (!folderChildren.length) continue;
|
||||||
|
children.push({
|
||||||
|
type: 'folder',
|
||||||
|
id: item.id ? `nav-${item.id}` : `nav-folder-${children.length + 1}`,
|
||||||
|
title: item.title || '分组',
|
||||||
|
defaultExpanded: item.defaultExpanded !== false,
|
||||||
|
children: folderChildren,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSidebarPrototypes(projectRoot) {
|
||||||
|
const sidebarPath = path.join(projectRoot, '.axhub/make/sidebar-tree.json');
|
||||||
|
if (!fs.existsSync(sidebarPath)) {
|
||||||
|
throw new Error(`缺少侧边栏配置:${sidebarPath}`);
|
||||||
|
}
|
||||||
|
const sidebar = readJson(sidebarPath);
|
||||||
|
return Array.isArray(sidebar.prototypes) ? sidebar.prototypes : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function listPrototypeIdsWithAnnotation(projectRoot) {
|
||||||
|
const prototypesRoot = path.join(projectRoot, 'src/prototypes');
|
||||||
|
if (!fs.existsSync(prototypesRoot)) return [];
|
||||||
|
return fs.readdirSync(prototypesRoot, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isDirectory()
|
||||||
|
&& fs.existsSync(path.join(prototypesRoot, entry.name, 'annotation-source.json')))
|
||||||
|
.map((entry) => entry.name)
|
||||||
|
.sort((a, b) => a.localeCompare(b, 'zh-CN'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixPublishedLinks(nodes, prototypeId) {
|
||||||
|
const walk = (items) => {
|
||||||
|
for (const node of items || []) {
|
||||||
|
if (node?.type === 'link' && typeof node.href === 'string') {
|
||||||
|
const href = node.href.trim();
|
||||||
|
const selfMatch = href.match(new RegExp(`^/prototypes/${prototypeId.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')}(?:[#?].*)?$`, 'u'));
|
||||||
|
if (selfMatch) {
|
||||||
|
node.href = './index.html';
|
||||||
|
node.target = node.target || 'self';
|
||||||
|
} else {
|
||||||
|
const otherMatch = href.match(/^\/prototypes\/([^/?#]+)(?:[#?].*)?$/u);
|
||||||
|
if (otherMatch) {
|
||||||
|
node.href = `../${otherMatch[1]}/index.html`;
|
||||||
|
node.target = node.target || 'self';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node?.type === 'folder' && Array.isArray(node.children)) {
|
||||||
|
walk(node.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncPrototypeDirectory(prototypeId, sidebarItems, projectRoot) {
|
||||||
|
const annotationPath = path.join(projectRoot, 'src/prototypes', prototypeId, 'annotation-source.json');
|
||||||
|
if (!fs.existsSync(annotationPath)) {
|
||||||
|
console.log(`[skip] ${prototypeId}: 无 annotation-source.json`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const annotation = readJson(annotationPath);
|
||||||
|
const navChildren = sidebarItemsToDirectoryChildren(sidebarItems, prototypeId);
|
||||||
|
const navFolder = {
|
||||||
|
type: 'folder',
|
||||||
|
id: NAV_FOLDER_ID,
|
||||||
|
title: 'ONE-OS 原型导航',
|
||||||
|
defaultExpanded: true,
|
||||||
|
children: navChildren,
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingNodes = Array.isArray(annotation.directory?.nodes) ? annotation.directory.nodes : [];
|
||||||
|
const preservedNodes = existingNodes.filter((node) => node?.id !== NAV_FOLDER_ID);
|
||||||
|
fixPublishedLinks(preservedNodes, prototypeId);
|
||||||
|
|
||||||
|
annotation.directory = {
|
||||||
|
nodes: [navFolder, ...preservedNodes],
|
||||||
|
};
|
||||||
|
annotation.data = annotation.data || {};
|
||||||
|
annotation.data.updatedAt = Date.now();
|
||||||
|
|
||||||
|
writeJson(annotationPath, annotation);
|
||||||
|
console.log(`[sync] ${prototypeId}: 已写入 ${navChildren.length} 个顶级目录项(含分组)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const args = parseArgs(process.argv.slice(2));
|
||||||
|
const projectRoot = args.projectRoot || path.resolve(__dirname, '..');
|
||||||
|
const sidebarItems = loadSidebarPrototypes(projectRoot);
|
||||||
|
const targets = args.prototype
|
||||||
|
? [args.prototype]
|
||||||
|
: listPrototypeIdsWithAnnotation(projectRoot);
|
||||||
|
|
||||||
|
if (!sidebarItems.length) {
|
||||||
|
console.error('sidebar-tree.json 中未找到 prototypes 菜单。');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (!targets.length) {
|
||||||
|
console.log('未找到带 annotation-source.json 的原型。');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed = 0;
|
||||||
|
for (const prototypeId of targets) {
|
||||||
|
if (syncPrototypeDirectory(prototypeId, sidebarItems, projectRoot)) {
|
||||||
|
changed += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`完成:处理 ${targets.length} 个原型,更新 ${changed} 个 annotation-source.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -110,6 +110,19 @@ function notifyHostPrototypeRouteInfo(
|
|||||||
}, '*');
|
}, '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 单页原型挂载时调用,清除 Make 侧栏中残留的多页子路由(如从「工作台」切过来后仍显示子页) */
|
||||||
|
export function clearHostPrototypeRouteInfo() {
|
||||||
|
if (typeof window === 'undefined' || window.parent === window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'AXHUB_PROTOTYPE_ROUTE_INFO',
|
||||||
|
pages: [],
|
||||||
|
defaultPageId: '',
|
||||||
|
activePageId: '',
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
export function useHashPage(routeOrDefault: HashPageRoute | string = 'home') {
|
export function useHashPage(routeOrDefault: HashPageRoute | string = 'home') {
|
||||||
const route = normalizeRouteInput(routeOrDefault);
|
const route = normalizeRouteInput(routeOrDefault);
|
||||||
const { pages, defaultPageId } = route;
|
const { pages, defaultPageId } = route;
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Prototype Review
|
||||||
|
|
||||||
|
- 审查目标:`src/prototypes/contract-template-management`
|
||||||
|
- 用户资料/参考资料:
|
||||||
|
- 本轮用户任务:处理评审结论中 P1–P3 共 5 条问题
|
||||||
|
- `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(复审)
|
||||||
|
|
||||||
|
## 总体点评
|
||||||
|
|
||||||
|
上一轮评审中的 **5 条优先级问题均已落地**。合同模板管理原型现已具备:
|
||||||
|
|
||||||
|
- 租赁创建预览优先消费 store 中 `sectionContents`(静态 HTML 仅兜底)
|
||||||
|
- 冷启动种子:直接进入租赁创建即可选用已启用模板
|
||||||
|
- PRD / 标注与实现对齐(`contractType`、列表筛选、子表列、章节 Tab 交互)
|
||||||
|
- `localStorage` 持久化(兼写 `sessionStorage`)
|
||||||
|
- 移除 `isDefault` /「标准模板」残留逻辑
|
||||||
|
|
||||||
|
原型可作为研发与测试的**端到端演示基线**;正式环境仍需 API 持久化、权限与异常分支。
|
||||||
|
|
||||||
|
## P0-P3 优先级问题(复审)
|
||||||
|
|
||||||
|
| 级别 | 问题 | 状态 |
|
||||||
|
|---|---|---|
|
||||||
|
| 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 默认逻辑、编辑区文案已清理 |
|
||||||
|
|
||||||
|
## 完整性与项目对齐
|
||||||
|
|
||||||
|
| 维度 | 评估 |
|
||||||
|
|---|---|
|
||||||
|
| 跨模块正文 | 模板管理编辑 ↔ 租赁创建预览:**已贯通**(同会话 / localStorage 内) |
|
||||||
|
| 冷启动 | 无需先打开模板管理:**已支持** |
|
||||||
|
| 资料一致性 | PRD、标注、列表筛选/列、子表、§8.3 Tab:**已对齐** |
|
||||||
|
| 正式环境差距 | API 持久化、角色权限、Word 解析失败等异常分支仍待研发 |
|
||||||
|
|
||||||
|
## 验收建议(请用户在预览中确认)
|
||||||
|
|
||||||
|
1. **租赁预览贯通**:在模板管理编辑某章节并保存 → 打开租赁创建 → 选用同一模板 → 预览正文应反映修改。
|
||||||
|
2. **冷启动**:清空站点 localStorage 后**直接**打开租赁创建 → 应能选择已启用模板(如「正式合同」「试用合同」)。
|
||||||
|
3. **列表筛选**:列表页「合同类型」筛选与主表「合同类型」列一致。
|
||||||
|
4. **持久化**:刷新浏览器后模板停用/发布状态应保留(localStorage)。
|
||||||
|
|
||||||
|
## 修订记录
|
||||||
|
|
||||||
|
| 日期 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 2026-06-29 | 复审:5 条评审问题全部处理完毕 |
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# UI Review
|
||||||
|
|
||||||
|
- 审查目标:`src/prototypes/contract-template-management`
|
||||||
|
- 使用设计依据:`src/themes/linear/DESIGN.md`(实现侧通过 `vehicle-management/style.css` 声明为 Linear **light / inverse** 模式,主色覆写为 ONE-OS 翡翠绿 `--ln-primary: #32a06e`;与 Linear 原文档薰衣草色 `#5e6ad2` 存在有意的品牌偏移)
|
||||||
|
- 生成时间:2026-06-29
|
||||||
|
|
||||||
|
## 总体点评
|
||||||
|
|
||||||
|
合同模板管理在整体视觉上与车辆管理等 ONE-OS 中后台模块保持一致:浅灰画布(`--ln-canvas-parchment`)、白卡片、8px 圆角控件、Inter 字体与翡翠绿主色,表格/筛选区复用 `.vm-filter-card`、`.vm-table-card` 模式,信息密度适合法务日常维护任务。编辑页双栏(Word 编辑 + A4 预览)、章节 Tab、预览工具栏的 `aria-label` / `role` 标注较完整,禁用态操作按钮与 `focus-visible` 内描边也体现了一定的可访问性意识。
|
||||||
|
|
||||||
|
主要风险集中在**列表操作区触控尺寸**、**宽表横向滚动**以及**多操作并列带来的扫描成本**。Impeccable detector 另提示条件说明卡片的紫色左边框(`#7c3aed`)与全站绿色主色体系不一致,属于品牌表达层面的杂音。整体未达到 Linear 营销站那种极简留白,但作为 dense admin 工具界面,层次与组件复用是成立且可继续打磨的。
|
||||||
|
|
||||||
|
## P0-P3 优先级问题
|
||||||
|
|
||||||
|
### P1 - 列表行内操作按钮触控高度不足 44px
|
||||||
|
|
||||||
|
- 证据:`.ct-action-btn.ant-btn-link` 使用 `min-height: var(--vm-btn-height)`(36px,`vehicle-management/style.css`);对比同模块预览翻页按钮已设 `min-width/min-height: 44px`(`contract-template.css` 约 1563 行)。列表操作列含「启用/停用」「版本日志」「编辑」「删除」等链接按钮。
|
||||||
|
- 影响:在触控屏或高精度鼠标场景下,误触率上升;不符合常见 WCAG 2.5.5 目标尺寸建议,与 DESIGN 中间距/控件可点性预期不符。
|
||||||
|
- 修复方向:将行内操作 `min-height` 提升至 44px,或改为「更多」下拉 + 主操作外露;保持 `cursor-pointer` 与禁用态对比度(当前禁用灰 `#94A3B8` 已处理)。
|
||||||
|
|
||||||
|
### P1 - 主表固定最小宽度导致中小屏强依赖横向滚动
|
||||||
|
|
||||||
|
- 证据:`.ct-data-table--nested` 设 `--ct-list-table-width: 1232px` 且 `min-width` 强制(`contract-template.css` 72–77 行);`LIST_TABLE_SCROLL_X` 合计约 1332px(含操作列 300px,`ContractTemplate.jsx`)。右侧还有 `@axhub/annotation` 工具栏占用宽度。
|
||||||
|
- 影响:13 寸笔记本或分屏预览时,用户需频繁横向拖动才能看到「操作」列;筛选结果核对与批量处理效率下降。
|
||||||
|
- 修复方向:≥1280px 保持全列;`<1200px` 将「启用状态」与次要时间列折叠为 expandable 摘要,或操作列收进「更多」菜单;与 Linear inverse 表格「密度可伸缩」原则对齐。
|
||||||
|
|
||||||
|
### P2 - 操作列 4~5 个并列链接,视觉密度与扫读成本高
|
||||||
|
|
||||||
|
- 证据:已发布行同时展示「停用/启用」「版本日志」「编辑」「删除」(`getListColumns` 操作列宽 300px);多行换行时(`Space wrap`)行高不齐。
|
||||||
|
- 影响:法务需逐行扫描多个同色链接,难以快速区分主操作与危险操作;与 DESIGN 中 button-primary / button-secondary 层级未充分映射到表格场景。
|
||||||
|
- 修复方向:主操作保留 1~2 个(如「编辑」);其余收入「更多」`Dropdown`;危险操作「删除」保持 `danger` 但与次要操作拉开间距或使用图标+文字。
|
||||||
|
|
||||||
|
### P2 - 条件条款说明卡使用紫色左边框,偏离主色体系
|
||||||
|
|
||||||
|
- 证据:detector 命中 `border-left: 3px solid #7c3aed`(`ContractTemplate.jsx` 内联样式与 `contract-template.css` 约 1390 行);全站主色为 `--ln-primary #32a06e`,Linear DESIGN 强调单一 chromatic accent。
|
||||||
|
- 影响:编辑页唯一紫色强调与表格/按钮绿色系统冲突,削弱品牌一致性,易被识别为「拼贴式」装饰。
|
||||||
|
- 修复方向:改为 `border-left-color: var(--ln-primary)` 或 `var(--ln-primary-soft)` 背景 + 无侧栏;与 `.ct-main-clause-tip` 信息层级一致即可。
|
||||||
|
|
||||||
|
### P3 - 字号阶梯偏平,表头与正文层次不够清晰
|
||||||
|
|
||||||
|
- 证据:Impeccable detector 报告 11–20px 多档字号、比例约 1.8:1(`flat-type-hierarchy`);表头 `0.75rem`(12px)/`--ln-muted`,正文 `0.8125rem`(13px)/`--ln-body`,差距仅 1px。
|
||||||
|
- 影响:长列表扫读时列标题与单元格内容对比偏弱;不影响完成任务,但略损专业感。
|
||||||
|
- 修复方向:收敛为 3 档:表头 12px medium + `--ln-muted-80`、正文 14px、辅助 12px;确保表头对比度 ≥4.5:1。
|
||||||
|
|
||||||
|
## 核心元件
|
||||||
|
|
||||||
|
### 筛选区(`ct-list-filter`)
|
||||||
|
|
||||||
|
- **符合**:四字段栅格(合同名称可搜索 Select、状态、创建人、启用状态)+ 查询/重置,与 `vm-filter-card` 模式一致;字段带 `aria-label`。
|
||||||
|
- **调整**:筛选项增至 4 个后,窄屏下建议 `@media (max-width: 992px)` 改为两列栅格(当前 filter grid 响应式依赖 vehicle-management 全局样式,需确认 4 字段是否换行整齐)。
|
||||||
|
|
||||||
|
### 列表表格(`ct-list-table`)
|
||||||
|
|
||||||
|
- **符合**:表头浅灰底、hairline 分隔、hover 行背景、展开行嵌套章节子表;状态/启用 Tag 使用 Ant Design semantic 色。
|
||||||
|
- **调整**:操作列与总宽度过宽(见 P1/P2);禁用编辑/删除的 link 按钮样式已灰化,但缺少 `title` 提示「请先停用」可帮助理解禁用原因。
|
||||||
|
|
||||||
|
### 编辑页顶栏与章节 Tab(`ct-editor-topbar` / `ct-section-tabs`)
|
||||||
|
|
||||||
|
- **符合**:返回 + 保存/保存并发布 CTA 层级清晰;`ct-btn-cta` 使用主色实心按钮;章节 Tab 带 `role="tablist"`、`aria-selected`,「更多」章节有 `aria-expanded`。
|
||||||
|
- **调整**:顶栏在窄屏未 sticky;长章节名在「更多」Tab 内 ellipsis 至 140px(`max-width: 992px`),可考虑 tooltip 展示全名。
|
||||||
|
|
||||||
|
### 编辑区 + 预览区(`ct-editor-panel` / `ct-preview-panel`)
|
||||||
|
|
||||||
|
- **符合**:左右分栏、A4 预览分页/连续模式、缩放与翻页控件具备 44px 触控与 `prefers-reduced-motion`;富文本工具栏 `focus-visible` 内描边符合 `--vm-focus-border-color`。
|
||||||
|
- **保留亮点**:预览工具栏 `role="toolbar"` 与分组 `aria-label` 是全场最佳 a11y 实践,可复用到列表操作区。
|
||||||
|
- **调整**:编辑区 Word 画布 `210mm` 固定宽,小屏下横向滚动不可避免,需在容器上提示「可横向滑动查看版式」。
|
||||||
|
|
||||||
|
### 弹窗(删除 / 停用 / 未保存离开 / 版本日志)
|
||||||
|
|
||||||
|
- **符合**:`ct-modal-wrap` 统一包裹;停用与离开编辑有明确二次确认文案。
|
||||||
|
- **调整**:版本日志 Modal 宽 720px、快照预览 900px,小屏应验证 `max-width: calc(100vw - 32px)` 是否生效。
|
||||||
|
|
||||||
|
## 响应式与可访问性
|
||||||
|
|
||||||
|
| 维度 | 结论 |
|
||||||
|
|---|---|
|
||||||
|
| 桌面(≥1280px) | 编辑双栏与预览体验完整;列表需横向滚动但可接受 |
|
||||||
|
| 平板/窄屏(≤992px) | 编辑列 `max-height: none` 已放开;表格仍强制 min-width,体验明显降级 |
|
||||||
|
| 键盘 | 章节 Tab、预览控件、部分按钮有 `focus-visible`;行内 link 操作依赖 Tab 顺序,密集时冗长 |
|
||||||
|
| 语义 | 筛选、章节、预览工具栏标注较好;表格操作按钮未统一 `aria-label`(仅靠文字) |
|
||||||
|
| 对比度 | 正文 `#52525b` on 白 generally OK;表头 `#71717a` on `#f6f7f7` 建议复核 AA |
|
||||||
|
| 动效 | 多处 `@media (prefers-reduced-motion: reduce)` 已覆盖工具栏与章节 Tab |
|
||||||
|
|
||||||
|
## 证据与评估说明
|
||||||
|
|
||||||
|
- **浏览器/截图**:未启动本地预览与浏览器截图;结论来自源码、`contract-template.css`、`index.tsx` ConfigProvider token 及 detector 输出。
|
||||||
|
- **Scanner**:已运行 `impeccable/scripts/detect.mjs --json`(exit 0,含 side-tab、overused-font Inter、flat-type-hierarchy、em-dash-overuse 等 warning);已筛入 P2/P3 相关项,em-dash 主要来自合同正文 HTML 种子,非 UI chrome。
|
||||||
|
- **设计依据说明**:项目根 `DESIGN.md` 为 Vercel 营销规范,与本原型实现不一致;审查采用实现声明的 `src/themes/linear/DESIGN.md` + ONE-OS token 覆写作为对齐基准。
|
||||||
|
- **独立评估**:`degraded`(单轮完成设计阅读 + 源码 + detector;未拆分子代理,未做浏览器实机检查)。
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,352 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Popover } from 'antd';
|
||||||
|
import {
|
||||||
|
LEASE_VEHICLE_BRAND_MODEL_CATALOG,
|
||||||
|
formatBrandModelPair,
|
||||||
|
} from '../lease-contract-management/lease-order-vars.js';
|
||||||
|
import {
|
||||||
|
VEHICLE_BINDING_ALL_BRAND,
|
||||||
|
isAllVehicleBindings,
|
||||||
|
vehicleBindingKey,
|
||||||
|
} from './vehicle-binding-utils.js';
|
||||||
|
|
||||||
|
function SearchIcon() {
|
||||||
|
return React.createElement('svg', {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
strokeWidth: 2,
|
||||||
|
'aria-hidden': true,
|
||||||
|
},
|
||||||
|
React.createElement('circle', { cx: 11, cy: 11, r: 7 }),
|
||||||
|
React.createElement('path', { d: 'M20 20l-3-3' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CloseIcon() {
|
||||||
|
return React.createElement('svg', {
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
strokeWidth: 2,
|
||||||
|
'aria-hidden': true,
|
||||||
|
},
|
||||||
|
React.createElement('path', { d: 'M18 6L6 18M6 6l12 12' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChevronDownIcon() {
|
||||||
|
return React.createElement('svg', {
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
strokeWidth: 2,
|
||||||
|
'aria-hidden': true,
|
||||||
|
},
|
||||||
|
React.createElement('path', { d: 'M6 9l6 6 6-6' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function VehicleBrandModelPicker(props) {
|
||||||
|
var value = props.value || [];
|
||||||
|
var onChange = props.onChange;
|
||||||
|
var openState = useState(false);
|
||||||
|
var activeBrandState = useState(VEHICLE_BINDING_ALL_BRAND);
|
||||||
|
var queryState = useState('');
|
||||||
|
var open = openState[0];
|
||||||
|
var setOpen = openState[1];
|
||||||
|
var activeBrand = activeBrandState[0];
|
||||||
|
var setActiveBrand = activeBrandState[1];
|
||||||
|
var query = queryState[0];
|
||||||
|
var setQuery = queryState[1];
|
||||||
|
var searchText = query.trim();
|
||||||
|
var isSearchMode = searchText.length > 0;
|
||||||
|
var isAllMode = isAllVehicleBindings(value);
|
||||||
|
var isBrowsingAllTab = !isSearchMode && activeBrand === VEHICLE_BINDING_ALL_BRAND;
|
||||||
|
var resolvedBrand = (activeBrand && activeBrand !== VEHICLE_BINDING_ALL_BRAND)
|
||||||
|
? activeBrand
|
||||||
|
: (value.length > 0
|
||||||
|
? value[0].brand
|
||||||
|
: LEASE_VEHICLE_BRAND_MODEL_CATALOG[0].brand);
|
||||||
|
var searchLower = searchText.toLowerCase();
|
||||||
|
|
||||||
|
var displayModels = isSearchMode
|
||||||
|
? LEASE_VEHICLE_BRAND_MODEL_CATALOG.reduce(function (list, item) {
|
||||||
|
return list.concat(item.models.map(function (model) {
|
||||||
|
return { brand: item.brand, model: model };
|
||||||
|
}));
|
||||||
|
}, []).filter(function (item) {
|
||||||
|
var label = formatBrandModelPair(item.brand, item.model);
|
||||||
|
return label.toLowerCase().indexOf(searchLower) >= 0
|
||||||
|
|| item.brand.toLowerCase().indexOf(searchLower) >= 0;
|
||||||
|
})
|
||||||
|
: (function () {
|
||||||
|
var group = LEASE_VEHICLE_BRAND_MODEL_CATALOG.find(function (node) {
|
||||||
|
return node.brand === resolvedBrand;
|
||||||
|
});
|
||||||
|
if (!group) return [];
|
||||||
|
return group.models.map(function (model) {
|
||||||
|
return { brand: group.brand, model: model };
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
function handleOpenChange(nextOpen) {
|
||||||
|
setOpen(nextOpen);
|
||||||
|
if (nextOpen) {
|
||||||
|
if (isAllVehicleBindings(value)) {
|
||||||
|
setActiveBrand(VEHICLE_BINDING_ALL_BRAND);
|
||||||
|
} else if (value.length > 0) {
|
||||||
|
setActiveBrand(value[0].brand);
|
||||||
|
} else {
|
||||||
|
setActiveBrand(VEHICLE_BINDING_ALL_BRAND);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setQuery('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllBrands() {
|
||||||
|
onChange([]);
|
||||||
|
setActiveBrand(VEHICLE_BINDING_ALL_BRAND);
|
||||||
|
setQuery('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(brand, model) {
|
||||||
|
var key = vehicleBindingKey({ brand: brand, model: model });
|
||||||
|
return value.some(function (item) {
|
||||||
|
return vehicleBindingKey(item) === key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePair(brand, model) {
|
||||||
|
var key = vehicleBindingKey({ brand: brand, model: model });
|
||||||
|
if (isSelected(brand, model)) {
|
||||||
|
var next = value.filter(function (item) {
|
||||||
|
return vehicleBindingKey(item) !== key;
|
||||||
|
});
|
||||||
|
onChange(next);
|
||||||
|
if (!next.length) {
|
||||||
|
setActiveBrand(VEHICLE_BINDING_ALL_BRAND);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange(isAllMode ? [{ brand: brand, model: model }] : value.concat([{ brand: brand, model: model }]));
|
||||||
|
setActiveBrand(brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBinding(binding) {
|
||||||
|
var key = vehicleBindingKey(binding);
|
||||||
|
var next = value.filter(function (item) {
|
||||||
|
return vehicleBindingKey(item) !== key;
|
||||||
|
});
|
||||||
|
onChange(next);
|
||||||
|
if (!next.length) {
|
||||||
|
setActiveBrand(VEHICLE_BINDING_ALL_BRAND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAll(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
selectAllBrands();
|
||||||
|
}
|
||||||
|
|
||||||
|
var panelContent = React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-body vm-operate-city-picker-panel ct-vehicle-brand-model-panel',
|
||||||
|
},
|
||||||
|
React.createElement('label', { className: 'vm-operate-city-edit-search' },
|
||||||
|
React.createElement('span', { className: 'vm-operate-city-edit-search-icon', 'aria-hidden': true },
|
||||||
|
React.createElement(SearchIcon)
|
||||||
|
),
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'text',
|
||||||
|
className: 'vm-operate-city-edit-search-input',
|
||||||
|
value: query,
|
||||||
|
placeholder: '输入品牌或型号名称快速筛选',
|
||||||
|
'aria-label': '筛选品牌型号',
|
||||||
|
autoComplete: 'off',
|
||||||
|
onChange: function (e) { setQuery(e.target.value); },
|
||||||
|
}),
|
||||||
|
query
|
||||||
|
? React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'vm-operate-city-edit-search-clear',
|
||||||
|
onClick: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setQuery('');
|
||||||
|
},
|
||||||
|
'aria-label': '清空搜索',
|
||||||
|
}, React.createElement(CloseIcon))
|
||||||
|
: null
|
||||||
|
),
|
||||||
|
value.length > 0
|
||||||
|
? React.createElement('div', { className: 'vm-operate-city-edit-selected', 'aria-live': 'polite' },
|
||||||
|
React.createElement('span', { className: 'vm-operate-city-edit-selected-label' }, '已选'),
|
||||||
|
React.createElement('div', { className: 'ct-vehicle-brand-model-panel__selected-list' },
|
||||||
|
value.map(function (item) {
|
||||||
|
var label = formatBrandModelPair(item.brand, item.model);
|
||||||
|
return React.createElement('span', {
|
||||||
|
key: vehicleBindingKey(item),
|
||||||
|
className: 'ct-vehicle-brand-model-panel__selected-tag',
|
||||||
|
},
|
||||||
|
label,
|
||||||
|
React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
'aria-label': '移除' + label,
|
||||||
|
onMouseDown: function (e) { e.preventDefault(); },
|
||||||
|
onClick: function () { removeBinding(item); },
|
||||||
|
}, React.createElement(CloseIcon))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: isAllMode
|
||||||
|
? React.createElement('div', { className: 'vm-operate-city-edit-selected', 'aria-live': 'polite' },
|
||||||
|
React.createElement('span', { className: 'vm-operate-city-edit-selected-label' }, '已选'),
|
||||||
|
React.createElement('div', { className: 'ct-vehicle-brand-model-panel__selected-list' },
|
||||||
|
React.createElement('span', {
|
||||||
|
className: 'ct-vehicle-brand-model-panel__selected-tag ct-vehicle-brand-model-panel__selected-tag--all',
|
||||||
|
}, '全部')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-main' + (isSearchMode ? ' is-search' : ''),
|
||||||
|
},
|
||||||
|
!isSearchMode
|
||||||
|
? React.createElement('div', { className: 'vm-operate-city-edit-step vm-operate-city-edit-step--province' },
|
||||||
|
React.createElement('span', { className: 'vm-operate-city-step-label' }, '品牌'),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-chips',
|
||||||
|
role: 'tablist',
|
||||||
|
'aria-label': '品牌列表',
|
||||||
|
},
|
||||||
|
React.createElement('button', {
|
||||||
|
key: VEHICLE_BINDING_ALL_BRAND,
|
||||||
|
type: 'button',
|
||||||
|
role: 'tab',
|
||||||
|
'aria-selected': isBrowsingAllTab,
|
||||||
|
className: 'vm-operate-city-chip ct-vehicle-brand-model-picker__all-brand'
|
||||||
|
+ (isBrowsingAllTab ? ' active' : ''),
|
||||||
|
onClick: selectAllBrands,
|
||||||
|
}, '全部'),
|
||||||
|
LEASE_VEHICLE_BRAND_MODEL_CATALOG.map(function (item) {
|
||||||
|
var active = activeBrand === item.brand;
|
||||||
|
return React.createElement('button', {
|
||||||
|
key: item.brand,
|
||||||
|
type: 'button',
|
||||||
|
role: 'tab',
|
||||||
|
'aria-selected': active,
|
||||||
|
className: 'vm-operate-city-chip' + (active ? ' active' : ''),
|
||||||
|
onClick: function () {
|
||||||
|
setActiveBrand(item.brand);
|
||||||
|
setQuery('');
|
||||||
|
},
|
||||||
|
}, item.brand);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
React.createElement('div', { className: 'vm-operate-city-edit-step vm-operate-city-edit-step--city' },
|
||||||
|
React.createElement('span', { className: 'vm-operate-city-step-label' },
|
||||||
|
isSearchMode ? ('搜索结果(' + displayModels.length + ')') : '型号',
|
||||||
|
),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-chips',
|
||||||
|
role: 'listbox',
|
||||||
|
'aria-label': '型号列表',
|
||||||
|
'aria-multiselectable': true,
|
||||||
|
},
|
||||||
|
isBrowsingAllTab && isAllMode
|
||||||
|
? React.createElement('p', { className: 'vm-operate-city-empty ct-vehicle-brand-model-picker__all-hint' },
|
||||||
|
'当前章节适用于全部品牌、全部型号;如需限定车型,请先选择左侧品牌'
|
||||||
|
)
|
||||||
|
: displayModels.length === 0
|
||||||
|
? React.createElement('p', { className: 'vm-operate-city-empty' },
|
||||||
|
isSearchMode ? '未找到匹配型号,请换个关键词' : '暂无型号'
|
||||||
|
)
|
||||||
|
: displayModels.map(function (item) {
|
||||||
|
var checked = isSelected(item.brand, item.model);
|
||||||
|
return React.createElement('button', {
|
||||||
|
key: vehicleBindingKey(item),
|
||||||
|
type: 'button',
|
||||||
|
role: 'option',
|
||||||
|
'aria-selected': checked,
|
||||||
|
className: 'vm-operate-city-chip' + (checked ? ' active' : ''),
|
||||||
|
onClick: function () { togglePair(item.brand, item.model); },
|
||||||
|
}, item.model);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return React.createElement('div', { className: 'vm-operate-city-field ct-vehicle-brand-model-picker' },
|
||||||
|
React.createElement(Popover, {
|
||||||
|
trigger: 'click',
|
||||||
|
placement: 'bottomLeft',
|
||||||
|
open: open,
|
||||||
|
onOpenChange: handleOpenChange,
|
||||||
|
overlayClassName: 'ct-vehicle-brand-model-popover',
|
||||||
|
getPopupContainer: function () { return document.body; },
|
||||||
|
content: panelContent,
|
||||||
|
},
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'vm-filter-picker-control ct-vehicle-brand-model-picker__trigger' + (open ? ' open' : ''),
|
||||||
|
role: 'combobox',
|
||||||
|
'aria-label': '品牌型号',
|
||||||
|
'aria-expanded': open,
|
||||||
|
tabIndex: 0,
|
||||||
|
onKeyDown: function (e) {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isAllMode
|
||||||
|
? React.createElement('span', {
|
||||||
|
className: 'ct-vehicle-brand-model-picker__summary-tag ct-vehicle-brand-model-picker__summary-tag--all',
|
||||||
|
title: '全部品牌、全部型号',
|
||||||
|
}, '全部')
|
||||||
|
: value.length > 0
|
||||||
|
? React.createElement('div', { className: 'ct-vehicle-brand-model-picker__summary-tags' },
|
||||||
|
value.map(function (item) {
|
||||||
|
var label = formatBrandModelPair(item.brand, item.model);
|
||||||
|
return React.createElement('span', {
|
||||||
|
key: vehicleBindingKey(item),
|
||||||
|
className: 'ct-vehicle-brand-model-picker__summary-tag',
|
||||||
|
title: label,
|
||||||
|
},
|
||||||
|
React.createElement('span', { className: 'ct-vehicle-brand-model-picker__summary-tag-brand' }, item.brand),
|
||||||
|
React.createElement('span', { className: 'ct-vehicle-brand-model-picker__summary-tag-sep', 'aria-hidden': true }, '·'),
|
||||||
|
React.createElement('span', { className: 'ct-vehicle-brand-model-picker__summary-tag-model' }, item.model)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: React.createElement('span', {
|
||||||
|
className: 'vm-filter-picker-input ct-vehicle-brand-model-picker__summary is-placeholder',
|
||||||
|
}, '请选择品牌 / 型号'),
|
||||||
|
!isAllMode && value.length > 0
|
||||||
|
? React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'vm-filter-picker-clear',
|
||||||
|
onClick: clearAll,
|
||||||
|
'aria-label': '清空品牌型号',
|
||||||
|
tabIndex: -1,
|
||||||
|
}, React.createElement(CloseIcon))
|
||||||
|
: null,
|
||||||
|
React.createElement('span', { className: 'vm-filter-picker-chevron' },
|
||||||
|
React.createElement(ChevronDownIcon)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
75
src/prototypes/contract-template-management/contract-template-catalog.js
vendored
Normal file
75
src/prototypes/contract-template-management/contract-template-catalog.js
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { V266_LEASE_DOCUMENT_HTML } from './v266-lease-document.js';
|
||||||
|
import { TRIAL_AGREEMENT_DOCUMENT_HTML } from './trial-agreement-document.js';
|
||||||
|
import { TRIAL_AGREEMENT_18T_DOCUMENT_HTML } from './trial-agreement-18t-document.js';
|
||||||
|
import { mergeMonolithicHtml } from './contract-template-section-html.js';
|
||||||
|
import { buildInitialWordTemplates } from './contract-template-seed.js';
|
||||||
|
import {
|
||||||
|
ensureContractTemplateStore,
|
||||||
|
getPublishedEnabledLeaseOptions,
|
||||||
|
getPublishedContractTypeOptions,
|
||||||
|
getTemplateDocumentById,
|
||||||
|
getDefaultTemplateIdForContractType,
|
||||||
|
subscribeContractTemplates,
|
||||||
|
mapTemplateToLeaseOption,
|
||||||
|
restoreContractTemplatesFromSession,
|
||||||
|
} from './contract-template-store.js';
|
||||||
|
|
||||||
|
export {
|
||||||
|
getContractTypeLabel,
|
||||||
|
normalizeContractType,
|
||||||
|
resolveContractType,
|
||||||
|
} from './contract-template-types.js';
|
||||||
|
|
||||||
|
restoreContractTemplatesFromSession();
|
||||||
|
ensureContractTemplateStore(buildInitialWordTemplates);
|
||||||
|
|
||||||
|
export function getPublishedContractTemplateOptions(category) {
|
||||||
|
return getPublishedEnabledLeaseOptions(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPublishedContractTypeChoices(category) {
|
||||||
|
return getPublishedContractTypeOptions(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribePublishedContractTemplateOptions(listener) {
|
||||||
|
return subscribeContractTemplates(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContractTemplateOption(templateId) {
|
||||||
|
var doc = getTemplateDocumentById(templateId);
|
||||||
|
if (!doc || doc.status !== 'published' || doc.enabled === false) return null;
|
||||||
|
return mapTemplateToLeaseOption(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStaticTemplateBaseHtml(templateId) {
|
||||||
|
switch (templateId) {
|
||||||
|
case 'doc-4':
|
||||||
|
return TRIAL_AGREEMENT_DOCUMENT_HTML || '';
|
||||||
|
case 'doc-5':
|
||||||
|
return TRIAL_AGREEMENT_18T_DOCUMENT_HTML || '';
|
||||||
|
case 'doc-1':
|
||||||
|
case 'doc-2':
|
||||||
|
default:
|
||||||
|
return V266_LEASE_DOCUMENT_HTML || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 优先读取 store 中模板的 sectionContents,静态 HTML 仅作兜底 */
|
||||||
|
export function getContractTemplateBaseHtml(templateId) {
|
||||||
|
var doc = getTemplateDocumentById(templateId);
|
||||||
|
if (doc && doc.sectionContents) {
|
||||||
|
var merged = mergeMonolithicHtml(doc.sectionContents);
|
||||||
|
if (merged && String(merged).trim()) return merged;
|
||||||
|
}
|
||||||
|
return getStaticTemplateBaseHtml(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultContractTemplateId(contractType, category) {
|
||||||
|
return getDefaultTemplateIdForContractType(contractType, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContractTemplatePreviewBadge(templateId) {
|
||||||
|
var option = getContractTemplateOption(templateId);
|
||||||
|
if (!option) return '请先选择合同模板';
|
||||||
|
return [option.contractTypeLabel, option.fileName || option.title].filter(Boolean).join(' · ');
|
||||||
|
}
|
||||||
95
src/prototypes/contract-template-management/contract-template-section-filter.js
vendored
Normal file
95
src/prototypes/contract-template-management/contract-template-section-filter.js
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* 按章节适用车型绑定,从整份合同 HTML 中移除未命中的附件章节。
|
||||||
|
*/
|
||||||
|
import { sectionBindingsMatchOrder } from './vehicle-binding-utils.js';
|
||||||
|
|
||||||
|
var ATTACHMENT_SECTIONS = [
|
||||||
|
{ key: 'lease_order', markers: ['<b>1:租赁订单 </b>', '附件 1:租赁订单', '附件1:租赁订单', '1:租赁订单'] },
|
||||||
|
{ key: 'vehicle_cost', markers: ['附件2:车辆产生的费用', '附件2:车辆产生的费用', '<b>附件2:车辆产生的费用</b>'] },
|
||||||
|
{ key: 'safety_notice', markers: ['附件3:安全责任告知书', '附件3:安全责任告知书', '<b>附件3:安全责任告知书</b>'] },
|
||||||
|
{ key: 'dynamic_supervision', markers: ['附件4:重型普通货运车辆租赁动态监管责任告知与确认书', '附件4:'] },
|
||||||
|
{ key: 'safety_commitment', markers: ['<b>附件5:</b>', '<b>安全责任承诺书</b></p><p class="ct-word-body">为严格落实', '附件5:', '附件5:'] },
|
||||||
|
{ key: 'vehicle_spec', markers: ['<b>附件6:车辆使用规范和驾驶员要求</b>', '附件6:车辆使用规范和驾驶员要求', '附件6:'] },
|
||||||
|
{ key: 'authorization', markers: ['font: 28.0px Times; color: #000000"><b>授权委托书</b>', '<b>授权委托书</b></p><p class="ct-word-body">致:', '<b>授权委托书</b><b></b></p><p class="ct-word-body">致:'] },
|
||||||
|
{ key: 'safety_agreement', markers: ['data-risk-redline="1"><b>安全责任协议</b></span></p>', '<b>安全责任协议</b></p><p class="ct-word-body"><br></p><p class="ct-word-body"><b>甲方(出租方):</b>', '<b>安全责任协议</b></p><p class="ct-word-body"><br></p>'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
function isValidSectionMarkerMatch(source, index, marker) {
|
||||||
|
if (index < 0) return false;
|
||||||
|
var text = String(source || '');
|
||||||
|
var before = text.slice(Math.max(0, index - 8), index);
|
||||||
|
var after = text.slice(index, index + String(marker || '').length + 12);
|
||||||
|
if (/[((]$/.test(before)) return false;
|
||||||
|
if (/、$/.test(before)) return false;
|
||||||
|
if (/^《/.test(after.slice(String(marker).length))) return false;
|
||||||
|
var nearby = text.slice(Math.max(0, index - 24), index + String(marker).length + 24);
|
||||||
|
if (/详见.{0,20}附件/.test(nearby)) return false;
|
||||||
|
if (/以附件/.test(nearby)) return false;
|
||||||
|
if (String(marker).length <= 10 && !/<b>/.test(nearby)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstMarker(source, markers) {
|
||||||
|
var text = String(source || '');
|
||||||
|
var best = -1;
|
||||||
|
(markers || []).forEach(function (marker) {
|
||||||
|
var from = 0;
|
||||||
|
while (from < text.length) {
|
||||||
|
var idx = text.indexOf(marker, from);
|
||||||
|
if (idx < 0) break;
|
||||||
|
if (isValidSectionMarkerMatch(text, idx, marker)) {
|
||||||
|
if (best < 0 || idx < best) best = idx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
from = idx + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectSectionRanges(html) {
|
||||||
|
var source = String(html || '');
|
||||||
|
var points = ATTACHMENT_SECTIONS.map(function (section) {
|
||||||
|
return {
|
||||||
|
key: section.key,
|
||||||
|
index: findFirstMarker(source, section.markers),
|
||||||
|
};
|
||||||
|
}).filter(function (point) { return point.index >= 0; });
|
||||||
|
|
||||||
|
points.sort(function (a, b) { return a.index - b.index; });
|
||||||
|
|
||||||
|
return points.map(function (point, idx) {
|
||||||
|
var next = points[idx + 1];
|
||||||
|
return {
|
||||||
|
key: point.key,
|
||||||
|
start: point.index,
|
||||||
|
end: next ? next.index : source.length,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyAttachmentSectionVehicleFilter(html, sectionVehicleBindings, vehicleKeys) {
|
||||||
|
var source = String(html || '');
|
||||||
|
if (!source) return source;
|
||||||
|
|
||||||
|
var bindingsMap = sectionVehicleBindings || {};
|
||||||
|
var ranges = collectSectionRanges(source);
|
||||||
|
if (!ranges.length) return source;
|
||||||
|
|
||||||
|
var removeMask = new Array(source.length);
|
||||||
|
ranges.forEach(function (range) {
|
||||||
|
var bindings = bindingsMap[range.key] || [];
|
||||||
|
if (sectionBindingsMatchOrder(bindings, vehicleKeys)) return;
|
||||||
|
for (var i = range.start; i < range.end; i++) {
|
||||||
|
removeMask[i] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!removeMask.some(Boolean)) return source;
|
||||||
|
|
||||||
|
var out = '';
|
||||||
|
for (var pos = 0; pos < source.length; pos++) {
|
||||||
|
if (!removeMask[pos]) out += source.charAt(pos);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
102
src/prototypes/contract-template-management/contract-template-section-html.js
vendored
Normal file
102
src/prototypes/contract-template-management/contract-template-section-html.js
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* 章节 HTML 合并 / 拆分(合同模板管理 ↔ 租赁合同预览共用)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export var DOCUMENT_SECTION_TABS = [
|
||||||
|
{ key: 'main', label: '合同主体' },
|
||||||
|
{ key: 'lease_order', label: '租赁订单', markers: ['<b>1:租赁订单 </b>', '附件 1:租赁订单', '附件1:租赁订单'] },
|
||||||
|
{ key: 'vehicle_cost', label: '车辆产生的费用', markers: ['附件2:车辆产生的费用', '附件2:车辆产生的费用', '<b>附件2:车辆产生的费用</b>'] },
|
||||||
|
{ key: 'safety_notice', label: '安全责任告知书', markers: ['附件3:安全责任告知书', '附件3:安全责任告知书', '<b>附件3:安全责任告知书</b>'] },
|
||||||
|
{ key: 'dynamic_supervision', label: '重型普通货运车辆租赁动态监管责任告知与确认书', markers: ['附件4:重型普通货运车辆租赁动态监管责任告知与确认书', '附件4:'] },
|
||||||
|
{ key: 'safety_commitment', label: '安全责任承诺书', markers: ['<b>附件5:</b>', '<b>安全责任承诺书</b></p><p class="ct-word-body">为严格落实', '附件5:', '附件5:'] },
|
||||||
|
{ key: 'vehicle_spec', label: '车辆使用规范和驾驶员要求', markers: ['<b>附件6:车辆使用规范和驾驶员要求</b>', '附件6:车辆使用规范和驾驶员要求', '附件6:'] },
|
||||||
|
{ key: 'authorization', label: '授权委托书', markers: ['font: 28.0px Times; color: #000000"><b>授权委托书</b>', '<b>授权委托书</b></p><p class="ct-word-body">致:', '<b>授权委托书</b><b></b></p><p class="ct-word-body">致:'] },
|
||||||
|
{ key: 'safety_agreement', label: '安全责任协议', markers: ['data-risk-redline="1"><b>安全责任协议</b></span></p>', '<b>安全责任协议</b></p><p class="ct-word-body"><br></p><p class="ct-word-body"><b>甲方(出租方):</b>', '<b>安全责任协议</b></p><p class="ct-word-body"><br></p>'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function emptySectionMap() {
|
||||||
|
var map = {};
|
||||||
|
DOCUMENT_SECTION_TABS.forEach(function (tab) { map[tab.key] = ''; });
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidSectionMarkerMatch(source, index, marker) {
|
||||||
|
if (index < 0) return false;
|
||||||
|
var text = String(source || '');
|
||||||
|
var before = text.slice(Math.max(0, index - 8), index);
|
||||||
|
var after = text.slice(index, index + String(marker || '').length + 12);
|
||||||
|
if (/[((]$/.test(before)) return false;
|
||||||
|
if (/、$/.test(before)) return false;
|
||||||
|
if (/^《/.test(after.slice(String(marker).length))) return false;
|
||||||
|
var nearby = text.slice(Math.max(0, index - 24), index + String(marker).length + 24);
|
||||||
|
if (/详见.{0,20}附件/.test(nearby)) return false;
|
||||||
|
if (/以附件/.test(nearby)) return false;
|
||||||
|
if (String(marker).length <= 10 && !/<b>/.test(nearby)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFirstMarker(source, markers) {
|
||||||
|
var text = String(source || '');
|
||||||
|
var best = -1;
|
||||||
|
(markers || []).forEach(function (marker) {
|
||||||
|
var from = 0;
|
||||||
|
while (from < text.length) {
|
||||||
|
var idx = text.indexOf(marker, from);
|
||||||
|
if (idx < 0) break;
|
||||||
|
if (isValidSectionMarkerMatch(text, idx, marker)) {
|
||||||
|
if (best < 0 || idx < best) best = idx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
from = idx + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSafeHtmlSplitIndex(source, index) {
|
||||||
|
var text = String(source || '');
|
||||||
|
var candidates = ['</p>', '</table>', '</div>'];
|
||||||
|
var best = index;
|
||||||
|
candidates.forEach(function (tag) {
|
||||||
|
var idx = text.lastIndexOf(tag, index);
|
||||||
|
if (idx >= 0) {
|
||||||
|
var end = idx + tag.length;
|
||||||
|
if (end > best) best = end;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return best > 0 ? best : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将整份 Word HTML 按附件标记拆为章节 map(种子数据 / 导入用) */
|
||||||
|
export function splitMonolithicHtml(html) {
|
||||||
|
var source = String(html || '');
|
||||||
|
var result = emptySectionMap();
|
||||||
|
if (!source.trim()) return result;
|
||||||
|
var splitPoints = [];
|
||||||
|
DOCUMENT_SECTION_TABS.slice(1).forEach(function (tab) {
|
||||||
|
var idx = findFirstMarker(source, tab.markers || [tab.label]);
|
||||||
|
if (idx >= 0) splitPoints.push({ key: tab.key, index: idx });
|
||||||
|
});
|
||||||
|
splitPoints.sort(function (a, b) { return a.index - b.index; });
|
||||||
|
var deduped = [];
|
||||||
|
splitPoints.forEach(function (point) {
|
||||||
|
if (!deduped.length || deduped[deduped.length - 1].index !== point.index) deduped.push(point);
|
||||||
|
});
|
||||||
|
result.main = deduped.length ? source.slice(0, findSafeHtmlSplitIndex(source, deduped[0].index)) : source;
|
||||||
|
for (var i = 0; i < deduped.length; i++) {
|
||||||
|
var start = deduped[i].index;
|
||||||
|
var end = i + 1 < deduped.length ? findSafeHtmlSplitIndex(source, deduped[i + 1].index) : source.length;
|
||||||
|
result[deduped[i].key] = source.slice(start, end);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
164
src/prototypes/contract-template-management/contract-template-seed.js
vendored
Normal file
164
src/prototypes/contract-template-management/contract-template-seed.js
vendored
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import { V266_LEASE_DOCUMENT_HTML } from './v266-lease-document.js';
|
||||||
|
import { TRIAL_AGREEMENT_DOCUMENT_HTML } from './trial-agreement-document.js';
|
||||||
|
import { TRIAL_AGREEMENT_18T_DOCUMENT_HTML } from './trial-agreement-18t-document.js';
|
||||||
|
import { emptySectionMap, splitMonolithicHtml } from './contract-template-section-html.js';
|
||||||
|
|
||||||
|
function getLeaseDocumentSectionMap() {
|
||||||
|
if (typeof V266_LEASE_DOCUMENT_HTML !== 'undefined' && V266_LEASE_DOCUMENT_HTML) {
|
||||||
|
return splitMonolithicHtml(V266_LEASE_DOCUMENT_HTML);
|
||||||
|
}
|
||||||
|
return emptySectionMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTrialAgreementFileName(name) {
|
||||||
|
return /商用车试用协议/i.test(String(name || ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTrialAgreement18tFileName(name) {
|
||||||
|
return /商用车试用协议.*现代18吨|现代18吨.*商用车试用协议/i.test(String(name || ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrialAgreementHtml(fileName) {
|
||||||
|
if (isTrialAgreement18tFileName(fileName)) {
|
||||||
|
return typeof TRIAL_AGREEMENT_18T_DOCUMENT_HTML !== 'undefined' ? TRIAL_AGREEMENT_18T_DOCUMENT_HTML : '';
|
||||||
|
}
|
||||||
|
if (isTrialAgreementFileName(fileName)) {
|
||||||
|
return typeof TRIAL_AGREEMENT_DOCUMENT_HTML !== 'undefined' ? TRIAL_AGREEMENT_DOCUMENT_HTML : '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrialAgreementSectionMap(fileName) {
|
||||||
|
var html = getTrialAgreementHtml(fileName);
|
||||||
|
if (!html) return emptySectionMap();
|
||||||
|
return splitMonolithicHtml(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWordDocument(params) {
|
||||||
|
return Object.assign({
|
||||||
|
category: 'lease',
|
||||||
|
contractType: '',
|
||||||
|
fileName: '未命名合同.docx',
|
||||||
|
versionName: '未命名合同',
|
||||||
|
versionNo: null,
|
||||||
|
status: 'draft',
|
||||||
|
enabled: false,
|
||||||
|
versionLogs: [],
|
||||||
|
sectionContents: emptySectionMap(),
|
||||||
|
sectionVehicleBindings: {},
|
||||||
|
remark: '',
|
||||||
|
}, params || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 冷启动种子:未打开模板管理页时租赁创建等场景仍可列出已启用模板 */
|
||||||
|
export function buildInitialWordTemplates() {
|
||||||
|
var v266 = getLeaseDocumentSectionMap();
|
||||||
|
var trialBase = getTrialAgreementSectionMap('商用车试用协议.docx');
|
||||||
|
var trial18t = getTrialAgreementSectionMap('商用车试用协议-现代18吨.docx');
|
||||||
|
var model18t = [{ brand: '现代', model: '18吨氢燃料电池车' }];
|
||||||
|
var model45t = [{ brand: '现代', model: '帕力安牌4.5吨冷链车' }];
|
||||||
|
var model96 = [{ brand: '苏龙', model: '9.6米氢燃料电池车' }];
|
||||||
|
var fullSections = Object.assign(emptySectionMap(), v266);
|
||||||
|
return [
|
||||||
|
createWordDocument({
|
||||||
|
id: 'doc-1',
|
||||||
|
contractType: '现代18吨正式合同',
|
||||||
|
fileName: '现代18吨车型-数智中心版.docx',
|
||||||
|
versionName: '现代18吨车型-数智中心版',
|
||||||
|
versionNo: 'v1.00',
|
||||||
|
status: 'published',
|
||||||
|
enabled: true,
|
||||||
|
versionLogs: [{
|
||||||
|
id: 'log-doc-1-v0',
|
||||||
|
versionNo: 'v0.99',
|
||||||
|
action: '发布变更',
|
||||||
|
operator: '法务-王静',
|
||||||
|
time: '2026-05-20 16:40',
|
||||||
|
remark: '数智中心版条款调整前的归档快照',
|
||||||
|
snapshot: {
|
||||||
|
fileName: '现代18吨车型-数智中心版.docx',
|
||||||
|
versionName: '现代18吨车型-数智中心版',
|
||||||
|
versionNo: 'v0.99',
|
||||||
|
sectionContents: Object.assign(emptySectionMap(), v266),
|
||||||
|
sectionVehicleBindings: { lease_order: model18t },
|
||||||
|
remark: '历史快照(原型)',
|
||||||
|
archivedAt: '2026-05-20 16:40',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
sectionContents: fullSections,
|
||||||
|
sectionVehicleBindings: {
|
||||||
|
lease_order: model18t,
|
||||||
|
dynamic_supervision: model96,
|
||||||
|
vehicle_spec: model45t,
|
||||||
|
},
|
||||||
|
creator: '法务-王静',
|
||||||
|
createTime: '2026-05-25 09:13',
|
||||||
|
updater: '法务-王静',
|
||||||
|
updateTime: '2026-05-25 09:13',
|
||||||
|
remark: 'V26.6 现代18吨数智中心版标准合同',
|
||||||
|
}),
|
||||||
|
createWordDocument({
|
||||||
|
id: 'doc-2',
|
||||||
|
contractType: '正式合同',
|
||||||
|
fileName: '2026年标准商用车租赁合同.docx',
|
||||||
|
versionName: '2026年标准商用车租赁合同',
|
||||||
|
versionNo: 'v1.01',
|
||||||
|
status: 'published',
|
||||||
|
enabled: true,
|
||||||
|
sectionContents: fullSections,
|
||||||
|
sectionVehicleBindings: {},
|
||||||
|
creator: '法务-王静',
|
||||||
|
createTime: '2026-03-15 14:30',
|
||||||
|
updater: '法务-王静',
|
||||||
|
updateTime: '2026-03-15 14:30',
|
||||||
|
remark: '通用主体条款',
|
||||||
|
}),
|
||||||
|
createWordDocument({
|
||||||
|
id: 'doc-3',
|
||||||
|
contractType: '正式合同',
|
||||||
|
fileName: '2026年二季度草案.docx',
|
||||||
|
versionName: '2026年二季度草案',
|
||||||
|
versionNo: null,
|
||||||
|
status: 'draft',
|
||||||
|
sectionContents: fullSections,
|
||||||
|
sectionVehicleBindings: {},
|
||||||
|
creator: '法务-李明',
|
||||||
|
createTime: '2026-06-01 09:15',
|
||||||
|
updater: '法务-李明',
|
||||||
|
updateTime: '2026-06-01 09:15',
|
||||||
|
remark: '待法务审核发布',
|
||||||
|
}),
|
||||||
|
createWordDocument({
|
||||||
|
id: 'doc-4',
|
||||||
|
contractType: '试用合同',
|
||||||
|
fileName: '商用车试用协议.docx',
|
||||||
|
versionName: '商用车试用协议',
|
||||||
|
versionNo: 'v1.00',
|
||||||
|
status: 'published',
|
||||||
|
enabled: true,
|
||||||
|
sectionContents: Object.assign(emptySectionMap(), trialBase),
|
||||||
|
sectionVehicleBindings: {},
|
||||||
|
creator: '法务-王静',
|
||||||
|
createTime: '2026-06-20 10:00',
|
||||||
|
updater: '法务-王静',
|
||||||
|
updateTime: '2026-06-20 10:00',
|
||||||
|
remark: '通用商用车试用协议范本',
|
||||||
|
}),
|
||||||
|
createWordDocument({
|
||||||
|
id: 'doc-5',
|
||||||
|
contractType: '现代18吨试用合同',
|
||||||
|
fileName: '商用车试用协议-现代18吨.docx',
|
||||||
|
versionName: '商用车试用协议-现代18吨',
|
||||||
|
versionNo: 'v1.00',
|
||||||
|
status: 'published',
|
||||||
|
enabled: false,
|
||||||
|
sectionContents: Object.assign(emptySectionMap(), trial18t),
|
||||||
|
sectionVehicleBindings: {},
|
||||||
|
creator: '法务-李明',
|
||||||
|
createTime: '2026-06-25 14:20',
|
||||||
|
updater: '法务-李明',
|
||||||
|
updateTime: '2026-06-25 14:20',
|
||||||
|
remark: '现代18吨车型试用协议;含完整安全附件',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
205
src/prototypes/contract-template-management/contract-template-store.js
vendored
Normal file
205
src/prototypes/contract-template-management/contract-template-store.js
vendored
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* 合同模板跨原型共享状态(合同模板管理 ↔ 租赁合同创建)
|
||||||
|
* 原型级 store;localStorage 优先持久化,sessionStorage 作兼容回退。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
getContractTypeLabel,
|
||||||
|
normalizeContractType,
|
||||||
|
resolveContractType,
|
||||||
|
} from './contract-template-types.js';
|
||||||
|
|
||||||
|
var STORAGE_KEY = 'oneos_contract_templates_v1';
|
||||||
|
var templates = null;
|
||||||
|
var listeners = new Set();
|
||||||
|
|
||||||
|
function readFromStorage() {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
try {
|
||||||
|
var localRaw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (localRaw) {
|
||||||
|
var localParsed = JSON.parse(localRaw);
|
||||||
|
if (Array.isArray(localParsed) && localParsed.length) return localParsed;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
/* 忽略 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof sessionStorage === 'undefined') return null;
|
||||||
|
try {
|
||||||
|
var raw = sessionStorage.getItem(STORAGE_KEY);
|
||||||
|
if (!raw) return null;
|
||||||
|
var parsed = JSON.parse(raw);
|
||||||
|
return Array.isArray(parsed) ? parsed : null;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeToStorage() {
|
||||||
|
if (!templates) return;
|
||||||
|
var payload = JSON.stringify(templates);
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(STORAGE_KEY, payload);
|
||||||
|
} catch (err) {
|
||||||
|
/* 原型忽略存储失败 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof sessionStorage !== 'undefined') {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(STORAGE_KEY, payload);
|
||||||
|
} catch (err) {
|
||||||
|
/* 原型忽略存储失败 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify() {
|
||||||
|
var snapshot = templates ? templates.slice() : [];
|
||||||
|
listeners.forEach(function (fn) {
|
||||||
|
try {
|
||||||
|
fn(snapshot);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[contract-template-store] listener error', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restoreContractTemplatesFromSession() {
|
||||||
|
if (templates) return templates;
|
||||||
|
var restored = readFromStorage();
|
||||||
|
if (restored) {
|
||||||
|
templates = restored;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
return templates || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureContractTemplateStore(seedFactory) {
|
||||||
|
if (!templates) {
|
||||||
|
templates = readFromStorage();
|
||||||
|
}
|
||||||
|
if (!templates && typeof seedFactory === 'function') {
|
||||||
|
templates = seedFactory();
|
||||||
|
writeToStorage();
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
return templates || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContractTemplates() {
|
||||||
|
return templates ? templates.slice() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setContractTemplates(updater) {
|
||||||
|
var current = templates ? templates.slice() : [];
|
||||||
|
var next = typeof updater === 'function' ? updater(current) : updater;
|
||||||
|
templates = Array.isArray(next) ? next : current;
|
||||||
|
writeToStorage();
|
||||||
|
notify();
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribeContractTemplates(listener) {
|
||||||
|
if (typeof listener !== 'function') return function () {};
|
||||||
|
listeners.add(listener);
|
||||||
|
return function () {
|
||||||
|
listeners.delete(listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var VEHICLE_HINT_BY_ID = {
|
||||||
|
'doc-1': '现代 · 18吨氢燃料电池车',
|
||||||
|
'doc-2': '通用车型',
|
||||||
|
'doc-4': '通用车型',
|
||||||
|
'doc-5': '现代 · 18吨',
|
||||||
|
};
|
||||||
|
|
||||||
|
function inferKind(doc) {
|
||||||
|
return resolveContractType(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferVehicleHint(doc) {
|
||||||
|
if (doc.id && VEHICLE_HINT_BY_ID[doc.id]) return VEHICLE_HINT_BY_ID[doc.id];
|
||||||
|
return '通用车型';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapTemplateToLeaseOption(doc) {
|
||||||
|
var contractType = resolveContractType(doc);
|
||||||
|
return {
|
||||||
|
id: doc.id,
|
||||||
|
title: doc.versionName || stripExtension(doc.fileName) || doc.id,
|
||||||
|
fileName: doc.fileName || doc.versionName || '',
|
||||||
|
versionNo: doc.versionNo || '—',
|
||||||
|
contractType: contractType,
|
||||||
|
contractTypeLabel: getContractTypeLabel(contractType),
|
||||||
|
kind: inferKind(doc),
|
||||||
|
vehicleHint: inferVehicleHint(doc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripExtension(name) {
|
||||||
|
return String(name || '').replace(/\.[^.]+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 已发布且启用、可供租赁合同创建选用的模板 */
|
||||||
|
export function getPublishedEnabledLeaseOptions(category) {
|
||||||
|
var cat = category || 'lease';
|
||||||
|
return (templates || [])
|
||||||
|
.filter(function (doc) {
|
||||||
|
return doc.category === cat
|
||||||
|
&& doc.status === 'published'
|
||||||
|
&& doc.enabled !== false;
|
||||||
|
})
|
||||||
|
.map(mapTemplateToLeaseOption)
|
||||||
|
.sort(function (a, b) {
|
||||||
|
return String(a.fileName).localeCompare(String(b.fileName), 'zh-CN');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 已发布且启用模板中去重后的合同类型(供租赁合同创建选用) */
|
||||||
|
export function getPublishedContractTypeOptions(category) {
|
||||||
|
var cat = category || 'lease';
|
||||||
|
var seen = {};
|
||||||
|
var types = [];
|
||||||
|
(templates || []).forEach(function (doc) {
|
||||||
|
if (doc.category !== cat || doc.status !== 'published' || doc.enabled === false) return;
|
||||||
|
var type = resolveContractType(doc);
|
||||||
|
if (!type || seen[type]) return;
|
||||||
|
seen[type] = true;
|
||||||
|
types.push({ value: type, label: type });
|
||||||
|
});
|
||||||
|
return types.sort(function (a, b) {
|
||||||
|
return a.label.localeCompare(b.label, 'zh-CN');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTemplateDocumentById(templateId) {
|
||||||
|
if (!templateId || !templates) return null;
|
||||||
|
return templates.find(function (doc) { return doc.id === templateId; }) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTemplateSectionVehicleBindings(templateId) {
|
||||||
|
var doc = getTemplateDocumentById(templateId);
|
||||||
|
if (!doc || !doc.sectionVehicleBindings) return {};
|
||||||
|
return doc.sectionVehicleBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 某合同类型下已发布且启用的模板(取最近更新的一条) */
|
||||||
|
export function getDefaultTemplateIdForContractType(contractType, category) {
|
||||||
|
var normalizedType = normalizeContractType(contractType);
|
||||||
|
if (!normalizedType) return '';
|
||||||
|
var cat = category || 'lease';
|
||||||
|
var matches = (templates || []).filter(function (doc) {
|
||||||
|
return doc.category === cat
|
||||||
|
&& doc.status === 'published'
|
||||||
|
&& doc.enabled !== false
|
||||||
|
&& resolveContractType(doc) === normalizedType;
|
||||||
|
});
|
||||||
|
if (!matches.length) return '';
|
||||||
|
matches.sort(function (a, b) {
|
||||||
|
return String(b.updateTime || '').localeCompare(String(a.updateTime || ''), 'zh-CN');
|
||||||
|
});
|
||||||
|
return matches[0].id;
|
||||||
|
}
|
||||||
23
src/prototypes/contract-template-management/contract-template-types.js
vendored
Normal file
23
src/prototypes/contract-template-management/contract-template-types.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 合同类型 — 用户自定义文本,合同模板管理与租赁合同创建共用
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function normalizeContractType(value) {
|
||||||
|
return String(value || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContractTypeLabel(value) {
|
||||||
|
var text = normalizeContractType(value);
|
||||||
|
return text || '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 无 contractType 字段时,按文件名推断(兼容旧数据) */
|
||||||
|
export function inferContractTypeFromDoc(doc) {
|
||||||
|
if (doc && doc.contractType) return normalizeContractType(doc.contractType);
|
||||||
|
var name = String((doc && (doc.fileName || doc.versionName)) || '');
|
||||||
|
return /试用/.test(name) ? '试用合同' : '正式合同';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveContractType(doc) {
|
||||||
|
return inferContractTypeFromDoc(doc);
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @name 合同模板管理
|
* @name 合同模板管理
|
||||||
*/
|
*/
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { clearHostPrototypeRouteInfo } from '../../common/useHashPage';
|
||||||
import * as antd from 'antd';
|
import * as antd from 'antd';
|
||||||
import { ConfigProvider } from 'antd';
|
import { ConfigProvider } from 'antd';
|
||||||
import {
|
import {
|
||||||
@@ -72,6 +73,10 @@ export default function ContractTemplateManagementPage() {
|
|||||||
const [annotationPageId, setAnnotationPageId] = useState<ContractPageId>('list');
|
const [annotationPageId, setAnnotationPageId] = useState<ContractPageId>('list');
|
||||||
const navigationRef = useRef<ContractNavigationRef>({});
|
const navigationRef = useRef<ContractNavigationRef>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearHostPrototypeRouteInfo();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleDirectoryRoute = useCallback((node: AnnotationDirectoryRouteNode) => {
|
const handleDirectoryRoute = useCallback((node: AnnotationDirectoryRouteNode) => {
|
||||||
const payload = node.payload as { pageId?: string } | undefined;
|
const payload = node.payload as { pageId?: string } | undefined;
|
||||||
const pageId: ContractPageId = payload?.pageId === 'editor' ? 'editor' : 'list';
|
const pageId: ContractPageId = payload?.pageId === 'editor' ? 'editor' : 'list';
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const protoRoot = path.resolve(__dirname, '..');
|
||||||
|
const prdPath = path.resolve(protoRoot, '../../resources/contract-template-management/PRD.md');
|
||||||
|
const sourcePath = path.join(protoRoot, 'annotation-source.json');
|
||||||
|
|
||||||
|
const fullPrd = fs.readFileSync(prdPath, 'utf8');
|
||||||
|
const source = JSON.parse(fs.readFileSync(sourcePath, 'utf8'));
|
||||||
|
|
||||||
|
function extractSection(md, startHeading) {
|
||||||
|
const lines = md.split('\n');
|
||||||
|
let start = lines.findIndex((line) => line.trim() === startHeading || line.startsWith(startHeading));
|
||||||
|
if (start < 0) return null;
|
||||||
|
const startLevel = (lines[start].match(/^(#+)/)?.[1] ?? '##').length;
|
||||||
|
let end = lines.length;
|
||||||
|
for (let i = start + 1; i < lines.length; i++) {
|
||||||
|
if (/^#{1,6}\s/.test(lines[i])) {
|
||||||
|
const level = lines[i].match(/^(#+)/)?.[1].length ?? 2;
|
||||||
|
if (level <= startLevel) {
|
||||||
|
end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines.slice(start, end).join('\n').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function combineSections(headings) {
|
||||||
|
return headings.map((h) => extractSection(fullPrd, h)).filter(Boolean).join('\n\n---\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const overviewMd = `## 模块定位
|
||||||
|
|
||||||
|
| 项 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 模块名称 | 合同模板管理 |
|
||||||
|
| 目标用户 | 法务人员 |
|
||||||
|
| 核心任务 | 维护 Word 版商用车租赁合同模板,按章节编辑,发布版本并控制启用态 |
|
||||||
|
| 数据模型 | 一份 Word 文件 = 一条记录,内含 9 个章节内容 |
|
||||||
|
| PRD 文档 | 见目录「PRD 全文 → PRD 完整文档」 |
|
||||||
|
| 资源文件 | \`src/resources/contract-template-management/PRD.md\` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 产品目标(摘要)
|
||||||
|
|
||||||
|
| 目标 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |
|
||||||
|
| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |
|
||||||
|
| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |
|
||||||
|
| 版本可追溯 | 草稿 / 已发布分离;发布变更写入版本日志 |
|
||||||
|
| 多套模板并存 | 租赁创建时由用户选择已启用模板 |
|
||||||
|
|
||||||
|
**同步命令**:\`node src/prototypes/contract-template-management/scripts/build-annotation-source.mjs\``;
|
||||||
|
|
||||||
|
const DOC_MARKDOWN = {
|
||||||
|
'ct-doc-overview': overviewMd,
|
||||||
|
'ct-doc-prd-full': fullPrd,
|
||||||
|
'ct-doc-prd-overview': combineSections(['## 1. 背景与目标', '## 2. 用户与场景', '## 3. 名词解释', '## 4. 信息架构', '## 5. 数据模型', '## 6. 业务规则(全局)']),
|
||||||
|
'ct-doc-prd-list': `# 合同模板管理 · 列表页 PRD\n\n${extractSection(fullPrd, '## 7. 列表页需求') ?? ''}`,
|
||||||
|
'ct-doc-prd-editor': `# 合同模板管理 · 编辑/新增页 PRD\n\n${extractSection(fullPrd, '## 8. 编辑 / 新增页需求') ?? ''}`,
|
||||||
|
'ct-doc-prd-acceptance': extractSection(fullPrd, '## 16. 验收标准') ?? '## 16. 验收标准',
|
||||||
|
'ct-doc-node-filter': extractSection(fullPrd, '### 7.2 筛选区') ?? '',
|
||||||
|
'ct-doc-node-table': combineSections(['### 7.4 主表字段', '### 7.5 主表操作', '### 7.6 子表(展开行)']),
|
||||||
|
'ct-doc-node-tabs': combineSections(['### 8.3 章节 Tab', '### 8.4 条件条款说明(合同主体及租赁订单~安全责任协议)', '### 8.5 适用车型绑定(仅附件章节 Tab)']),
|
||||||
|
'ct-doc-node-workspace': combineSections(['### 8.6 编辑区(左侧)', '### 8.7 实时预览(右侧)']),
|
||||||
|
'ct-doc-annotation-hint': '## 标注查看提示\n\n右侧标注工具栏可浏览目录、切换主题与颜色筛选。列表页与编辑页标注按当前页面自动显隐。\n\n**PRD 同步**:目录「PRD 全文」已与 `src/resources/contract-template-management/PRD.md` 对齐;修改 PRD 后运行 `node src/prototypes/contract-template-management/scripts/build-annotation-source.mjs` 重新生成 `annotation-source.json`。',
|
||||||
|
};
|
||||||
|
|
||||||
|
const MARKDOWN_MAP = {
|
||||||
|
'ct-list-filter': `### 7.2 筛选区\n\n${extractSection(fullPrd, '### 7.2 筛选区')?.replace(/^### 7\.2 筛选区\n*/m, '') ?? ''}`.trim(),
|
||||||
|
'ct-list-table': combineSections(['### 7.4 主表字段', '### 7.5 主表操作', '### 7.6 子表(展开行)']),
|
||||||
|
'ct-editor-topbar': combineSections(['### 8.2 顶栏', '### 6.2 保存与发布']),
|
||||||
|
'ct-section-tabs': extractSection(fullPrd, '### 8.3 章节 Tab') ?? '',
|
||||||
|
'ct-contract-type': source.markdownMap?.['ct-contract-type'] ?? '',
|
||||||
|
'ct-vehicle-binding': extractSection(fullPrd, '### 8.5 适用车型绑定(仅附件章节 Tab)') ?? source.markdownMap?.['ct-vehicle-binding'] ?? '',
|
||||||
|
'ct-editor-panel': combineSections(['### 8.6 编辑区(左侧)']),
|
||||||
|
'ct-preview-panel': extractSection(fullPrd, '### 8.7 实时预览(右侧)') ?? '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function patchDirectoryMarkdown(nodes) {
|
||||||
|
for (const node of nodes ?? []) {
|
||||||
|
if (node.type === 'markdown' && DOC_MARKDOWN[node.id]) {
|
||||||
|
node.markdown = DOC_MARKDOWN[node.id];
|
||||||
|
if (node.id === 'ct-doc-prd-full') {
|
||||||
|
node.markdownPath = 'src/resources/contract-template-management/PRD.md';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.children) patchDirectoryMarkdown(node.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctDocRoot = source.directory?.nodes?.find((n) => n.id === 'ct-doc-root');
|
||||||
|
if (ctDocRoot) patchDirectoryMarkdown(ctDocRoot.children);
|
||||||
|
|
||||||
|
source.markdownMap = { ...source.markdownMap, ...MARKDOWN_MAP };
|
||||||
|
source.data.updatedAt = Date.now();
|
||||||
|
|
||||||
|
fs.writeFileSync(sourcePath, `${JSON.stringify(source, null, 2)}\n`);
|
||||||
|
console.log(`Synced annotation-source.json from PRD.md (${fullPrd.length} chars).`);
|
||||||
@@ -9,16 +9,16 @@
|
|||||||
|
|
||||||
.vm-page.ct-page .ct-data-table.ant-table-wrapper .ant-table-thead > tr > th {
|
.vm-page.ct-page .ct-data-table.ant-table-wrapper .ant-table-thead > tr > th {
|
||||||
background: var(--ln-canvas-soft) !important;
|
background: var(--ln-canvas-soft) !important;
|
||||||
color: var(--ln-muted) !important;
|
color: var(--ln-muted-80) !important;
|
||||||
font-size: 0.75rem !important;
|
font-size: 12px !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 600 !important;
|
||||||
letter-spacing: 0 !important;
|
letter-spacing: 0 !important;
|
||||||
border-bottom: 1px solid var(--ln-hairline) !important;
|
border-bottom: 1px solid var(--ln-hairline) !important;
|
||||||
padding: 8px 12px !important;
|
padding: 8px 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-data-table.ant-table-wrapper .ant-table-tbody > tr > td {
|
.vm-page.ct-page .ct-data-table.ant-table-wrapper .ant-table-tbody > tr > td {
|
||||||
font-size: 0.8125rem !important;
|
font-size: 0.875rem !important;
|
||||||
color: var(--ln-body) !important;
|
color: var(--ln-body) !important;
|
||||||
border-bottom: 1px solid var(--ln-hairline) !important;
|
border-bottom: 1px solid var(--ln-hairline) !important;
|
||||||
padding: 8px 12px !important;
|
padding: 8px 12px !important;
|
||||||
@@ -78,7 +78,50 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-data-table--nested {
|
.vm-page.ct-page .ct-data-table--nested {
|
||||||
--ct-list-table-width: 1232px;
|
--ct-list-table-width: 1100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表页全宽:表格与卡片铺满内容区(预留右侧标注工具栏空间) */
|
||||||
|
.vm-page.ct-page.ct-list-shell {
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: max(24px, 88px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page.ct-list-shell .vm-filter-card,
|
||||||
|
.vm-page.ct-page.ct-list-shell .vm-table-section,
|
||||||
|
.vm-page.ct-page.ct-list-shell .vm-table-card,
|
||||||
|
.vm-page.ct-page.ct-list-shell .vm-table-card.ct-list-table-card,
|
||||||
|
.vm-page.ct-page.ct-list-shell .ct-data-table.ant-table-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page.ct-list-shell .ct-data-table--nested {
|
||||||
|
--ct-list-table-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page.ct-list-shell .ct-data-table--nested .ant-table-content > table,
|
||||||
|
.vm-page.ct-page.ct-list-shell .ct-data-table--nested .ant-table-header table,
|
||||||
|
.vm-page.ct-page.ct-list-shell .ct-data-table--nested .ant-table-body table {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 100% !important;
|
||||||
|
table-layout: fixed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
.vm-page.ct-page .ct-data-table--nested {
|
||||||
|
--ct-list-table-width: 920px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page.ct-list-shell .ct-data-table--nested {
|
||||||
|
--ct-list-table-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-data-table.ant-table-wrapper .ct-col-optional {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-data-table--nested .ant-table-thead > tr > th.ant-table-row-expand-icon-cell,
|
.vm-page.ct-page .ct-data-table--nested .ant-table-thead > tr > th.ant-table-row-expand-icon-cell,
|
||||||
@@ -159,6 +202,19 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ct-modal-wrap .ant-modal {
|
||||||
|
max-width: calc(100vw - 32px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-layout-hint {
|
||||||
|
flex-basis: 100%;
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .vm-table-section .vm-table-toolbar {
|
.vm-page.ct-page .vm-table-section .vm-table-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -223,7 +279,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ct-section-tabs-more-popover .ct-section-tabs-more {
|
.ct-section-tabs-more-popover .ct-section-tabs-more {
|
||||||
width: min(320px, calc(100vw - 32px));
|
width: min(380px, calc(100vw - 32px));
|
||||||
padding: 12px 0 8px;
|
padding: 12px 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +307,8 @@
|
|||||||
|
|
||||||
.ct-section-tabs-more-popover .ct-section-tabs-more__item {
|
.ct-section-tabs-more-popover .ct-section-tabs-more__item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -265,12 +322,37 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: normal;
|
|
||||||
word-break: break-word;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s ease, color 0.15s ease;
|
transition: background 0.15s ease, color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ct-section-tabs-more-popover .ct-section-tabs-more__index {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--ln-surface-strong, #f1f5f4);
|
||||||
|
color: var(--ln-muted, #64748b);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-section-tabs-more-popover .ct-section-tabs-more__item.is-active .ct-section-tabs-more__index {
|
||||||
|
background: color-mix(in srgb, var(--ln-primary, #32a06e) 18%, #fff);
|
||||||
|
color: var(--ln-primary-focus, #2a8a5e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-section-tabs-more-popover .ct-section-tabs-more__label {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.ct-section-tabs-more-popover .ct-section-tabs-more__item:hover {
|
.ct-section-tabs-more-popover .ct-section-tabs-more__item:hover {
|
||||||
background: var(--ln-surface-strong, #f1f5f4);
|
background: var(--ln-surface-strong, #f1f5f4);
|
||||||
color: var(--ln-ink, #0f172a);
|
color: var(--ln-ink, #0f172a);
|
||||||
@@ -417,17 +499,42 @@
|
|||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
.vm-page.ct-page .ct-action-btn.ant-btn-link {
|
.vm-page.ct-page .ct-action-btn.ant-btn-link {
|
||||||
min-height: var(--vm-btn-height);
|
min-height: 44px;
|
||||||
padding: 0 8px !important;
|
padding: 0 10px !important;
|
||||||
font-size: 0.875rem !important;
|
font-size: 0.875rem !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
color: var(--ln-primary) !important;
|
color: var(--ln-primary) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-row-actions {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 2px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-row-actions__more.ant-btn-link {
|
||||||
|
padding: 0 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-action-btn.ant-btn-link:hover:not(:disabled) {
|
.vm-page.ct-page .ct-action-btn.ant-btn-link:hover:not(:disabled) {
|
||||||
color: var(--ln-primary-hover) !important;
|
color: var(--ln-primary-hover) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-action-btn.ant-btn-link:disabled,
|
||||||
|
.vm-page.ct-page .ct-action-btn.ant-btn-link.ant-btn-disabled {
|
||||||
|
color: #94A3B8 !important;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-action-btn.ant-btn-link.ant-btn-dangerous:disabled,
|
||||||
|
.vm-page.ct-page .ct-action-btn.ant-btn-link.ant-btn-dangerous.ant-btn-disabled {
|
||||||
|
color: #94A3B8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-btn-cta.ant-btn-primary {
|
.vm-page.ct-page .ct-btn-cta.ant-btn-primary {
|
||||||
background: var(--ln-primary) !important;
|
background: var(--ln-primary) !important;
|
||||||
border-color: var(--ln-primary) !important;
|
border-color: var(--ln-primary) !important;
|
||||||
@@ -461,6 +568,7 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-panel-head__badge {
|
.vm-page.ct-page .ct-panel-head__badge {
|
||||||
@@ -716,22 +824,101 @@
|
|||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
gap: 8px 10px;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid var(--ln-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tabs-scroll {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tabs-scroll::-webkit-scrollbar {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tabs-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: color-mix(in srgb, var(--ln-muted) 35%, transparent);
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid var(--ln-hairline);
|
||||||
|
border-radius: var(--ln-radius-control);
|
||||||
|
background: var(--ln-surface-card, #fff);
|
||||||
|
color: var(--ln-body);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-btn:hover,
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-btn.is-open {
|
||||||
|
border-color: color-mix(in srgb, var(--ln-primary) 35%, var(--ln-hairline));
|
||||||
|
background: var(--ln-primary-soft);
|
||||||
|
color: var(--ln-primary-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: inset 0 0 0 1px var(--vm-focus-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav__current {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-row: 2;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 2px;
|
||||||
|
color: var(--ln-ink);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.45;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-section-tabs {
|
.vm-page.ct-page .ct-section-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
padding-bottom: 10px;
|
padding: 2px 2px 4px;
|
||||||
border-bottom: 1px solid var(--ln-hairline);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-section-tab {
|
.vm-page.ct-page .ct-section-tab {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
max-width: 100%;
|
flex-shrink: 0;
|
||||||
|
max-width: 180px;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
padding: 6px 12px;
|
padding: 6px 10px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: var(--ln-radius-control);
|
border-radius: var(--ln-radius-control);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -743,6 +930,29 @@
|
|||||||
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tab__index {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--ln-surface-strong);
|
||||||
|
color: var(--ln-muted);
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tab__label {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-section-tab:hover {
|
.vm-page.ct-page .ct-section-tab:hover {
|
||||||
color: var(--ln-ink);
|
color: var(--ln-ink);
|
||||||
background: var(--ln-surface-strong);
|
background: var(--ln-surface-strong);
|
||||||
@@ -754,31 +964,52 @@
|
|||||||
border-color: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline));
|
border-color: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tab.is-active .ct-section-tab__index {
|
||||||
|
background: color-mix(in srgb, var(--ln-primary) 18%, #fff);
|
||||||
|
color: var(--ln-primary-focus);
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-section-tab:focus-visible {
|
.vm-page.ct-page .ct-section-tab:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: inset 0 0 0 1px var(--vm-focus-border-color);
|
box-shadow: inset 0 0 0 1px var(--vm-focus-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-section-tab--more .ct-section-tab__current {
|
|
||||||
max-width: 220px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--ln-muted);
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vm-page.ct-page .ct-section-tab--more.is-active .ct-section-tab__current {
|
|
||||||
color: var(--ln-primary-focus);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
.vm-page.ct-page .ct-section-tab {
|
.vm-page.ct-page .ct-section-nav {
|
||||||
min-height: 44px;
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-section-tab--more .ct-section-tab__current {
|
.vm-page.ct-page .ct-section-tabs-scroll {
|
||||||
max-width: 140px;
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-btn {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-nav__current {
|
||||||
|
grid-row: 3;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tab,
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-btn {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.vm-page.ct-page .ct-section-tabs-scroll {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-section-tab,
|
||||||
|
.vm-page.ct-page .ct-section-nav__list-btn {
|
||||||
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,7 +1066,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-vehicle-binding-card + .ct-editor-page__body,
|
.vm-page.ct-page .ct-vehicle-binding-card + .ct-editor-page__body,
|
||||||
.vm-page.ct-page .ct-editor-page > .ct-vehicle-binding-card {
|
.vm-page.ct-page .ct-contract-type-card + .ct-editor-page__body,
|
||||||
|
.vm-page.ct-page .ct-contract-type-card + .ct-vehicle-binding-card,
|
||||||
|
.vm-page.ct-page .ct-editor-page > .ct-vehicle-binding-card,
|
||||||
|
.vm-page.ct-page .ct-editor-page > .ct-contract-type-card {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,6 +1082,18 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-page__topbar-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-page__topbar-meta .ct-editor-category-field {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-editor-page__topbar-actions {
|
.vm-page.ct-page .ct-editor-page__topbar-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -965,7 +1211,7 @@
|
|||||||
color: var(--ln-muted);
|
color: var(--ln-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-editor-category-select {
|
.vm-page.ct-page .ct-editor-contract-type-input {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -998,7 +1244,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
min-height: var(--vm-btn-height, 36px);
|
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding: 0 0 10px;
|
padding: 0 0 10px;
|
||||||
border-bottom: 1px solid var(--ln-hairline);
|
border-bottom: 1px solid var(--ln-hairline);
|
||||||
@@ -1006,6 +1251,81 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn.ant-btn,
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ant-upload .ct-panel-tool-btn.ant-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
box-shadow: none;
|
||||||
|
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn__icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn--ghost.ant-btn {
|
||||||
|
background: var(--ln-surface-card);
|
||||||
|
color: var(--ln-body);
|
||||||
|
border: 1px solid var(--ln-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn--ghost.ant-btn:hover:not(:disabled) {
|
||||||
|
background: var(--ln-canvas-soft);
|
||||||
|
border-color: var(--ln-hairline-strong);
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn--accent.ant-btn {
|
||||||
|
background: var(--ln-primary-soft);
|
||||||
|
color: var(--ln-primary-focus);
|
||||||
|
border: 1px solid color-mix(in srgb, var(--ln-primary) 35%, var(--ln-hairline));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn--accent.ant-btn:hover:not(:disabled) {
|
||||||
|
background: color-mix(in srgb, var(--ln-primary) 14%, #fff);
|
||||||
|
border-color: var(--ln-primary);
|
||||||
|
color: var(--ln-primary-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn.is-active.ant-btn {
|
||||||
|
background: var(--ln-primary-soft);
|
||||||
|
color: var(--ln-primary-focus);
|
||||||
|
border-color: color-mix(in srgb, var(--ln-primary) 45%, var(--ln-hairline));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn.ant-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: inset 0 0 0 1px var(--vm-focus-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ant-upload {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn.ant-btn,
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ant-upload .ct-panel-tool-btn.ant-btn {
|
||||||
|
min-height: 44px;
|
||||||
|
height: 44px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__actions .ct-panel-tool-btn.ant-btn {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__title {
|
.vm-page.ct-page .ct-editor-workspace .ct-panel-head__title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1038,7 +1358,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-height: var(--vm-btn-height, 36px);
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-editor-workspace .ct-editor-column-card .ct-editor-panel,
|
.vm-page.ct-page .ct-editor-workspace .ct-editor-column-card .ct-editor-panel,
|
||||||
@@ -1373,11 +1693,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-main-clause-tip {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
border-left: 3px solid #7c3aed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ct-preview-doc .ct-vehicle-clause,
|
.ct-preview-doc .ct-vehicle-clause,
|
||||||
.ct-rich-editor .ct-vehicle-clause {
|
.ct-rich-editor .ct-vehicle-clause {
|
||||||
box-decoration-break: clone;
|
box-decoration-break: clone;
|
||||||
@@ -1725,6 +2040,20 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-form-field__required {
|
||||||
|
color: #dc2626;
|
||||||
|
margin-right: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-contract-type-card__form {
|
||||||
|
max-width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-contract-type-card__form .ct-editor-contract-type-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-vehicle-binding-card {
|
.vm-page.ct-page .ct-vehicle-binding-card {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
@@ -1756,6 +2085,192 @@
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__trigger {
|
||||||
|
width: 100%;
|
||||||
|
min-height: var(--vm-control-height, 32px);
|
||||||
|
height: var(--vm-control-height, 32px);
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
border-color: var(--ln-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__trigger:has(.ct-vehicle-brand-model-picker__summary-tags),
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__trigger:has(.ct-vehicle-brand-model-picker__summary-tag--all) {
|
||||||
|
height: auto;
|
||||||
|
min-height: var(--vm-control-height, 32px);
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__summary-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__summary-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: color-mix(in srgb, var(--ln-primary) 12%, var(--ln-surface-card));
|
||||||
|
border: 1px solid color-mix(in srgb, var(--ln-primary) 30%, transparent);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: var(--ln-primary-focus);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__summary-tag-brand,
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__summary-tag-model {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__summary-tag-sep {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: color-mix(in srgb, var(--ln-primary-focus) 70%, var(--ln-muted));
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__summary-tag--all,
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel__selected-tag--all {
|
||||||
|
background: color-mix(in srgb, var(--ln-primary) 16%, var(--ln-surface-card));
|
||||||
|
border-color: color-mix(in srgb, var(--ln-primary) 38%, transparent);
|
||||||
|
color: var(--ln-primary-focus);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__all-brand {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__all-hint {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-picker__summary.is-placeholder {
|
||||||
|
color: var(--ln-muted-soft);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-self: center;
|
||||||
|
height: auto;
|
||||||
|
line-height: 1.5715;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-vehicle-brand-model-popover .ant-popover-inner {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-vehicle-brand-model-popover .ct-vehicle-brand-model-panel {
|
||||||
|
width: min(560px, calc(100vw - 32px));
|
||||||
|
max-height: min(420px, calc(100vh - 120px));
|
||||||
|
padding: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-vehicle-brand-model-popover .vm-operate-city-edit-selected {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-vehicle-brand-model-popover .vm-operate-city-edit-main {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.ct-vehicle-brand-model-popover .vm-operate-city-edit-main {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-vehicle-brand-model-popover .vm-operate-city-edit-step--province {
|
||||||
|
padding-right: 0;
|
||||||
|
border-right: none;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid var(--ln-hairline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel .vm-operate-city-edit-main {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel__selected-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel__selected-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--ln-surface-card);
|
||||||
|
border: 1px solid color-mix(in srgb, var(--ln-primary) 22%, transparent);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel__selected-tag button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel__selected-tag button:hover {
|
||||||
|
background: var(--ln-hairline);
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel .vm-operate-city-edit-main {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.ct-page .ct-vehicle-brand-model-panel .vm-operate-city-edit-step--province {
|
||||||
|
padding-right: 0;
|
||||||
|
border-right: none;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid var(--ln-hairline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.ct-page .ct-vehicle-binding-brand {
|
.vm-page.ct-page .ct-vehicle-binding-brand {
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
@@ -1897,8 +2412,8 @@
|
|||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
|
background: linear-gradient(135deg, var(--ln-primary-soft) 0%, rgba(50, 160, 110, 0.06) 100%);
|
||||||
border: 1px solid #ddd6fe;
|
border: 1px solid color-mix(in srgb, var(--ln-primary) 22%, var(--ln-hairline));
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-vc-modal__hero-icon {
|
.ct-vc-modal__hero-icon {
|
||||||
@@ -1909,23 +2424,23 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #7c3aed;
|
background: var(--ln-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-vc-modal__hero-title {
|
.ct-vc-modal__hero-title {
|
||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #5b21b6;
|
color: var(--ln-ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-vc-modal__hero-list {
|
.ct-vc-modal__hero-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 1.1em;
|
padding-left: 1.1em;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
color: #4c1d95;
|
color: var(--ln-body);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-vc-modal__hero-list li + li {
|
.ct-vc-modal__hero-list li + li {
|
||||||
@@ -2021,8 +2536,8 @@
|
|||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: #ede9fe;
|
background: var(--ln-primary-soft);
|
||||||
color: #6d28d9;
|
color: var(--ln-primary-focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-vc-modal__chips {
|
.ct-vc-modal__chips {
|
||||||
@@ -2032,8 +2547,8 @@
|
|||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px dashed #c4b5fd;
|
border: 1px dashed color-mix(in srgb, var(--ln-primary) 30%, var(--ln-hairline));
|
||||||
background: #faf5ff;
|
background: var(--ln-canvas-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-vc-modal__chips.is-empty {
|
.ct-vc-modal__chips.is-empty {
|
||||||
@@ -2043,9 +2558,8 @@
|
|||||||
|
|
||||||
.ct-vc-modal__empty-hint {
|
.ct-vc-modal__empty-hint {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.875rem;
|
||||||
color: #7c3aed;
|
color: var(--ln-muted);
|
||||||
opacity: 0.8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ct-vc-modal__chip.ant-tag {
|
.ct-vc-modal__chip.ant-tag {
|
||||||
|
|||||||
58
src/prototypes/contract-template-management/vehicle-binding-utils.js
vendored
Normal file
58
src/prototypes/contract-template-management/vehicle-binding-utils.js
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* 章节适用车型绑定:空列表或显式「全部」表示全部品牌型号适用。
|
||||||
|
*/
|
||||||
|
|
||||||
|
export var VEHICLE_BINDING_ALL_BRAND = '__all__';
|
||||||
|
export var VEHICLE_BINDING_ALL_MODEL = '__all__';
|
||||||
|
|
||||||
|
export function vehicleBindingKey(binding) {
|
||||||
|
return String(binding.brand || '') + '|' + String(binding.model || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseVehicleBindingKey(key) {
|
||||||
|
var parts = String(key || '').split('|');
|
||||||
|
return { brand: parts[0] || '', model: parts[1] || '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAllVehicleBindings(bindings) {
|
||||||
|
var list = bindings || [];
|
||||||
|
if (!list.length) return true;
|
||||||
|
if (list.length !== 1) return false;
|
||||||
|
var item = list[0] || {};
|
||||||
|
return item.brand === VEHICLE_BINDING_ALL_BRAND && item.model === VEHICLE_BINDING_ALL_MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSpecificVehicleBindings(bindings) {
|
||||||
|
return !isAllVehicleBindings(bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeAllVehicleBindings() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function vehicleMatchesBindings(brand, model, bindings) {
|
||||||
|
if (isAllVehicleBindings(bindings)) return true;
|
||||||
|
var list = bindings || [];
|
||||||
|
return list.some(function (item) {
|
||||||
|
return item.brand === brand && item.model === model;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 订单车辆 keys 是否命中章节绑定(任意一辆命中即可;未选车辆时仅「全部」章节可见) */
|
||||||
|
export function sectionBindingsMatchOrder(bindings, vehicleKeys) {
|
||||||
|
if (isAllVehicleBindings(bindings)) return true;
|
||||||
|
var keys = vehicleKeys || [];
|
||||||
|
if (!keys.length) return false;
|
||||||
|
return keys.some(function (key) {
|
||||||
|
var pair = parseVehicleBindingKey(key);
|
||||||
|
return vehicleMatchesBindings(pair.brand, pair.model, bindings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatVehicleBindings(bindings) {
|
||||||
|
if (isAllVehicleBindings(bindings)) return '全部车型';
|
||||||
|
var list = bindings || [];
|
||||||
|
return list.map(function (item) {
|
||||||
|
return item.brand + ' · ' + item.model;
|
||||||
|
}).join(';');
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"prototypeName": "customer-management",
|
"prototypeName": "customer-management",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"updatedAt": 1782697342692,
|
"updatedAt": 1782810809219,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "cm-list-filter",
|
"id": "cm-list-filter",
|
||||||
@@ -161,6 +161,198 @@
|
|||||||
"assetMap": {},
|
"assetMap": {},
|
||||||
"directory": {
|
"directory": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "oneos-project-nav",
|
||||||
|
"title": "ONE-OS 原型导航",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-insurance-procurement",
|
||||||
|
"title": "保险采购",
|
||||||
|
"href": "/prototypes/insurance-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-weekly-report",
|
||||||
|
"title": "数智部周报",
|
||||||
|
"href": "/prototypes/oneos-weekly-report",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-management",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/vehicle-management",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-contract-template-management",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/contract-template-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": "租赁业务台账",
|
||||||
|
"href": "/prototypes/lease-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-h2-fee-ledger",
|
||||||
|
"title": "车辆氢费明细",
|
||||||
|
"href": "/prototypes/vehicle-h2-fee-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-self-operated-business-ledger",
|
||||||
|
"title": "自营业务台账",
|
||||||
|
"href": "/prototypes/self-operated-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-xll-miniapp",
|
||||||
|
"title": "小羚羚小程序",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-safety-training",
|
||||||
|
"title": "安全培训扫码",
|
||||||
|
"href": "/prototypes/oneos-web-safety-training",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-oneos-web",
|
||||||
|
"title": "ONE-OS Web 端",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-workbench",
|
||||||
|
"title": "工作台",
|
||||||
|
"href": "/prototypes/oneos-web-workbench",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-login",
|
||||||
|
"title": "登录",
|
||||||
|
"href": "/prototypes/oneos-web-login",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-vehicle-asset",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/oneos-web-vehicle-asset",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-contract-template",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/oneos-web-contract-template",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-help-center",
|
||||||
|
"title": "帮助中心",
|
||||||
|
"href": "/prototypes/oneos-web-help-center",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-finance",
|
||||||
|
"title": "财务管理",
|
||||||
|
"href": "/prototypes/oneos-web-finance",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-procurement",
|
||||||
|
"title": "采购管理",
|
||||||
|
"href": "/prototypes/oneos-web-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-lease-contract",
|
||||||
|
"title": "车辆租赁合同",
|
||||||
|
"href": "/prototypes/oneos-web-lease-contract",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-h2-station",
|
||||||
|
"title": "加氢站管理",
|
||||||
|
"href": "/prototypes/oneos-web-h2-station",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-data-analysis",
|
||||||
|
"title": "数据分析",
|
||||||
|
"href": "/prototypes/oneos-web-data-analysis",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ledger-data",
|
||||||
|
"title": "台账数据",
|
||||||
|
"href": "/prototypes/oneos-web-ledger-data",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-business",
|
||||||
|
"title": "业务管理",
|
||||||
|
"href": "/prototypes/oneos-web-business",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ops",
|
||||||
|
"title": "运维管理",
|
||||||
|
"href": "/prototypes/oneos-web-ops",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-requirements",
|
||||||
|
"title": "需求说明",
|
||||||
|
"href": "/prototypes/oneos-web-requirements",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "folder",
|
"type": "folder",
|
||||||
"id": "cm-doc-root",
|
"id": "cm-doc-root",
|
||||||
@@ -177,7 +369,7 @@
|
|||||||
"type": "link",
|
"type": "link",
|
||||||
"id": "cm-link-self",
|
"id": "cm-link-self",
|
||||||
"title": "客户管理原型",
|
"title": "客户管理原型",
|
||||||
"href": "/prototypes/customer-management",
|
"href": "./index.html",
|
||||||
"target": "self"
|
"target": "self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,14 +5,20 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"prototypeName": "insurance-procurement",
|
"prototypeName": "insurance-procurement",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"updatedAt": 1740009600000,
|
"updatedAt": 1782810809220,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "ipc-header",
|
"id": "ipc-header",
|
||||||
"index": 1,
|
"index": 1,
|
||||||
"title": "页面标题与模块边界",
|
"title": "页面标题与模块边界",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-header\"]"], "fingerprint": "header|ipc", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-header\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "header|ipc",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "说明保险采购模块双业务线边界。",
|
"aiPrompt": "说明保险采购模块双业务线边界。",
|
||||||
"annotationText": "保单管理与比价单相互独立,不自动双向同步。",
|
"annotationText": "保单管理与比价单相互独立,不自动双向同步。",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -26,7 +32,13 @@
|
|||||||
"index": 2,
|
"index": 2,
|
||||||
"title": "保单管理 · 筛选条件",
|
"title": "保单管理 · 筛选条件",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-filter\"]"], "fingerprint": "card|filter", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-filter\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "card|filter",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "列表筛选字段与查询逻辑。",
|
"aiPrompt": "列表筛选字段与查询逻辑。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -40,7 +52,13 @@
|
|||||||
"index": 3,
|
"index": 3,
|
||||||
"title": "台账车辆总数",
|
"title": "台账车辆总数",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-kpi-total\"]"], "fingerprint": "kpi|total", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-kpi-total\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "kpi|total",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "KPI 点击筛选行为。",
|
"aiPrompt": "KPI 点击筛选行为。",
|
||||||
"annotationText": "纳入保险采购台账管理的车辆总数(一车一档)。点击可筛选列表。",
|
"annotationText": "纳入保险采购台账管理的车辆总数(一车一档)。点击可筛选列表。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -54,7 +72,13 @@
|
|||||||
"index": 4,
|
"index": 4,
|
||||||
"title": "保险状态正常",
|
"title": "保险状态正常",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-kpi-normal\"]"], "fingerprint": "kpi|normal", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-kpi-normal\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "kpi|normal",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "与车辆管理保险状态联动口径。",
|
"aiPrompt": "与车辆管理保险状态联动口径。",
|
||||||
"annotationText": "交强险、商业险均已购买且在有效期内,与车辆管理「保险状态=正常」一致。",
|
"annotationText": "交强险、商业险均已购买且在有效期内,与车辆管理「保险状态=正常」一致。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -68,7 +92,13 @@
|
|||||||
"index": 5,
|
"index": 5,
|
||||||
"title": "险种临期预警",
|
"title": "险种临期预警",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-kpi-warning\"]"], "fingerprint": "kpi|warning", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-kpi-warning\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "kpi|warning",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "临期预警弹窗入口。",
|
"aiPrompt": "临期预警弹窗入口。",
|
||||||
"annotationText": "任一类险种止期 ≤ 30 天。点击打开临期预警弹窗,可按险种筛选。",
|
"annotationText": "任一类险种止期 ≤ 30 天。点击打开临期预警弹窗,可按险种筛选。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -82,7 +112,13 @@
|
|||||||
"index": 6,
|
"index": 6,
|
||||||
"title": "核心险种逾期",
|
"title": "核心险种逾期",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-kpi-expired\"]"], "fingerprint": "kpi|expired", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-kpi-expired\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "kpi|expired",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "交车合规禁止规则。",
|
"aiPrompt": "交车合规禁止规则。",
|
||||||
"annotationText": "交强险或商业险已到期 → 车辆管理保险状态异常,禁止交车。",
|
"annotationText": "交强险或商业险已到期 → 车辆管理保险状态异常,禁止交车。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -96,7 +132,13 @@
|
|||||||
"index": 7,
|
"index": 7,
|
||||||
"title": "核心险种待购",
|
"title": "核心险种待购",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-kpi-unuploaded\"]"], "fingerprint": "kpi|unuploaded", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-kpi-unuploaded\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "kpi|unuploaded",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "未购买核心险种规则。",
|
"aiPrompt": "未购买核心险种规则。",
|
||||||
"annotationText": "交强险或商业险未录入/未购买 → 保险状态异常,禁止交车。",
|
"annotationText": "交强险或商业险未录入/未购买 → 保险状态异常,禁止交车。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -110,7 +152,13 @@
|
|||||||
"index": 8,
|
"index": 8,
|
||||||
"title": "比价单管理",
|
"title": "比价单管理",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-btn-compare-mgmt\"]"], "fingerprint": "btn|compare-mgmt", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-btn-compare-mgmt\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "btn|compare-mgmt",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "比价单独立业务线入口。",
|
"aiPrompt": "比价单独立业务线入口。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -124,7 +172,13 @@
|
|||||||
"index": 9,
|
"index": 9,
|
||||||
"title": "保单批量识别",
|
"title": "保单批量识别",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-btn-policy-ocr\"]"], "fingerprint": "btn|ocr", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-btn-policy-ocr\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "btn|ocr",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "OCR 录入通道与确认规则。",
|
"aiPrompt": "OCR 录入通道与确认规则。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -138,7 +192,13 @@
|
|||||||
"index": 10,
|
"index": 10,
|
||||||
"title": "识别任务记录",
|
"title": "识别任务记录",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-btn-recogn-tasks\"]"], "fingerprint": "btn|tasks", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-btn-recogn-tasks\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "btn|tasks",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "识别任务列表与确认进度。",
|
"aiPrompt": "识别任务列表与确认进度。",
|
||||||
"annotationText": "查看 OCR/导入任务状态、识别进度与「确认识别结果」入口。",
|
"annotationText": "查看 OCR/导入任务状态、识别进度与「确认识别结果」入口。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -152,7 +212,13 @@
|
|||||||
"index": 11,
|
"index": 11,
|
||||||
"title": "批量导入",
|
"title": "批量导入",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-btn-batch-import\"]"], "fingerprint": "btn|import", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-btn-batch-import\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "btn|import",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "Excel 批量导入规则。",
|
"aiPrompt": "Excel 批量导入规则。",
|
||||||
"annotationText": "仅支持新保/续保类业务行;停保/复驶/退保行须自动跳过。",
|
"annotationText": "仅支持新保/续保类业务行;停保/复驶/退保行须自动跳过。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -166,7 +232,13 @@
|
|||||||
"index": 12,
|
"index": 12,
|
||||||
"title": "新增保单",
|
"title": "新增保单",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-btn-policy-add\"]"], "fingerprint": "btn|add", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-btn-policy-add\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "btn|add",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "手工逐条录入入口。",
|
"aiPrompt": "手工逐条录入入口。",
|
||||||
"annotationText": "手工新增单条保单记录,支持承保险种明细等完整字段(OCR 确认页不展示部分字段)。",
|
"annotationText": "手工新增单条保单记录,支持承保险种明细等完整字段(OCR 确认页不展示部分字段)。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -180,7 +252,13 @@
|
|||||||
"index": 13,
|
"index": 13,
|
||||||
"title": "保险台账列表",
|
"title": "保险台账列表",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-vehicle-table\"]"], "fingerprint": "table|ledger", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-vehicle-table\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "table|ledger",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "列表列含义与操作列。",
|
"aiPrompt": "列表列含义与操作列。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -193,7 +271,13 @@
|
|||||||
"id": "ipc-vehicle-mgmt",
|
"id": "ipc-vehicle-mgmt",
|
||||||
"index": 14,
|
"index": 14,
|
||||||
"title": "车辆保险档案",
|
"title": "车辆保险档案",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-vehicle-mgmt\"]"], "fingerprint": "modal|vehicle-mgmt", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-vehicle-mgmt\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|vehicle-mgmt",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "管理弹窗结构与 Tab。",
|
"aiPrompt": "管理弹窗结构与 Tab。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -206,7 +290,13 @@
|
|||||||
"id": "ipc-vehicle-mgmt-tabs",
|
"id": "ipc-vehicle-mgmt-tabs",
|
||||||
"index": 15,
|
"index": 15,
|
||||||
"title": "险种 Tab 与全周期记录",
|
"title": "险种 Tab 与全周期记录",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-vehicle-mgmt-tabs\"]"], "fingerprint": "tabs|vehicle-mgmt", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-vehicle-mgmt-tabs\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "tabs|vehicle-mgmt",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "全周期时间轴与分险种历史表。",
|
"aiPrompt": "全周期时间轴与分险种历史表。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -219,7 +309,13 @@
|
|||||||
"id": "ipc-compare-mgmt",
|
"id": "ipc-compare-mgmt",
|
||||||
"index": 16,
|
"index": 16,
|
||||||
"title": "比价单管理弹窗",
|
"title": "比价单管理弹窗",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-compare-mgmt\"]"], "fingerprint": "modal|compare-mgmt", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-compare-mgmt\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|compare-mgmt",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "比价单批次列表与付费预警。",
|
"aiPrompt": "比价单批次列表与付费预警。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -232,7 +328,13 @@
|
|||||||
"id": "ipc-compare-pay-alerts",
|
"id": "ipc-compare-pay-alerts",
|
||||||
"index": 17,
|
"index": 17,
|
||||||
"title": "最晚付费临期/超期",
|
"title": "最晚付费临期/超期",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-compare-pay-alerts\"]"], "fingerprint": "compare|pay-alert", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-compare-pay-alerts\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "compare|pay-alert",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "最晚付费日预警规则。",
|
"aiPrompt": "最晚付费日预警规则。",
|
||||||
"annotationText": "最晚付费日期 ≤ 3 天视为临期;已过期为超期。用于采购前提醒,不写入保单台账。",
|
"annotationText": "最晚付费日期 ≤ 3 天视为临期;已过期为超期。用于采购前提醒,不写入保单台账。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -245,7 +347,13 @@
|
|||||||
"id": "ipc-compare-editor",
|
"id": "ipc-compare-editor",
|
||||||
"index": 18,
|
"index": 18,
|
||||||
"title": "比价单编辑",
|
"title": "比价单编辑",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-compare-editor\"]"], "fingerprint": "modal|compare-editor", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-compare-editor\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|compare-editor",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "报价、最终比价、采购审批提交流程。",
|
"aiPrompt": "报价、最终比价、采购审批提交流程。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -258,7 +366,13 @@
|
|||||||
"id": "ipc-compare-procurement",
|
"id": "ipc-compare-procurement",
|
||||||
"index": 19,
|
"index": 19,
|
||||||
"title": "提交采购申请",
|
"title": "提交采购申请",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-compare-procurement\"]"], "fingerprint": "btn|procurement", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-compare-procurement\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "btn|procurement",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "采购状态与工作流回写。",
|
"aiPrompt": "采购状态与工作流回写。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -271,7 +385,13 @@
|
|||||||
"id": "ipc-policy-recogn",
|
"id": "ipc-policy-recogn",
|
||||||
"index": 20,
|
"index": 20,
|
||||||
"title": "保单识别/导入",
|
"title": "保单识别/导入",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-policy-recogn\"]"], "fingerprint": "modal|recogn", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-policy-recogn\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|recogn",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "OCR 与导入确认页字段差异。",
|
"aiPrompt": "OCR 与导入确认页字段差异。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -284,7 +404,13 @@
|
|||||||
"id": "ipc-policy-add",
|
"id": "ipc-policy-add",
|
||||||
"index": 21,
|
"index": 21,
|
||||||
"title": "新增保单表单",
|
"title": "新增保单表单",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-policy-add\"]"], "fingerprint": "modal|policy-add", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-policy-add\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|policy-add",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "手工录入字段与附件。",
|
"aiPrompt": "手工录入字段与附件。",
|
||||||
"annotationText": "支持完整保单要素、承保险种明细与附件;落库后更新对应险种台账。",
|
"annotationText": "支持完整保单要素、承保险种明细与附件;落库后更新对应险种台账。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -297,7 +423,13 @@
|
|||||||
"id": "ipc-insurance-alert",
|
"id": "ipc-insurance-alert",
|
||||||
"index": 22,
|
"index": 22,
|
||||||
"title": "险种临期/逾期预警弹窗",
|
"title": "险种临期/逾期预警弹窗",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-insurance-alert\"]"], "fingerprint": "modal|alert", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-insurance-alert\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|alert",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "预警列表与比价采购状态展示。",
|
"aiPrompt": "预警列表与比价采购状态展示。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -310,7 +442,13 @@
|
|||||||
"id": "ipc-policy-biz",
|
"id": "ipc-policy-biz",
|
||||||
"index": 23,
|
"index": 23,
|
||||||
"title": "停保 / 复驶 / 退保",
|
"title": "停保 / 复驶 / 退保",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-policy-biz\"]"], "fingerprint": "modal|policy-biz", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-policy-biz\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|policy-biz",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "业务变更表单与附件要求。",
|
"aiPrompt": "业务变更表单与附件要求。",
|
||||||
"annotationText": "",
|
"annotationText": "",
|
||||||
"hasMarkdown": true,
|
"hasMarkdown": true,
|
||||||
@@ -323,7 +461,13 @@
|
|||||||
"id": "ipc-recogn-tasks",
|
"id": "ipc-recogn-tasks",
|
||||||
"index": 24,
|
"index": 24,
|
||||||
"title": "识别任务列表",
|
"title": "识别任务列表",
|
||||||
"locator": { "selectors": ["[data-annotation-id=\"ipc-recogn-tasks\"]"], "fingerprint": "modal|recogn-tasks", "path": [] },
|
"locator": {
|
||||||
|
"selectors": [
|
||||||
|
"[data-annotation-id=\"ipc-recogn-tasks\"]"
|
||||||
|
],
|
||||||
|
"fingerprint": "modal|recogn-tasks",
|
||||||
|
"path": []
|
||||||
|
},
|
||||||
"aiPrompt": "任务状态机与确认进度。",
|
"aiPrompt": "任务状态机与确认进度。",
|
||||||
"annotationText": "任务含识别中/已完成;确认识别结果后写入台账。",
|
"annotationText": "任务含识别中/已完成;确认识别结果后写入台账。",
|
||||||
"hasMarkdown": false,
|
"hasMarkdown": false,
|
||||||
@@ -352,6 +496,198 @@
|
|||||||
"assetMap": {},
|
"assetMap": {},
|
||||||
"directory": {
|
"directory": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "oneos-project-nav",
|
||||||
|
"title": "ONE-OS 原型导航",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-insurance-procurement",
|
||||||
|
"title": "保险采购(当前)",
|
||||||
|
"href": "/prototypes/insurance-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-weekly-report",
|
||||||
|
"title": "数智部周报",
|
||||||
|
"href": "/prototypes/oneos-weekly-report",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-management",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/vehicle-management",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-contract-template-management",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/contract-template-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": "租赁业务台账",
|
||||||
|
"href": "/prototypes/lease-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-h2-fee-ledger",
|
||||||
|
"title": "车辆氢费明细",
|
||||||
|
"href": "/prototypes/vehicle-h2-fee-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-self-operated-business-ledger",
|
||||||
|
"title": "自营业务台账",
|
||||||
|
"href": "/prototypes/self-operated-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-xll-miniapp",
|
||||||
|
"title": "小羚羚小程序",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-safety-training",
|
||||||
|
"title": "安全培训扫码",
|
||||||
|
"href": "/prototypes/oneos-web-safety-training",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-oneos-web",
|
||||||
|
"title": "ONE-OS Web 端",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-workbench",
|
||||||
|
"title": "工作台",
|
||||||
|
"href": "/prototypes/oneos-web-workbench",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-login",
|
||||||
|
"title": "登录",
|
||||||
|
"href": "/prototypes/oneos-web-login",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-vehicle-asset",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/oneos-web-vehicle-asset",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-contract-template",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/oneos-web-contract-template",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-help-center",
|
||||||
|
"title": "帮助中心",
|
||||||
|
"href": "/prototypes/oneos-web-help-center",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-finance",
|
||||||
|
"title": "财务管理",
|
||||||
|
"href": "/prototypes/oneos-web-finance",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-procurement",
|
||||||
|
"title": "采购管理",
|
||||||
|
"href": "/prototypes/oneos-web-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-lease-contract",
|
||||||
|
"title": "车辆租赁合同",
|
||||||
|
"href": "/prototypes/oneos-web-lease-contract",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-h2-station",
|
||||||
|
"title": "加氢站管理",
|
||||||
|
"href": "/prototypes/oneos-web-h2-station",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-data-analysis",
|
||||||
|
"title": "数据分析",
|
||||||
|
"href": "/prototypes/oneos-web-data-analysis",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ledger-data",
|
||||||
|
"title": "台账数据",
|
||||||
|
"href": "/prototypes/oneos-web-ledger-data",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-business",
|
||||||
|
"title": "业务管理",
|
||||||
|
"href": "/prototypes/oneos-web-business",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ops",
|
||||||
|
"title": "运维管理",
|
||||||
|
"href": "/prototypes/oneos-web-ops",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-requirements",
|
||||||
|
"title": "需求说明",
|
||||||
|
"href": "/prototypes/oneos-web-requirements",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "folder",
|
"type": "folder",
|
||||||
"id": "ipc-doc-root",
|
"id": "ipc-doc-root",
|
||||||
@@ -368,7 +704,7 @@
|
|||||||
"type": "link",
|
"type": "link",
|
||||||
"id": "ipc-link-vehicle",
|
"id": "ipc-link-vehicle",
|
||||||
"title": "车辆管理原型",
|
"title": "车辆管理原型",
|
||||||
"href": "/prototypes/vehicle-management",
|
"href": "../vehicle-management/index.html",
|
||||||
"target": "self"
|
"target": "self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"prototypeName": "lease-business-ledger",
|
"prototypeName": "lease-business-ledger",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"updatedAt": 1782712021271,
|
"updatedAt": 1782810809220,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "lbl-filter",
|
"id": "lbl-filter",
|
||||||
@@ -872,6 +872,198 @@
|
|||||||
"assetMap": {},
|
"assetMap": {},
|
||||||
"directory": {
|
"directory": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "oneos-project-nav",
|
||||||
|
"title": "ONE-OS 原型导航",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-insurance-procurement",
|
||||||
|
"title": "保险采购",
|
||||||
|
"href": "/prototypes/insurance-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-weekly-report",
|
||||||
|
"title": "数智部周报",
|
||||||
|
"href": "/prototypes/oneos-weekly-report",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-management",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/vehicle-management",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-contract-template-management",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/contract-template-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": "租赁业务台账(当前)",
|
||||||
|
"href": "/prototypes/lease-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-h2-fee-ledger",
|
||||||
|
"title": "车辆氢费明细",
|
||||||
|
"href": "/prototypes/vehicle-h2-fee-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-self-operated-business-ledger",
|
||||||
|
"title": "自营业务台账",
|
||||||
|
"href": "/prototypes/self-operated-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-xll-miniapp",
|
||||||
|
"title": "小羚羚小程序",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-safety-training",
|
||||||
|
"title": "安全培训扫码",
|
||||||
|
"href": "/prototypes/oneos-web-safety-training",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-oneos-web",
|
||||||
|
"title": "ONE-OS Web 端",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-workbench",
|
||||||
|
"title": "工作台",
|
||||||
|
"href": "/prototypes/oneos-web-workbench",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-login",
|
||||||
|
"title": "登录",
|
||||||
|
"href": "/prototypes/oneos-web-login",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-vehicle-asset",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/oneos-web-vehicle-asset",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-contract-template",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/oneos-web-contract-template",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-help-center",
|
||||||
|
"title": "帮助中心",
|
||||||
|
"href": "/prototypes/oneos-web-help-center",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-finance",
|
||||||
|
"title": "财务管理",
|
||||||
|
"href": "/prototypes/oneos-web-finance",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-procurement",
|
||||||
|
"title": "采购管理",
|
||||||
|
"href": "/prototypes/oneos-web-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-lease-contract",
|
||||||
|
"title": "车辆租赁合同",
|
||||||
|
"href": "/prototypes/oneos-web-lease-contract",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-h2-station",
|
||||||
|
"title": "加氢站管理",
|
||||||
|
"href": "/prototypes/oneos-web-h2-station",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-data-analysis",
|
||||||
|
"title": "数据分析",
|
||||||
|
"href": "/prototypes/oneos-web-data-analysis",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ledger-data",
|
||||||
|
"title": "台账数据",
|
||||||
|
"href": "/prototypes/oneos-web-ledger-data",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-business",
|
||||||
|
"title": "业务管理",
|
||||||
|
"href": "/prototypes/oneos-web-business",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ops",
|
||||||
|
"title": "运维管理",
|
||||||
|
"href": "/prototypes/oneos-web-ops",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-requirements",
|
||||||
|
"title": "需求说明",
|
||||||
|
"href": "/prototypes/oneos-web-requirements",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "folder",
|
"type": "folder",
|
||||||
"id": "lbl-doc-root",
|
"id": "lbl-doc-root",
|
||||||
@@ -888,7 +1080,7 @@
|
|||||||
"type": "link",
|
"type": "link",
|
||||||
"id": "lbl-link-self",
|
"id": "lbl-link-self",
|
||||||
"title": "租赁业务台账原型",
|
"title": "租赁业务台账原型",
|
||||||
"href": "/prototypes/lease-business-ledger",
|
"href": "./index.html",
|
||||||
"target": "self"
|
"target": "self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,6 +85,8 @@
|
|||||||
|
|
||||||
支持新增行、删除行。
|
支持新增行、删除行。
|
||||||
|
|
||||||
|
**合同备注**:授权委托书区块最下方提供文本域,支持自定义输入,非必填;列表页不再展示备注列。
|
||||||
|
|
||||||
**完成度**:至少一行且三项均非空。
|
**完成度**:至少一行且三项均非空。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -110,8 +112,8 @@
|
|||||||
|
|
||||||
| 类型 | 触发条件 |
|
| 类型 | 触发条件 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 标准合同审批 | 默认可编辑预览相对基线未改动风控红线条款 |
|
| 标准合同 | 默认可编辑预览相对基线未改动风控红线条款 |
|
||||||
| 非标准合同审批 | 预览中 `.ct-risk-redline[data-risk-redline="1"]` 正文相对基线有增删改 |
|
| 非标准合同 | 预览中 `.ct-risk-redline[data-risk-redline="1"]` 正文相对基线有增删改 |
|
||||||
|
|
||||||
检测逻辑:`lease-contract-risk-detect.js` 对比基线 HTML 与当前合并页 HTML。
|
检测逻辑:`lease-contract-risk-detect.js` 对比基线 HTML 与当前合并页 HTML。
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
| 业务部门 | 多选下拉 | 业务相关部门 |
|
| 业务部门 | 多选下拉 | 业务相关部门 |
|
||||||
| 业务负责人 | 多选下拉 | 业务组人员 |
|
| 业务负责人 | 多选下拉 | 业务组人员 |
|
||||||
| 合同类型 | 多选下拉 | 全部 / 正式合同 / 试用合同 |
|
| 合同类型 | 多选下拉 | 全部 / 正式合同 / 试用合同 |
|
||||||
| 合同审批类型 | 多选下拉 | 全部 / 标准合同审批 / 非标准合同审批 |
|
| 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 |
|
||||||
| 创建人 | 多选下拉 | 业务相关人员 |
|
| 创建人 | 多选下拉 | 业务相关人员 |
|
||||||
| 合同结束日期 | 日期范围 | 开始—结束,精确至日 |
|
| 合同结束日期 | 日期范围 | 开始—结束,精确至日 |
|
||||||
|
|
||||||
@@ -90,17 +90,16 @@
|
|||||||
|
|
||||||
| 列 | 说明 |
|
| 列 | 说明 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| 审批类型 | 标准合同 / 非标准合同;位于审批状态左侧 |
|
||||||
| 审批状态 | 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交;「审批中」悬停展示审批流节点 |
|
| 审批状态 | 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交;「审批中」悬停展示审批流节点 |
|
||||||
| 合同状态 | 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |
|
| 合同状态 | 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |
|
||||||
| 签约公司 | 创建时所选甲方签约主体简称 |
|
| 签约公司 | 创建时所选甲方签约主体简称 |
|
||||||
| 业务部门 / 业务负责人 | 创建时选取 |
|
| 业务部门 / 业务负责人 | 创建时选取 |
|
||||||
| 合同类型 | 正式合同 / 试用合同 |
|
| 合同类型 | 正式合同 / 试用合同 |
|
||||||
| 合同审批类型 | 标准合同审批 / 非标准合同审批 |
|
|
||||||
| 合同结束日期 | YYYY-MM-DD |
|
|
||||||
| 客户联系人 / 联系电话 | 来自客户档案 |
|
| 客户联系人 / 联系电话 | 来自客户档案 |
|
||||||
|
| 被授权人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 |
|
||||||
| 创建人 / 创建时间 | 精确至分钟 |
|
| 创建人 / 创建时间 | 精确至分钟 |
|
||||||
| 更新人 / 最后更新时间 | 无则显示 `-` |
|
| 最后更新 | 更新人 / 最后更新时间,无则显示 `-` |
|
||||||
| 备注 | 超长省略 |
|
|
||||||
|
|
||||||
**分页**:每页 10 / 20 / 50 条可选。
|
**分页**:每页 10 / 20 / 50 条可选。
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Row, Col, message } from 'antd';
|
import { Row, Col, Select, message } from 'antd';
|
||||||
|
import { Info, Check } from 'lucide-react';
|
||||||
import { DEFAULT_MILEAGE_STANDARD, DEFAULT_FEE_INFO } from '../contract-template-management/contract-template-vars.js';
|
import { DEFAULT_MILEAGE_STANDARD, DEFAULT_FEE_INFO } from '../contract-template-management/contract-template-vars.js';
|
||||||
|
import {
|
||||||
|
getPublishedContractTemplateOptions,
|
||||||
|
subscribePublishedContractTemplateOptions,
|
||||||
|
getContractTemplatePreviewBadge,
|
||||||
|
getContractTypeLabel,
|
||||||
|
} from '../contract-template-management/contract-template-catalog.js';
|
||||||
import { createDefaultLeaseOrderState, createDefaultPowerOfAttorneyState } from './lease-order-vars.js';
|
import { createDefaultLeaseOrderState, createDefaultPowerOfAttorneyState } from './lease-order-vars.js';
|
||||||
import { buildLeaseContractPreviewHtml } from './lease-contract-preview-build.js';
|
import { buildLeaseContractPreviewHtml } from './lease-contract-preview-build.js';
|
||||||
import {
|
import {
|
||||||
@@ -12,15 +19,72 @@ import {
|
|||||||
isMainContractFormComplete,
|
isMainContractFormComplete,
|
||||||
isLeaseOrderFormComplete,
|
isLeaseOrderFormComplete,
|
||||||
isPowerOfAttorneyFormComplete,
|
isPowerOfAttorneyFormComplete,
|
||||||
|
isPowerOfAttorneySubmitReady,
|
||||||
isPowerOfAttorneyOptionalEmpty,
|
isPowerOfAttorneyOptionalEmpty,
|
||||||
|
isSealTypeSelected,
|
||||||
|
isLeaseContractFormComplete,
|
||||||
} from './lease-contract-form-validation.js';
|
} from './lease-contract-form-validation.js';
|
||||||
import LeaseContractPreviewPanel from './LeaseContractPreviewPanel.jsx';
|
import LeaseContractPreviewPanel, { PreviewEditableHint } from './LeaseContractPreviewPanel.jsx';
|
||||||
import LeaseContractEditorForm, {
|
import LeaseContractEditorForm, {
|
||||||
LeaseContractLeaseOrderSection,
|
LeaseContractLeaseOrderSection,
|
||||||
LeaseContractPowerOfAttorneySection,
|
LeaseContractPowerOfAttorneySection,
|
||||||
} from './LeaseContractEditorForm.jsx';
|
} from './LeaseContractEditorForm.jsx';
|
||||||
|
|
||||||
|
var SEAL_TYPE_OPTIONS = [
|
||||||
|
{ value: 'contract', label: '合同章' },
|
||||||
|
{ value: 'official', label: '公章' },
|
||||||
|
{ value: 'legal_person', label: '法人章' },
|
||||||
|
{ value: 'finance', label: '财务章' },
|
||||||
|
{ value: 'invoice', label: '发票章' },
|
||||||
|
];
|
||||||
|
|
||||||
export default function LeaseContractCreate({ onBack }) {
|
export default function LeaseContractCreate({ onBack }) {
|
||||||
|
var contractTypeState = useState('');
|
||||||
|
var contractType = contractTypeState[0];
|
||||||
|
var setContractType = contractTypeState[1];
|
||||||
|
var contractTemplateIdState = useState('');
|
||||||
|
var contractTemplateId = contractTemplateIdState[0];
|
||||||
|
var setContractTemplateId = contractTemplateIdState[1];
|
||||||
|
var contractTemplateIdRef = useRef('');
|
||||||
|
useEffect(function () {
|
||||||
|
contractTemplateIdRef.current = contractTemplateId;
|
||||||
|
}, [contractTemplateId]);
|
||||||
|
var templateOptionsState = useState(function () { return getPublishedContractTemplateOptions(); });
|
||||||
|
var templateOptions = templateOptionsState[0];
|
||||||
|
var setTemplateOptions = templateOptionsState[1];
|
||||||
|
var templateSelectOptions = useMemo(function () {
|
||||||
|
return templateOptions.map(function (option) {
|
||||||
|
var typeLabel = getContractTypeLabel(option.contractType) || option.contractType || '—';
|
||||||
|
var fileLabel = option.fileName || option.title || option.id;
|
||||||
|
return {
|
||||||
|
value: option.id,
|
||||||
|
label: typeLabel + ' · ' + fileLabel,
|
||||||
|
contractType: option.contractType,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [templateOptions]);
|
||||||
|
function handleTemplateSelect(templateId) {
|
||||||
|
if (!templateId) {
|
||||||
|
setContractTemplateId('');
|
||||||
|
setContractType('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var match = templateOptions.find(function (item) { return item.id === templateId; });
|
||||||
|
setContractTemplateId(templateId);
|
||||||
|
setContractType(match ? match.contractType : '');
|
||||||
|
}
|
||||||
|
useEffect(function () {
|
||||||
|
return subscribePublishedContractTemplateOptions(function () {
|
||||||
|
var next = getPublishedContractTemplateOptions();
|
||||||
|
setTemplateOptions(next);
|
||||||
|
var currentId = contractTemplateIdRef.current;
|
||||||
|
if (currentId && !next.some(function (item) { return item.id === currentId; })) {
|
||||||
|
setContractTemplateId('');
|
||||||
|
setContractType('');
|
||||||
|
message.warning('所选模板已停用或不可用,请重新选择');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
var workspaceZoomState = useState(100);
|
var workspaceZoomState = useState(100);
|
||||||
var workspaceZoom = workspaceZoomState[0];
|
var workspaceZoom = workspaceZoomState[0];
|
||||||
var setWorkspaceZoom = workspaceZoomState[1];
|
var setWorkspaceZoom = workspaceZoomState[1];
|
||||||
@@ -29,6 +93,8 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
var mileageState = useState(Object.assign({}, DEFAULT_MILEAGE_STANDARD));
|
var mileageState = useState(Object.assign({}, DEFAULT_MILEAGE_STANDARD));
|
||||||
var feeInfoState = useState(Object.assign({}, DEFAULT_FEE_INFO));
|
var feeInfoState = useState(Object.assign({}, DEFAULT_FEE_INFO));
|
||||||
var contractCodeState = useState('');
|
var contractCodeState = useState('');
|
||||||
|
var businessDeptState = useState('');
|
||||||
|
var businessOwnerState = useState('');
|
||||||
var leaseOrderState = useState(function () {
|
var leaseOrderState = useState(function () {
|
||||||
var initial = createDefaultLeaseOrderState();
|
var initial = createDefaultLeaseOrderState();
|
||||||
return {
|
return {
|
||||||
@@ -54,6 +120,10 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
var setFeeInfo = feeInfoState[1];
|
var setFeeInfo = feeInfoState[1];
|
||||||
var contractCode = contractCodeState[0];
|
var contractCode = contractCodeState[0];
|
||||||
var setContractCode = contractCodeState[1];
|
var setContractCode = contractCodeState[1];
|
||||||
|
var businessDept = businessDeptState[0];
|
||||||
|
var setBusinessDept = businessDeptState[1];
|
||||||
|
var businessOwner = businessOwnerState[0];
|
||||||
|
var setBusinessOwner = businessOwnerState[1];
|
||||||
var leaseOrder = leaseOrderState[0];
|
var leaseOrder = leaseOrderState[0];
|
||||||
var setLeaseOrder = leaseOrderState[1];
|
var setLeaseOrder = leaseOrderState[1];
|
||||||
var powerOfAttorneyState = useState(function () {
|
var powerOfAttorneyState = useState(function () {
|
||||||
@@ -66,12 +136,20 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
});
|
});
|
||||||
var powerOfAttorney = powerOfAttorneyState[0];
|
var powerOfAttorney = powerOfAttorneyState[0];
|
||||||
var setPowerOfAttorney = powerOfAttorneyState[1];
|
var setPowerOfAttorney = powerOfAttorneyState[1];
|
||||||
|
var contractRemarkState = useState('');
|
||||||
|
var contractRemark = contractRemarkState[0];
|
||||||
|
var setContractRemark = contractRemarkState[1];
|
||||||
|
var sealTypesState = useState(['contract']);
|
||||||
|
var sealTypes = sealTypesState[0];
|
||||||
|
var setSealTypes = sealTypesState[1];
|
||||||
var contractApprovalTypeState = useState(STANDARD_CONTRACT_APPROVAL);
|
var contractApprovalTypeState = useState(STANDARD_CONTRACT_APPROVAL);
|
||||||
var contractApprovalType = contractApprovalTypeState[0];
|
var contractApprovalType = contractApprovalTypeState[0];
|
||||||
var setContractApprovalType = contractApprovalTypeState[1];
|
var setContractApprovalType = contractApprovalTypeState[1];
|
||||||
|
|
||||||
var previewHtml = useMemo(function () {
|
var previewHtml = useMemo(function () {
|
||||||
|
if (!contractTemplateId) return '';
|
||||||
return buildLeaseContractPreviewHtml({
|
return buildLeaseContractPreviewHtml({
|
||||||
|
contractTemplateId: contractTemplateId,
|
||||||
lessorId: lessorId,
|
lessorId: lessorId,
|
||||||
customerId: customerId,
|
customerId: customerId,
|
||||||
mileage: mileage,
|
mileage: mileage,
|
||||||
@@ -80,7 +158,7 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
leaseOrder: leaseOrder,
|
leaseOrder: leaseOrder,
|
||||||
powerOfAttorney: powerOfAttorney,
|
powerOfAttorney: powerOfAttorney,
|
||||||
});
|
});
|
||||||
}, [lessorId, customerId, mileage, feeInfo, contractCode, leaseOrder, powerOfAttorney]);
|
}, [contractTemplateId, lessorId, customerId, mileage, feeInfo, contractCode, leaseOrder, powerOfAttorney]);
|
||||||
|
|
||||||
var handlePreviewContentChange = useCallback(function (mergedHtml) {
|
var handlePreviewContentChange = useCallback(function (mergedHtml) {
|
||||||
setContractApprovalType(resolveContractApprovalType(previewHtml, mergedHtml));
|
setContractApprovalType(resolveContractApprovalType(previewHtml, mergedHtml));
|
||||||
@@ -91,16 +169,20 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
}, [previewHtml]);
|
}, [previewHtml]);
|
||||||
|
|
||||||
var isNonStandardApproval = contractApprovalType === NONSTANDARD_CONTRACT_APPROVAL;
|
var isNonStandardApproval = contractApprovalType === NONSTANDARD_CONTRACT_APPROVAL;
|
||||||
|
var isTemplateSelected = Boolean(contractTemplateId);
|
||||||
|
var previewBadge = getContractTemplatePreviewBadge(contractTemplateId);
|
||||||
|
|
||||||
var isMainContractComplete = useMemo(function () {
|
var isMainContractComplete = useMemo(function () {
|
||||||
return isMainContractFormComplete({
|
return isMainContractFormComplete({
|
||||||
lessorId: lessorId,
|
lessorId: lessorId,
|
||||||
customerId: customerId,
|
customerId: customerId,
|
||||||
contractCode: contractCode,
|
contractCode: contractCode,
|
||||||
|
businessDept: businessDept,
|
||||||
|
businessOwner: businessOwner,
|
||||||
mileage: mileage,
|
mileage: mileage,
|
||||||
feeInfo: feeInfo,
|
feeInfo: feeInfo,
|
||||||
});
|
});
|
||||||
}, [lessorId, customerId, contractCode, mileage, feeInfo]);
|
}, [lessorId, customerId, contractCode, businessDept, businessOwner, mileage, feeInfo]);
|
||||||
|
|
||||||
var isLeaseOrderComplete = useMemo(function () {
|
var isLeaseOrderComplete = useMemo(function () {
|
||||||
return isLeaseOrderFormComplete({ leaseOrder: leaseOrder });
|
return isLeaseOrderFormComplete({ leaseOrder: leaseOrder });
|
||||||
@@ -110,10 +192,182 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
return isPowerOfAttorneyFormComplete({ powerOfAttorney: powerOfAttorney });
|
return isPowerOfAttorneyFormComplete({ powerOfAttorney: powerOfAttorney });
|
||||||
}, [powerOfAttorney]);
|
}, [powerOfAttorney]);
|
||||||
|
|
||||||
|
var isPoaSubmitReady = useMemo(function () {
|
||||||
|
return isPowerOfAttorneySubmitReady({ powerOfAttorney: powerOfAttorney });
|
||||||
|
}, [powerOfAttorney]);
|
||||||
|
|
||||||
var isPowerOfAttorneyEmpty = useMemo(function () {
|
var isPowerOfAttorneyEmpty = useMemo(function () {
|
||||||
return isPowerOfAttorneyOptionalEmpty({ powerOfAttorney: powerOfAttorney });
|
return isPowerOfAttorneyOptionalEmpty({ powerOfAttorney: powerOfAttorney });
|
||||||
}, [powerOfAttorney]);
|
}, [powerOfAttorney]);
|
||||||
|
|
||||||
|
var isSealTypeComplete = useMemo(function () {
|
||||||
|
return isSealTypeSelected({ sealTypes: sealTypes });
|
||||||
|
}, [sealTypes]);
|
||||||
|
|
||||||
|
var isFormSubmitReady = useMemo(function () {
|
||||||
|
return isLeaseContractFormComplete({
|
||||||
|
lessorId: lessorId,
|
||||||
|
customerId: customerId,
|
||||||
|
contractCode: contractCode,
|
||||||
|
mileage: mileage,
|
||||||
|
feeInfo: feeInfo,
|
||||||
|
leaseOrder: leaseOrder,
|
||||||
|
powerOfAttorney: powerOfAttorney,
|
||||||
|
sealTypes: sealTypes,
|
||||||
|
});
|
||||||
|
}, [lessorId, customerId, contractCode, mileage, feeInfo, leaseOrder, powerOfAttorney, sealTypes]);
|
||||||
|
|
||||||
|
function toggleSealType(value) {
|
||||||
|
setSealTypes(function (current) {
|
||||||
|
var index = current.indexOf(value);
|
||||||
|
if (index >= 0) {
|
||||||
|
var next = current.filter(function (item) { return item !== value; });
|
||||||
|
return next.length ? next : current;
|
||||||
|
}
|
||||||
|
return current.concat([value]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSealTypeLabel(value) {
|
||||||
|
var match = SEAL_TYPE_OPTIONS.find(function (item) { return item.value === value; });
|
||||||
|
return match ? match.label : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnRef = useRef(null);
|
||||||
|
var sectionRefs = {
|
||||||
|
template: useRef(null),
|
||||||
|
main: useRef(null),
|
||||||
|
leaseOrder: useRef(null),
|
||||||
|
poa: useRef(null),
|
||||||
|
seal: useRef(null),
|
||||||
|
};
|
||||||
|
|
||||||
|
var createSteps = useMemo(function () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'template',
|
||||||
|
label: '选择模板',
|
||||||
|
done: isTemplateSelected,
|
||||||
|
optional: false,
|
||||||
|
locked: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'main',
|
||||||
|
label: '主体合同',
|
||||||
|
done: isMainContractComplete,
|
||||||
|
optional: false,
|
||||||
|
locked: !isTemplateSelected,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'leaseOrder',
|
||||||
|
label: '租赁订单',
|
||||||
|
done: isLeaseOrderComplete,
|
||||||
|
optional: false,
|
||||||
|
locked: !isTemplateSelected,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'poa',
|
||||||
|
label: '授权委托',
|
||||||
|
done: !isPowerOfAttorneyEmpty && isPowerOfAttorneyComplete,
|
||||||
|
optional: isPowerOfAttorneyEmpty,
|
||||||
|
locked: !isTemplateSelected,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'seal',
|
||||||
|
label: '用章类型',
|
||||||
|
done: isSealTypeComplete,
|
||||||
|
optional: false,
|
||||||
|
locked: !isTemplateSelected,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
isTemplateSelected,
|
||||||
|
isMainContractComplete,
|
||||||
|
isLeaseOrderComplete,
|
||||||
|
isPowerOfAttorneyComplete,
|
||||||
|
isPowerOfAttorneyEmpty,
|
||||||
|
isSealTypeComplete,
|
||||||
|
]);
|
||||||
|
|
||||||
|
var requiredDoneCount = useMemo(function () {
|
||||||
|
var count = 0;
|
||||||
|
if (isTemplateSelected) count += 1;
|
||||||
|
if (isMainContractComplete) count += 1;
|
||||||
|
if (isLeaseOrderComplete) count += 1;
|
||||||
|
if (isPowerOfAttorneyComplete) count += 1;
|
||||||
|
if (isSealTypeComplete) count += 1;
|
||||||
|
return count;
|
||||||
|
}, [isTemplateSelected, isMainContractComplete, isLeaseOrderComplete, isPowerOfAttorneyComplete, isSealTypeComplete]);
|
||||||
|
|
||||||
|
function scrollToCreateSection(stepKey) {
|
||||||
|
var target = sectionRefs[stepKey] && sectionRefs[stepKey].current;
|
||||||
|
if (!target) return;
|
||||||
|
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStepNavClick(step) {
|
||||||
|
if (step.locked && step.key !== 'template') {
|
||||||
|
message.info('请先选择合同模板');
|
||||||
|
scrollToCreateSection('template');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scrollToCreateSection(step.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSectionLock(stepKey) {
|
||||||
|
if (isTemplateSelected) return null;
|
||||||
|
return React.createElement('div', {
|
||||||
|
className: 'lc-create-section__lock',
|
||||||
|
role: 'region',
|
||||||
|
'aria-label': '需先选择合同模板',
|
||||||
|
},
|
||||||
|
React.createElement('p', { className: 'lc-create-section__lock-text' }, '请先完成第 1 步选择合同模板,再填写本节内容。'),
|
||||||
|
React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'vm-btn vm-btn-secondary lc-create-section__lock-btn',
|
||||||
|
onClick: function () { scrollToCreateSection('template'); },
|
||||||
|
}, '前往选择模板'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStepNav() {
|
||||||
|
return React.createElement('nav', {
|
||||||
|
className: 'lc-create-step-nav',
|
||||||
|
'aria-label': '填写步骤',
|
||||||
|
'data-annotation-id': 'lc-create-step-nav',
|
||||||
|
},
|
||||||
|
React.createElement('ol', { className: 'lc-create-step-nav__list' },
|
||||||
|
createSteps.map(function (step, index) {
|
||||||
|
var statusClass = step.done
|
||||||
|
? 'lc-create-step-nav__item--done'
|
||||||
|
: (step.optional ? 'lc-create-step-nav__item--optional' : 'lc-create-step-nav__item--pending');
|
||||||
|
if (step.locked) statusClass += ' lc-create-step-nav__item--locked';
|
||||||
|
return React.createElement('li', { key: step.key, className: 'lc-create-step-nav__item ' + statusClass },
|
||||||
|
React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'lc-create-step-nav__btn',
|
||||||
|
onClick: function () { handleStepNavClick(step); },
|
||||||
|
'aria-current': step.key === 'template' && !isTemplateSelected ? 'step' : undefined,
|
||||||
|
},
|
||||||
|
React.createElement('span', { className: 'lc-create-step-nav__index', 'aria-hidden': true },
|
||||||
|
step.done
|
||||||
|
? React.createElement(Check, { size: 14, strokeWidth: 2.5, 'aria-hidden': true })
|
||||||
|
: String(index + 1),
|
||||||
|
),
|
||||||
|
React.createElement('span', { className: 'lc-create-step-nav__label' }, step.label),
|
||||||
|
step.optional
|
||||||
|
? React.createElement('span', { className: 'lc-create-step-nav__tag' }, '选填')
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
index < createSteps.length - 1
|
||||||
|
? React.createElement('span', { className: 'lc-create-step-nav__sep', 'aria-hidden': true })
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return React.createElement('div', { className: 'vm-page lc-page lc-create-page ct-page' },
|
return React.createElement('div', { className: 'vm-page lc-page lc-create-page ct-page' },
|
||||||
React.createElement('div', { className: 'lc-create-topbar' },
|
React.createElement('div', { className: 'lc-create-topbar' },
|
||||||
React.createElement('button', {
|
React.createElement('button', {
|
||||||
@@ -121,15 +375,89 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
className: 'vm-btn vm-btn-back',
|
className: 'vm-btn vm-btn-back',
|
||||||
onClick: onBack,
|
onClick: onBack,
|
||||||
}, '返回列表'),
|
}, '返回列表'),
|
||||||
|
React.createElement('div', { className: 'lc-create-topbar__intro' },
|
||||||
|
React.createElement('h1', { className: 'lc-create-topbar__title' }, '新增租赁合同'),
|
||||||
|
React.createElement('p', { className: 'lc-create-topbar__meta', role: 'status' },
|
||||||
|
'必填进度 ',
|
||||||
|
React.createElement('strong', null, requiredDoneCount),
|
||||||
|
' / 5',
|
||||||
|
isTemplateSelected
|
||||||
|
? React.createElement('span', { className: 'lc-create-topbar__meta-divider', 'aria-hidden': true }, '·')
|
||||||
|
: null,
|
||||||
|
isTemplateSelected
|
||||||
|
? React.createElement('span', { className: 'lc-create-topbar__meta-template' }, previewBadge)
|
||||||
|
: React.createElement('span', { className: 'lc-create-topbar__meta-hint' }, '请先选择合同模板'),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
React.createElement('div', { className: 'ct-editor-page lc-create-editor-page' },
|
React.createElement('div', { className: 'ct-editor-page lc-create-editor-page' },
|
||||||
React.createElement('div', { className: 'ct-editor-workspace' },
|
React.createElement('div', { className: 'ct-editor-workspace' },
|
||||||
React.createElement(Row, { gutter: [16, 16] },
|
React.createElement(Row, { gutter: [16, 16] },
|
||||||
React.createElement(Col, { xs: 24, lg: 12 },
|
React.createElement(Col, { xs: 24, lg: 12 },
|
||||||
React.createElement('div', { className: 'lc-create-editor-column' },
|
React.createElement('div', { className: 'lc-create-editor-column', ref: columnRef },
|
||||||
React.createElement('section', { className: 'ct-editor-column-card lc-main-contract-card', 'aria-label': '主体合同信息', 'data-annotation-id': 'lc-create-main-contract' },
|
renderStepNav(),
|
||||||
|
React.createElement('section', {
|
||||||
|
className: 'ct-editor-column-card lc-template-step-card lc-create-section',
|
||||||
|
'aria-label': '选择合同类型',
|
||||||
|
'data-annotation-id': 'lc-create-template',
|
||||||
|
id: 'lc-create-section-template',
|
||||||
|
ref: sectionRefs.template,
|
||||||
|
},
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'ct-panel-head lc-main-contract-card__head lc-template-step-card__head',
|
||||||
|
'aria-describedby': 'lc-template-step-hint',
|
||||||
|
},
|
||||||
|
React.createElement('div', { className: 'lc-template-step-card__title-row' },
|
||||||
|
React.createElement('div', { className: 'ct-panel-head__title' }, '第 1 步:选择合同类型'),
|
||||||
|
React.createElement('span', {
|
||||||
|
className: 'lc-form-card__hint',
|
||||||
|
role: 'note',
|
||||||
|
id: 'lc-template-step-hint',
|
||||||
|
},
|
||||||
|
React.createElement(Info, {
|
||||||
|
size: 13,
|
||||||
|
className: 'lc-form-card__hint-icon',
|
||||||
|
'aria-hidden': true,
|
||||||
|
}),
|
||||||
|
React.createElement('span', { className: 'lc-form-card__hint-text' },
|
||||||
|
'请选择模板管理中已发布且启用的合同模板,右侧将自动生成预览。',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
React.createElement('span', {
|
||||||
|
className: 'lc-form-completion-badge ' + (isTemplateSelected ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'),
|
||||||
|
role: 'status',
|
||||||
|
'aria-live': 'polite',
|
||||||
|
}, isTemplateSelected ? '已选择' : '必选'),
|
||||||
|
),
|
||||||
|
React.createElement('div', { className: 'ct-editor-panel lc-create-editor-panel lc-template-step-panel' },
|
||||||
|
React.createElement(Select, {
|
||||||
|
className: 'lc-template-select',
|
||||||
|
value: contractTemplateId || undefined,
|
||||||
|
placeholder: templateSelectOptions.length ? '请选择合同模板' : '暂无可用模板',
|
||||||
|
allowClear: true,
|
||||||
|
showSearch: true,
|
||||||
|
optionFilterProp: 'label',
|
||||||
|
disabled: !templateSelectOptions.length,
|
||||||
|
onChange: handleTemplateSelect,
|
||||||
|
options: templateSelectOptions,
|
||||||
|
'aria-label': '合同模板',
|
||||||
|
}),
|
||||||
|
!templateSelectOptions.length
|
||||||
|
? React.createElement('p', { className: 'lc-template-step-empty' }, '暂无已启用的合同模板,请先在合同模板管理中发布并启用。')
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
React.createElement('section', {
|
||||||
|
className: 'ct-editor-column-card lc-main-contract-card lc-create-section'
|
||||||
|
+ (isTemplateSelected ? '' : ' lc-create-section--disabled'),
|
||||||
|
'aria-label': '主体合同信息',
|
||||||
|
'data-annotation-id': 'lc-create-main-contract',
|
||||||
|
id: 'lc-create-section-main',
|
||||||
|
ref: sectionRefs.main,
|
||||||
|
},
|
||||||
React.createElement('div', { className: 'ct-panel-head lc-main-contract-card__head' },
|
React.createElement('div', { className: 'ct-panel-head lc-main-contract-card__head' },
|
||||||
React.createElement('div', { className: 'ct-panel-head__title' }, '主体合同信息'),
|
React.createElement('div', { className: 'ct-panel-head__title' }, '第 2 步:主体合同信息'),
|
||||||
React.createElement('span', {
|
React.createElement('span', {
|
||||||
className: 'lc-form-completion-badge ' + (isMainContractComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'),
|
className: 'lc-form-completion-badge ' + (isMainContractComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'),
|
||||||
role: 'status',
|
role: 'status',
|
||||||
@@ -141,19 +469,33 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
lessorId: lessorId,
|
lessorId: lessorId,
|
||||||
customerId: customerId,
|
customerId: customerId,
|
||||||
contractCode: contractCode,
|
contractCode: contractCode,
|
||||||
|
businessDept: businessDept,
|
||||||
|
businessOwner: businessOwner,
|
||||||
mileage: mileage,
|
mileage: mileage,
|
||||||
feeInfo: feeInfo,
|
feeInfo: feeInfo,
|
||||||
onLessorChange: setLessorId,
|
onLessorChange: setLessorId,
|
||||||
onCustomerChange: setCustomerId,
|
onCustomerChange: setCustomerId,
|
||||||
onContractCodeChange: setContractCode,
|
onContractCodeChange: setContractCode,
|
||||||
|
onBusinessAssignmentChange: function (dept, owner) {
|
||||||
|
setBusinessDept(dept);
|
||||||
|
setBusinessOwner(owner);
|
||||||
|
},
|
||||||
onMileageChange: setMileage,
|
onMileageChange: setMileage,
|
||||||
onFeeInfoChange: setFeeInfo,
|
onFeeInfoChange: setFeeInfo,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
renderSectionLock('main'),
|
||||||
),
|
),
|
||||||
React.createElement('section', { className: 'ct-editor-column-card', 'aria-label': '附件1:租赁订单', 'data-annotation-id': 'lc-create-lease-order' },
|
React.createElement('section', {
|
||||||
|
className: 'ct-editor-column-card lc-create-section'
|
||||||
|
+ (isTemplateSelected ? '' : ' lc-create-section--disabled'),
|
||||||
|
'aria-label': '附件1:租赁订单',
|
||||||
|
'data-annotation-id': 'lc-create-lease-order',
|
||||||
|
id: 'lc-create-section-lease-order',
|
||||||
|
ref: sectionRefs.leaseOrder,
|
||||||
|
},
|
||||||
React.createElement('div', { className: 'ct-panel-head' },
|
React.createElement('div', { className: 'ct-panel-head' },
|
||||||
React.createElement('div', { className: 'ct-panel-head__title' }, '附件1:租赁订单'),
|
React.createElement('div', { className: 'ct-panel-head__title' }, '第 3 步:附件1 · 租赁订单'),
|
||||||
React.createElement('span', {
|
React.createElement('span', {
|
||||||
className: 'lc-form-completion-badge ' + (isLeaseOrderComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'),
|
className: 'lc-form-completion-badge ' + (isLeaseOrderComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'),
|
||||||
role: 'status',
|
role: 'status',
|
||||||
@@ -166,15 +508,45 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
onLeaseOrderChange: setLeaseOrder,
|
onLeaseOrderChange: setLeaseOrder,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
renderSectionLock('leaseOrder'),
|
||||||
),
|
),
|
||||||
React.createElement('section', { className: 'ct-editor-column-card', 'aria-label': '授权委托书', 'data-annotation-id': 'lc-create-poa' },
|
React.createElement('section', {
|
||||||
React.createElement('div', { className: 'ct-panel-head' },
|
className: 'ct-editor-column-card lc-create-section'
|
||||||
React.createElement('div', { className: 'ct-panel-head__title' }, '授权委托书'),
|
+ (isTemplateSelected ? '' : ' lc-create-section--disabled'),
|
||||||
|
'aria-label': '授权委托书',
|
||||||
|
'data-annotation-id': 'lc-create-poa',
|
||||||
|
id: 'lc-create-section-poa',
|
||||||
|
ref: sectionRefs.poa,
|
||||||
|
},
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'ct-panel-head lc-create-section__head lc-create-section-poa-head',
|
||||||
|
'aria-describedby': 'lc-create-poa-hint',
|
||||||
|
},
|
||||||
|
React.createElement('div', { className: 'lc-template-step-card__title-row' },
|
||||||
|
React.createElement('div', { className: 'ct-panel-head__title' }, '第 4 步:授权委托书'),
|
||||||
|
React.createElement('span', {
|
||||||
|
className: 'lc-form-card__hint lc-create-poa-hint',
|
||||||
|
role: 'note',
|
||||||
|
id: 'lc-create-poa-hint',
|
||||||
|
'data-annotation-id': 'lc-create-poa-hint',
|
||||||
|
},
|
||||||
|
React.createElement(Info, {
|
||||||
|
size: 13,
|
||||||
|
className: 'lc-form-card__hint-icon',
|
||||||
|
'aria-hidden': true,
|
||||||
|
}),
|
||||||
|
React.createElement('span', { className: 'lc-form-card__hint-text' },
|
||||||
|
'添加授权人须同时上传授权委托书',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
React.createElement('span', {
|
React.createElement('span', {
|
||||||
className: 'lc-form-completion-badge '
|
className: 'lc-form-completion-badge '
|
||||||
+ (isPowerOfAttorneyEmpty
|
+ (isPowerOfAttorneyEmpty
|
||||||
? 'lc-form-completion-badge--optional'
|
? 'lc-form-completion-badge--optional'
|
||||||
: (isPowerOfAttorneyComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending')),
|
: (isPowerOfAttorneyComplete
|
||||||
|
? 'lc-form-completion-badge--done'
|
||||||
|
: 'lc-form-completion-badge--pending')),
|
||||||
role: 'status',
|
role: 'status',
|
||||||
'aria-live': 'polite',
|
'aria-live': 'polite',
|
||||||
}, isPowerOfAttorneyEmpty ? '选填' : (isPowerOfAttorneyComplete ? '已完成' : '未完成')),
|
}, isPowerOfAttorneyEmpty ? '选填' : (isPowerOfAttorneyComplete ? '已完成' : '未完成')),
|
||||||
@@ -183,8 +555,65 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
React.createElement(LeaseContractPowerOfAttorneySection, {
|
React.createElement(LeaseContractPowerOfAttorneySection, {
|
||||||
powerOfAttorney: powerOfAttorney,
|
powerOfAttorney: powerOfAttorney,
|
||||||
onPowerOfAttorneyChange: setPowerOfAttorney,
|
onPowerOfAttorneyChange: setPowerOfAttorney,
|
||||||
|
contractRemark: contractRemark,
|
||||||
|
onContractRemarkChange: setContractRemark,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
renderSectionLock('poa'),
|
||||||
|
),
|
||||||
|
React.createElement('section', {
|
||||||
|
className: 'ct-editor-column-card lc-create-section'
|
||||||
|
+ (isTemplateSelected ? '' : ' lc-create-section--disabled'),
|
||||||
|
'aria-label': '用章类型',
|
||||||
|
'data-annotation-id': 'lc-create-seal',
|
||||||
|
id: 'lc-create-section-seal',
|
||||||
|
ref: sectionRefs.seal,
|
||||||
|
},
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'ct-panel-head lc-create-section__head lc-create-section-seal-head',
|
||||||
|
},
|
||||||
|
React.createElement('div', { className: 'lc-template-step-card__title-row' },
|
||||||
|
React.createElement('div', { className: 'ct-panel-head__title' }, '第 5 步:用章类型'),
|
||||||
|
sealTypes.length
|
||||||
|
? React.createElement('span', {
|
||||||
|
className: 'lc-seal-type-head-tags',
|
||||||
|
'aria-label': '已选用章',
|
||||||
|
},
|
||||||
|
sealTypes.map(function (value) {
|
||||||
|
return React.createElement('span', {
|
||||||
|
key: value,
|
||||||
|
className: 'lc-seal-type-head-tag',
|
||||||
|
}, getSealTypeLabel(value));
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
React.createElement('span', {
|
||||||
|
className: 'lc-form-completion-badge ' + (isSealTypeComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'),
|
||||||
|
role: 'status',
|
||||||
|
'aria-live': 'polite',
|
||||||
|
}, isSealTypeComplete ? '已选择' : '必选'),
|
||||||
|
),
|
||||||
|
React.createElement('div', { className: 'ct-editor-panel lc-create-editor-panel' },
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'lc-seal-type-chips',
|
||||||
|
role: 'group',
|
||||||
|
'aria-label': '用章类型',
|
||||||
|
'data-annotation-id': 'lc-create-seal-type',
|
||||||
|
},
|
||||||
|
SEAL_TYPE_OPTIONS.map(function (option) {
|
||||||
|
var active = sealTypes.indexOf(option.value) >= 0;
|
||||||
|
return React.createElement('button', {
|
||||||
|
key: option.value,
|
||||||
|
type: 'button',
|
||||||
|
className: 'lc-seal-type-chip' + (active ? ' is-active' : ''),
|
||||||
|
'aria-pressed': active,
|
||||||
|
onClick: function () { toggleSealType(option.value); },
|
||||||
|
}, option.label);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
renderSectionLock('seal'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -194,9 +623,13 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
'aria-label': '实时预览',
|
'aria-label': '实时预览',
|
||||||
'data-annotation-id': 'lc-create-preview',
|
'data-annotation-id': 'lc-create-preview',
|
||||||
},
|
},
|
||||||
React.createElement('div', { className: 'ct-panel-head' },
|
React.createElement('div', {
|
||||||
React.createElement('div', { className: 'ct-panel-head__title' }, '实时预览',
|
className: 'ct-panel-head lc-create-preview-head',
|
||||||
React.createElement('span', { className: 'ct-panel-head__badge' }, 'V26.6 完整合同'),
|
'aria-describedby': isTemplateSelected ? 'lc-preview-editable-hint' : undefined,
|
||||||
|
},
|
||||||
|
React.createElement('div', { className: 'lc-template-step-card__title-row' },
|
||||||
|
React.createElement('div', { className: 'ct-panel-head__title' }, '实时预览'),
|
||||||
|
isTemplateSelected ? React.createElement(PreviewEditableHint) : null,
|
||||||
),
|
),
|
||||||
React.createElement('div', { className: 'ct-panel-head__actions' },
|
React.createElement('div', { className: 'ct-panel-head__actions' },
|
||||||
React.createElement('span', {
|
React.createElement('span', {
|
||||||
@@ -208,13 +641,15 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
React.createElement('div', { className: 'ct-preview-panel ct-preview-panel--word' },
|
React.createElement('div', { className: 'ct-preview-panel ct-preview-panel--word' },
|
||||||
React.createElement(LeaseContractPreviewPanel, {
|
isTemplateSelected
|
||||||
html: previewHtml,
|
? React.createElement(LeaseContractPreviewPanel, {
|
||||||
zoom: workspaceZoom,
|
html: previewHtml,
|
||||||
onZoomChange: setWorkspaceZoom,
|
zoom: workspaceZoom,
|
||||||
editable: true,
|
onZoomChange: setWorkspaceZoom,
|
||||||
onContentChange: handlePreviewContentChange,
|
editable: true,
|
||||||
}),
|
onContentChange: handlePreviewContentChange,
|
||||||
|
})
|
||||||
|
: React.createElement('div', { className: 'lc-template-preview-empty' }, '请先在左侧选择合同模板,右侧将自动生成对应预览。'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -231,14 +666,31 @@ export default function LeaseContractCreate({ onBack }) {
|
|||||||
type: 'button',
|
type: 'button',
|
||||||
className: 'vm-btn vm-btn-secondary',
|
className: 'vm-btn vm-btn-secondary',
|
||||||
onClick: function () {
|
onClick: function () {
|
||||||
message.success('已保存草稿(' + contractApprovalType + ',原型)');
|
if (!contractTemplateId) {
|
||||||
|
message.warning('请先选择合同模板');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.success('已保存草稿(' + getContractTypeLabel(contractType) + ' · ' + contractApprovalType + ',原型)');
|
||||||
},
|
},
|
||||||
}, '保存草稿'),
|
}, '保存草稿'),
|
||||||
React.createElement('button', {
|
React.createElement('button', {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
className: 'vm-btn vm-btn-primary',
|
className: 'vm-btn vm-btn-primary',
|
||||||
onClick: function () {
|
onClick: function () {
|
||||||
message.info('提交审核:' + contractApprovalType + '(原型)');
|
if (!contractTemplateId) {
|
||||||
|
message.warning('请先选择合同模板');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isFormSubmitReady) {
|
||||||
|
if (!isPoaSubmitReady) {
|
||||||
|
message.warning('提交审核前须完成授权委托书填写');
|
||||||
|
scrollToCreateSection('poa');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.warning('请先完成左侧必填项');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.info('提交审核:' + getContractTypeLabel(contractType) + ' · ' + contractApprovalType + '(原型)');
|
||||||
},
|
},
|
||||||
}, '提交审核'),
|
}, '提交审核'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ import {
|
|||||||
formatKpiAmount,
|
formatKpiAmount,
|
||||||
getExtraServiceNames,
|
getExtraServiceNames,
|
||||||
} from './lease-order-vars.js';
|
} from './lease-order-vars.js';
|
||||||
|
import {
|
||||||
|
LEASE_BUSINESS_ORG_TREE,
|
||||||
|
flattenBusinessOwnerOptions,
|
||||||
|
matchBusinessOwnerOption,
|
||||||
|
formatBusinessAssignmentLabel,
|
||||||
|
getBusinessOwnersByDept,
|
||||||
|
} from './lease-business-org-data.js';
|
||||||
import { getMatchedPrototypeClauses } from './lease-contract-vehicle-clause.js';
|
import { getMatchedPrototypeClauses } from './lease-contract-vehicle-clause.js';
|
||||||
|
|
||||||
function FieldHint(props) {
|
function FieldHint(props) {
|
||||||
@@ -63,6 +70,20 @@ function FieldHint(props) {
|
|||||||
}, props.children);
|
}, props.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function aggregateOrderBrandModels(rows) {
|
||||||
|
var seen = {};
|
||||||
|
var result = [];
|
||||||
|
(rows || []).forEach(function (row) {
|
||||||
|
normalizeBrandModels(row).forEach(function (pair) {
|
||||||
|
var key = pair[0] + '\0' + pair[1];
|
||||||
|
if (seen[key]) return;
|
||||||
|
seen[key] = true;
|
||||||
|
result.push(pair);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function BrandModelsClauseHint(props) {
|
function BrandModelsClauseHint(props) {
|
||||||
var brandModels = normalizeBrandModels({ brandModels: props.brandModels });
|
var brandModels = normalizeBrandModels({ brandModels: props.brandModels });
|
||||||
if (!brandModels.length) return null;
|
if (!brandModels.length) return null;
|
||||||
@@ -286,9 +307,6 @@ function LeaseOrderServiceContentField(props) {
|
|||||||
var query = queryState[0];
|
var query = queryState[0];
|
||||||
var setQuery = queryState[1];
|
var setQuery = queryState[1];
|
||||||
var serviceNames = getExtraServiceNames(selected);
|
var serviceNames = getExtraServiceNames(selected);
|
||||||
var summary = serviceNames.length <= 2
|
|
||||||
? serviceNames.join('、')
|
|
||||||
: (serviceNames.length > 0 ? '已选 ' + serviceNames.length + ' 项' : '');
|
|
||||||
var searchText = query.trim();
|
var searchText = query.trim();
|
||||||
var isSearchMode = searchText.length > 0;
|
var isSearchMode = searchText.length > 0;
|
||||||
var resolvedCategory = activeCategory || LEASE_EXTRA_SERVICE_TREE[0].category;
|
var resolvedCategory = activeCategory || LEASE_EXTRA_SERVICE_TREE[0].category;
|
||||||
@@ -459,7 +477,13 @@ function LeaseOrderServiceContentField(props) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return React.createElement('div', { className: 'vm-operate-city-field lc-lease-order-service-picker' },
|
return React.createElement('div', { className: 'lc-lease-order-service-field' },
|
||||||
|
selected.length > 0
|
||||||
|
? React.createElement('p', {
|
||||||
|
className: 'lc-lease-order-service-field__summary',
|
||||||
|
'aria-live': 'polite',
|
||||||
|
}, serviceNames.join('、'))
|
||||||
|
: null,
|
||||||
React.createElement(Popover, {
|
React.createElement(Popover, {
|
||||||
trigger: 'click',
|
trigger: 'click',
|
||||||
placement: 'bottomLeft',
|
placement: 'bottomLeft',
|
||||||
@@ -469,34 +493,200 @@ function LeaseOrderServiceContentField(props) {
|
|||||||
getPopupContainer: function () { return document.body; },
|
getPopupContainer: function () { return document.body; },
|
||||||
content: popoverContent,
|
content: popoverContent,
|
||||||
},
|
},
|
||||||
React.createElement('div', {
|
React.createElement('button', {
|
||||||
className: 'vm-filter-picker-control lc-lease-order-service-picker__trigger' + (open ? ' open' : ''),
|
type: 'button',
|
||||||
role: 'combobox',
|
className: 'lc-lease-order-service-picker__btn',
|
||||||
'aria-label': '服务内容',
|
'aria-label': '服务内容',
|
||||||
'aria-expanded': open,
|
'aria-expanded': open,
|
||||||
tabIndex: 0,
|
|
||||||
'data-annotation-id': 'lc-lease-order-service-content',
|
'data-annotation-id': 'lc-lease-order-service-content',
|
||||||
onKeyDown: function (e) {
|
}, '选择服务项'),
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
),
|
||||||
e.preventDefault();
|
);
|
||||||
setOpen(true);
|
}
|
||||||
}
|
|
||||||
|
function BusinessDeptOwnerPickerField(props) {
|
||||||
|
var businessDept = props.businessDept || '';
|
||||||
|
var businessOwner = props.businessOwner || '';
|
||||||
|
var onChange = props.onChange;
|
||||||
|
var openState = React.useState(false);
|
||||||
|
var deptState = React.useState('');
|
||||||
|
var queryState = React.useState('');
|
||||||
|
var open = openState[0];
|
||||||
|
var setOpen = openState[1];
|
||||||
|
var activeDept = deptState[0];
|
||||||
|
var setActiveDept = deptState[1];
|
||||||
|
var query = queryState[0];
|
||||||
|
var setQuery = queryState[1];
|
||||||
|
var searchText = query.trim();
|
||||||
|
var isSearchMode = searchText.length > 0;
|
||||||
|
var resolvedDept = activeDept || businessDept || LEASE_BUSINESS_ORG_TREE[0].dept;
|
||||||
|
var displayOptions = isSearchMode
|
||||||
|
? flattenBusinessOwnerOptions().filter(function (item) {
|
||||||
|
return matchBusinessOwnerOption(item, searchText);
|
||||||
|
})
|
||||||
|
: getBusinessOwnersByDept(resolvedDept).map(function (owner) {
|
||||||
|
return { dept: resolvedDept, owner: owner };
|
||||||
|
});
|
||||||
|
|
||||||
|
function resolveInitialDept() {
|
||||||
|
if (businessDept) return businessDept;
|
||||||
|
return LEASE_BUSINESS_ORG_TREE[0].dept;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpenChange(nextOpen) {
|
||||||
|
setOpen(nextOpen);
|
||||||
|
if (nextOpen) {
|
||||||
|
setActiveDept(resolveInitialDept());
|
||||||
|
} else {
|
||||||
|
setQuery('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectOwner(dept, owner) {
|
||||||
|
onChange(dept, owner);
|
||||||
|
setActiveDept(dept);
|
||||||
|
setOpen(false);
|
||||||
|
setQuery('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelection(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
onChange('', '');
|
||||||
|
setQuery('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setQuery('');
|
||||||
|
}
|
||||||
|
|
||||||
|
var summary = formatBusinessAssignmentLabel(businessDept, businessOwner);
|
||||||
|
|
||||||
|
var popoverContent = React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-body vm-operate-city-picker-panel lc-lease-order-service-popover__panel',
|
||||||
|
},
|
||||||
|
React.createElement('label', { className: 'vm-operate-city-edit-search' },
|
||||||
|
React.createElement(Search, { size: 16, 'aria-hidden': true, className: 'vm-operate-city-edit-search-icon' }),
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'text',
|
||||||
|
className: 'vm-operate-city-edit-search-input',
|
||||||
|
value: query,
|
||||||
|
placeholder: '输入业务员姓名快速筛选',
|
||||||
|
'aria-label': '搜索业务员姓名',
|
||||||
|
autoComplete: 'off',
|
||||||
|
onChange: function (e) { setQuery(e.target.value); },
|
||||||
|
}),
|
||||||
|
query
|
||||||
|
? React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'vm-operate-city-edit-search-clear',
|
||||||
|
onClick: clearSearch,
|
||||||
|
'aria-label': '清空搜索',
|
||||||
|
}, React.createElement(X, { size: 14, 'aria-hidden': true }))
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
summary
|
||||||
|
? React.createElement('div', { className: 'vm-operate-city-edit-selected', 'aria-live': 'polite' },
|
||||||
|
React.createElement('span', { className: 'vm-operate-city-edit-selected-label' }, '已选'),
|
||||||
|
React.createElement('div', { className: 'lc-lease-order-service-popover__selected-tags' },
|
||||||
|
React.createElement('span', { className: 'lc-lease-order-service-popover__selected-tag' },
|
||||||
|
summary,
|
||||||
|
React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
'aria-label': '清除业务部门及业务人员',
|
||||||
|
onMouseDown: function (e) { e.preventDefault(); },
|
||||||
|
onClick: clearSelection,
|
||||||
|
}, React.createElement(X, { size: 12, 'aria-hidden': true })),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-main' + (isSearchMode ? ' is-search' : ''),
|
||||||
|
},
|
||||||
|
!isSearchMode
|
||||||
|
? React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-step vm-operate-city-edit-step--province',
|
||||||
},
|
},
|
||||||
|
React.createElement('span', { className: 'vm-operate-city-step-label' }, '业务部门'),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-chips',
|
||||||
|
role: 'tablist',
|
||||||
|
'aria-label': '业务部门',
|
||||||
|
},
|
||||||
|
LEASE_BUSINESS_ORG_TREE.map(function (group) {
|
||||||
|
var active = resolvedDept === group.dept;
|
||||||
|
return React.createElement('button', {
|
||||||
|
key: group.dept,
|
||||||
|
type: 'button',
|
||||||
|
role: 'tab',
|
||||||
|
'aria-selected': active,
|
||||||
|
className: 'vm-operate-city-chip' + (active ? ' active' : ''),
|
||||||
|
onClick: function () {
|
||||||
|
setActiveDept(group.dept);
|
||||||
|
setQuery('');
|
||||||
|
},
|
||||||
|
}, group.dept);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'vm-operate-city-edit-step vm-operate-city-edit-step--city',
|
||||||
},
|
},
|
||||||
React.createElement('span', {
|
React.createElement('span', { className: 'vm-operate-city-step-label' },
|
||||||
className: 'vm-filter-picker-input lc-lease-order-service-picker__summary'
|
isSearchMode ? ('搜索结果(' + displayOptions.length + ')') : '业务人员',
|
||||||
+ (summary ? '' : ' is-placeholder'),
|
),
|
||||||
}, summary || '请选择服务内容'),
|
React.createElement('div', {
|
||||||
selected.length > 0
|
className: 'vm-operate-city-edit-chips',
|
||||||
? React.createElement('button', {
|
role: 'listbox',
|
||||||
type: 'button',
|
'aria-label': '业务人员列表',
|
||||||
className: 'vm-filter-picker-clear',
|
},
|
||||||
onClick: clearAll,
|
displayOptions.length === 0
|
||||||
'aria-label': '清空服务内容',
|
? React.createElement('p', { className: 'vm-operate-city-empty' },
|
||||||
tabIndex: -1,
|
isSearchMode ? '未找到匹配的业务人员,请换个关键词' : '该部门暂无可选业务人员',
|
||||||
}, React.createElement(X, { size: 14, 'aria-hidden': true }))
|
)
|
||||||
: null,
|
: displayOptions.map(function (item) {
|
||||||
React.createElement(ChevronDown, { size: 14, className: 'vm-filter-picker-chevron', 'aria-hidden': true }),
|
var checked = businessDept === item.dept && businessOwner === item.owner;
|
||||||
|
return React.createElement('button', {
|
||||||
|
key: item.dept + '::' + item.owner,
|
||||||
|
type: 'button',
|
||||||
|
role: 'option',
|
||||||
|
'aria-selected': checked,
|
||||||
|
className: 'vm-operate-city-chip' + (checked ? ' active' : ''),
|
||||||
|
onClick: function () { selectOwner(item.dept, item.owner); },
|
||||||
|
}, item.owner);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return React.createElement('div', { className: 'lc-business-dept-picker' },
|
||||||
|
React.createElement(Popover, {
|
||||||
|
trigger: 'click',
|
||||||
|
placement: 'bottomLeft',
|
||||||
|
open: open,
|
||||||
|
onOpenChange: handleOpenChange,
|
||||||
|
overlayClassName: 'lc-lease-order-service-popover lc-business-dept-picker-popover',
|
||||||
|
getPopupContainer: function () { return document.body; },
|
||||||
|
content: popoverContent,
|
||||||
|
},
|
||||||
|
React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'vm-filter-picker-control lc-business-dept-picker__trigger' + (open ? ' open' : ''),
|
||||||
|
'aria-label': '业务部门及业务人员',
|
||||||
|
'aria-expanded': open,
|
||||||
|
'data-annotation-id': 'lc-signing-business-assignment',
|
||||||
|
},
|
||||||
|
summary
|
||||||
|
? React.createElement('span', { className: 'vm-filter-picker-input lc-business-dept-picker__summary' }, summary)
|
||||||
|
: React.createElement('span', {
|
||||||
|
className: 'vm-filter-picker-input lc-business-dept-picker__summary is-placeholder',
|
||||||
|
}, '请选择业务部门及业务人员'),
|
||||||
|
React.createElement(ChevronDown, { size: 16, className: 'vm-filter-picker-chevron', 'aria-hidden': true }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -977,11 +1167,14 @@ function LeaseOrderEditor(props) {
|
|||||||
),
|
),
|
||||||
React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '辆'),
|
React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '辆'),
|
||||||
),
|
),
|
||||||
insuredCountEditable
|
|
||||||
? React.createElement(FieldHint, { annotationId: 'lc-lease-order-insured-count-hint' }, '车牌号为「以实际交付为准」时,可手动填写车辆数量(正整数)。')
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
React.createElement(FormField, { label: '保险金额/辆' },
|
React.createElement(FormField, {
|
||||||
|
label: '保险金额/辆',
|
||||||
|
labelHint: React.createElement(FormFieldLabelHint, {
|
||||||
|
annotationId: 'lc-lease-order-third-party-hint',
|
||||||
|
text: '仅记录保额并同步至右侧预览,不计入本卡片及其他费用计算。',
|
||||||
|
}),
|
||||||
|
},
|
||||||
React.createElement('div', { className: 'lc-form-inline-sentence' },
|
React.createElement('div', { className: 'lc-form-inline-sentence' },
|
||||||
React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '三者责任险'),
|
React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '三者责任险'),
|
||||||
React.createElement(InputNumber, {
|
React.createElement(InputNumber, {
|
||||||
@@ -997,9 +1190,15 @@ function LeaseOrderEditor(props) {
|
|||||||
}),
|
}),
|
||||||
React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '万元、车损'),
|
React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '万元、车损'),
|
||||||
),
|
),
|
||||||
React.createElement(FieldHint, { annotationId: 'lc-lease-order-third-party-hint' }, '三者责任险仅用于记录保额,不计入费用计算;填写后会同步显示在右侧附件一「三者责任险【】万元」中。'),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'lc-lease-order-editor__clause-hint',
|
||||||
|
id: 'lc-brand-clause-hint-order',
|
||||||
|
'data-annotation-id': 'lc-brand-clause-hint-order',
|
||||||
|
},
|
||||||
|
React.createElement(BrandModelsClauseHint, { brandModels: aggregateOrderBrandModels(rows) }),
|
||||||
|
),
|
||||||
React.createElement('div', { className: 'lc-lease-order-editor__table-wrap', role: 'region', 'aria-label': '租赁订单车辆列表' },
|
React.createElement('div', { className: 'lc-lease-order-editor__table-wrap', role: 'region', 'aria-label': '租赁订单车辆列表' },
|
||||||
React.createElement('table', { className: 'lc-lease-order-editor__table' },
|
React.createElement('table', { className: 'lc-lease-order-editor__table' },
|
||||||
React.createElement('thead', null,
|
React.createElement('thead', null,
|
||||||
@@ -1009,7 +1208,7 @@ function LeaseOrderEditor(props) {
|
|||||||
React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__th-subtotal' }, '租金及服务费小计(元/月/辆,含税)'),
|
React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__th-subtotal' }, '租金及服务费小计(元/月/辆,含税)'),
|
||||||
React.createElement('th', { colSpan: 3, scope: 'colgroup', className: 'lc-lease-order-editor__th-group' }, '价格及服务内容'),
|
React.createElement('th', { colSpan: 3, scope: 'colgroup', className: 'lc-lease-order-editor__th-group' }, '价格及服务内容'),
|
||||||
React.createElement('th', { rowSpan: 2, scope: 'col' }, '保证金(元/辆)'),
|
React.createElement('th', { rowSpan: 2, scope: 'col' }, '保证金(元/辆)'),
|
||||||
React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__th-period' }, '车辆租赁期限'),
|
React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__th-period' }, '租赁期限(月)'),
|
||||||
React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__actions-col' }, '操作'),
|
React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__actions-col' }, '操作'),
|
||||||
),
|
),
|
||||||
React.createElement('tr', { className: 'lc-lease-order-editor__head-row lc-lease-order-editor__head-row--sub' },
|
React.createElement('tr', { className: 'lc-lease-order-editor__head-row lc-lease-order-editor__head-row--sub' },
|
||||||
@@ -1025,9 +1224,6 @@ function LeaseOrderEditor(props) {
|
|||||||
var plateNos = normalizePlateNos(row);
|
var plateNos = normalizePlateNos(row);
|
||||||
var vehicleCount = getRowVehicleCountForPricing(row, order);
|
var vehicleCount = getRowVehicleCountForPricing(row, order);
|
||||||
var subtotal = calcRentServiceSubtotal(row.rent, row.serviceFee, vehicleCount);
|
var subtotal = calcRentServiceSubtotal(row.rent, row.serviceFee, vehicleCount);
|
||||||
var leasePeriodValue = row.leasePeriodStart && row.leasePeriodEnd
|
|
||||||
? [dayjs(row.leasePeriodStart), dayjs(row.leasePeriodEnd)]
|
|
||||||
: null;
|
|
||||||
return React.createElement('tr', { key: row.id },
|
return React.createElement('tr', { key: row.id },
|
||||||
React.createElement('td', { className: 'lc-lease-order-editor__brand-cell' },
|
React.createElement('td', { className: 'lc-lease-order-editor__brand-cell' },
|
||||||
React.createElement(LeaseOrderBrandModelField, {
|
React.createElement(LeaseOrderBrandModelField, {
|
||||||
@@ -1036,9 +1232,6 @@ function LeaseOrderEditor(props) {
|
|||||||
updateRow(row.id, { brandModels: values || [] });
|
updateRow(row.id, { brandModels: values || [] });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
React.createElement('div', { id: 'lc-brand-clause-hint-' + row.id },
|
|
||||||
React.createElement(BrandModelsClauseHint, { brandModels: brandModels }),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
React.createElement('td', null,
|
React.createElement('td', null,
|
||||||
React.createElement(LeaseOrderPlateNoField, {
|
React.createElement(LeaseOrderPlateNoField, {
|
||||||
@@ -1113,19 +1306,24 @@ function LeaseOrderEditor(props) {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
React.createElement('td', null,
|
React.createElement('td', { className: 'lc-lease-order-editor__period-cell' },
|
||||||
React.createElement(DatePicker.RangePicker, {
|
React.createElement(InputNumber, {
|
||||||
className: 'lc-form-datepicker vm-focus-border',
|
className: 'lc-form-number lc-lease-order-period-input vm-focus-border',
|
||||||
style: { width: '100%' },
|
controls: false,
|
||||||
placeholder: ['开始日期', '结束日期'],
|
min: 1,
|
||||||
value: leasePeriodValue,
|
step: 1,
|
||||||
onChange: function (_dates, dateStrings) {
|
precision: 0,
|
||||||
|
value: row.leasePeriodMonths,
|
||||||
|
onChange: function (v) {
|
||||||
updateRow(row.id, {
|
updateRow(row.id, {
|
||||||
leasePeriodStart: dateStrings && dateStrings[0] ? dateStrings[0] : null,
|
leasePeriodMonths: v,
|
||||||
leasePeriodEnd: dateStrings && dateStrings[1] ? dateStrings[1] : null,
|
leasePeriodStart: null,
|
||||||
|
leasePeriodEnd: null,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
placeholder: '月数',
|
||||||
'aria-label': '车辆租赁期限',
|
'aria-label': '车辆租赁期限',
|
||||||
|
'data-annotation-id': 'lc-lease-order-lease-period',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
React.createElement('td', { className: 'lc-lease-order-editor__actions-col' },
|
React.createElement('td', { className: 'lc-lease-order-editor__actions-col' },
|
||||||
@@ -1140,7 +1338,13 @@ function LeaseOrderEditor(props) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
React.createElement('div', { className: 'lc-lease-order-editor__delivery lc-form-grid lc-form-grid--pair' },
|
React.createElement('div', { className: 'lc-lease-order-editor__delivery lc-form-grid lc-form-grid--pair' },
|
||||||
React.createElement(FormField, { label: '车辆交付(交还)地点' },
|
React.createElement(FormField, {
|
||||||
|
label: '车辆交付(交还)地点',
|
||||||
|
labelHint: React.createElement(FormFieldLabelHint, {
|
||||||
|
annotationId: 'lc-lease-order-delivery-region-hint',
|
||||||
|
text: '交还地点决定由哪个区域运维负责交车与还车。',
|
||||||
|
}),
|
||||||
|
},
|
||||||
React.createElement(Cascader, {
|
React.createElement(Cascader, {
|
||||||
className: 'lc-form-cascader vm-focus-border',
|
className: 'lc-form-cascader vm-focus-border',
|
||||||
options: PROVINCE_CITY_CASCADER_OPTIONS,
|
options: PROVINCE_CITY_CASCADER_OPTIONS,
|
||||||
@@ -1151,9 +1355,14 @@ function LeaseOrderEditor(props) {
|
|||||||
'aria-label': '车辆交付交还地点',
|
'aria-label': '车辆交付交还地点',
|
||||||
'data-annotation-id': 'lc-lease-order-delivery-region',
|
'data-annotation-id': 'lc-lease-order-delivery-region',
|
||||||
}),
|
}),
|
||||||
React.createElement(FieldHint, { annotationId: 'lc-lease-order-delivery-region-hint' }, '交付、交还地点决定由哪个区域的运维负责向客户交车;若与运维用户权限省市重合,则由该地区运维负责交车。'),
|
|
||||||
),
|
),
|
||||||
React.createElement(FormField, { label: '车辆交付时间' },
|
React.createElement(FormField, {
|
||||||
|
label: '车辆交付时间',
|
||||||
|
labelHint: React.createElement(FormFieldLabelHint, {
|
||||||
|
annotationId: 'lc-lease-order-delivery-date-hint',
|
||||||
|
text: '用于生成交车任务时,提示运维何时交车。',
|
||||||
|
}),
|
||||||
|
},
|
||||||
React.createElement(DatePicker, {
|
React.createElement(DatePicker, {
|
||||||
className: 'lc-form-datepicker vm-focus-border',
|
className: 'lc-form-datepicker vm-focus-border',
|
||||||
style: { width: '100%' },
|
style: { width: '100%' },
|
||||||
@@ -1165,7 +1374,6 @@ function LeaseOrderEditor(props) {
|
|||||||
'aria-label': '车辆交付时间',
|
'aria-label': '车辆交付时间',
|
||||||
'data-annotation-id': 'lc-lease-order-delivery-date',
|
'data-annotation-id': 'lc-lease-order-delivery-date',
|
||||||
}),
|
}),
|
||||||
React.createElement(FieldHint, { annotationId: 'lc-lease-order-delivery-date-hint' }, '用于生成交车任务时,提示运维何时交车。'),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1210,9 +1418,6 @@ function PowerOfAttorneyEditor(props) {
|
|||||||
var hasStartedDelegate = delegates.some(isDelegateRowStarted);
|
var hasStartedDelegate = delegates.some(isDelegateRowStarted);
|
||||||
|
|
||||||
return React.createElement('div', { className: 'lc-poa-editor' },
|
return React.createElement('div', { className: 'lc-poa-editor' },
|
||||||
React.createElement(FieldHint, { annotationId: 'lc-create-poa-hint' },
|
|
||||||
'授权委托书为选填;如未填写,提交审核后须在交车前完成上传。可从合同列表「添加授权委托书」补充委托人信息并自动衔接入合同。填写姓名、联系方式、证件号码任一字段后,其余两项为必填;最多 ' + POA_MAX_DELEGATES + ' 个委托人,信息将同步至右侧授权委托书。',
|
|
||||||
),
|
|
||||||
delegates.map(function (row, index) {
|
delegates.map(function (row, index) {
|
||||||
var started = isDelegateRowStarted(row);
|
var started = isDelegateRowStarted(row);
|
||||||
return React.createElement('div', {
|
return React.createElement('div', {
|
||||||
@@ -1297,12 +1502,38 @@ function CustomerCredentialThumb(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FormFieldLabelHint(props) {
|
||||||
|
if (!props.text) return null;
|
||||||
|
|
||||||
|
return React.createElement('span', {
|
||||||
|
className: 'lc-form-card__hint lc-form-field__inline-hint',
|
||||||
|
role: 'note',
|
||||||
|
id: props.id,
|
||||||
|
'data-annotation-id': props.annotationId || undefined,
|
||||||
|
},
|
||||||
|
React.createElement(Info, {
|
||||||
|
size: 13,
|
||||||
|
className: 'lc-form-card__hint-icon',
|
||||||
|
'aria-hidden': true,
|
||||||
|
}),
|
||||||
|
React.createElement('span', { className: 'lc-form-card__hint-text' }, props.text),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function FormField(props) {
|
function FormField(props) {
|
||||||
|
var labelContent = [
|
||||||
|
props.required ? React.createElement('span', { className: 'lc-form-field__required', 'aria-hidden': 'true' }, '*') : null,
|
||||||
|
props.label,
|
||||||
|
];
|
||||||
|
var labelEl = props.labelHint
|
||||||
|
? React.createElement('span', { className: 'lc-form-field__label-row' },
|
||||||
|
React.createElement('span', { className: 'lc-form-field__label' }, labelContent),
|
||||||
|
props.labelHint,
|
||||||
|
)
|
||||||
|
: React.createElement('span', { className: 'lc-form-field__label' }, labelContent);
|
||||||
|
|
||||||
return React.createElement('label', { className: 'lc-form-field' },
|
return React.createElement('label', { className: 'lc-form-field' },
|
||||||
React.createElement('span', { className: 'lc-form-field__label' },
|
labelEl,
|
||||||
props.required ? React.createElement('span', { className: 'lc-form-field__required', 'aria-hidden': 'true' }, '*') : null,
|
|
||||||
props.label,
|
|
||||||
),
|
|
||||||
props.children,
|
props.children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1397,11 +1628,14 @@ export default function LeaseContractEditorForm(props) {
|
|||||||
var lessorId = props.lessorId;
|
var lessorId = props.lessorId;
|
||||||
var customerId = props.customerId;
|
var customerId = props.customerId;
|
||||||
var contractCode = props.contractCode || '';
|
var contractCode = props.contractCode || '';
|
||||||
|
var businessDept = props.businessDept || '';
|
||||||
|
var businessOwner = props.businessOwner || '';
|
||||||
var mileage = props.mileage || DEFAULT_MILEAGE_STANDARD;
|
var mileage = props.mileage || DEFAULT_MILEAGE_STANDARD;
|
||||||
var feeInfo = props.feeInfo || DEFAULT_FEE_INFO;
|
var feeInfo = props.feeInfo || DEFAULT_FEE_INFO;
|
||||||
var onLessorChange = props.onLessorChange;
|
var onLessorChange = props.onLessorChange;
|
||||||
var onCustomerChange = props.onCustomerChange;
|
var onCustomerChange = props.onCustomerChange;
|
||||||
var onContractCodeChange = props.onContractCodeChange;
|
var onContractCodeChange = props.onContractCodeChange;
|
||||||
|
var onBusinessAssignmentChange = props.onBusinessAssignmentChange;
|
||||||
var onMileageChange = props.onMileageChange;
|
var onMileageChange = props.onMileageChange;
|
||||||
var onFeeInfoChange = props.onFeeInfoChange;
|
var onFeeInfoChange = props.onFeeInfoChange;
|
||||||
|
|
||||||
@@ -1457,10 +1691,28 @@ export default function LeaseContractEditorForm(props) {
|
|||||||
'aria-label': '合同编号',
|
'aria-label': '合同编号',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
React.createElement(FormField, {
|
||||||
|
label: '业务部门 / 业务人员',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
React.createElement(BusinessDeptOwnerPickerField, {
|
||||||
|
businessDept: businessDept,
|
||||||
|
businessOwner: businessOwner,
|
||||||
|
onChange: function (dept, owner) {
|
||||||
|
if (onBusinessAssignmentChange) onBusinessAssignmentChange(dept, owner);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
React.createElement('div', { className: 'lc-form-grid lc-form-grid--pair' },
|
React.createElement('div', { className: 'lc-form-grid lc-form-grid--pair' },
|
||||||
React.createElement(FormField, {
|
React.createElement(FormField, {
|
||||||
label: '甲方',
|
label: '甲方',
|
||||||
required: true,
|
required: true,
|
||||||
|
labelHint: lessorId
|
||||||
|
? React.createElement(FormFieldLabelHint, {
|
||||||
|
annotationId: 'lc-lessor-select-hint',
|
||||||
|
text: '将使用该公司用章进行电子签章',
|
||||||
|
})
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
React.createElement(Select, {
|
React.createElement(Select, {
|
||||||
className: 'lc-form-select vm-focus-border',
|
className: 'lc-form-select vm-focus-border',
|
||||||
@@ -1733,11 +1985,32 @@ export function LeaseContractLeaseOrderSection(props) {
|
|||||||
export function LeaseContractPowerOfAttorneySection(props) {
|
export function LeaseContractPowerOfAttorneySection(props) {
|
||||||
var powerOfAttorney = props.powerOfAttorney;
|
var powerOfAttorney = props.powerOfAttorney;
|
||||||
var onPowerOfAttorneyChange = props.onPowerOfAttorneyChange;
|
var onPowerOfAttorneyChange = props.onPowerOfAttorneyChange;
|
||||||
|
var contractRemark = props.contractRemark;
|
||||||
|
var onContractRemarkChange = props.onContractRemarkChange;
|
||||||
|
|
||||||
return React.createElement('div', { className: 'lc-create-editor-scroll lc-create-editor-scroll--section' },
|
return React.createElement('div', { className: 'lc-create-editor-scroll lc-create-editor-scroll--section' },
|
||||||
React.createElement(PowerOfAttorneyEditor, {
|
React.createElement(PowerOfAttorneyEditor, {
|
||||||
poa: powerOfAttorney || createDefaultPowerOfAttorneyState(),
|
poa: powerOfAttorney || createDefaultPowerOfAttorneyState(),
|
||||||
onChange: onPowerOfAttorneyChange,
|
onChange: onPowerOfAttorneyChange,
|
||||||
}),
|
}),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'lc-contract-remark',
|
||||||
|
'data-annotation-id': 'lc-create-contract-remark',
|
||||||
|
},
|
||||||
|
React.createElement(FormField, { label: '合同备注' },
|
||||||
|
React.createElement(Input.TextArea, {
|
||||||
|
className: 'lc-contract-remark__textarea vm-focus-border',
|
||||||
|
rows: 4,
|
||||||
|
maxLength: 500,
|
||||||
|
showCount: true,
|
||||||
|
value: contractRemark || '',
|
||||||
|
onChange: function (e) {
|
||||||
|
if (onContractRemarkChange) onContractRemarkChange(e.target.value);
|
||||||
|
},
|
||||||
|
placeholder: '请输入合同备注(选填)',
|
||||||
|
'aria-label': '合同备注',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,26 @@ function EditablePreviewPageBody(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PreviewEditableHint() {
|
||||||
|
return React.createElement('span', {
|
||||||
|
className: 'lc-form-card__hint lc-preview-editable-hint lc-preview-editable-hint--inline',
|
||||||
|
role: 'note',
|
||||||
|
'data-annotation-id': 'lc-preview-editable-hint',
|
||||||
|
id: 'lc-preview-editable-hint',
|
||||||
|
},
|
||||||
|
React.createElement(Info, {
|
||||||
|
size: 13,
|
||||||
|
className: 'lc-form-card__hint-icon',
|
||||||
|
'aria-hidden': true,
|
||||||
|
}),
|
||||||
|
React.createElement('span', { className: 'lc-form-card__hint-text lc-preview-editable-hint__text' },
|
||||||
|
'正文可编辑;改动',
|
||||||
|
React.createElement('span', { className: 'lc-preview-editable-hint__risk' }, '风控红线条款'),
|
||||||
|
'走非标准审批,未改则标准审批。',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function previewPagerIcon(name) {
|
function previewPagerIcon(name) {
|
||||||
var common = {
|
var common = {
|
||||||
xmlns: 'http://www.w3.org/2000/svg',
|
xmlns: 'http://www.w3.org/2000/svg',
|
||||||
@@ -143,31 +163,6 @@ export default function LeaseContractPreviewPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var editableHint = editable
|
|
||||||
? React.createElement('div', {
|
|
||||||
className: 'lc-preview-editable-hint',
|
|
||||||
role: 'note',
|
|
||||||
'data-annotation-id': 'lc-preview-editable-hint',
|
|
||||||
},
|
|
||||||
React.createElement('div', { className: 'lc-preview-editable-hint__card' },
|
|
||||||
React.createElement(Info, {
|
|
||||||
size: 16,
|
|
||||||
className: 'lc-preview-editable-hint__icon',
|
|
||||||
'aria-hidden': true,
|
|
||||||
}),
|
|
||||||
React.createElement('p', { className: 'lc-preview-editable-hint__text' },
|
|
||||||
'预览正文可直接修改。若修改了',
|
|
||||||
React.createElement('span', { className: 'lc-preview-editable-hint__risk' }, '风控红线条款'),
|
|
||||||
',提交时将自动走',
|
|
||||||
React.createElement('span', { className: 'lc-preview-editable-hint__tag lc-preview-editable-hint__tag--nonstd' }, '非标准合同审批'),
|
|
||||||
';未改动风控条款则走',
|
|
||||||
React.createElement('span', { className: 'lc-preview-editable-hint__tag lc-preview-editable-hint__tag--std' }, '标准审批'),
|
|
||||||
'。',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
var previewToolbar = React.createElement('div', { className: 'ct-preview-toolbar' },
|
var previewToolbar = React.createElement('div', { className: 'ct-preview-toolbar' },
|
||||||
React.createElement('div', { className: 'ct-preview-toolbar__row ct-preview-toolbar__row--primary', role: 'toolbar', 'aria-label': '预览缩放' },
|
React.createElement('div', { className: 'ct-preview-toolbar__row ct-preview-toolbar__row--primary', role: 'toolbar', 'aria-label': '预览缩放' },
|
||||||
React.createElement('div', { className: 'ct-preview-pager__group', role: 'group', 'aria-label': '缩放' },
|
React.createElement('div', { className: 'ct-preview-pager__group', role: 'group', 'aria-label': '缩放' },
|
||||||
@@ -275,7 +270,6 @@ export default function LeaseContractPreviewPanel({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return React.createElement('div', { className: 'ct-preview-doc ct-word-doc ct-word-paginated' + (editable ? ' lc-preview-doc--editable' : '') },
|
return React.createElement('div', { className: 'ct-preview-doc ct-word-doc ct-word-paginated' + (editable ? ' lc-preview-doc--editable' : '') },
|
||||||
editableHint,
|
|
||||||
previewToolbar,
|
previewToolbar,
|
||||||
React.createElement('div', { className: 'ct-preview-body-shell' },
|
React.createElement('div', { className: 'ct-preview-body-shell' },
|
||||||
React.createElement('div', { className: 'ct-preview-pages-wrap' },
|
React.createElement('div', { className: 'ct-preview-pages-wrap' },
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -13,6 +13,7 @@ import {
|
|||||||
import 'antd/dist/reset.css';
|
import 'antd/dist/reset.css';
|
||||||
import '../vehicle-management/style.css';
|
import '../vehicle-management/style.css';
|
||||||
import './styles/index.css';
|
import './styles/index.css';
|
||||||
|
import '../contract-template-management/contract-template-catalog.js';
|
||||||
import LeaseContractApp from './LeaseContractManagement.jsx';
|
import LeaseContractApp from './LeaseContractManagement.jsx';
|
||||||
import annotationSourceDocument from './annotation-source.json';
|
import annotationSourceDocument from './annotation-source.json';
|
||||||
|
|
||||||
|
|||||||
37
src/prototypes/lease-contract-management/lease-business-org-data.js
vendored
Normal file
37
src/prototypes/lease-contract-management/lease-business-org-data.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 业务部门 / 业务人员级联数据(与列表筛选项、台账样例对齐)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export var LEASE_BUSINESS_ORG_TREE = [
|
||||||
|
{ dept: '业务1部', owners: ['张经理', '赵经理'] },
|
||||||
|
{ dept: '业务2部', owners: ['李专员', '钱专员'] },
|
||||||
|
{ dept: '业务3部', owners: ['王专员'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getBusinessOwnersByDept(dept) {
|
||||||
|
var group = LEASE_BUSINESS_ORG_TREE.find(function (item) {
|
||||||
|
return item.dept === dept;
|
||||||
|
});
|
||||||
|
return group ? group.owners.slice() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenBusinessOwnerOptions() {
|
||||||
|
return LEASE_BUSINESS_ORG_TREE.reduce(function (list, group) {
|
||||||
|
return list.concat(group.owners.map(function (owner) {
|
||||||
|
return { dept: group.dept, owner: owner };
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchBusinessOwnerOption(item, searchText) {
|
||||||
|
if (!searchText) return true;
|
||||||
|
var q = searchText.toLowerCase();
|
||||||
|
return item.owner.toLowerCase().indexOf(q) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatBusinessAssignmentLabel(dept, owner) {
|
||||||
|
if (dept && owner) return dept + ' · ' + owner;
|
||||||
|
if (dept) return dept;
|
||||||
|
if (owner) return owner;
|
||||||
|
return '';
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ function isMainContractFieldsComplete(form) {
|
|||||||
if (!contractCode) return false;
|
if (!contractCode) return false;
|
||||||
if (!form.lessorId) return false;
|
if (!form.lessorId) return false;
|
||||||
if (!form.customerId) return false;
|
if (!form.customerId) return false;
|
||||||
|
if (!form.businessDept) return false;
|
||||||
|
if (!form.businessOwner) return false;
|
||||||
|
|
||||||
var mileage = form.mileage || {};
|
var mileage = form.mileage || {};
|
||||||
if (mileage.hasRequirement) {
|
if (mileage.hasRequirement) {
|
||||||
@@ -47,23 +49,38 @@ export function isLeaseOrderFormComplete(form) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 授权委托书卡片完成度(选填;有填写时须完整) */
|
/** 授权委托书卡片完成度(提交前须至少一名完整委托人,且已填行须完整) */
|
||||||
export function isPowerOfAttorneyFormComplete(form) {
|
export function isPowerOfAttorneyFormComplete(form) {
|
||||||
var delegates = (form.powerOfAttorney && form.powerOfAttorney.delegates) || [];
|
var delegates = (form.powerOfAttorney && form.powerOfAttorney.delegates) || [];
|
||||||
var started = delegates.filter(isDelegateRowStarted);
|
var started = delegates.filter(isDelegateRowStarted);
|
||||||
if (!started.length) return true;
|
if (!started.length) return false;
|
||||||
return started.every(isDelegateRowComplete);
|
return started.every(isDelegateRowComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 授权委托书是否满足提交审核要求 */
|
||||||
|
export function isPowerOfAttorneySubmitReady(form) {
|
||||||
|
var delegates = (form.powerOfAttorney && form.powerOfAttorney.delegates) || [];
|
||||||
|
return delegates.some(isDelegateRowComplete);
|
||||||
|
}
|
||||||
|
|
||||||
/** 授权委托书是否有未完成行(用于徽章展示) */
|
/** 授权委托书是否有未完成行(用于徽章展示) */
|
||||||
export function isPowerOfAttorneyOptionalEmpty(form) {
|
export function isPowerOfAttorneyOptionalEmpty(form) {
|
||||||
var delegates = (form.powerOfAttorney && form.powerOfAttorney.delegates) || [];
|
var delegates = (form.powerOfAttorney && form.powerOfAttorney.delegates) || [];
|
||||||
return !delegates.some(isDelegateRowStarted);
|
return !delegates.some(isDelegateRowStarted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断新增租赁合同左侧表单必填项是否均已填写 */
|
/** 用章类型是否已选择 */
|
||||||
|
export function isSealTypeSelected(form) {
|
||||||
|
if (Array.isArray(form.sealTypes)) {
|
||||||
|
return form.sealTypes.length > 0;
|
||||||
|
}
|
||||||
|
return Boolean(form.sealType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判断新增租赁合同左侧表单必填项是否均已填写(提交审核) */
|
||||||
export function isLeaseContractFormComplete(form) {
|
export function isLeaseContractFormComplete(form) {
|
||||||
return isMainContractFieldsComplete(form)
|
return isMainContractFieldsComplete(form)
|
||||||
&& isLeaseOrderFormComplete(form)
|
&& isLeaseOrderFormComplete(form)
|
||||||
&& isPowerOfAttorneyFormComplete(form);
|
&& isPowerOfAttorneySubmitReady(form)
|
||||||
|
&& isSealTypeSelected(form);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,24 @@
|
|||||||
* 租赁合同列表样例数据 — 字段与新增/编辑页表单结构对齐
|
* 租赁合同列表样例数据 — 字段与新增/编辑页表单结构对齐
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { getLessorCompanyById } from '../contract-template-management/contract-template-vars.js';
|
||||||
|
import { getContractTemplateOption } from '../contract-template-management/contract-template-catalog.js';
|
||||||
|
import { getContractTypeLabel } from '../contract-template-management/contract-template-types.js';
|
||||||
|
|
||||||
|
export var SIGNING_COMPANY_LESSOR_IDS = {
|
||||||
|
'嘉兴羚牛': 'jx',
|
||||||
|
'上海羚牛': 'sh',
|
||||||
|
'广东羚牛': 'gd',
|
||||||
|
};
|
||||||
|
|
||||||
|
export var PAYMENT_PERIOD_CYCLE_LABELS = {
|
||||||
|
1: '月付',
|
||||||
|
3: '季付',
|
||||||
|
6: '半年付',
|
||||||
|
12: '年付',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @deprecated 列表展示请用 formatPaymentPeriodLabel */
|
||||||
export var PAYMENT_PERIOD_LABELS = {
|
export var PAYMENT_PERIOD_LABELS = {
|
||||||
1: '1个月',
|
1: '1个月',
|
||||||
3: '3个月',
|
3: '3个月',
|
||||||
@@ -14,12 +32,194 @@ export var HYDROGEN_PAYMENT_LABELS = {
|
|||||||
prepay: '预付款',
|
prepay: '预付款',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var LEGACY_PAYMENT_PERIOD_MAP = {
|
||||||
|
'1个月': 1,
|
||||||
|
'3个月': 3,
|
||||||
|
'6个月': 6,
|
||||||
|
'12个月': 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
var LEGACY_HYDROGEN_PAYMENT_MAP = {
|
||||||
|
'自行解决': 'self',
|
||||||
|
'预付款': 'prepay',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatPaymentPeriodLabel(period) {
|
||||||
|
if (period == null || period === '') return '-';
|
||||||
|
var months = typeof period === 'number' ? period : LEGACY_PAYMENT_PERIOD_MAP[period];
|
||||||
|
if (months != null) return PAYMENT_PERIOD_CYCLE_LABELS[months] || '-';
|
||||||
|
return String(period);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatHydrogenPaymentLabel(method) {
|
||||||
|
if (!method) return '-';
|
||||||
|
if (HYDROGEN_PAYMENT_LABELS[method]) return HYDROGEN_PAYMENT_LABELS[method];
|
||||||
|
var code = LEGACY_HYDROGEN_PAYMENT_MAP[method];
|
||||||
|
if (code) return HYDROGEN_PAYMENT_LABELS[code] || '-';
|
||||||
|
return String(method);
|
||||||
|
}
|
||||||
|
|
||||||
export var MILEAGE_PERIOD_LABELS = {
|
export var MILEAGE_PERIOD_LABELS = {
|
||||||
month: '每月',
|
month: '每月',
|
||||||
quarter: '每季度',
|
quarter: '每季度',
|
||||||
year: '每年度',
|
year: '每年度',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 子表里程要求周期标签:每月 / 每季度 / 每年度 */
|
||||||
|
export var CONTRACT_MILEAGE_PERIOD_TAG_LABELS = {
|
||||||
|
month: '每月',
|
||||||
|
quarter: '每季度',
|
||||||
|
year: '每年度',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getContractMileagePeriodTag(period) {
|
||||||
|
return CONTRACT_MILEAGE_PERIOD_TAG_LABELS[period] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatContractMileageTargetKm(km) {
|
||||||
|
if (km == null || km === '') return '-';
|
||||||
|
var num = Number(km);
|
||||||
|
if (!Number.isFinite(num)) return String(km);
|
||||||
|
return num.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 0 }) + ' km';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveContractVehicleMileageSource(vehicle) {
|
||||||
|
if (!vehicle) return '人工';
|
||||||
|
if (vehicle.mileageSource) return vehicle.mileageSource;
|
||||||
|
if (vehicle.telematicsLinked === true || vehicle.onlineStatus === '在线') return '车机';
|
||||||
|
if (vehicle.telematicsLinked === false && vehicle.gpsTime) return 'GPS';
|
||||||
|
if (vehicle.gpsTime) return 'GPS';
|
||||||
|
return '人工';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContractVehicleMileageSourceTagClass(source) {
|
||||||
|
if (source === '车机') return 'vm-tag vm-tag-teal';
|
||||||
|
if (source === 'GPS') return 'vm-tag vm-tag-gps';
|
||||||
|
return 'vm-tag vm-tag-manual';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeContractVehicleRemainingMileage(record, vehicle) {
|
||||||
|
if (!record || !record.hasMinimumMileage) return null;
|
||||||
|
var target = Number(record.mileageTargetKm);
|
||||||
|
if (!Number.isFinite(target)) return null;
|
||||||
|
var progress = Number(vehicle && vehicle.mileageProgress);
|
||||||
|
if (!Number.isFinite(progress)) progress = 0;
|
||||||
|
return Math.max(0, Math.round(target * (100 - progress) / 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatContractVehicleRemainingMileage(km) {
|
||||||
|
if (km == null) return '—';
|
||||||
|
return '剩余 ' + Number(km).toLocaleString('zh-CN') + ' km';
|
||||||
|
}
|
||||||
|
|
||||||
|
var CONTRACT_TYPE_FULL_NAME_FALLBACK = {
|
||||||
|
'正式合同': '现代18吨正式合同',
|
||||||
|
'试用合同': '现代18吨试用合同',
|
||||||
|
};
|
||||||
|
|
||||||
|
var RETURN_SETTLEMENT_STATUSES = ['待提交', '审批中', '已结算'];
|
||||||
|
|
||||||
|
export function parseContractDateOnly(raw) {
|
||||||
|
if (raw == null || raw === '' || raw === '-') return null;
|
||||||
|
var text = String(raw).trim().slice(0, 10);
|
||||||
|
var parts = text.split('-');
|
||||||
|
if (parts.length !== 3) return null;
|
||||||
|
var date = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
|
||||||
|
return Number.isNaN(date.getTime()) ? null : date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatContractPeriodStartDate(date) {
|
||||||
|
if (!date) return '—';
|
||||||
|
var y = date.getFullYear();
|
||||||
|
var m = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
var d = String(date.getDate()).padStart(2, '0');
|
||||||
|
return y + '-' + m + '-' + d;
|
||||||
|
}
|
||||||
|
|
||||||
|
function daysBetween(startDate, endDate) {
|
||||||
|
var ms = endDate.getTime() - startDate.getTime();
|
||||||
|
return Math.max(0, Math.floor(ms / 86400000));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeCurrentMileagePeriodStart(deliveryRaw, period, referenceDate) {
|
||||||
|
var delivery = parseContractDateOnly(deliveryRaw);
|
||||||
|
var ref = referenceDate instanceof Date ? referenceDate : (parseContractDateOnly(referenceDate) || new Date());
|
||||||
|
if (!delivery) return null;
|
||||||
|
if (period === 'month') {
|
||||||
|
var monthStart = new Date(ref.getFullYear(), ref.getMonth(), delivery.getDate());
|
||||||
|
if (monthStart > ref) monthStart.setMonth(monthStart.getMonth() - 1);
|
||||||
|
if (monthStart < delivery) return new Date(delivery.getTime());
|
||||||
|
return monthStart;
|
||||||
|
}
|
||||||
|
if (period === 'quarter') {
|
||||||
|
var quarterStart = new Date(delivery.getTime());
|
||||||
|
while (true) {
|
||||||
|
var nextQuarter = new Date(quarterStart.getFullYear(), quarterStart.getMonth() + 3, quarterStart.getDate());
|
||||||
|
if (nextQuarter > ref) return quarterStart;
|
||||||
|
quarterStart = nextQuarter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (period === 'year') {
|
||||||
|
var yearStart = new Date(ref.getFullYear(), delivery.getMonth(), delivery.getDate());
|
||||||
|
if (yearStart > ref) yearStart.setFullYear(yearStart.getFullYear() - 1);
|
||||||
|
if (yearStart < delivery) return new Date(delivery.getTime());
|
||||||
|
return yearStart;
|
||||||
|
}
|
||||||
|
return new Date(delivery.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function estimatePeriodStartMileage(deliveryRaw, periodStart, deliveryMileage, currentMileage) {
|
||||||
|
var delivery = parseContractDateOnly(deliveryRaw);
|
||||||
|
var start = periodStart instanceof Date ? periodStart : parseContractDateOnly(periodStart);
|
||||||
|
var deliveryMile = Number(deliveryMileage);
|
||||||
|
var currentMile = Number(currentMileage);
|
||||||
|
if (!Number.isFinite(deliveryMile)) deliveryMile = 0;
|
||||||
|
if (!Number.isFinite(currentMile)) currentMile = deliveryMile;
|
||||||
|
if (!delivery || !start) return deliveryMile;
|
||||||
|
if (start.getTime() <= delivery.getTime()) return deliveryMile;
|
||||||
|
var ref = new Date();
|
||||||
|
var totalDays = Math.max(1, daysBetween(delivery, ref));
|
||||||
|
var periodDays = Math.max(0, daysBetween(start, ref));
|
||||||
|
var driven = Math.max(0, currentMile - deliveryMile);
|
||||||
|
var ratioBeforePeriod = Math.max(0, Math.min(1, 1 - periodDays / totalDays));
|
||||||
|
var startMileage = deliveryMile + Math.round(driven * ratioBeforePeriod);
|
||||||
|
return Math.min(currentMile, Math.max(deliveryMile, startMileage));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inferContractTemplateId(record) {
|
||||||
|
if (record && record.contractTemplateId) return record.contractTemplateId;
|
||||||
|
var contractType = record && record.contractType;
|
||||||
|
if (contractType === '试用合同') {
|
||||||
|
var vehicles = record.vehicles || [];
|
||||||
|
var has18t = vehicles.some(function (vehicle) {
|
||||||
|
var text = String(vehicle.model || vehicle.vehicleType || '');
|
||||||
|
return /18/.test(text);
|
||||||
|
});
|
||||||
|
return has18t ? 'doc-5' : 'doc-4';
|
||||||
|
}
|
||||||
|
if (contractType === '正式合同') return 'doc-2';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveContractTemplateTypeLabel(record) {
|
||||||
|
if (!record) return '—';
|
||||||
|
if (record.contractTemplateTypeLabel) return record.contractTemplateTypeLabel;
|
||||||
|
var templateId = inferContractTemplateId(record);
|
||||||
|
if (templateId) {
|
||||||
|
var option = getContractTemplateOption(templateId);
|
||||||
|
if (option && option.contractTypeLabel) return option.contractTypeLabel;
|
||||||
|
}
|
||||||
|
var shortType = record.contractType || '';
|
||||||
|
if (CONTRACT_TYPE_FULL_NAME_FALLBACK[shortType]) return CONTRACT_TYPE_FULL_NAME_FALLBACK[shortType];
|
||||||
|
return getContractTypeLabel(shortType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReturnSettlementStatusTone(status) {
|
||||||
|
if (status === '已结算') return 'green';
|
||||||
|
if (status === '审批中') return 'amber';
|
||||||
|
return 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
export function formatMileageSummary(record) {
|
export function formatMileageSummary(record) {
|
||||||
if (!record.hasMinimumMileage) return '无最低里程要求';
|
if (!record.hasMinimumMileage) return '无最低里程要求';
|
||||||
var period = MILEAGE_PERIOD_LABELS[record.mileagePeriod] || '';
|
var period = MILEAGE_PERIOD_LABELS[record.mileagePeriod] || '';
|
||||||
@@ -27,11 +227,67 @@ export function formatMileageSummary(record) {
|
|||||||
return period + km + ' 公里';
|
return period + km + ' 公里';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 合同子表车辆行:无里程要求时显示「无里程要求」 */
|
||||||
|
export function formatContractMileageRequirement(record) {
|
||||||
|
if (!record || !record.hasMinimumMileage) return '无里程要求';
|
||||||
|
var period = MILEAGE_PERIOD_LABELS[record.mileagePeriod] || '';
|
||||||
|
var km = record.mileageTargetKm != null ? record.mileageTargetKm : '-';
|
||||||
|
return period + km + ' 公里';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 与车辆管理 formatMileage 一致:车机在线且空值时展示 0 km */
|
||||||
|
export function formatContractVehicleCurrentMileage(mile, vehicle, emptyFallback) {
|
||||||
|
var empty = emptyFallback != null ? emptyFallback : '—';
|
||||||
|
var source = resolveContractVehicleMileageSource(vehicle);
|
||||||
|
var forceZero = source === '车机' && vehicle && vehicle.onlineStatus === '在线';
|
||||||
|
if (mile == null || mile === '' || mile === '-') {
|
||||||
|
return forceZero ? '0 km' : empty;
|
||||||
|
}
|
||||||
|
var num = Number(mile);
|
||||||
|
if (!Number.isFinite(num)) return String(mile);
|
||||||
|
return num.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }) + ' km';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isContractVehicleMileageOnline(vehicle) {
|
||||||
|
return vehicle && vehicle.onlineStatus === '在线';
|
||||||
|
}
|
||||||
|
|
||||||
export function formatDeliveryRegion(region) {
|
export function formatDeliveryRegion(region) {
|
||||||
if (!region || !region.length) return '-';
|
if (!region || !region.length) return '-';
|
||||||
return region.join(' / ');
|
return region.join(' / ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var LEGACY_CONTRACT_APPROVAL_TYPE_MAP = {
|
||||||
|
'标准合同审批': '标准合同',
|
||||||
|
'非标准合同审批': '非标准合同',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatContractApprovalTypeLabel(value) {
|
||||||
|
if (!value) return '-';
|
||||||
|
return LEGACY_CONTRACT_APPROVAL_TYPE_MAP[value] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var SAMPLE_AUTHORIZED_DELEGATES = [
|
||||||
|
{ name: '王明', contact: '13900139001', idNumber: '330102199001011234' },
|
||||||
|
{ name: '李华', contact: '13900139002', idNumber: '330102199002022345' },
|
||||||
|
{ name: '赵强', contact: '13900139003', idNumber: '330102199003033456' },
|
||||||
|
{ name: '陈静', contact: '13900139004', idNumber: '330102199004044567' },
|
||||||
|
{ name: '周磊', contact: '13900139005', idNumber: '330102199005055678' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getAuthorizedDelegates(record) {
|
||||||
|
if (record.authorizedDelegates && record.authorizedDelegates.length) {
|
||||||
|
return record.authorizedDelegates;
|
||||||
|
}
|
||||||
|
var count = record.delegateCount != null ? record.delegateCount : 0;
|
||||||
|
if (count <= 0) return [];
|
||||||
|
var list = [];
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
list.push(SAMPLE_AUTHORIZED_DELEGATES[i % SAMPLE_AUTHORIZED_DELEGATES.length]);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
export var LEASE_CONTRACT_LIST_RECORDS = [
|
export var LEASE_CONTRACT_LIST_RECORDS = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
@@ -49,11 +305,19 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务1部',
|
businessDept: '业务1部',
|
||||||
businessOwner: '张经理',
|
businessOwner: '张经理',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '标准合同审批',
|
contractApprovalType: '标准合同',
|
||||||
paymentPeriod: '1个月',
|
paymentPeriod: 1,
|
||||||
hydrogenPaymentMethod: '自行解决',
|
hydrogenPaymentMethod: 'self',
|
||||||
deliveryRegion: ['浙江省', '嘉兴市'],
|
deliveryRegion: ['浙江省', '嘉兴市'],
|
||||||
deliveryDate: '2025-03-01',
|
deliveryDate: '2025-03-01',
|
||||||
|
deliveryDateChangeLogs: [
|
||||||
|
{
|
||||||
|
beforeDate: '2025-02-28',
|
||||||
|
afterDate: '2025-03-01',
|
||||||
|
operatorName: '张经理',
|
||||||
|
operateTime: '2025-02-10 16:20:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
insuredVehicleCount: 2,
|
insuredVehicleCount: 2,
|
||||||
thirdPartyLiabilityMillion: 200,
|
thirdPartyLiabilityMillion: 200,
|
||||||
hasMinimumMileage: true,
|
hasMinimumMileage: true,
|
||||||
@@ -85,9 +349,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务2部',
|
businessDept: '业务2部',
|
||||||
businessOwner: '李专员',
|
businessOwner: '李专员',
|
||||||
contractType: '试用合同',
|
contractType: '试用合同',
|
||||||
contractApprovalType: '非标准合同审批',
|
contractApprovalType: '非标准合同',
|
||||||
paymentPeriod: '3个月',
|
paymentPeriod: 3,
|
||||||
hydrogenPaymentMethod: '预付款',
|
hydrogenPaymentMethod: 'prepay',
|
||||||
deliveryRegion: ['上海市', '上海市'],
|
deliveryRegion: ['上海市', '上海市'],
|
||||||
deliveryDate: '2025-02-20',
|
deliveryDate: '2025-02-20',
|
||||||
insuredVehicleCount: 1,
|
insuredVehicleCount: 1,
|
||||||
@@ -121,9 +385,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务3部',
|
businessDept: '业务3部',
|
||||||
businessOwner: '王专员',
|
businessOwner: '王专员',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '标准合同审批',
|
contractApprovalType: '标准合同',
|
||||||
paymentPeriod: '1个月',
|
paymentPeriod: 1,
|
||||||
hydrogenPaymentMethod: '自行解决',
|
hydrogenPaymentMethod: 'self',
|
||||||
deliveryRegion: ['浙江省', '杭州市'],
|
deliveryRegion: ['浙江省', '杭州市'],
|
||||||
deliveryDate: '2025-03-10',
|
deliveryDate: '2025-03-10',
|
||||||
insuredVehicleCount: 1,
|
insuredVehicleCount: 1,
|
||||||
@@ -141,6 +405,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
updateTime: '-',
|
updateTime: '-',
|
||||||
remark: '-',
|
remark: '-',
|
||||||
legalStampedContractUploaded: undefined,
|
legalStampedContractUploaded: undefined,
|
||||||
|
approvalFlowNodes: [
|
||||||
|
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['王专员'] },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
@@ -158,9 +425,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务1部',
|
businessDept: '业务1部',
|
||||||
businessOwner: '张经理',
|
businessOwner: '张经理',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '非标准合同审批',
|
contractApprovalType: '非标准合同',
|
||||||
paymentPeriod: '6个月',
|
paymentPeriod: 6,
|
||||||
hydrogenPaymentMethod: '预付款',
|
hydrogenPaymentMethod: 'prepay',
|
||||||
deliveryRegion: ['浙江省', '宁波市'],
|
deliveryRegion: ['浙江省', '宁波市'],
|
||||||
deliveryDate: '2025-04-01',
|
deliveryDate: '2025-04-01',
|
||||||
insuredVehicleCount: 2,
|
insuredVehicleCount: 2,
|
||||||
@@ -179,7 +446,7 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
remark: '-',
|
remark: '-',
|
||||||
legalStampedContractUploaded: undefined,
|
legalStampedContractUploaded: undefined,
|
||||||
approvalFlowNodes: [
|
approvalFlowNodes: [
|
||||||
{ nodeTitle: '业务服务主管审批', result: 'passed', operatorName: '姚守涛', operatorTime: '2026-04-29 17:57:15' },
|
{ nodeTitle: '业务服务主管审批', result: 'passed', operatorName: '姚守涛', operatorTime: '2026-04-29 17:57:15', comment: '租赁订单车辆配置与报价一致,同意进入下一节点。' },
|
||||||
{ nodeTitle: '发起审批', result: 'passed', operatorName: '超级用户', operatorTime: '2026-04-28 17:44:45' },
|
{ nodeTitle: '发起审批', result: 'passed', operatorName: '超级用户', operatorTime: '2026-04-28 17:44:45' },
|
||||||
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['超级用户', '金可鹏'] },
|
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['超级用户', '金可鹏'] },
|
||||||
],
|
],
|
||||||
@@ -199,9 +466,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务2部',
|
businessDept: '业务2部',
|
||||||
businessOwner: '李专员',
|
businessOwner: '李专员',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '标准合同审批',
|
contractApprovalType: '标准合同',
|
||||||
paymentPeriod: '3个月',
|
paymentPeriod: 3,
|
||||||
hydrogenPaymentMethod: '自行解决',
|
hydrogenPaymentMethod: 'self',
|
||||||
deliveryRegion: ['江苏省', '苏州市'],
|
deliveryRegion: ['江苏省', '苏州市'],
|
||||||
deliveryDate: '2025-03-15',
|
deliveryDate: '2025-03-15',
|
||||||
insuredVehicleCount: 1,
|
insuredVehicleCount: 1,
|
||||||
@@ -220,8 +487,8 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
remark: '变更车辆数量',
|
remark: '变更车辆数量',
|
||||||
legalStampedContractUploaded: undefined,
|
legalStampedContractUploaded: undefined,
|
||||||
approvalFlowNodes: [
|
approvalFlowNodes: [
|
||||||
{ nodeTitle: '法务审核', result: 'passed', operatorName: '李法务', operatorTime: '2025-02-21 18:00:00' },
|
{ nodeTitle: '法务审核', result: 'passed', operatorName: '李法务', operatorTime: '2025-02-21 18:00:00', comment: '变更后条款已复核,未发现新增合规风险。' },
|
||||||
{ nodeTitle: '发起审批', result: 'passed', operatorName: '李专员', operatorTime: '2025-02-22 10:00:00' },
|
{ nodeTitle: '发起审批', result: 'passed', operatorName: '李专员', operatorTime: '2025-02-22 10:00:00', comment: '因客户增购 1 台车辆发起变更审批。' },
|
||||||
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['张经理', '赵总监'] },
|
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['张经理', '赵总监'] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -231,9 +498,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
projectName: '南京氢能示范项目',
|
projectName: '南京氢能示范项目',
|
||||||
vehicleCount: 3,
|
vehicleCount: 3,
|
||||||
vehicles: [
|
vehicles: [
|
||||||
{ vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', plateNo: '苏A60006', actualDelivery: '2025-01-20 08:00', deliveryPerson: '吴运维' },
|
{ vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', plateNo: '苏A60006', actualDelivery: '2025-01-20 08:00', deliveryPerson: '吴运维', leasePeriodMonths: 24 },
|
||||||
{ vehicleType: '18吨厢式货车', brand: '现代', model: '18吨氢燃料电池车', plateNo: '苏A60007', actualDelivery: '2025-01-21 10:00', deliveryPerson: '郑运维' },
|
{ vehicleType: '18吨厢式货车', brand: '现代', model: '18吨氢燃料电池车', plateNo: '苏A60007', actualDelivery: '2025-01-21 10:00', deliveryPerson: '郑运维', leasePeriodMonths: 24 },
|
||||||
{ vehicleType: '牵引车头', brand: '飞驰', model: '集卡头', plateNo: '苏A60008', actualDelivery: '2025-01-22 14:00', deliveryPerson: '冯运维' },
|
{ vehicleType: '牵引车头', brand: '飞驰', model: '集卡头', plateNo: '苏A60008', actualDelivery: '2025-01-22 14:00', deliveryPerson: '冯运维', leasePeriodMonths: 24 },
|
||||||
],
|
],
|
||||||
approvalStatus: '审批通过',
|
approvalStatus: '审批通过',
|
||||||
contractStatus: '合同进行中',
|
contractStatus: '合同进行中',
|
||||||
@@ -242,9 +509,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务3部',
|
businessDept: '业务3部',
|
||||||
businessOwner: '王专员',
|
businessOwner: '王专员',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '标准合同审批',
|
contractApprovalType: '标准合同',
|
||||||
paymentPeriod: '12个月',
|
paymentPeriod: 12,
|
||||||
hydrogenPaymentMethod: '预付款',
|
hydrogenPaymentMethod: 'prepay',
|
||||||
deliveryRegion: ['江苏省', '南京市'],
|
deliveryRegion: ['江苏省', '南京市'],
|
||||||
deliveryDate: '2025-01-15',
|
deliveryDate: '2025-01-15',
|
||||||
insuredVehicleCount: 3,
|
insuredVehicleCount: 3,
|
||||||
@@ -262,6 +529,12 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
updateTime: '2025-01-25 11:00',
|
updateTime: '2025-01-25 11:00',
|
||||||
remark: '-',
|
remark: '-',
|
||||||
legalStampedContractUploaded: true,
|
legalStampedContractUploaded: true,
|
||||||
|
approvalFlowNodes: [
|
||||||
|
{ nodeTitle: '法务审核', result: 'passed', operatorName: '陈法务', operatorTime: '2025-01-22 16:40:00', comment: '标准合同审批,正文无红线条款改动。' },
|
||||||
|
{ nodeTitle: '业务负责人审批', result: 'passed', operatorName: '赵总监', operatorTime: '2025-01-20 11:05:00', comment: '客户为 A 级客户,三车批量租赁方案可行,同意通过。' },
|
||||||
|
{ nodeTitle: '业务服务主管审批', result: 'passed', operatorName: '刘主管', operatorTime: '2025-01-18 15:20:00', comment: '氢费预付条款已与客户确认。' },
|
||||||
|
{ nodeTitle: '发起审批', result: 'passed', operatorName: '王专员', operatorTime: '2025-01-16 09:30:00' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '7',
|
id: '7',
|
||||||
@@ -269,7 +542,7 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
projectName: '无锡试用租赁项目',
|
projectName: '无锡试用租赁项目',
|
||||||
vehicleCount: 1,
|
vehicleCount: 1,
|
||||||
vehicles: [
|
vehicles: [
|
||||||
{ vehicleType: '小客车', brand: '宇通', model: '氢燃料电池客车', plateNo: '苏B70007', actualDelivery: '2025-02-01 09:30', deliveryPerson: '陈运维' },
|
{ vehicleType: '小客车', brand: '宇通', model: '氢燃料电池客车', plateNo: '苏B70007', actualDelivery: '2025-02-01 09:30', deliveryPerson: '陈运维', leasePeriodMonths: 3 },
|
||||||
],
|
],
|
||||||
approvalStatus: '审批通过',
|
approvalStatus: '审批通过',
|
||||||
contractStatus: '合同进行中',
|
contractStatus: '合同进行中',
|
||||||
@@ -278,9 +551,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务1部',
|
businessDept: '业务1部',
|
||||||
businessOwner: '张经理',
|
businessOwner: '张经理',
|
||||||
contractType: '试用合同',
|
contractType: '试用合同',
|
||||||
contractApprovalType: '非标准合同审批',
|
contractApprovalType: '非标准合同',
|
||||||
paymentPeriod: '1个月',
|
paymentPeriod: 1,
|
||||||
hydrogenPaymentMethod: '自行解决',
|
hydrogenPaymentMethod: 'self',
|
||||||
deliveryRegion: ['江苏省', '无锡市'],
|
deliveryRegion: ['江苏省', '无锡市'],
|
||||||
deliveryDate: '2025-02-05',
|
deliveryDate: '2025-02-05',
|
||||||
insuredVehicleCount: 1,
|
insuredVehicleCount: 1,
|
||||||
@@ -298,6 +571,11 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
updateTime: '-',
|
updateTime: '-',
|
||||||
remark: '试用 3 个月',
|
remark: '试用 3 个月',
|
||||||
legalStampedContractUploaded: false,
|
legalStampedContractUploaded: false,
|
||||||
|
approvalFlowNodes: [
|
||||||
|
{ nodeTitle: '法务审核', result: 'passed', operatorName: '周法务', operatorTime: '2025-01-30 17:10:00', comment: '试用合同条款已按非标准审批要求补充说明。' },
|
||||||
|
{ nodeTitle: '业务负责人审批', result: 'passed', operatorName: '张经理', operatorTime: '2025-01-29 10:22:00', comment: '试用期 3 个月,到期后转正式合同。' },
|
||||||
|
{ nodeTitle: '发起审批', result: 'passed', operatorName: '张经理', operatorTime: '2025-01-28 10:15:00' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '8',
|
id: '8',
|
||||||
@@ -315,9 +593,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务2部',
|
businessDept: '业务2部',
|
||||||
businessOwner: '李专员',
|
businessOwner: '李专员',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '标准合同审批',
|
contractApprovalType: '标准合同',
|
||||||
paymentPeriod: '3个月',
|
paymentPeriod: 3,
|
||||||
hydrogenPaymentMethod: '预付款',
|
hydrogenPaymentMethod: 'prepay',
|
||||||
deliveryRegion: ['江苏省', '常州市'],
|
deliveryRegion: ['江苏省', '常州市'],
|
||||||
deliveryDate: '2025-04-20',
|
deliveryDate: '2025-04-20',
|
||||||
insuredVehicleCount: 2,
|
insuredVehicleCount: 2,
|
||||||
@@ -335,6 +613,10 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
updateTime: '2025-02-23 09:00',
|
updateTime: '2025-02-23 09:00',
|
||||||
remark: '驳回原因:费用条款需调整',
|
remark: '驳回原因:费用条款需调整',
|
||||||
legalStampedContractUploaded: undefined,
|
legalStampedContractUploaded: undefined,
|
||||||
|
approvalFlowNodes: [
|
||||||
|
{ nodeTitle: '发起审批', result: 'passed', operatorName: '李专员', operatorTime: '2025-02-20 14:30:00' },
|
||||||
|
{ nodeTitle: '业务负责人审批', result: 'rejected', operatorName: '张经理', operatorTime: '2025-02-23 09:00:00', comment: '预付金额与商务报价不一致,请按最新报价单调整费用条款后重新提交。' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '9',
|
id: '9',
|
||||||
@@ -342,8 +624,8 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
projectName: '南通去年到期项目',
|
projectName: '南通去年到期项目',
|
||||||
vehicleCount: 2,
|
vehicleCount: 2,
|
||||||
vehicles: [
|
vehicles: [
|
||||||
{ vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '9.6米氢燃料电池车', plateNo: '苏F90009', actualDelivery: '2024-03-01 09:00', deliveryPerson: '褚运维' },
|
{ vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '9.6米氢燃料电池车', plateNo: '苏F90009', actualDelivery: '2024-03-01 09:00', deliveryPerson: '褚运维', leasePeriodMonths: 12 },
|
||||||
{ vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', plateNo: '苏F90010', actualDelivery: '2024-03-02 10:00', deliveryPerson: '卫运维' },
|
{ vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', plateNo: '苏F90010', actualDelivery: '2024-03-02 10:00', deliveryPerson: '卫运维', leasePeriodMonths: 12 },
|
||||||
],
|
],
|
||||||
approvalStatus: '审批通过',
|
approvalStatus: '审批通过',
|
||||||
contractStatus: '到期合同',
|
contractStatus: '到期合同',
|
||||||
@@ -352,9 +634,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务3部',
|
businessDept: '业务3部',
|
||||||
businessOwner: '王专员',
|
businessOwner: '王专员',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '标准合同审批',
|
contractApprovalType: '标准合同',
|
||||||
paymentPeriod: '6个月',
|
paymentPeriod: 6,
|
||||||
hydrogenPaymentMethod: '自行解决',
|
hydrogenPaymentMethod: 'self',
|
||||||
deliveryRegion: ['江苏省', '南通市'],
|
deliveryRegion: ['江苏省', '南通市'],
|
||||||
deliveryDate: '2024-02-15',
|
deliveryDate: '2024-02-15',
|
||||||
insuredVehicleCount: 2,
|
insuredVehicleCount: 2,
|
||||||
@@ -372,6 +654,11 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
updateTime: '2024-12-20 16:00',
|
updateTime: '2024-12-20 16:00',
|
||||||
remark: '已到期可续签',
|
remark: '已到期可续签',
|
||||||
legalStampedContractUploaded: true,
|
legalStampedContractUploaded: true,
|
||||||
|
approvalFlowNodes: [
|
||||||
|
{ nodeTitle: '法务审核', result: 'passed', operatorName: '陈法务', operatorTime: '2024-02-25 16:00:00', comment: '归档合同与系统版本一致。' },
|
||||||
|
{ nodeTitle: '业务负责人审批', result: 'passed', operatorName: '王专员', operatorTime: '2024-02-22 14:30:00', comment: '南通区域试点项目,同意按标准流程审批。' },
|
||||||
|
{ nodeTitle: '发起审批', result: 'passed', operatorName: '王专员', operatorTime: '2024-02-20 10:30:00' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '10',
|
id: '10',
|
||||||
@@ -388,9 +675,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
businessDept: '业务1部',
|
businessDept: '业务1部',
|
||||||
businessOwner: '张经理',
|
businessOwner: '张经理',
|
||||||
contractType: '正式合同',
|
contractType: '正式合同',
|
||||||
contractApprovalType: '非标准合同审批',
|
contractApprovalType: '非标准合同',
|
||||||
paymentPeriod: '1个月',
|
paymentPeriod: 1,
|
||||||
hydrogenPaymentMethod: '自行解决',
|
hydrogenPaymentMethod: 'self',
|
||||||
deliveryRegion: ['江苏省', '镇江市'],
|
deliveryRegion: ['江苏省', '镇江市'],
|
||||||
deliveryDate: '2024-05-10',
|
deliveryDate: '2024-05-10',
|
||||||
insuredVehicleCount: 1,
|
insuredVehicleCount: 1,
|
||||||
@@ -408,5 +695,322 @@ export var LEASE_CONTRACT_LIST_RECORDS = [
|
|||||||
updateTime: '2025-01-10 14:00',
|
updateTime: '2025-01-10 14:00',
|
||||||
remark: '-',
|
remark: '-',
|
||||||
legalStampedContractUploaded: true,
|
legalStampedContractUploaded: true,
|
||||||
|
approvalFlowNodes: [
|
||||||
|
{ nodeTitle: '业务负责人审批', result: 'passed', operatorName: '张经理', operatorTime: '2024-05-22 11:00:00', comment: '单车试用转正式合作,条款无异常。' },
|
||||||
|
{ nodeTitle: '发起审批', result: 'passed', operatorName: '张经理', operatorTime: '2024-05-20 09:10:00' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '11',
|
||||||
|
contractCode: 'HT-ZL-2025-011',
|
||||||
|
projectName: '温州撤回示例项目',
|
||||||
|
vehicleCount: 1,
|
||||||
|
vehicles: [
|
||||||
|
{ vehicleType: '4.5吨货车', brand: '跃进', model: '4.2米冷链车', plateNo: '-', actualDelivery: '-', deliveryPerson: '-' },
|
||||||
|
],
|
||||||
|
approvalStatus: '撤回',
|
||||||
|
contractStatus: '草稿',
|
||||||
|
customerName: '杭州某某租赁有限公司',
|
||||||
|
signingCompany: '嘉兴羚牛',
|
||||||
|
businessDept: '业务3部',
|
||||||
|
businessOwner: '王专员',
|
||||||
|
contractType: '正式合同',
|
||||||
|
contractApprovalType: '标准合同',
|
||||||
|
paymentPeriod: 1,
|
||||||
|
hydrogenPaymentMethod: 'self',
|
||||||
|
deliveryRegion: ['浙江省', '温州市'],
|
||||||
|
deliveryDate: '2025-05-01',
|
||||||
|
insuredVehicleCount: 1,
|
||||||
|
thirdPartyLiabilityMillion: 200,
|
||||||
|
hasMinimumMileage: false,
|
||||||
|
mileagePeriod: 'month',
|
||||||
|
mileageTargetKm: null,
|
||||||
|
delegateCount: 0,
|
||||||
|
contractEndDate: '2026-04-30',
|
||||||
|
contactName: '王五',
|
||||||
|
contactPhone: '13800138003',
|
||||||
|
creator: '王专员',
|
||||||
|
createTime: '2025-03-01 10:00',
|
||||||
|
updater: '王专员',
|
||||||
|
updateTime: '2025-03-02 15:00',
|
||||||
|
remark: '提交后撤回',
|
||||||
|
legalStampedContractUploaded: undefined,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function resolveSigningCompanyFullName(record) {
|
||||||
|
var lessorId = record.lessorId || SIGNING_COMPANY_LESSOR_IDS[record.signingCompany];
|
||||||
|
var lessor = getLessorCompanyById(lessorId || record.signingCompany);
|
||||||
|
return lessor && lessor.legalName ? lessor.legalName : (record.signingCompany || '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentApproverLabel(record) {
|
||||||
|
if (record.currentApprover) return record.currentApprover;
|
||||||
|
var nodes = record.approvalFlowNodes || [];
|
||||||
|
var pendingNode = null;
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
if (nodes[i].result === 'pending') {
|
||||||
|
pendingNode = nodes[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!pendingNode) return '';
|
||||||
|
if (pendingNode.pendingApprovers && pendingNode.pendingApprovers.length) {
|
||||||
|
return pendingNode.pendingApprovers.join('、');
|
||||||
|
}
|
||||||
|
return pendingNode.nodeTitle || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldHideCurrentApprover(approvalStatus) {
|
||||||
|
return approvalStatus === '审批通过'
|
||||||
|
|| approvalStatus === '审批驳回'
|
||||||
|
|| approvalStatus === '撤回';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatContractHandoverMileage(mile) {
|
||||||
|
if (mile == null || mile === '' || mile === '-') return '';
|
||||||
|
var num = Number(mile);
|
||||||
|
if (!Number.isFinite(num)) return String(mile);
|
||||||
|
return num.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }) + ' km';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatContractHandoverDateTimeMinute(raw) {
|
||||||
|
if (raw == null || raw === '' || raw === '-') return '';
|
||||||
|
var text = String(raw).trim().replace('T', ' ');
|
||||||
|
var match = text.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/);
|
||||||
|
if (match) return match[1] + ' ' + match[2];
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(text)) return text;
|
||||||
|
return text.slice(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
export var DELIVERY_DATE_TBD_LABEL = '暂未确定';
|
||||||
|
|
||||||
|
export function isDeliveryDateTbd(value) {
|
||||||
|
return value === DELIVERY_DATE_TBD_LABEL || value === 'TBD';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatContractDeliveryDateLabel(record) {
|
||||||
|
if (!record) return '-';
|
||||||
|
if (record.deliveryDateTbd || isDeliveryDateTbd(record.deliveryDate)) return DELIVERY_DATE_TBD_LABEL;
|
||||||
|
return record.deliveryDate || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMonthsToDate(date, months) {
|
||||||
|
var next = new Date(date.getTime());
|
||||||
|
next.setMonth(next.getMonth() + months);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVehicleLeasePeriodMonths(vehicle, record) {
|
||||||
|
if (vehicle && vehicle.leasePeriodMonths != null && vehicle.leasePeriodMonths !== '') {
|
||||||
|
return Number(vehicle.leasePeriodMonths);
|
||||||
|
}
|
||||||
|
if (record && record.defaultLeasePeriodMonths != null) {
|
||||||
|
return Number(record.defaultLeasePeriodMonths);
|
||||||
|
}
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isVehicleLeasePeriodExpired(vehicle, record, referenceDate) {
|
||||||
|
if (!isContractVehicleDelivered(vehicle)) return false;
|
||||||
|
var delivery = parseContractDateOnly(vehicle.deliveryTime || vehicle.actualDelivery);
|
||||||
|
if (!delivery) return false;
|
||||||
|
var months = getVehicleLeasePeriodMonths(vehicle, record);
|
||||||
|
if (!Number.isFinite(months) || months <= 0) return false;
|
||||||
|
var leaseEnd = addMonthsToDate(delivery, months);
|
||||||
|
leaseEnd.setHours(23, 59, 59, 999);
|
||||||
|
var ref = referenceDate || new Date();
|
||||||
|
return ref.getTime() > leaseEnd.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
var LEASE_STATUS_DYNAMIC_BASE = ['合同进行中', '到期合同'];
|
||||||
|
|
||||||
|
export function resolveContractStatusByVehicleLease(record) {
|
||||||
|
if (!record) return '-';
|
||||||
|
var baseStatus = record.contractStatus || '-';
|
||||||
|
if (LEASE_STATUS_DYNAMIC_BASE.indexOf(baseStatus) === -1) return baseStatus;
|
||||||
|
var vehicles = record.vehicles || [];
|
||||||
|
if (!vehicles.length) return baseStatus;
|
||||||
|
var allDelivered = vehicles.every(isContractVehicleDelivered);
|
||||||
|
if (!allDelivered) return baseStatus === '到期合同' ? '合同进行中' : baseStatus;
|
||||||
|
var allLeaseExpired = vehicles.every(function (vehicle) {
|
||||||
|
return isVehicleLeasePeriodExpired(vehicle, record);
|
||||||
|
});
|
||||||
|
if (allLeaseExpired) return '到期合同';
|
||||||
|
return '合同进行中';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isContractVehicleDelivered(vehicle) {
|
||||||
|
var time = vehicle.deliveryTime || vehicle.actualDelivery;
|
||||||
|
return Boolean(time && String(time).trim() && time !== '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isContractVehicleReturned(vehicle) {
|
||||||
|
var time = vehicle.returnTime;
|
||||||
|
return Boolean(time && String(time).trim() && time !== '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canContractVehicleReturn(vehicle) {
|
||||||
|
return isContractVehicleDelivered(vehicle) && !isContractVehicleReturned(vehicle);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeContractOverallMileageProgress(record) {
|
||||||
|
if (!record || !record.hasMinimumMileage) return null;
|
||||||
|
var vehicles = record.vehicles || [];
|
||||||
|
var total = 0;
|
||||||
|
var count = 0;
|
||||||
|
vehicles.forEach(function (vehicle) {
|
||||||
|
if (!isContractVehicleDelivered(vehicle)) return;
|
||||||
|
if (vehicle.mileageProgress == null) return;
|
||||||
|
total += Math.min(100, Math.max(0, Number(vehicle.mileageProgress) || 0));
|
||||||
|
count += 1;
|
||||||
|
});
|
||||||
|
if (count === 0) return 0;
|
||||||
|
return Math.round(total / count);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeContractMileageForecastStatus(record) {
|
||||||
|
if (!record || !record.hasMinimumMileage) return null;
|
||||||
|
var percent = record.overallMileageProgress;
|
||||||
|
if (percent == null) percent = computeContractOverallMileageProgress(record);
|
||||||
|
percent = Math.min(100, Math.max(0, Number(percent) || 0));
|
||||||
|
var vehicles = record.vehicles || [];
|
||||||
|
var hasRiskVehicle = false;
|
||||||
|
var hasUnableVehicle = false;
|
||||||
|
vehicles.forEach(function (vehicle) {
|
||||||
|
if (!isContractVehicleDelivered(vehicle)) return;
|
||||||
|
var progress = Math.min(100, Math.max(0, Number(vehicle.mileageProgress) || 0));
|
||||||
|
if (progress < 40) hasUnableVehicle = true;
|
||||||
|
else if (progress < 70) hasRiskVehicle = true;
|
||||||
|
});
|
||||||
|
if (percent < 45 || hasUnableVehicle) return '预计无法完成';
|
||||||
|
if (percent < 75 || hasRiskVehicle) return '存在完成风险';
|
||||||
|
return '预计可完成';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContractMileageForecastStatusTone(status) {
|
||||||
|
if (status === '预计可完成') return 'green';
|
||||||
|
if (status === '存在完成风险') return 'amber';
|
||||||
|
if (status === '预计无法完成') return 'red';
|
||||||
|
return 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enrichLeaseContractVehicle(vehicle, index, recordIndex, record) {
|
||||||
|
var delivered = vehicle.actualDelivery && String(vehicle.actualDelivery).trim() && vehicle.actualDelivery !== '-';
|
||||||
|
var seed = (recordIndex || 0) * 10 + (index || 0);
|
||||||
|
var demoDeliveryMiles = [25468, 6210, 31233, 48202, 12580];
|
||||||
|
var demoReturnMiles = [25372, 127012, 11578];
|
||||||
|
var demoCurrentMiles = [25468, 6210, 31233, 45822, 127012, 11578, 45195];
|
||||||
|
var demoReturnPersons = ['刘洋', '李娜', '吴磊'];
|
||||||
|
var hasExplicitReturn = vehicle.returnTime && String(vehicle.returnTime).trim() && vehicle.returnTime !== '-';
|
||||||
|
var hasMinMileage = record && record.hasMinimumMileage;
|
||||||
|
var progress = vehicle.mileageProgress;
|
||||||
|
if (!hasMinMileage) {
|
||||||
|
progress = null;
|
||||||
|
} else if (progress == null) {
|
||||||
|
if (!delivered) progress = 0;
|
||||||
|
else progress = [38, 56, 72, 91][seed % 4];
|
||||||
|
}
|
||||||
|
var hasReturn = hasExplicitReturn || (progress >= 90 && delivered);
|
||||||
|
var deliveryTime = vehicle.deliveryTime || (delivered ? vehicle.actualDelivery : null);
|
||||||
|
var deliveryMileage = vehicle.deliveryMileage != null && vehicle.deliveryMileage !== ''
|
||||||
|
? vehicle.deliveryMileage
|
||||||
|
: (delivered ? demoDeliveryMiles[seed % demoDeliveryMiles.length] : null);
|
||||||
|
var deliveryPerson = delivered
|
||||||
|
? (vehicle.deliveryPerson && vehicle.deliveryPerson !== '-' ? vehicle.deliveryPerson : '张运维')
|
||||||
|
: null;
|
||||||
|
var returnTime = hasReturn
|
||||||
|
? (hasExplicitReturn ? vehicle.returnTime : '2025-12-01 17:00')
|
||||||
|
: null;
|
||||||
|
var returnMileage = hasReturn
|
||||||
|
? (vehicle.returnMileage != null && vehicle.returnMileage !== '' ? vehicle.returnMileage : demoReturnMiles[seed % demoReturnMiles.length])
|
||||||
|
: null;
|
||||||
|
var returnPerson = hasReturn
|
||||||
|
? (vehicle.returnPerson && vehicle.returnPerson !== '-' ? vehicle.returnPerson : demoReturnPersons[seed % demoReturnPersons.length])
|
||||||
|
: null;
|
||||||
|
var currentMileage = vehicle.currentMileage != null && vehicle.currentMileage !== ''
|
||||||
|
? vehicle.currentMileage
|
||||||
|
: (delivered ? demoCurrentMiles[seed % demoCurrentMiles.length] : '-');
|
||||||
|
var mileageSources = ['车机', 'GPS', '人工'];
|
||||||
|
var mileageSource = vehicle.mileageSource || mileageSources[seed % mileageSources.length];
|
||||||
|
var telematicsLinked = mileageSource === '车机';
|
||||||
|
var gpsTime = mileageSource === 'GPS'
|
||||||
|
? (vehicle.gpsTime || '2026-06-28 14:32:15')
|
||||||
|
: (vehicle.gpsTime || null);
|
||||||
|
var onlineStatus = vehicle.onlineStatus
|
||||||
|
|| (mileageSource === '车机' ? '在线' : '离线');
|
||||||
|
var pickupPaymentPaid = vehicle.pickupPaymentPaid;
|
||||||
|
if (pickupPaymentPaid == null) {
|
||||||
|
pickupPaymentPaid = delivered ? seed % 4 !== 1 : false;
|
||||||
|
}
|
||||||
|
var remainingMileage = computeContractVehicleRemainingMileage(record, { mileageProgress: progress });
|
||||||
|
var deliveryRaw = deliveryTime || vehicle.actualDelivery;
|
||||||
|
var periodStartDate = null;
|
||||||
|
var periodStartMileage = null;
|
||||||
|
if (delivered && hasMinMileage && record && record.mileagePeriod) {
|
||||||
|
periodStartDate = computeCurrentMileagePeriodStart(deliveryRaw, record.mileagePeriod);
|
||||||
|
periodStartMileage = estimatePeriodStartMileage(
|
||||||
|
deliveryRaw,
|
||||||
|
periodStartDate,
|
||||||
|
deliveryMileage,
|
||||||
|
currentMileage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var returnSettlementStatus = vehicle.returnSettlementStatus;
|
||||||
|
if (hasReturn && !returnSettlementStatus) {
|
||||||
|
returnSettlementStatus = RETURN_SETTLEMENT_STATUSES[seed % RETURN_SETTLEMENT_STATUSES.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, vehicle, {
|
||||||
|
vin: vehicle.vin || (vehicle.plateNo && vehicle.plateNo !== '-'
|
||||||
|
? 'LFWNKVPH8K1' + String(10000 + seed).slice(-5)
|
||||||
|
: '-'),
|
||||||
|
deliveryTime: deliveryTime,
|
||||||
|
deliveryMileage: deliveryMileage,
|
||||||
|
deliveryPerson: deliveryPerson,
|
||||||
|
returnTime: returnTime,
|
||||||
|
returnMileage: returnMileage,
|
||||||
|
returnPerson: returnPerson,
|
||||||
|
mileageProgress: progress,
|
||||||
|
currentMileage: currentMileage,
|
||||||
|
onlineStatus: onlineStatus,
|
||||||
|
mileageSource: mileageSource,
|
||||||
|
telematicsLinked: telematicsLinked,
|
||||||
|
gpsTime: gpsTime,
|
||||||
|
pickupPaymentPaid: pickupPaymentPaid,
|
||||||
|
remainingMileage: remainingMileage,
|
||||||
|
periodStartDate: periodStartDate,
|
||||||
|
periodStartMileage: periodStartMileage,
|
||||||
|
returnSettlementStatus: hasReturn ? returnSettlementStatus : null,
|
||||||
|
leasePeriodMonths: vehicle.leasePeriodMonths != null
|
||||||
|
? vehicle.leasePeriodMonths
|
||||||
|
: getVehicleLeasePeriodMonths(vehicle, record),
|
||||||
|
plannedDeliveryDate: vehicle.plannedDeliveryDate != null
|
||||||
|
? vehicle.plannedDeliveryDate
|
||||||
|
: (delivered ? null : (record && record.deliveryDate) || null),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enrichLeaseContractRecord(record, recordIndex) {
|
||||||
|
var lessorId = record.lessorId || SIGNING_COMPANY_LESSOR_IDS[record.signingCompany];
|
||||||
|
var templateId = inferContractTemplateId(record);
|
||||||
|
var enriched = Object.assign({}, record, {
|
||||||
|
lessorId: lessorId,
|
||||||
|
contractTemplateId: record.contractTemplateId || templateId,
|
||||||
|
signingCompanyFullName: resolveSigningCompanyFullName(Object.assign({}, record, { lessorId: lessorId })),
|
||||||
|
vehicles: (record.vehicles || []).map(function (vehicle, index) {
|
||||||
|
return enrichLeaseContractVehicle(vehicle, index, recordIndex, record);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
enriched.contractTemplateTypeLabel = resolveContractTemplateTypeLabel(enriched);
|
||||||
|
enriched.overallMileageProgress = computeContractOverallMileageProgress(enriched);
|
||||||
|
enriched.overallMileageForecastStatus = computeContractMileageForecastStatus(enriched);
|
||||||
|
enriched.contractStatus = resolveContractStatusByVehicleLease(enriched);
|
||||||
|
return enriched;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enrichLeaseContractListRecords(records) {
|
||||||
|
return (records || []).map(function (record, index) {
|
||||||
|
return enrichLeaseContractRecord(record, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* 租赁合同预览 HTML 拼装:占位符替换 + 2.6.1 开票信息 + 里程标准 + 客户资质附件
|
* 租赁合同预览 HTML 拼装:占位符替换 + 2.6.1 开票信息 + 里程标准 + 客户资质附件
|
||||||
*/
|
*/
|
||||||
import { V266_LEASE_DOCUMENT_HTML } from '../contract-template-management/v266-lease-document.js';
|
import { getContractTemplateBaseHtml } from '../contract-template-management/contract-template-catalog.js';
|
||||||
|
import { getTemplateSectionVehicleBindings } from '../contract-template-management/contract-template-store.js';
|
||||||
|
import { applyAttachmentSectionVehicleFilter } from '../contract-template-management/contract-template-section-filter.js';
|
||||||
import {
|
import {
|
||||||
applyTemplateVars,
|
applyTemplateVars,
|
||||||
buildLeasePreviewVars,
|
buildLeasePreviewVars,
|
||||||
@@ -405,9 +407,13 @@ function stripUnresolvedPlaceholders(html) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildLeaseContractPreviewHtml(form) {
|
export function buildLeaseContractPreviewHtml(form) {
|
||||||
var base = V266_LEASE_DOCUMENT_HTML || '';
|
var templateId = form && form.contractTemplateId;
|
||||||
|
var base = getContractTemplateBaseHtml(templateId) || '';
|
||||||
|
var sectionBindings = getTemplateSectionVehicleBindings(templateId);
|
||||||
var vars = buildLeasePreviewVars(form || {});
|
var vars = buildLeasePreviewVars(form || {});
|
||||||
var html = stripUnresolvedPlaceholders(applyTemplateVars(base, vars));
|
var html = stripUnresolvedPlaceholders(applyTemplateVars(base, vars));
|
||||||
|
var vehicleKeys = collectVehicleKeysFromLeaseOrder(form);
|
||||||
|
html = applyAttachmentSectionVehicleFilter(html, sectionBindings, vehicleKeys);
|
||||||
html = injectPrototypeVehicleClauses(html);
|
html = injectPrototypeVehicleClauses(html);
|
||||||
var customer = getLeaseCustomerById(form && form.customerId);
|
var customer = getLeaseCustomerById(form && form.customerId);
|
||||||
var lessor = getLessorCompanyById(form && form.lessorId);
|
var lessor = getLessorCompanyById(form && form.lessorId);
|
||||||
@@ -420,7 +426,6 @@ export function buildLeaseContractPreviewHtml(form) {
|
|||||||
html = applyLeaseOrderAttachmentPatch(html, form, customer, lessor);
|
html = applyLeaseOrderAttachmentPatch(html, form, customer, lessor);
|
||||||
html = applyPowerOfAttorneyPatch(html, form);
|
html = applyPowerOfAttorneyPatch(html, form);
|
||||||
html = appendCustomerCredentialAttachments(html, customer);
|
html = appendCustomerCredentialAttachments(html, customer);
|
||||||
var vehicleKeys = collectVehicleKeysFromLeaseOrder(form);
|
|
||||||
html = applyVehicleClausePreviewFilter(html, vehicleKeys);
|
html = applyVehicleClausePreviewFilter(html, vehicleKeys);
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* 租赁合同预览:检测风控红线条款(模板标记)是否被修改
|
* 租赁合同预览:检测风控红线条款(模板标记)是否被修改
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export var STANDARD_CONTRACT_APPROVAL = '标准合同审批';
|
export var STANDARD_CONTRACT_APPROVAL = '标准合同';
|
||||||
export var NONSTANDARD_CONTRACT_APPROVAL = '非标准合同审批';
|
export var NONSTANDARD_CONTRACT_APPROVAL = '非标准合同';
|
||||||
|
|
||||||
var RISK_SELECTOR = '.ct-risk-redline[data-risk-redline="1"]';
|
var RISK_SELECTOR = '.ct-risk-redline[data-risk-redline="1"]';
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export function createEmptyLeaseOrderRow() {
|
|||||||
serviceFee: null,
|
serviceFee: null,
|
||||||
deposit: null,
|
deposit: null,
|
||||||
extraServices: [],
|
extraServices: [],
|
||||||
|
leasePeriodMonths: null,
|
||||||
leasePeriodStart: null,
|
leasePeriodStart: null,
|
||||||
leasePeriodEnd: null,
|
leasePeriodEnd: null,
|
||||||
};
|
};
|
||||||
@@ -214,6 +215,9 @@ export function isInsuredVehicleCountEditable(order) {
|
|||||||
|
|
||||||
export function formatLeasePeriod(row) {
|
export function formatLeasePeriod(row) {
|
||||||
if (!row) return '';
|
if (!row) return '';
|
||||||
|
if (row.leasePeriodMonths != null && row.leasePeriodMonths !== '') {
|
||||||
|
return String(row.leasePeriodMonths) + ' 个月';
|
||||||
|
}
|
||||||
if (row.leasePeriodStart && row.leasePeriodEnd) {
|
if (row.leasePeriodStart && row.leasePeriodEnd) {
|
||||||
return row.leasePeriodStart + ' ~ ' + row.leasePeriodEnd;
|
return row.leasePeriodStart + ' ~ ' + row.leasePeriodEnd;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const NODE_DEFS = [
|
|||||||
{ id: 'lc-create-main-contract', title: '主体合同信息', pageId: 'create', color: '#32a06e', aiPrompt: '签约、里程、费用三块表单。', annotationText: '完成度徽章;甲乙档案只读同步。' },
|
{ id: 'lc-create-main-contract', title: '主体合同信息', pageId: 'create', color: '#32a06e', aiPrompt: '签约、里程、费用三块表单。', annotationText: '完成度徽章;甲乙档案只读同步。' },
|
||||||
{ id: 'lc-create-lease-order', title: '附件1:租赁订单', pageId: 'create', color: '#2563eb', aiPrompt: '订单车辆与交车信息。', annotationText: '三者责任险等为完成度必填项。' },
|
{ id: 'lc-create-lease-order', title: '附件1:租赁订单', pageId: 'create', color: '#2563eb', aiPrompt: '订单车辆与交车信息。', annotationText: '三者责任险等为完成度必填项。' },
|
||||||
{ id: 'lc-create-poa', title: '授权委托书', pageId: 'create', color: '#7c3aed', aiPrompt: '被委托人列表编辑。', annotationText: '至少一行且姓名/联系方式/身份证完整。' },
|
{ id: 'lc-create-poa', title: '授权委托书', pageId: 'create', color: '#7c3aed', aiPrompt: '被委托人列表编辑。', annotationText: '至少一行且姓名/联系方式/身份证完整。' },
|
||||||
{ id: 'lc-create-preview', title: '实时预览', pageId: 'create', color: '#0ea5e9', aiPrompt: 'Word 版式预览与可编辑正文。', annotationText: '改动风控红线条款将触发非标准合同审批。' },
|
{ id: 'lc-create-preview', title: '实时预览', pageId: 'create', color: '#0ea5e9', aiPrompt: 'Word 版式预览与可编辑正文。', annotationText: '改动风控红线条款将触发非标准合同。' },
|
||||||
{ id: 'lc-create-footer', title: '底栏操作', pageId: 'create', color: '#64748b', aiPrompt: '取消、保存草稿、提交审核。', annotationText: '保存/提交时展示将走的审批类型。' },
|
{ id: 'lc-create-footer', title: '底栏操作', pageId: 'create', color: '#64748b', aiPrompt: '取消、保存草稿、提交审核。', annotationText: '保存/提交时展示将走的审批类型。' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,338 @@
|
|||||||
background: var(--ln-primary-soft);
|
background: var(--ln-primary-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section--disabled {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-editor-column > .ct-editor-column-card.lc-create-section--disabled {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section--disabled .ct-editor-panel,
|
||||||
|
.vm-page.lc-create-page .lc-create-section--disabled .lc-main-contract-panel {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section {
|
||||||
|
scroll-margin-top: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section__lock {
|
||||||
|
position: absolute;
|
||||||
|
inset: 48px 0 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 24px 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: color-mix(in srgb, var(--ln-surface-card) 82%, transparent);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
border-radius: 0 0 var(--ln-radius-card) var(--ln-radius-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section__lock-text {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 18rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section__lock-btn {
|
||||||
|
min-height: 44px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 5;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--ln-hairline);
|
||||||
|
border-radius: var(--ln-radius-card);
|
||||||
|
background: color-mix(in srgb, var(--ln-surface-card) 94%, transparent);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: var(--ln-shadow-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__list {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: var(--ln-radius-control);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__btn:hover {
|
||||||
|
background: var(--ln-canvas-soft);
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__btn:focus-visible {
|
||||||
|
outline: 2px solid var(--ln-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__index {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--ln-surface-strong);
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__item--done .lc-create-step-nav__index {
|
||||||
|
background: var(--ln-primary-soft);
|
||||||
|
color: var(--ln-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__item--done .lc-create-step-nav__btn {
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__item--pending .lc-create-step-nav__index {
|
||||||
|
background: var(--ln-canvas-soft);
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__item--locked .lc-create-step-nav__btn {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__label {
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__tag {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: var(--ln-radius-pill);
|
||||||
|
background: var(--ln-canvas-soft);
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-step-nav__sep {
|
||||||
|
flex: 0 0 12px;
|
||||||
|
width: 12px;
|
||||||
|
height: 1px;
|
||||||
|
margin-top: 18px;
|
||||||
|
background: var(--ln-hairline-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-topbar__intro {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-topbar__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-topbar__meta {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-topbar__meta strong {
|
||||||
|
color: var(--ln-primary);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-topbar__meta-divider {
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-topbar__meta-template {
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-topbar__meta-hint {
|
||||||
|
color: #c2410c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-panel {
|
||||||
|
padding: 12px 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-card__head {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-card__title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px 10px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-card__title-row .ct-panel-head__title {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-card .lc-form-card__hint {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-card .lc-form-card__hint-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: color-mix(in srgb, var(--ln-primary) 72%, var(--ln-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-card .lc-form-card__hint-text {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-select .ant-select-selector {
|
||||||
|
min-height: 44px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: var(--ln-radius-control);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-subtitle {
|
||||||
|
margin: 16px 0 8px;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ln-ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-step-empty {
|
||||||
|
margin: 12px 0 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: var(--ln-radius-control);
|
||||||
|
background: rgba(234, 88, 12, 0.08);
|
||||||
|
color: #c2410c;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-option-list--nested {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-option-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid var(--ln-line);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-option:hover {
|
||||||
|
border-color: #94b8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-option--active {
|
||||||
|
border-color: var(--ln-primary);
|
||||||
|
box-shadow: 0 0 0 1px var(--ln-primary);
|
||||||
|
background: var(--ln-primary-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-option__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ln-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-option__meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-editor-column > .ct-editor-column-card .lc-create-editor-panel {
|
||||||
|
padding: 12px 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-template-preview-empty {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
min-height: 320px;
|
||||||
|
padding: 24px;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* —— 主体合同信息编辑区 —— */
|
/* —— 主体合同信息编辑区 —— */
|
||||||
.vm-page.lc-create-page .lc-main-contract-card__head {
|
.vm-page.lc-create-page .lc-main-contract-card__head {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -107,6 +439,21 @@
|
|||||||
border: 1px solid var(--ln-hairline);
|
border: 1px solid var(--ln-hairline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-signing-core .lc-form-field__label-row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-signing-core .lc-form-field__label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-signing-core .lc-form-field__inline-hint {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-party-profiles {
|
.vm-page.lc-create-page .lc-party-profiles {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -316,7 +663,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger,
|
.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger,
|
||||||
.vm-page.lc-create-page .lc-lease-order-plate-picker__trigger {
|
.vm-page.lc-create-page .lc-lease-order-plate-picker__trigger,
|
||||||
|
.vm-page.lc-create-page .lc-business-dept-picker__trigger {
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
height: auto;
|
height: auto;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -324,6 +672,18 @@
|
|||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-business-dept-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-business-dept-picker__trigger {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-business-dept-picker__summary {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-brand-model-picker__summary-list,
|
.vm-page.lc-create-page .lc-lease-order-brand-model-picker__summary-list,
|
||||||
.vm-page.lc-create-page .lc-lease-order-plate-picker__summary-list {
|
.vm-page.lc-create-page .lc-lease-order-plate-picker__summary-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -342,6 +702,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
max-width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lc-lease-order-brand-model-picker__selected-list {
|
.lc-lease-order-brand-model-picker__selected-list {
|
||||||
@@ -450,6 +811,67 @@
|
|||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-brand-model-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-service-field {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-service-field__summary {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--ln-ink);
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-service-picker__btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--ln-primary) 32%, var(--ln-hairline));
|
||||||
|
border-radius: var(--ln-radius-control);
|
||||||
|
background: var(--ln-surface-card, #fff);
|
||||||
|
color: var(--ln-primary-focus, #15803d);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-service-picker__btn:hover {
|
||||||
|
background: var(--ln-primary-soft, #ecfdf3);
|
||||||
|
border-color: color-mix(in srgb, var(--ln-primary) 45%, var(--ln-hairline));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-service-picker__btn:focus-visible {
|
||||||
|
outline: 2px solid color-mix(in srgb, var(--ln-primary) 45%, transparent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-period-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-service-picker {
|
.vm-page.lc-create-page .lc-lease-order-service-picker {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 148px;
|
min-width: 148px;
|
||||||
@@ -877,14 +1299,16 @@
|
|||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__table-wrap {
|
.vm-page.lc-create-page .lc-lease-order-editor__table-wrap {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
border: 1px solid var(--ln-hairline);
|
border: 1px solid var(--ln-hairline);
|
||||||
border-radius: var(--ln-radius-control);
|
border-radius: var(--ln-radius-control);
|
||||||
background: var(--ln-surface-card);
|
background: var(--ln-surface-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__table {
|
.vm-page.lc-create-page .lc-lease-order-editor__table {
|
||||||
width: 100%;
|
width: max-content;
|
||||||
min-width: 1120px;
|
min-width: 100%;
|
||||||
|
table-layout: auto;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -894,6 +1318,7 @@
|
|||||||
border: 1px solid var(--ln-hairline);
|
border: 1px solid var(--ln-hairline);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__table th {
|
.vm-page.lc-create-page .lc-lease-order-editor__table th {
|
||||||
@@ -910,12 +1335,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__th-brand {
|
.vm-page.lc-create-page .lc-lease-order-editor__th-brand {
|
||||||
min-width: 148px;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__brand-cell {
|
.vm-page.lc-create-page .lc-lease-order-editor__brand-cell {
|
||||||
min-width: 148px;
|
min-width: 0;
|
||||||
vertical-align: top;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__brand-cell .lc-form-cascader {
|
.vm-page.lc-create-page .lc-lease-order-editor__brand-cell .lc-form-cascader {
|
||||||
@@ -928,6 +1353,14 @@
|
|||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-editor__clause-hint {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-editor__clause-hint .lc-brand-model-clause-hint {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-brand-model-clause-hint--empty {
|
.vm-page.lc-create-page .lc-brand-model-clause-hint--empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -977,7 +1410,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__th-period {
|
.vm-page.lc-create-page .lc-lease-order-editor__th-period {
|
||||||
min-width: 200px;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-editor__period-cell {
|
||||||
|
width: 1%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-period-input {
|
||||||
|
width: 72px;
|
||||||
|
min-width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-lease-order-period-input.ant-input-number {
|
||||||
|
width: 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-lease-order-editor__head-row--sub th {
|
.vm-page.lc-create-page .lc-lease-order-editor__head-row--sub th {
|
||||||
@@ -1192,6 +1638,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-contract-remark {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--ln-hairline, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-contract-remark__textarea.ant-input {
|
||||||
|
min-height: 96px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-form-cascader.ant-cascader {
|
.vm-page.lc-create-page .lc-form-cascader.ant-cascader {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -1212,7 +1669,7 @@
|
|||||||
|
|
||||||
.vm-page.lc-create-page .lc-create-topbar {
|
.vm-page.lc-create-page .lc-create-topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -1288,7 +1745,66 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lc-preview-editable-hint {
|
.lc-preview-editable-hint--inline {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-preview-head {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-preview-head .lc-template-step-card__title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 6px 10px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-preview-head .lc-template-step-card__title-row .ct-panel-head__title {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-preview-head .lc-form-card__hint {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: min(100%, 520px);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-preview-head .lc-form-card__hint-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: color-mix(in srgb, var(--ln-primary) 72%, var(--ln-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-preview-head .lc-preview-editable-hint__text {
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-preview-editable-hint__risk {
|
||||||
|
color: #b91c1c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-preview-editable-hint:not(.lc-preview-editable-hint--inline) {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -1314,7 +1830,7 @@
|
|||||||
color: var(--ln-primary-focus, #15803d);
|
color: var(--ln-primary-focus, #15803d);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lc-preview-editable-hint__text {
|
.lc-preview-editable-hint__text:not(.lc-form-card__hint-text) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
@@ -1519,6 +2035,135 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-form-field__label-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-form-field__inline-hint {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-form-field__inline-hint .lc-form-card__hint-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: color-mix(in srgb, var(--ln-primary) 72%, var(--ln-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section__head .lc-template-step-card__title-row {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section-poa-head .lc-template-step-card__title-row,
|
||||||
|
.vm-page.lc-create-page .lc-create-section-seal-head .lc-template-step-card__title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 6px 10px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section-poa-head .lc-create-poa-hint {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: var(--ln-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section-poa-head .lc-form-card__hint-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
color: color-mix(in srgb, var(--ln-primary) 72%, var(--ln-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-seal-type-head-tags {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-seal-type-head-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--ln-primary-focus, #15803d);
|
||||||
|
background: var(--ln-primary-soft, #ecfdf3);
|
||||||
|
border: 1px solid color-mix(in srgb, var(--ln-primary, #32a06e) 24%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-seal-type-chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-seal-type-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--ln-hairline);
|
||||||
|
background: var(--ln-surface-card, #fff);
|
||||||
|
color: var(--ln-ink);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-seal-type-chip:hover {
|
||||||
|
border-color: color-mix(in srgb, var(--ln-primary) 35%, var(--ln-hairline));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-seal-type-chip.is-active {
|
||||||
|
color: var(--ln-primary-focus, #15803d);
|
||||||
|
background: var(--ln-primary-soft, #ecfdf3);
|
||||||
|
border-color: color-mix(in srgb, var(--ln-primary, #32a06e) 35%, transparent);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-seal-type-chip:focus-visible {
|
||||||
|
outline: 2px solid color-mix(in srgb, var(--ln-primary) 45%, transparent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-page.lc-create-page .lc-create-section-poa-head,
|
||||||
|
.vm-page.lc-create-page .lc-create-section-seal-head {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.vm-page.lc-create-page .lc-form-field__required {
|
.vm-page.lc-create-page .lc-form-field__required {
|
||||||
color: #dc2626;
|
color: #dc2626;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"prototypeName": "self-operated-business-ledger",
|
"prototypeName": "self-operated-business-ledger",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"updatedAt": 1782697685043,
|
"updatedAt": 1782810809221,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "sob-filter",
|
"id": "sob-filter",
|
||||||
@@ -98,6 +98,198 @@
|
|||||||
"assetMap": {},
|
"assetMap": {},
|
||||||
"directory": {
|
"directory": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "oneos-project-nav",
|
||||||
|
"title": "ONE-OS 原型导航",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-insurance-procurement",
|
||||||
|
"title": "保险采购",
|
||||||
|
"href": "/prototypes/insurance-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-weekly-report",
|
||||||
|
"title": "数智部周报",
|
||||||
|
"href": "/prototypes/oneos-weekly-report",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-management",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/vehicle-management",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-contract-template-management",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/contract-template-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": "租赁业务台账",
|
||||||
|
"href": "/prototypes/lease-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-h2-fee-ledger",
|
||||||
|
"title": "车辆氢费明细",
|
||||||
|
"href": "/prototypes/vehicle-h2-fee-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-self-operated-business-ledger",
|
||||||
|
"title": "自营业务台账(当前)",
|
||||||
|
"href": "/prototypes/self-operated-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-xll-miniapp",
|
||||||
|
"title": "小羚羚小程序",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-safety-training",
|
||||||
|
"title": "安全培训扫码",
|
||||||
|
"href": "/prototypes/oneos-web-safety-training",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-oneos-web",
|
||||||
|
"title": "ONE-OS Web 端",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-workbench",
|
||||||
|
"title": "工作台",
|
||||||
|
"href": "/prototypes/oneos-web-workbench",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-login",
|
||||||
|
"title": "登录",
|
||||||
|
"href": "/prototypes/oneos-web-login",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-vehicle-asset",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/oneos-web-vehicle-asset",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-contract-template",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/oneos-web-contract-template",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-help-center",
|
||||||
|
"title": "帮助中心",
|
||||||
|
"href": "/prototypes/oneos-web-help-center",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-finance",
|
||||||
|
"title": "财务管理",
|
||||||
|
"href": "/prototypes/oneos-web-finance",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-procurement",
|
||||||
|
"title": "采购管理",
|
||||||
|
"href": "/prototypes/oneos-web-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-lease-contract",
|
||||||
|
"title": "车辆租赁合同",
|
||||||
|
"href": "/prototypes/oneos-web-lease-contract",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-h2-station",
|
||||||
|
"title": "加氢站管理",
|
||||||
|
"href": "/prototypes/oneos-web-h2-station",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-data-analysis",
|
||||||
|
"title": "数据分析",
|
||||||
|
"href": "/prototypes/oneos-web-data-analysis",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ledger-data",
|
||||||
|
"title": "台账数据",
|
||||||
|
"href": "/prototypes/oneos-web-ledger-data",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-business",
|
||||||
|
"title": "业务管理",
|
||||||
|
"href": "/prototypes/oneos-web-business",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ops",
|
||||||
|
"title": "运维管理",
|
||||||
|
"href": "/prototypes/oneos-web-ops",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-requirements",
|
||||||
|
"title": "需求说明",
|
||||||
|
"href": "/prototypes/oneos-web-requirements",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "folder",
|
"type": "folder",
|
||||||
"id": "sob-doc-root",
|
"id": "sob-doc-root",
|
||||||
@@ -114,7 +306,7 @@
|
|||||||
"type": "link",
|
"type": "link",
|
||||||
"id": "sob-link-self",
|
"id": "sob-link-self",
|
||||||
"title": "自营业务台账原型",
|
"title": "自营业务台账原型",
|
||||||
"href": "/prototypes/self-operated-business-ledger",
|
"href": "./index.html",
|
||||||
"target": "self"
|
"target": "self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"prototypeName": "vehicle-h2-fee-ledger",
|
"prototypeName": "vehicle-h2-fee-ledger",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"updatedAt": 1782796599832,
|
"updatedAt": 1782810809221,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "vh2-header",
|
"id": "vh2-header",
|
||||||
@@ -252,6 +252,198 @@
|
|||||||
"assetMap": {},
|
"assetMap": {},
|
||||||
"directory": {
|
"directory": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "oneos-project-nav",
|
||||||
|
"title": "ONE-OS 原型导航",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-insurance-procurement",
|
||||||
|
"title": "保险采购",
|
||||||
|
"href": "/prototypes/insurance-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-weekly-report",
|
||||||
|
"title": "数智部周报",
|
||||||
|
"href": "/prototypes/oneos-weekly-report",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-management",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/vehicle-management",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-contract-template-management",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/contract-template-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": "租赁业务台账",
|
||||||
|
"href": "/prototypes/lease-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-h2-fee-ledger",
|
||||||
|
"title": "车辆氢费明细(当前)",
|
||||||
|
"href": "/prototypes/vehicle-h2-fee-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-self-operated-business-ledger",
|
||||||
|
"title": "自营业务台账",
|
||||||
|
"href": "/prototypes/self-operated-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-xll-miniapp",
|
||||||
|
"title": "小羚羚小程序",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-safety-training",
|
||||||
|
"title": "安全培训扫码",
|
||||||
|
"href": "/prototypes/oneos-web-safety-training",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-oneos-web",
|
||||||
|
"title": "ONE-OS Web 端",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-workbench",
|
||||||
|
"title": "工作台",
|
||||||
|
"href": "/prototypes/oneos-web-workbench",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-login",
|
||||||
|
"title": "登录",
|
||||||
|
"href": "/prototypes/oneos-web-login",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-vehicle-asset",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/oneos-web-vehicle-asset",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-contract-template",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/oneos-web-contract-template",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-help-center",
|
||||||
|
"title": "帮助中心",
|
||||||
|
"href": "/prototypes/oneos-web-help-center",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-finance",
|
||||||
|
"title": "财务管理",
|
||||||
|
"href": "/prototypes/oneos-web-finance",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-procurement",
|
||||||
|
"title": "采购管理",
|
||||||
|
"href": "/prototypes/oneos-web-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-lease-contract",
|
||||||
|
"title": "车辆租赁合同",
|
||||||
|
"href": "/prototypes/oneos-web-lease-contract",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-h2-station",
|
||||||
|
"title": "加氢站管理",
|
||||||
|
"href": "/prototypes/oneos-web-h2-station",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-data-analysis",
|
||||||
|
"title": "数据分析",
|
||||||
|
"href": "/prototypes/oneos-web-data-analysis",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ledger-data",
|
||||||
|
"title": "台账数据",
|
||||||
|
"href": "/prototypes/oneos-web-ledger-data",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-business",
|
||||||
|
"title": "业务管理",
|
||||||
|
"href": "/prototypes/oneos-web-business",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ops",
|
||||||
|
"title": "运维管理",
|
||||||
|
"href": "/prototypes/oneos-web-ops",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-requirements",
|
||||||
|
"title": "需求说明",
|
||||||
|
"href": "/prototypes/oneos-web-requirements",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "folder",
|
"type": "folder",
|
||||||
"id": "vh2-doc-root",
|
"id": "vh2-doc-root",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"prototypeName": "vehicle-management",
|
"prototypeName": "vehicle-management",
|
||||||
"pageId": "list",
|
"pageId": "list",
|
||||||
"updatedAt": 1782486426114,
|
"updatedAt": 1782810809222,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "vm-doc-toolbar",
|
"id": "vm-doc-toolbar",
|
||||||
@@ -668,6 +668,198 @@
|
|||||||
"assetMap": {},
|
"assetMap": {},
|
||||||
"directory": {
|
"directory": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "oneos-project-nav",
|
||||||
|
"title": "ONE-OS 原型导航",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-insurance-procurement",
|
||||||
|
"title": "保险采购",
|
||||||
|
"href": "/prototypes/insurance-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-weekly-report",
|
||||||
|
"title": "数智部周报",
|
||||||
|
"href": "/prototypes/oneos-weekly-report",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-management",
|
||||||
|
"title": "车辆管理(当前)",
|
||||||
|
"href": "/prototypes/vehicle-management",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-contract-template-management",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/contract-template-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": "租赁业务台账",
|
||||||
|
"href": "/prototypes/lease-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-vehicle-h2-fee-ledger",
|
||||||
|
"title": "车辆氢费明细",
|
||||||
|
"href": "/prototypes/vehicle-h2-fee-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-self-operated-business-ledger",
|
||||||
|
"title": "自营业务台账",
|
||||||
|
"href": "/prototypes/self-operated-business-ledger",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-xll-miniapp",
|
||||||
|
"title": "小羚羚小程序",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-safety-training",
|
||||||
|
"title": "安全培训扫码",
|
||||||
|
"href": "/prototypes/oneos-web-safety-training",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "folder",
|
||||||
|
"id": "nav-folder-prototypes-oneos-web",
|
||||||
|
"title": "ONE-OS Web 端",
|
||||||
|
"defaultExpanded": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-workbench",
|
||||||
|
"title": "工作台",
|
||||||
|
"href": "/prototypes/oneos-web-workbench",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-login",
|
||||||
|
"title": "登录",
|
||||||
|
"href": "/prototypes/oneos-web-login",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-vehicle-asset",
|
||||||
|
"title": "车辆管理",
|
||||||
|
"href": "/prototypes/oneos-web-vehicle-asset",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-contract-template",
|
||||||
|
"title": "合同模板管理",
|
||||||
|
"href": "/prototypes/oneos-web-contract-template",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-help-center",
|
||||||
|
"title": "帮助中心",
|
||||||
|
"href": "/prototypes/oneos-web-help-center",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-finance",
|
||||||
|
"title": "财务管理",
|
||||||
|
"href": "/prototypes/oneos-web-finance",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-procurement",
|
||||||
|
"title": "采购管理",
|
||||||
|
"href": "/prototypes/oneos-web-procurement",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-lease-contract",
|
||||||
|
"title": "车辆租赁合同",
|
||||||
|
"href": "/prototypes/oneos-web-lease-contract",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-h2-station",
|
||||||
|
"title": "加氢站管理",
|
||||||
|
"href": "/prototypes/oneos-web-h2-station",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-data-analysis",
|
||||||
|
"title": "数据分析",
|
||||||
|
"href": "/prototypes/oneos-web-data-analysis",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ledger-data",
|
||||||
|
"title": "台账数据",
|
||||||
|
"href": "/prototypes/oneos-web-ledger-data",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-business",
|
||||||
|
"title": "业务管理",
|
||||||
|
"href": "/prototypes/oneos-web-business",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-ops",
|
||||||
|
"title": "运维管理",
|
||||||
|
"href": "/prototypes/oneos-web-ops",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "link",
|
||||||
|
"id": "nav-link-oneos-web-requirements",
|
||||||
|
"title": "需求说明",
|
||||||
|
"href": "/prototypes/oneos-web-requirements",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "folder",
|
"type": "folder",
|
||||||
"id": "vm-doc-root",
|
"id": "vm-doc-root",
|
||||||
@@ -684,7 +876,7 @@
|
|||||||
"type": "link",
|
"type": "link",
|
||||||
"id": "vm-link-self",
|
"id": "vm-link-self",
|
||||||
"title": "车辆管理原型",
|
"title": "车辆管理原型",
|
||||||
"href": "/prototypes/vehicle-management",
|
"href": "./index.html",
|
||||||
"target": "self"
|
"target": "self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
| 项 | 内容 |
|
| 项 | 内容 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 文档版本 | v1.0 |
|
| 文档版本 | v1.2 |
|
||||||
| 模块名称 | 合同模板管理 |
|
| 模块名称 | 合同模板管理 |
|
||||||
| 所属系统 | ONE-OS 业务管理 |
|
| 所属系统 | ONE-OS 业务管理 |
|
||||||
| 目标读者 | 前端 / 后端 / 测试 / 法务产品对接人 |
|
| 目标读者 | 前端 / 后端 / 测试 / 法务产品对接人 |
|
||||||
| 交互原型 | `/prototypes/contract-template-management` |
|
| 交互原型 | `/prototypes/contract-template-management` |
|
||||||
| 关联原型 | [车辆管理](/prototypes/vehicle-management)(车型主数据口径参考) |
|
| 关联原型 | [车辆管理](/prototypes/vehicle-management)(车型主数据口径参考) |
|
||||||
| 文档状态 | 已对齐原型,待研发排期 |
|
| 文档状态 | 已对齐原型(v1.2:租赁预览贯通 / 冷启动 / 合同类型筛选) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
- 以 **Word 版式保真** 的方式维护整份合同;
|
- 以 **Word 版式保真** 的方式维护整份合同;
|
||||||
- 按 **章节** 管理内容,并配置 **适用车型**;
|
- 按 **章节** 管理内容,并配置 **适用车型**;
|
||||||
- 发布 **版本**,并指定 **标准模板** 供客服草拟新合同时默认使用;
|
- 发布 **版本**,通过 **启用/停用** 控制模板是否可供租赁合同选用;
|
||||||
- 在合同主体中配置 **条件条款**(按订单车辆命中显隐)。
|
- 在合同主体及租赁订单~安全责任协议各章节中配置 **条件条款**(按订单车辆命中显隐)。
|
||||||
|
|
||||||
### 1.2 产品目标
|
### 1.2 产品目标
|
||||||
|
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |
|
| 降低维护成本 | 一份 Word 文件 = 一条记录,导入后自动拆分章节 |
|
||||||
| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |
|
| 保证合规可控 | 支持锁定区、风控红线条款标记与统计 |
|
||||||
| 支撑业务差异化 | 附件章节按车型绑定;合同主体支持条件条款 |
|
| 支撑业务差异化 | 附件章节按车型绑定;各章节均支持条件条款 |
|
||||||
| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本 |
|
| 版本可追溯 | 草稿 / 已发布分离;已发布修改产生新版本并写入版本日志 |
|
||||||
| 标准模板唯一 | 同一合同分类同时仅一套标准模板生效 |
|
| 多套模板并存 | 正式/试用、不同车型等多套已发布模板可同时存在;租赁创建时由用户选择 |
|
||||||
|
|
||||||
### 1.3 非目标(本期不做)
|
### 1.3 非目标(本期不做)
|
||||||
|
|
||||||
@@ -48,18 +48,19 @@
|
|||||||
|
|
||||||
| 角色 | 诉求 |
|
| 角色 | 诉求 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、设标准模板 |
|
| 法务人员(主用户) | 导入/编辑 Word 模板、发布版本、启用/停用模板 |
|
||||||
| 客服人员(间接用户) | 草拟新合同时默认引用标准模板(下游模块消费) |
|
| 客服人员(间接用户) | 草拟新合同时从已启用模板中选择(下游「租赁合同管理」消费) |
|
||||||
| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |
|
| 运营 / 风控(间接用户) | 查看风控红线条款配置情况 |
|
||||||
|
|
||||||
### 2.2 核心用户故事
|
### 2.2 核心用户故事
|
||||||
|
|
||||||
1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。
|
1. **作为法务**,我希望导入一份完整 Word 合同,系统自动拆成 9 个章节,以便分章节维护。
|
||||||
2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。
|
2. **作为法务**,我希望为「租赁订单」等附件章节绑定适用车型,以便创建合同时只纳入匹配车辆的条款。
|
||||||
3. **作为法务**,我希望在合同主体中为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。
|
3. **作为法务**,我希望在合同主体及租赁订单~安全责任协议各章节为某段落设置「条件条款」并绑定车型,以便不同车型看到不同正文。
|
||||||
4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。
|
4. **作为法务**,我希望保存草稿不产生版本号,发布后才生成版本号,以便区分未上线内容。
|
||||||
5. **作为法务**,我希望将某已发布版本设为「标准合同」,且系统自动禁用旧标准,以便客服始终使用唯一标准。
|
5. **作为法务**,我希望对已发布模板执行启用/停用,停用后方可编辑,并在发布变更时自动归档旧版快照到版本日志。
|
||||||
6. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。
|
6. **作为客服**,我希望在创建租赁合同时第一步选择要使用的已启用合同模板。
|
||||||
|
7. **作为法务**,我希望在列表展开行查看各章节的风控条款数、条件条款数,以便快速评估模板完整度。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -71,10 +72,12 @@
|
|||||||
| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |
|
| 章节 | 合同内的逻辑切片,共 9 个(1 个主体 + 8 个附件) |
|
||||||
| 草稿 | `status = draft`,无版本号 |
|
| 草稿 | `status = draft`,无版本号 |
|
||||||
| 已发布 | `status = published`,有版本号 `vX.XX` |
|
| 已发布 | `status = published`,有版本号 `vX.XX` |
|
||||||
| 标准模板 | `isDefault = true` 且已发布的模板;同分类唯一 |
|
| 启用 | `enabled = true` 的已发布模板,可供租赁合同创建选用 |
|
||||||
|
| 停用 | `enabled = false` 的已发布模板,不可编辑/删除,不可被租赁创建选用 |
|
||||||
|
| 版本日志 | `versionLogs[]`,记录发布变更前自动归档的快照 |
|
||||||
| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock="1"` |
|
| 锁定区 | 编辑区内不可随意修改的段落,HTML 标记 `data-inline-lock="1"` |
|
||||||
| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline="1"` |
|
| 风控红线条款 | 需重点合规关注的条款,HTML 标记 `data-risk-redline="1"` |
|
||||||
| 条件条款 | 仅合同主体可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause="1"` |
|
| 条件条款 | 各章节可用,按订单车辆命中显隐,HTML 标记 `data-vehicle-clause="1"` |
|
||||||
| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |
|
| 适用车型绑定 | 附件章节级别配置,决定该章节是否纳入某份合同 |
|
||||||
| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |
|
| 模板占位符 | 形如 `{{contractCode}}` 的变量,创建合同时由系统替换 |
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@
|
|||||||
└── 编辑 / 新增页
|
└── 编辑 / 新增页
|
||||||
├── 顶栏(返回、保存、保存并发布)
|
├── 顶栏(返回、保存、保存并发布)
|
||||||
├── 章节 Tab(仅本页展示)
|
├── 章节 Tab(仅本页展示)
|
||||||
├── 条件条款说明(合同主体 Tab 时显示)
|
├── 条件条款说明(各章节均可使用工具栏「条件」)
|
||||||
├── 适用车型绑定(附件章节 Tab 时显示)
|
├── 适用车型绑定(附件章节 Tab 时显示)
|
||||||
└── 工作区
|
└── 工作区
|
||||||
├── 左侧:Word 版式编辑区
|
├── 左侧:Word 版式编辑区
|
||||||
@@ -112,11 +115,13 @@
|
|||||||
interface ContractTemplateDocument {
|
interface ContractTemplateDocument {
|
||||||
id: string; // 唯一 ID
|
id: string; // 唯一 ID
|
||||||
category: 'lease'; // 合同分类,当前固定商用车租赁
|
category: 'lease'; // 合同分类,当前固定商用车租赁
|
||||||
|
contractType: string; // 用户自定义合同类型,如「正式合同」「试用合同」
|
||||||
fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」
|
fileName: string; // Word 文件名,如「现代18吨车型-数智中心版.docx」
|
||||||
versionName: string; // 展示用版本名称
|
versionName: string; // 展示用版本名称
|
||||||
versionNo: string | null; // 已发布:v1.00;草稿:null
|
versionNo: string | null; // 已发布:v1.00;草稿:null
|
||||||
status: 'draft' | 'published';
|
status: 'draft' | 'published';
|
||||||
isDefault: boolean; // 是否标准模板
|
enabled: boolean; // 已发布模板是否启用(草稿恒为 false)
|
||||||
|
versionLogs: VersionLogEntry[]; // 版本变更日志与快照
|
||||||
sectionContents: Record<SectionKey, string>; // 各章节 HTML
|
sectionContents: Record<SectionKey, string>; // 各章节 HTML
|
||||||
sectionVehicleBindings: Record<SectionKey, VehicleBinding[]>; // 各章节车型绑定
|
sectionVehicleBindings: Record<SectionKey, VehicleBinding[]>; // 各章节车型绑定
|
||||||
creator: string;
|
creator: string;
|
||||||
@@ -126,6 +131,24 @@ interface ContractTemplateDocument {
|
|||||||
remark?: string; // 内部备注(列表不展示)
|
remark?: string; // 内部备注(列表不展示)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface VersionLogEntry {
|
||||||
|
id: string;
|
||||||
|
versionNo: string; // 归档时的版本号
|
||||||
|
action: string; // 如「发布变更」
|
||||||
|
operator: string;
|
||||||
|
time: string;
|
||||||
|
remark?: string;
|
||||||
|
snapshot: {
|
||||||
|
fileName: string;
|
||||||
|
versionName: string;
|
||||||
|
versionNo: string;
|
||||||
|
sectionContents: Record<SectionKey, string>;
|
||||||
|
sectionVehicleBindings: Record<SectionKey, VehicleBinding[]>;
|
||||||
|
remark?: string;
|
||||||
|
archivedAt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface VehicleBinding {
|
interface VehicleBinding {
|
||||||
brand: string; // 品牌,如「现代」
|
brand: string; // 品牌,如「现代」
|
||||||
model: string; // 型号,如「18吨氢燃料电池车」
|
model: string; // 型号,如「18吨氢燃料电池车」
|
||||||
@@ -137,14 +160,14 @@ interface VehicleBinding {
|
|||||||
| key | 章节名称 | 车型绑定 | 条件条款 |
|
| key | 章节名称 | 车型绑定 | 条件条款 |
|
||||||
|---|---|:---:|:---:|
|
|---|---|:---:|:---:|
|
||||||
| `main` | 合同主体 | 否 | 是 |
|
| `main` | 合同主体 | 否 | 是 |
|
||||||
| `lease_order` | 租赁订单 | 是 | 否 |
|
| `lease_order` | 租赁订单 | 是 | 是 |
|
||||||
| `vehicle_cost` | 车辆产生的费用 | 是 | 否 |
|
| `vehicle_cost` | 车辆产生的费用 | 是 | 是 |
|
||||||
| `safety_notice` | 安全责任告知书 | 是 | 否 |
|
| `safety_notice` | 安全责任告知书 | 是 | 是 |
|
||||||
| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 否 |
|
| `dynamic_supervision` | 重型普通货运车辆租赁动态监管责任告知与确认书 | 是 | 是 |
|
||||||
| `safety_commitment` | 安全责任承诺书 | 是 | 否 |
|
| `safety_commitment` | 安全责任承诺书 | 是 | 是 |
|
||||||
| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 否 |
|
| `vehicle_spec` | 车辆使用规范和驾驶员要求 | 是 | 是 |
|
||||||
| `authorization` | 授权委托书 | 是 | 否 |
|
| `authorization` | 授权委托书 | 是 | 是 |
|
||||||
| `safety_agreement` | 安全责任协议 | 是 | 否 |
|
| `safety_agreement` | 安全责任协议 | 是 | 是 |
|
||||||
|
|
||||||
### 5.3 章节内容 HTML 标记规范
|
### 5.3 章节内容 HTML 标记规范
|
||||||
|
|
||||||
@@ -170,41 +193,48 @@ interface VehicleBinding {
|
|||||||
|
|
||||||
### 6.2 保存与发布
|
### 6.2 保存与发布
|
||||||
|
|
||||||
| 操作 | 新增模式 | 编辑草稿 | 编辑已发布 |
|
| 操作 | 新增模式 | 编辑草稿 | 编辑已发布(须先停用) |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**降为草稿**(清空版本号) |
|
| **保存** | 新建草稿记录 | 更新原记录,保持草稿 | 更新原记录,**保持已发布与版本号**,仍为停用态 |
|
||||||
| **保存并发布** | 新建已发布记录 + 版本号 | 原记录变已发布 + 版本号 | **新建一条已发布记录**(新版本),原版本保留 |
|
| **保存并发布** | 新建已发布记录 + 版本号,默认启用 | 原记录变已发布 + 版本号,默认启用 | **同记录升级版本号**;旧内容写入 `versionLogs` 快照;发布后默认停用,需手动启用 |
|
||||||
|
|
||||||
**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。
|
**校验**:保存/发布前,合同主体(`sectionContents.main`)不能为空。
|
||||||
|
|
||||||
### 6.3 标准模板规则
|
**离开编辑**:有未保存修改时返回列表需二次确认。
|
||||||
|
|
||||||
1. 同一 `category` **同时仅允许一条** `isDefault = true` 且 `status = published` 的记录。
|
### 6.3 启用 / 停用规则
|
||||||
2. 仅 **已发布且非当前标准** 的记录可执行「设为标准模板」。
|
|
||||||
3. 切换标准时:**先禁用**原标准(`isDefault → false`),再启用新标准(`isDefault → true`)。
|
|
||||||
4. **标准模板不可删除**;操作列不展示「设为标准模板」「删除」。
|
|
||||||
5. 下游「客服草拟新合同」默认引用当前标准模板(本期由其他模块消费)。
|
|
||||||
|
|
||||||
### 6.4 删除规则
|
1. 仅 **已发布** 模板具有启用态;草稿显示「—」。
|
||||||
|
2. **启用中**:不可编辑、不可删除;可供租赁合同创建选用。
|
||||||
|
3. **停用**:需二次确认(提示可能导致标准合同无法正常使用);停用后可编辑、可删除;租赁创建不可选用。
|
||||||
|
4. **启用**:直接生效,无需二次确认。
|
||||||
|
5. 编辑已发布模板并发布后,模板自动变为 **停用**,需重新启用后下游才可选。
|
||||||
|
|
||||||
- 仅 **非标准模板** 可删除。
|
### 6.4 版本日志规则
|
||||||
- 删除前二次确认;删除后不可恢复。
|
|
||||||
|
|
||||||
### 6.5 列表排序
|
1. 对已发布模板执行「保存并发布」前,系统将 **当前内容自动归档** 为快照写入 `versionLogs`。
|
||||||
|
2. 列表操作列提供「版本日志」,可查看历史版本号、操作人、时间与快照正文(只读)。
|
||||||
|
3. 列表主表 **一行对应一份 Word 文件**,历史版本通过日志追溯,不另增列表行。
|
||||||
|
|
||||||
1. 标准模板置顶;
|
### 6.5 删除规则
|
||||||
2. 其余按 `updateTime` 降序。
|
|
||||||
|
|
||||||
### 6.6 筛选匹配规则
|
- **启用中** 的已发布模板不可删除。
|
||||||
|
- 其余记录可删除;删除前二次确认;删除后不可恢复。
|
||||||
|
|
||||||
|
### 6.6 列表排序
|
||||||
|
|
||||||
|
按 `updateTime` 降序。
|
||||||
|
|
||||||
|
### 6.7 筛选匹配规则
|
||||||
|
|
||||||
| 字段 | 匹配逻辑 |
|
| 字段 | 匹配逻辑 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Word 文件名称 | 模糊匹配 `fileName`、`versionName`、`versionNo`、`creator`、`updater` |
|
| 合同类型 | 下拉选择(支持输入模糊搜索候选项);精确匹配 `contractType` |
|
||||||
| 状态 | 精确匹配 `draft` / `published` |
|
| 状态 | 精确匹配 `draft` / `published` |
|
||||||
| 创建人 | 精确匹配 `creator` |
|
| 创建人 | 精确匹配 `creator` |
|
||||||
| 适用品牌 | 文档内 **任一附件章节** 的车型绑定命中该品牌;无绑定的文档在筛品牌时视为「全部车型」可通过 |
|
| 启用状态 | `enabled`:仅已发布且启用;`disabled`:仅已发布且停用 |
|
||||||
|
|
||||||
> **已移除**:筛选项「适用型号」、主表列「备注」(产品确认不再展示)。
|
> **已移除**:筛选项「适用品牌/适用车型」、主表列「备注」、「设为标准模板」能力。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -218,10 +248,10 @@ interface VehicleBinding {
|
|||||||
|
|
||||||
| 字段 | 控件 | 说明 |
|
| 字段 | 控件 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Word 文件名称 | 文本输入 | 支持回车触发查询 |
|
| 合同类型 | 可搜索下拉 | 选项来自现有模板 `contractType` 去重;支持输入模糊搜索 |
|
||||||
| 状态 | 下拉 | 全部 / 已发布 / 草稿 |
|
| 状态 | 下拉 | 全部 / 已发布 / 草稿 |
|
||||||
| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |
|
| 创建人 | 下拉 | 选项来自现有模板 `creator` 去重 |
|
||||||
| 适用品牌 | 下拉 | 选项来自车型主数据品牌列表 |
|
| 启用状态 | 下拉 | 全部 / 启用 / 停用(仅作用于已发布记录) |
|
||||||
|
|
||||||
**按钮**:
|
**按钮**:
|
||||||
|
|
||||||
@@ -239,9 +269,10 @@ interface VehicleBinding {
|
|||||||
| 列名 | 字段 | 展示规则 |
|
| 列名 | 字段 | 展示规则 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| (展开列) | — | 宽 48px |
|
| (展开列) | — | 宽 48px |
|
||||||
| Word 文件名称 | `fileName` | 超长省略;标准模板显示金色标签「标准合同」 |
|
| 合同类型 | `contractType` | 展示用户自定义的合同类型文案 |
|
||||||
| 版本号 | `versionNo` | 草稿显示「—」 |
|
| 版本号 | `versionNo` | 草稿显示「—」 |
|
||||||
| 状态 | `status` | 已发布 / 草稿 Tag |
|
| 状态 | `status` | 已发布 / 草稿 Tag |
|
||||||
|
| 启用状态 | `enabled` | 已发布:启用/停用;草稿:「—」 |
|
||||||
| 创建时间 | `createTime` | — |
|
| 创建时间 | `createTime` | — |
|
||||||
| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |
|
| 创建人 | `creator` | 展示短名(如 `法务-王静` → `王静`) |
|
||||||
| 最后更新时间 | `updateTime` | — |
|
| 最后更新时间 | `updateTime` | — |
|
||||||
@@ -254,14 +285,10 @@ interface VehicleBinding {
|
|||||||
|
|
||||||
| 操作 | 显示条件 | 行为 |
|
| 操作 | 显示条件 | 行为 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 编辑 | 始终 | 进入编辑页,加载整份 `sectionContents` |
|
| 启用 / 停用 | 已发布 | 停用需确认;启用直接生效 |
|
||||||
| 设为标准模板 | 已发布 && 非标准 | 弹窗确认后切换标准 |
|
| 版本日志 | 已发布 | 查看历史快照 |
|
||||||
| 删除 | 非标准 | 弹窗确认后删除 |
|
| 编辑 | 草稿,或已发布且停用 | 启用中按钮禁用 |
|
||||||
|
| 删除 | 草稿,或已发布且停用 | 启用中按钮禁用;删除前确认 |
|
||||||
**设为标准模板弹窗文案要点**:
|
|
||||||
|
|
||||||
- 说明:同一分类同时只能有一套标准模板;客服草拟新合同时将默认使用新标准。
|
|
||||||
- 若存在旧标准:明示将先禁用「{旧标准文件名}」,再设置「{新标准文件名}」。
|
|
||||||
|
|
||||||
### 7.6 子表(展开行)
|
### 7.6 子表(展开行)
|
||||||
|
|
||||||
@@ -272,8 +299,7 @@ interface VehicleBinding {
|
|||||||
| 章节 | 章节名称 |
|
| 章节 | 章节名称 |
|
||||||
| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |
|
| 适用车型 | 附件章节展示绑定列表,格式「品牌 · 型号」;多个用「;」连接;未绑定显示「全部车型」;合同主体显示「—」 |
|
||||||
| 风控条款数 | 该章节 HTML 内风控红线标记数量 |
|
| 风控条款数 | 该章节 HTML 内风控红线标记数量 |
|
||||||
| 条件条款数 | 仅合同主体统计条件条款数;其他章节显示 `0` |
|
| 条件条款数 | 按各章节 HTML 统计条件条款标记数量 |
|
||||||
| 内容状态 | 有内容:「已配置」;无内容:「未配置」 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -293,7 +319,7 @@ interface VehicleBinding {
|
|||||||
|
|
||||||
### 8.3 章节 Tab
|
### 8.3 章节 Tab
|
||||||
|
|
||||||
- 共 9 个章节;**前 4 个直显**,其余收入「更多」下拉。
|
- 共 9 个章节;**横向滚动 Tab 条**展示全部章节,右侧提供「章节列表」按钮打开可搜索的章节面板(便于长标题章节快速定位)。
|
||||||
- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。
|
- 切换 Tab 后,编辑区与预览区 **同步切换** 至对应章节内容。
|
||||||
- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。
|
- **合同主体 Tab**:编辑/预览 **整份 Word 合并全文**。
|
||||||
- **其他 Tab**:仅展示该章节切片内容。
|
- **其他 Tab**:仅展示该章节切片内容。
|
||||||
@@ -310,11 +336,11 @@ interface VehicleBinding {
|
|||||||
8. 授权委托书
|
8. 授权委托书
|
||||||
9. 安全责任协议
|
9. 安全责任协议
|
||||||
|
|
||||||
### 8.4 条件条款说明(仅合同主体 Tab)
|
### 8.4 条件条款说明(合同主体及租赁订单~安全责任协议)
|
||||||
|
|
||||||
展示说明卡片,文案要点:
|
展示说明卡片,文案要点:
|
||||||
|
|
||||||
> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。
|
> 在编辑区选中段落后,点击工具栏「条件」并指定适用车型。适用于合同主体及全部附件章节。创建合同时,订单车辆任意命中即纳入正文;右侧预览可选择模拟车型验证显隐。
|
||||||
|
|
||||||
### 8.5 适用车型绑定(仅附件章节 Tab)
|
### 8.5 适用车型绑定(仅附件章节 Tab)
|
||||||
|
|
||||||
@@ -349,9 +375,11 @@ interface VehicleBinding {
|
|||||||
- **锁定**:选中内容设为锁定区
|
- **锁定**:选中内容设为锁定区
|
||||||
- **解锁**:解除锁定
|
- **解锁**:解除锁定
|
||||||
- **风控**:标记风控红线条款
|
- **风控**:标记风控红线条款
|
||||||
- **条件**(仅合同主体):选中段落绑定车型条件
|
- **条件**:在各章节选中段落绑定车型条件
|
||||||
|
|
||||||
#### 8.6.3 条件条款交互(合同主体)
|
#### 8.6.3 条件条款交互
|
||||||
|
|
||||||
|
**适用范围**:合同主体、租赁订单、车辆产生的费用、安全责任告知书、重型普通货运车辆租赁动态监管责任告知与确认书、安全责任承诺书、车辆使用规范和驾驶员要求、授权委托书、安全责任协议。
|
||||||
|
|
||||||
1. 用户选中段落或文字;
|
1. 用户选中段落或文字;
|
||||||
2. 点击工具栏「条件」;
|
2. 点击工具栏「条件」;
|
||||||
@@ -374,11 +402,11 @@ interface VehicleBinding {
|
|||||||
|
|
||||||
`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`
|
`锁定 N 处 · 风控红线 M 处 · 条件条款 K 处`
|
||||||
|
|
||||||
合同主体且已选模拟车型时追加:`(预览命中 X)`
|
当前章节支持条件且已选模拟车型时追加:`(预览命中 X)`
|
||||||
|
|
||||||
#### 8.7.3 模拟车型(仅合同主体)
|
#### 8.7.3 模拟车型
|
||||||
|
|
||||||
- 多选下拉,选项来自车型主数据(品牌 · 型号)。
|
- 在合同主体及全部附件章节可用;多选下拉,选项来自车型主数据(品牌 · 型号)。
|
||||||
- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。
|
- 未选车型时:**隐藏**所有条件条款段落(与创建合同时无车辆命中行为一致)。
|
||||||
- 选中后按命中规则过滤并展开条件条款正文。
|
- 选中后按命中规则过滤并展开条件条款正文。
|
||||||
|
|
||||||
@@ -493,7 +521,7 @@ interface VehicleBinding {
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A[合同模板管理] -->|标准模板 + 章节内容| B[租赁合同创建]
|
A[合同模板管理] -->|已启用模板 + 章节内容| B[租赁合同创建]
|
||||||
A -->|车型绑定口径| C[车辆管理]
|
A -->|车型绑定口径| C[车辆管理]
|
||||||
A -->|占位符变量定义| B
|
A -->|占位符变量定义| B
|
||||||
B -->|订单车辆列表| A
|
B -->|订单车辆列表| A
|
||||||
@@ -501,7 +529,7 @@ flowchart LR
|
|||||||
|
|
||||||
| 模块 | 关系 |
|
| 模块 | 关系 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 租赁合同创建 | 读取标准模板或指定版本;按订单车辆过滤条件条款与附件章节 |
|
| 租赁合同创建 | 创建流程第一步选择 **已启用** 的已发布模板;按订单车辆过滤条件条款与附件章节 |
|
||||||
| 车辆管理 | 车型品牌/型号枚举口径一致 |
|
| 车辆管理 | 车型品牌/型号枚举口径一致 |
|
||||||
| 非标准合同审批 | 合同正文出现 `data-nonstd-trigger` 时触发(标记规范已预留) |
|
| 非标准合同审批 | 合同正文出现 `data-nonstd-trigger` 时触发(标记规范已预留) |
|
||||||
|
|
||||||
@@ -513,10 +541,10 @@ flowchart LR
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 查看列表 | 法务、客服(只读) |
|
| 查看列表 | 法务、客服(只读) |
|
||||||
| 新增 / 编辑 / 发布 | 法务 |
|
| 新增 / 编辑 / 发布 | 法务 |
|
||||||
| 设为标准模板 | 法务负责人 |
|
| 启用 / 停用 | 法务 |
|
||||||
| 删除 | 法务负责人 |
|
| 删除 | 法务负责人 |
|
||||||
|
|
||||||
**审计字段**:`creator`、`createTime`、`updater`、`updateTime` 必须在每次保存时更新。
|
**审计字段**:`creator`、`createTime`、`updater`、`updateTime` 必须在每次保存时更新;发布变更写入 `versionLogs`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -525,7 +553,7 @@ flowchart LR
|
|||||||
### 14.1 列表查询
|
### 14.1 列表查询
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/contract-templates?category=lease&keyword=&status=&creator=&vehicleBrand=&page=1&pageSize=10
|
GET /api/contract-templates?category=lease&fileName=&status=&creator=&enabled=&page=1&pageSize=10
|
||||||
```
|
```
|
||||||
|
|
||||||
响应需包含主表字段;章节摘要可列表内嵌或展开时按需加载。
|
响应需包含主表字段;章节摘要可列表内嵌或展开时按需加载。
|
||||||
@@ -536,7 +564,7 @@ GET /api/contract-templates?category=lease&keyword=&status=&creator=&vehicleBran
|
|||||||
GET /api/contract-templates/{id}
|
GET /api/contract-templates/{id}
|
||||||
```
|
```
|
||||||
|
|
||||||
返回完整 `sectionContents`、`sectionVehicleBindings`。
|
返回完整 `sectionContents`、`sectionVehicleBindings`、`versionLogs`。
|
||||||
|
|
||||||
### 14.3 创建 / 更新
|
### 14.3 创建 / 更新
|
||||||
|
|
||||||
@@ -547,13 +575,14 @@ PUT /api/contract-templates/{id}
|
|||||||
|
|
||||||
请求体含章节 HTML 与绑定;`publish: boolean` 控制是否发布。
|
请求体含章节 HTML 与绑定;`publish: boolean` 控制是否发布。
|
||||||
|
|
||||||
### 14.4 设为标准
|
### 14.4 启用 / 停用
|
||||||
|
|
||||||
```
|
```
|
||||||
POST /api/contract-templates/{id}/set-default
|
POST /api/contract-templates/{id}/enable
|
||||||
|
POST /api/contract-templates/{id}/disable
|
||||||
```
|
```
|
||||||
|
|
||||||
服务端事务:同 category 旧标准 `isDefault=false`,新标准 `isDefault=true`。
|
停用前需二次确认;启用中记录拒绝编辑/删除类写操作(由业务层校验)。
|
||||||
|
|
||||||
### 14.5 删除
|
### 14.5 删除
|
||||||
|
|
||||||
@@ -561,7 +590,7 @@ POST /api/contract-templates/{id}/set-default
|
|||||||
DELETE /api/contract-templates/{id}
|
DELETE /api/contract-templates/{id}
|
||||||
```
|
```
|
||||||
|
|
||||||
拒绝删除 `isDefault=true` 的记录。
|
拒绝删除 `enabled=true` 的记录。
|
||||||
|
|
||||||
### 14.6 Word 导入
|
### 14.6 Word 导入
|
||||||
|
|
||||||
@@ -590,31 +619,35 @@ Content-Type: multipart/form-data
|
|||||||
|
|
||||||
### 16.1 列表页
|
### 16.1 列表页
|
||||||
|
|
||||||
- [ ] 筛选项包含:Word 文件名称、状态、创建人、适用品牌;**不含**适用型号、备注列
|
- [ ] 筛选项包含:合同类型(可搜索下拉)、状态、创建人、启用状态
|
||||||
|
- [ ] 主表含合同类型、启用状态列;操作含启用/停用、版本日志、编辑、删除
|
||||||
|
- [ ] 启用中模板编辑/删除按钮禁用;停用需二次确认
|
||||||
- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表
|
- [ ] 主表每行对应一份 Word 文件,可展开 9 个章节子表
|
||||||
- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数、内容状态
|
- [ ] 子表展示:章节名、适用车型、风控条款数、条件条款数
|
||||||
- [ ] 标准模板金色标签置顶,且不可删除
|
|
||||||
- [ ] 切换标准模板时旧标准自动禁用
|
|
||||||
|
|
||||||
### 16.2 编辑页
|
### 16.2 编辑页
|
||||||
|
|
||||||
- [ ] 章节 Tab 仅在编辑页展示;前 4 个直显 + 更多
|
- [ ] 章节 Tab 仅在编辑页展示;横向滚动 + 章节列表面板
|
||||||
- [ ] 导入 Word 后 9 章节正确拆分
|
- [ ] 导入 Word 后 9 章节正确拆分
|
||||||
- [ ] 附件章节可绑定车型;合同主体可设条件条款
|
- [ ] 附件章节可绑定车型;各章节均可设条件条款
|
||||||
- [ ] 预览支持连续/单页切换;合同主体可模拟车型验证条件条款
|
- [ ] 预览支持连续/单页切换;各章节可模拟车型验证条件条款
|
||||||
- [ ] 保存草稿无版本号;发布生成版本号
|
- [ ] 保存草稿无版本号;发布生成版本号
|
||||||
- [ ] 编辑已发布并发布时产生新版本,旧版本保留
|
- [ ] 编辑已发布(停用后)并发布时版本号递增,旧版写入版本日志
|
||||||
|
- [ ] 编辑已发布(停用后)点「保存」不改变发布态与版本号
|
||||||
|
- [ ] 有未保存修改时返回列表需确认
|
||||||
|
|
||||||
### 16.3 数据一致性
|
### 16.3 跨模块与数据一致性
|
||||||
|
|
||||||
|
- [ ] 模板管理停用后,租赁合同创建页不可再选用该模板
|
||||||
|
- [ ] 租赁创建预览读取 store 中模板 `sectionContents`(与模板管理编辑一致)
|
||||||
- [ ] 列表子表「风控条款数」与编辑区统计一致
|
- [ ] 列表子表「风控条款数」与编辑区统计一致
|
||||||
- [ ] 列表子表「条件条款数」仅合同主体可大于 0
|
- [ ] 列表子表「条件条款数」与各章节实际标记数一致
|
||||||
- [ ] 筛选「适用品牌」与子表车型绑定逻辑一致
|
|
||||||
|
|
||||||
### 16.4 标注与文档(原型)
|
### 16.4 标注与文档(原型)
|
||||||
|
|
||||||
- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`
|
- [ ] 原型已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`
|
||||||
- [ ] 目录路由可切换列表页 / 新增页
|
- [ ] 目录路由可切换列表页 / 新增页
|
||||||
|
- [ ] `PRD.md` 与 `annotation-source.json` 与当前实现一致
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -623,24 +656,29 @@ Content-Type: multipart/form-data
|
|||||||
| 能力 | 原型文件 / 符号 |
|
| 能力 | 原型文件 / 符号 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 主逻辑 | `src/prototypes/contract-template-management/ContractTemplate.jsx` |
|
| 主逻辑 | `src/prototypes/contract-template-management/ContractTemplate.jsx` |
|
||||||
|
| 跨模块模板状态 | `src/prototypes/contract-template-management/contract-template-store.js` |
|
||||||
|
| 章节 HTML 合并 | `src/prototypes/contract-template-management/contract-template-section-html.js` |
|
||||||
|
| 冷启动种子 | `src/prototypes/contract-template-management/contract-template-seed.js` |
|
||||||
|
| 租赁侧消费目录 | `src/prototypes/contract-template-management/contract-template-catalog.js` |
|
||||||
| 入口与标注 | `src/prototypes/contract-template-management/index.tsx` |
|
| 入口与标注 | `src/prototypes/contract-template-management/index.tsx` |
|
||||||
| 标注数据 | `src/prototypes/contract-template-management/annotation-source.json` |
|
| 标注数据 | `src/prototypes/contract-template-management/annotation-source.json` |
|
||||||
|
| 下游选用 | `src/prototypes/lease-contract-management/LeaseContractCreate.jsx` |
|
||||||
| 占位符与出租方 | `src/prototypes/contract-template-management/contract-template-vars.js` |
|
| 占位符与出租方 | `src/prototypes/contract-template-management/contract-template-vars.js` |
|
||||||
| 样式 | `src/prototypes/contract-template-management/styles/` |
|
| 样式 | `src/prototypes/contract-template-management/styles/` |
|
||||||
|
|
||||||
|
> **原型限制**:模板数据经 `localStorage`(兼写 `sessionStorage`)持久化;冷启动由 `contract-template-seed.js` 注入种子,无需先打开模板管理页即可在租赁创建选用已启用模板。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 18. 附录:标准模板确认弹窗(原文案)
|
## 18. 附录:停用确认弹窗(原文案)
|
||||||
|
|
||||||
**标题**:确认设为标准模板
|
**标题**:确认停用
|
||||||
|
|
||||||
**正文**:
|
**正文**:
|
||||||
|
|
||||||
> 同一分类同时只能有一套标准模板生效。确认后,客服草拟新合同时将默认使用新标准模板。
|
> 停用后,该模板将不可在租赁合同创建时选用,是否确定?
|
||||||
>
|
|
||||||
> (若存在旧标准)将先禁用当前标准模板「{旧标准名称}」,再设置「{新标准名称}」为新的标准模板。
|
|
||||||
|
|
||||||
**按钮**:取消 / 确认设置
|
**按钮**:取消 / 确定停用
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -648,4 +686,6 @@ Content-Type: multipart/form-data
|
|||||||
|
|
||||||
| 版本 | 日期 | 作者 | 说明 |
|
| 版本 | 日期 | 作者 | 说明 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| v1.0 | 2026-06-25 | 产品 | 基于原型与需求讨论首版;明确 Word 文档级模型、嵌套列表、标准模板唯一、移除适用型号筛选与备注列 |
|
| v1.2 | 2026-06-29 | 产品 | 租赁预览贯通 store;冷启动种子;列表按合同类型筛选;移除 isDefault;localStorage 持久化;PRD 与实现对齐 |
|
||||||
|
| v1.1 | 2026-06-29 | 产品 | 移除标准模板模型,改为启用/停用 + 版本日志;列表筛选与操作同步;各章节支持条件条款 |
|
||||||
|
| v1.0 | 2026-06-25 | 产品 | 首版:Word 文档级模型、嵌套列表、章节拆分与条件条款 |
|
||||||
|
|||||||
Reference in New Issue
Block a user