diff --git a/.axhub/make/sidebar-tree.json b/.axhub/make/sidebar-tree.json index 11f8614..da03d89 100644 --- a/.axhub/make/sidebar-tree.json +++ b/.axhub/make/sidebar-tree.json @@ -1,7 +1,13 @@ { "version": 1, - "updatedAt": "2026-07-09T09:46:07.467Z", + "updatedAt": "2026-07-11T03:51:46.250Z", "prototypes": [ + { + "id": "item-prototypes-task-work-order", + "kind": "item", + "title": "task work order", + "itemKey": "prototypes/task-work-order" + }, { "id": "item-prototypes-lease-business-line-overview", "kind": "item", @@ -56,12 +62,6 @@ "title": "oneos web ledger data", "itemKey": "prototypes/oneos-web-ledger-data" }, - { - "id": "item-prototypes-oneos-web-login", - "kind": "item", - "title": "oneos web login", - "itemKey": "prototypes/oneos-web-login" - }, { "id": "item-prototypes-oneos-web-ops", "kind": "item", @@ -74,18 +74,6 @@ "title": "oneos web procurement", "itemKey": "prototypes/oneos-web-procurement" }, - { - "id": "item-prototypes-oneos-web-requirements", - "kind": "item", - "title": "oneos web requirements", - "itemKey": "prototypes/oneos-web-requirements" - }, - { - "id": "item-prototypes-oneos-web-vehicle-asset", - "kind": "item", - "title": "oneos web vehicle asset", - "itemKey": "prototypes/oneos-web-vehicle-asset" - }, { "id": "item:prototypes:oneos-prototype-nav", "kind": "item", @@ -206,7 +194,7 @@ { "id": "item:prototypes:self-operated-business-ledger", "kind": "item", - "title": "物流业务台账", + "title": "物流业务明细", "itemKey": "prototypes/self-operated-business-ledger" }, { @@ -228,6 +216,12 @@ "kind": "folder", "title": "财务管理", "children": [ + { + "id": "item:prototypes:vehicle-pickup-receivable", + "kind": "item", + "title": "提车应收款", + "itemKey": "prototypes/vehicle-pickup-receivable" + }, { "id": "item:prototypes:payment-records", "kind": "item", diff --git a/.cursor/rules/prototype-annotation-layout.mdc b/.cursor/rules/prototype-annotation-layout.mdc new file mode 100644 index 0000000..5a364db --- /dev/null +++ b/.cursor/rules/prototype-annotation-layout.mdc @@ -0,0 +1,36 @@ +--- +description: 原型标注布局——PRD 在右侧目录分章展示,禁止侧边原型导航与目录内跳转 +globs: src/prototypes/**/* +alwaysApply: false +--- + +# 原型标注布局(全局) + +开发或改造 `src/prototypes/` 下任意带 `@axhub/annotation` 的原型时,**必须**遵循 `rules/prototype-annotation-layout-guide.md`。 + +## 硬性要求 + +1. 使用 `PrototypeAnnotationHost`,禁止直接挂载裸 `AnnotationViewer` +2. 禁止在 `annotation-source.json` 写入 `id: oneos-project-nav` 导航目录 +3. 跨原型跳转只通过 `/prototypes/oneos-prototype-nav`(原型导航页) +4. PRD / 模块说明放在 `directory` 的 `markdown` 节点,由右侧「原型标注」目录分章节展示 +5. 不在页面正文下方内联展示 PRD;目录内不展示 `link` / `route` 跳转节点 + +## 标准模板 + +```tsx +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; + + +``` + +## 同步命令 + +菜单或标注目录调整后: + +```bash +npm run project-nav:sync +``` diff --git a/.cursor/rules/prototype-nav-sync.mdc b/.cursor/rules/prototype-nav-sync.mdc index ca1eb97..4d6cf71 100644 --- a/.cursor/rules/prototype-nav-sync.mdc +++ b/.cursor/rules/prototype-nav-sync.mdc @@ -41,7 +41,7 @@ CHANGELOG_NOTE="修复筛选联动" npm run nav:sync -- --prototype customer-man - 脚本路径:`scripts/sync-prototype-nav-registry.mjs` - 检测原型目录内容哈希;有变化则版本递增(`v1.0` → `v1.1` → …) - 写入 `src/prototypes/oneos-prototype-nav/prototype-registry.json` -- 同步 `nav-menu.json` 与各原型标注目录(`npm run project-nav:sync` 逻辑) +- 同步 `nav-menu.json`,并清理各原型 `annotation-source.json` 中的 `oneos-project-nav` 节点(`npm run project-nav:sync`) - 同步小羚羚小程序目录(`npm run nav:xll-sync`,读取 `make-project-20260629` 项目标注目录) - 本地 `npm run dev` 时,Vite 插件会在保存文件后自动触发同步(无需手动跑命令) diff --git a/AGENTS.md b/AGENTS.md index 0310ab9..0c82f22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,7 @@ | 主题 | `src/themes//` | `rules/theme-guide.md` | | 项目资料和文档 | `src/resources/` | `rules/resource-management-guide.md` | | 画布 | `src/prototypes//canvas.excalidraw`、`canvas-assets/` | 原型画布和画布素材 | +| 原型标注布局 | `src/common/prototype-annotation-host.tsx` | `rules/prototype-annotation-layout-guide.md` | ## ⚠️ 重要原则 diff --git a/package-lock.json b/package-lock.json index 4bb4e90..3c2c4d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,12 +15,15 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@mermaid-js/mermaid-cli": "^11.16.0", "@tailwindcss/vite": "^4.1.18", "@vitejs/plugin-react": "^5.0.0", "@vitest/coverage-v8": "4.0.16", "@vitest/ui": "4.0.16", "extract-zip": "^2.0.1", "iconv-lite": "^0.7.1", + "jsdom": "^29.1.1", + "mermaid": "^11.16.0", "react": "^18.2.0", "react-dom": "^18.2.0", "subset-font": "^2.5.0", @@ -124,6 +127,71 @@ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@axhub/annotation": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@axhub/annotation/-/annotation-1.0.10.tgz", @@ -441,6 +509,173 @@ "node": ">=18" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz", + "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz", + "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", @@ -895,6 +1130,191 @@ "node": ">=12" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.20", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.20.tgz", + "integrity": "sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.9", + "@floating-ui/utils": "^0.2.12", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.3.0.tgz", + "integrity": "sha512-Yw4Qa43P6e4Xwh0TwEUkHQNRJInWaqIBo73VJmns3j2AIlZPAvUcR4yGIxCPqPRCgyJ5KIVIalF/I1GxhZ/Kgw==", + "dev": true, + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "engines": { + "node": ">=6" + } + }, + "node_modules/@headlessui/react": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.10.tgz", + "integrity": "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@headlessui/react/node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@headlessui/tailwindcss": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz", + "integrity": "sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "tailwindcss": "^3.0 || ^4.0" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.2.tgz", + "integrity": "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz", + "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.9.tgz", + "integrity": "sha512-kzP/M/mbQxODlmOt4bIQZ2SBVUWUSqMLXooXixnX7noche8WHaQcA+nwFN1K2KCF/cp+LDUhcJsCicwkvhD1pg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -945,6 +1365,403 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mermaid-js/layout-elk": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mermaid-js/layout-elk/-/layout-elk-0.2.2.tgz", + "integrity": "sha512-vnH3gtqfhyBiRVKNpT8iDENTw18q/OF0GF/SfYfHN43KZpu+6eZDEOMHTfNYAkpmUWJNgtRQFIS6BTc7vH/DYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "elkjs": "^0.9.3" + }, + "peerDependencies": { + "mermaid": "^11.0.2" + } + }, + "node_modules/@mermaid-js/layout-tidy-tree": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mermaid-js/layout-tidy-tree/-/layout-tidy-tree-0.2.2.tgz", + "integrity": "sha512-8RmjDXjKJBxqTS1mICStm8zWRM45fSzs0SOrkp28+KsOGS2YEMFMVTwwRU8CsC6M1L+pDYZVjf1m9AC1c9Wndg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "d3": "^7.9.0" + }, + "peerDependencies": { + "mermaid": "^11.0.2" + } + }, + "node_modules/@mermaid-js/mermaid-cli": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-cli/-/mermaid-cli-11.16.0.tgz", + "integrity": "sha512-0InK2nbVIMtzVzCugmdvPkAuvS6wRUqU6Utntff1n8c7lgfRZAdhKY6PSKvcIK9nFmuOUzAgB5+x/XWcroZ7Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-free": "^6.0.0 || ^7.0.1", + "@mermaid-js/layout-elk": "^0.1.5 || ^0.2.0", + "@mermaid-js/mermaid-zenuml": "^0.2.0", + "chalk": "^5.0.1", + "commander": "^13.1.0", + "import-meta-resolve": "^4.1.0", + "katex": "^0.16.25", + "mermaid": "^11.14.0", + "p-limit": "^6.2.0" + }, + "bin": { + "mmdc": "src/cli.js" + }, + "engines": { + "node": "^18.19 || >=20.0" + }, + "optionalDependencies": { + "@mermaid-js/layout-tidy-tree": "^0.2.1" + }, + "peerDependencies": { + "puppeteer": "^23 || ^24 || ^25" + } + }, + "node_modules/@mermaid-js/mermaid-cli/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@mermaid-js/mermaid-cli/node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mermaid-js/mermaid-cli/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mermaid-js/mermaid-zenuml": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-zenuml/-/mermaid-zenuml-0.2.3.tgz", + "integrity": "sha512-RGBtgL6fc+5Y2Jm9odOH9HRJ80BP4l6atBYnAK5bBzEowF0PU3UtvZRRcbFxImPGPuLIzqZq31ur8lVO0AoF3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zenuml/core": "^3.47.0" + }, + "peerDependencies": { + "mermaid": "^10 || ^11" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.0.tgz", + "integrity": "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz", + "integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==", + "dev": true, + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.100", + "@napi-rs/canvas-darwin-arm64": "0.1.100", + "@napi-rs/canvas-darwin-x64": "0.1.100", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", + "@napi-rs/canvas-linux-arm64-musl": "0.1.100", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-musl": "0.1.100", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", + "@napi-rs/canvas-win32-x64-msvc": "0.1.100" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz", + "integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz", + "integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz", + "integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz", + "integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz", + "integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz", + "integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz", + "integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz", + "integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz", + "integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz", + "integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz", + "integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -952,6 +1769,36 @@ "dev": true, "license": "MIT" }, + "node_modules/@puppeteer/browsers": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.6.tgz", + "integrity": "sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "modern-tar": "^0.7.6", + "yargs": "^18.0.0" + }, + "bin": { + "browsers": "lib/main-cli.js" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + }, + "yauzl": { + "optional": true + } + } + }, "node_modules/@rc-component/async-validator": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz", @@ -1649,6 +2496,47 @@ "react-dom": ">=18.0.0" } }, + "node_modules/@react-aria/focus": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.1.tgz", + "integrity": "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.1.tgz", + "integrity": "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.0.tgz", + "integrity": "sha512-DkP/H0C2YjjS7gZWKNqOmU8a16qHPjQNdzMwmTq9SzplM6Iw0kVMTZ0OIoe6FOgGqa+FwMsE2QbPjh/n3g/jXQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", @@ -2052,6 +2940,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@tailwindcss/node": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", @@ -2336,6 +3234,35 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.5.tgz", + "integrity": "sha512-4EKRXh7zBLkbKbFmG3AUVkircuHd+7OdT1pocJSepxtfBd3qnrJgJ5rtPkRYyo9fmyVb2+pI2xPy5oYvMLQy6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.3.tgz", + "integrity": "sha512-8Np/TFELpI0ySuJoVmjvOrQYXH/8sTX0Biv9szhFhY39xOdAAY+smrMxjxOum/ux3eM8MUJQsEJ0/R0UpvC8dw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2392,6 +3319,290 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -2406,6 +3617,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", @@ -2417,6 +3635,14 @@ "undici-types": "~8.3.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -2428,6 +3654,17 @@ "@types/node": "*" } }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -2596,6 +3833,90 @@ "node": ">=10.0.0" } }, + "node_modules/@zenuml/core": { + "version": "3.50.1", + "resolved": "https://registry.npmjs.org/@zenuml/core/-/core-3.50.1.tgz", + "integrity": "sha512-Q18BXvIfhRuGw0JfO4e+pltG04Phhv2E7n35EWTNSZKKH/HEkyE4Sh7KERb9dqaOfjOdRdKvLG0YekjK3fcDXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.16", + "@headlessui/react": "^2.2.9", + "@headlessui/tailwindcss": "^0.2.2", + "antlr4": "~4.11.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "color-string": "^2.1.4", + "dompurify": "^3.3.1", + "highlight.js": "^11.10.0", + "html-to-image": "^1.11.13", + "jotai": "^2.16.1", + "marked": "^4.3.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwind-merge": "^3.4.0" + }, + "bin": { + "zenuml": "dist/cli/zenuml.mjs" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.97" + }, + "peerDependencies": { + "playwright-core": "1.57.0" + }, + "peerDependenciesMeta": { + "playwright-core": { + "optional": true + } + } + }, + "node_modules/@zenuml/core/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@zenuml/core/node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@zenuml/core/node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/@zenuml/core/node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/adler-32": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", @@ -2605,6 +3926,34 @@ "node": ">=0.8" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/antd": { "version": "6.4.5", "resolved": "https://registry.npmjs.org/antd/-/antd-6.4.5.tgz", @@ -2668,6 +4017,16 @@ "react-dom": ">=18.0.0" } }, + "node_modules/antlr4": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.11.0.tgz", + "integrity": "sha512-GUGlpE2JUjAN+G8G5vY+nOoeyNhHsXoIJwP1XF1oRw89vifA1K46T6SEkwLwr7drihN7I/lf0DIjKc4OZvBX8w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2675,6 +4034,19 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2737,6 +4109,16 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", @@ -2831,6 +4213,66 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chromium-bidi": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz", + "integrity": "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2849,6 +4291,39 @@ "node": ">=0.8" } }, + "node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -2868,6 +4343,16 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -2880,12 +4365,594 @@ "node": ">=0.8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "dev": true, + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/dayjs": { "version": "1.11.21", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", @@ -2910,6 +4977,23 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2920,6 +5004,14 @@ "node": ">=8" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1638949", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1638949.tgz", + "integrity": "sha512-mXwg4Fqnv0WR4iuAT/gYUmctNkjILwXFHyZ+m7Ty1dfr0ezZt2U3gnrrJTfRobJTHoXf+IbuFvFITzLrLFjwJA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/dingbat-to-unicode": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", @@ -2935,6 +5027,16 @@ "jszip": ">=3.0.0" } }, + "node_modules/dompurify": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/duck": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", @@ -2951,6 +5053,21 @@ "dev": true, "license": "ISC" }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "dev": true, + "license": "EPL-2.0" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -2975,6 +5092,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -2982,6 +5112,17 @@ "dev": true, "license": "MIT" }, + "node_modules/es-toolkit": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", + "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -3186,6 +5327,31 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -3209,6 +5375,13 @@ "dev": true, "license": "ISC" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "dev": true, + "license": "MIT" + }, "node_modules/harfbuzzjs": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/harfbuzzjs/-/harfbuzzjs-0.10.3.tgz", @@ -3226,6 +5399,29 @@ "node": ">=8" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3233,6 +5429,13 @@ "dev": true, "license": "MIT" }, + "node_modules/html-to-image": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz", + "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", + "dev": true, + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -3256,18 +5459,46 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "license": "MIT" }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-mobile": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", "license": "MIT" }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3338,12 +5569,93 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jotai": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.20.1.tgz", + "integrity": "sha512-dnuKfU/GLi8B28RRMjQ3AfoN7kfzP8o41+AX2FmITZqEMY8PHnjABq+VkEooomLwYaGjda+pgy0yFSjaHX/ZPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0", + "@babel/template": ">=7.0.0", + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@babel/template": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -3391,6 +5703,46 @@ "setimmediate": "^1.0.5" } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "dev": true + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "dev": true, + "license": "MIT" + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -3673,6 +6025,20 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -3680,6 +6046,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3806,6 +6179,75 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/mermaid": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.16.0.tgz", + "integrity": "sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.2.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.20", + "dompurify": "^3.3.3", + "es-toolkit": "^1.45.1", + "katex": "^0.16.45", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/modern-tar": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", + "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/motion": { "version": "12.41.0", "resolved": "https://registry.npmjs.org/motion/-/motion-12.41.0.tgz", @@ -3939,12 +6381,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.7.0.tgz", + "integrity": "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "license": "(MIT AND Zlib)" }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "dev": true, + "license": "MIT" + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -3988,6 +6457,24 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -4034,6 +6521,58 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.3.0.tgz", + "integrity": "sha512-O1tx8S315aw8eI99HZ5ZNcVEzJ9+jKF//eO5UvfZ3cXJ6okZ5sX3Y50u7DJaM+ewEK4LqXP068tBhfRaWikj+g==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@puppeteer/browsers": "3.0.6", + "chromium-bidi": "16.0.1", + "devtools-protocol": "0.0.1638949", + "lilconfig": "^3.1.3", + "puppeteer-core": "25.3.0", + "typed-query-selector": "^2.12.2" + }, + "bin": { + "puppeteer": "lib/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/puppeteer-core": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.3.0.tgz", + "integrity": "sha512-fm+wpUr2oigH1PXZvwgATrM2tYWHMDG8ASzTEe9uukCye4X5Ldx1K5BTHPFKITrIWvQQAQ256d1NpbEveBcKjA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@puppeteer/browsers": "3.0.6", + "chromium-bidi": "16.0.1", + "devtools-protocol": "0.0.1638949", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.2", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -4046,6 +6585,28 @@ "node": ">=0.10.0" } }, + "node_modules/react-aria": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.50.0.tgz", + "integrity": "sha512-S0Os6QZk33fzUAKu1QLT9afoUaCBt1ZNdoiq0n2YMVgKIdNIQS8zxiZ8O9hYE6QyDkHKjD6q39LQZ+qaSAIgjw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.2", + "@internationalized/number": "^3.6.7", + "@internationalized/string": "^3.2.9", + "@react-types/shared": "^3.36.0", + "@swc/helpers": "^0.5.0", + "aria-hidden": "^1.2.3", + "clsx": "^2.0.0", + "react-stately": "3.48.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -4075,6 +6636,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-stately": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.48.0.tgz", + "integrity": "sha512-ImicSAG+lTotAe5izcs1fz49Zk48w7pDusqYg04WaPhCoej8BJ24soMu3iLXIrsi273s4P1gZrYGrqReMfgEEA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.2", + "@internationalized/number": "^3.6.7", + "@internationalized/string": "^3.2.9", + "@react-types/shared": "^3.36.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -4090,6 +6669,23 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", @@ -4135,6 +6731,26 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -4148,6 +6764,19 @@ "dev": true, "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -4261,6 +6890,42 @@ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", "license": "MIT" }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/stylis": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", @@ -4293,6 +6958,31 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", @@ -4367,6 +7057,26 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.8.tgz", + "integrity": "sha512-htwgN/8KRB3z3vnC0BOETVh2m499g5GmyTK9Wq5JBLX3FNz6tSBveAd+fQhzy9hkjif8vy2jwDMR1sGhLtZl2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.8" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.8.tgz", + "integrity": "sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==", + "dev": true, + "license": "MIT" + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -4377,12 +7087,56 @@ "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -4403,6 +7157,16 @@ "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", "license": "MIT" }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", @@ -4442,12 +7206,36 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -5121,6 +7909,19 @@ } } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/wawoff2": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wawoff2/-/wawoff2-2.0.1.tgz", @@ -5135,6 +7936,49 @@ "woff2_decompress.js": "bin/woff2_decompress.js" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.2.tgz", + "integrity": "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -5180,6 +8024,25 @@ "node": ">=0.8" } }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5230,6 +8093,16 @@ "node": ">=0.8" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/xmlbuilder": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", @@ -5239,6 +8112,24 @@ "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -5246,6 +8137,36 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -5269,6 +8190,17 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index e714179..5ac6290 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "metadata:sync": "node scripts/sync-project-metadata.mjs", "project-nav:sync": "node scripts/sync-project-prototype-directory.mjs", "nav:sync": "node scripts/sync-prototype-nav-registry.mjs", + "nav:enrich-git": "node scripts/enrich-prototype-registry-from-git.mjs", "nav:xll-sync": "node scripts/sync-xll-nav-menu.mjs", "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", @@ -28,12 +29,14 @@ "font:subset:beginner-guide": "node scripts/subset-beginner-guide-fonts.mjs" }, "devDependencies": { + "@mermaid-js/mermaid-cli": "^11.16.0", "@tailwindcss/vite": "^4.1.18", "@vitejs/plugin-react": "^5.0.0", "@vitest/coverage-v8": "4.0.16", "@vitest/ui": "4.0.16", "extract-zip": "^2.0.1", "iconv-lite": "^0.7.1", + "jsdom": "^29.1.1", "react": "^18.2.0", "react-dom": "^18.2.0", "subset-font": "^2.5.0", @@ -48,6 +51,7 @@ "docx-preview": "^0.3.7", "lucide-react": "^0.562.0", "mammoth": "^1.12.0", + "mermaid": "^11.16.0", "xlsx": "^0.18.5" } } diff --git a/rules/global-design-spec.md b/rules/global-design-spec.md index 0fc9abd..9c19dfc 100644 --- a/rules/global-design-spec.md +++ b/rules/global-design-spec.md @@ -53,6 +53,8 @@ |---|---| | 正文字体 `--vm-font` | Inter, -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Microsoft YaHei", sans-serif | | 等宽 `--vm-font-mono` | ui-monospace, JetBrains Mono, SFMono-Regular, Menlo, Consolas, monospace | +| 数字/金额 | 使用 `--vm-font-mono` + `tabular-nums`(全局见 `src/common/ln-numeric.css`,对齐 `.vm-expire-date`) | +| 金额格式 | `src/common/format-number.js`(千分位 + 2 位小数;Legacy 页可用 `window.__oneosFormatMoney`) | | 页面基础字号 | `0.875rem`(14px) | | 筛选标签 | `0.75rem` / 500 | | 表头 | `0.75rem` / 500 | @@ -186,7 +188,7 @@ import '../vehicle-management/style.css'; | 次要 | `vm-btn vm-btn-secondary` | 带边框白底 | | 幽灵 | `vm-btn vm-btn-ghost` | 取消、重置、工具栏次要操作 | | 文字链 | `vm-btn vm-btn-link` | 表格内操作、行内链接 | -| 返回 | `vm-btn vm-btn-back` | 详情/编辑顶栏返回 | +| 返回 | `vm-btn vm-btn-back` | 表单/详情/编辑顶栏左上角;Chevron 图标 + 白底描边;hover 主色字/边框 + 浅绿底 | **规格**:高度 36px;圆角 8px;字号 0.875rem / 500;禁用 opacity 0.5。 diff --git a/rules/prototype-annotation-layout-guide.md b/rules/prototype-annotation-layout-guide.md new file mode 100644 index 0000000..699b2af --- /dev/null +++ b/rules/prototype-annotation-layout-guide.md @@ -0,0 +1,115 @@ +# 原型标注布局规范 + +适用于 `src/prototypes/` 下所有接入 `@axhub/annotation` 的原型页面。 + +## 核心原则 + +1. **禁止在右侧「原型目录」中展示跨原型导航** + - 不得在 `annotation-source.json` 的 `directory.nodes` 中维护 `id: oneos-project-nav` 的「ONE-OS 原型导航」文件夹 + - 跨原型跳转统一由 **`/prototypes/oneos-prototype-nav`(原型导航页)** 承担 + +2. **PRD 说明展示在右侧「原型标注」目录面板** + - 产品需求、模块说明、分章节 PRD 等内容,通过 `directory` 的 `markdown` 节点写入 + - 用户在右侧工具栏打开「原型标注 / 原型目录」,按章节阅读 Markdown 正文 + - **不在**页面正文下方内联展示 PRD 大段内容 + +3. **去除目录内页面跳转** + - `type: link` 跨原型/跨模块链接不在目录树中展示 + - `type: route` 页面内跳转节点不在目录树中展示(`route` 仅用于声明 `pageId` 绑定,供按当前页过滤章节) + - 页面切换由原型自身导航(按钮、Tab、Hash 等)完成 + +4. **标注工具栏保留** + - 页面元素批注、主题切换、颜色筛选等 `@axhub/annotation` 能力继续可用 + +## 标准接入方式 + +所有新原型与存量改造,**必须**使用公共壳组件: + +```tsx +import { + type AnnotationSourceDocument, + type AnnotationViewerOptions, +} from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; +import annotationSourceDocument from './annotation-source.json'; + +export default function MyPrototypePage() { + const annotationOptions = useMemo(() => ({ + showToolbar: true, + showThemeToggle: true, + showColorFilter: true, + emptyWhenNoData: false, + toolbarEdge: 'right', + currentPageId: 'list', // 多页面原型按当前页传入 + }), []); + + return ( + <> + + + + ); +} +``` + +### 多页面原型 + +- `options.currentPageId` 与页面路由保持一致 +- `annotation-source.json` 中带 `route` 子节点的文件夹,仅在其 `payload.pageId` 匹配当前页时,才在右侧目录中展示对应 PRD 章节 +- 无 `route` 绑定的 PRD 文件夹(如「PRD 全文」「模块总览」)在所有子页面均展示 +- 一般**不需要**再实现 `onDirectoryRoute`(目录内已无 `route` 可点击节点) + +## annotation-source.json 目录约定 + +`directory.nodes` 中只允许保留 **本产品说明**,结构建议: + +```text +directory.nodes +├── folder · 模块说明 / PRD 全文 / 列表页模块 / 新增页模块 … +│ ├── markdown · 章节正文(内联 markdown 或 markdownMap) +│ └── route · 仅用于声明 pageId 绑定(不在 UI 展示) +``` + +**禁止写入:** + +- `id: oneos-project-nav` 导航文件夹 +- 依赖 `type: link` 的跨原型跳转(改放原型导航页) + +**不在目录 UI 展示(但可保留在 JSON 中供过滤):** + +- `type: link` 节点 +- `type: route` 节点(仅作 pageId 绑定元数据) + +## 脚本与同步 + +| 命令 | 作用 | +|------|------| +| `npm run project-nav:sync` | 更新原型导航页 `nav-menu.json`,并清理各原型目录中的导航节点 | +| `node scripts/strip-prototype-nav-from-directory.mjs` | 批量移除 `oneos-project-nav` | +| `node scripts/migrate-prototype-annotation-host.mjs` | 将 `AnnotationViewer` 迁移为 `PrototypeAnnotationHost` | + +修改 sidebar 菜单或批量整理标注目录后,执行 `npm run project-nav:sync`。 + +## Agent 开发检查清单 + +新建或改造带标注的原型时: + +- [ ] `index.tsx` 使用 `PrototypeAnnotationHost`,不直接挂载裸 `AnnotationViewer` +- [ ] `annotation-source.json` 无 `oneos-project-nav` 节点 +- [ ] PRD / 模块说明以 `markdown` 节点写入 `directory`,能在右侧「原型目录」分章节阅读 +- [ ] 页面正文下方**无**大块 PRD 内联区域 +- [ ] 右侧目录**无**跨原型 link、**无**可点击的 route 页面跳转 +- [ ] 跨原型入口统一引导用户打开「原型导航」页 +- [ ] 多页面原型正确传入 `currentPageId`,右侧 PRD 章节与当前页匹配 + +## 相关文件 + +| 文件 | 说明 | +|------|------| +| `src/common/prototype-annotation-host.tsx` | 标注壳:过滤目录 + 批注工具栏 | +| `src/common/prototype-annotation-utils.ts` | PRD 目录过滤与导航节点清理 | +| `src/prototypes/oneos-prototype-nav/` | 全局原型导航页 | +| `scripts/sync-project-prototype-directory.mjs` | 导航菜单同步(不再注入侧边导航树) | diff --git a/rules/prototype-development-guide.md b/rules/prototype-development-guide.md index b4fdd8e..692bce3 100644 --- a/rules/prototype-development-guide.md +++ b/rules/prototype-development-guide.md @@ -38,6 +38,15 @@ src/prototypes// - 只有 Make 批注/编辑工具启用、且当前选中的是带安全本地 `markdownPath` 的目录 Markdown 正文子节点时,批注气泡卡片才显示“文档编辑”按钮。 - 导出/发布时会构建期内联 `markdownPath` 正文,不依赖运行时请求 `.md` 文件。 +## 原型标注与 PRD 布局(强制) + +跨原型跳转与 PRD 阅读方式已全局统一,详见 `rules/prototype-annotation-layout-guide.md`: + +- **禁止** 在 `annotation-source.json` 的 `directory` 中维护 `oneos-project-nav`(ONE-OS 原型导航);跨原型入口统一使用 `/prototypes/oneos-prototype-nav` +- **必须** 通过 `PrototypeAnnotationHost`(`src/common/prototype-annotation-host.tsx`)挂载标注,不在各原型直接使用裸 `AnnotationViewer` +- PRD / 模块说明写在 `directory` 的 `markdown` 节点中,由右侧「原型标注」目录分章节展示;**不在**页面正文下方内联 PRD +- 目录内不展示 `link` / `route` 跳转节点;多页面原型通过 `options.currentPageId` 控制右侧 PRD 章节与当前页匹配 + 每个原型的 `index.tsx` 顶部建议包含面向用户的中文 `@name`,用于预览列表展示名: ```typescript diff --git a/scripts/enrich-prototype-registry-from-git.mjs b/scripts/enrich-prototype-registry-from-git.mjs new file mode 100644 index 0000000..bc45d49 --- /dev/null +++ b/scripts/enrich-prototype-registry-from-git.mjs @@ -0,0 +1,339 @@ +/** + * 从 Git 提交历史生成/补全原型导航注册表中的详细变更日志。 + * + * 用法: + * node scripts/enrich-prototype-registry-from-git.mjs + * node scripts/enrich-prototype-registry-from-git.mjs --prototype vehicle-h2-fee-ledger + */ +import { execSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, '..'); +const REGISTRY_PATH = path.join(projectRoot, 'src/prototypes/oneos-prototype-nav/prototype-registry.json'); +const TRACKED_EXTENSIONS = new Set(['.tsx', '.ts', '.jsx', '.js', '.css', '.json', '.md', '.html']); +const IGNORED_FILE_NAMES = new Set(['prototype-registry.json', 'nav-menu.json']); +const MAX_CHANGELOG = 30; +const MAX_RECENT = 40; + +function parseArgs(argv) { + const args = { prototype: '' }; + for (let i = 0; i < argv.length; i += 1) { + if (argv[i] === '--prototype' && argv[i + 1]) args.prototype = argv[++i].trim(); + } + return args; +} + +function shouldTrackFile(relativePath) { + const base = path.basename(relativePath); + if (IGNORED_FILE_NAMES.has(base)) return false; + if (relativePath === 'annotation-source.json') return false; + return TRACKED_EXTENSIONS.has(path.extname(relativePath).toLowerCase()); +} + +function normalizePrototypePath(filePath, prototypeId) { + const normalized = String(filePath || '').replace(/\\/gu, '/').trim(); + const prefix = `src/prototypes/${prototypeId}/`; + if (normalized.startsWith(prefix)) return normalized.slice(prefix.length); + return null; +} + +function parseCommitBlocks(raw) { + const blocks = raw.split(/\n?----\n/u).map((block) => block.trim()).filter(Boolean); + const commits = []; + for (const block of blocks) { + const lines = block.split('\n'); + const hash = lines[0]?.trim(); + if (!/^[0-9a-f]{7,40}$/iu.test(hash || '')) continue; + const dateLine = lines[1]?.trim(); + const subject = lines[2]?.trim() || ''; + if (!dateLine) continue; + + const fileStart = lines.findIndex((line, index) => index > 2 && line.trim() === ''); + const bodyEnd = fileStart >= 0 ? fileStart : lines.length; + const body = lines.slice(3, bodyEnd).join('\n').trim(); + const files = (fileStart >= 0 ? lines.slice(fileStart + 1) : []) + .map((line) => line.trim()) + .filter(Boolean); + + commits.push({ hash, dateLine, subject, body, files }); + } + return commits; +} + +function getPrototypeCommits(prototypeId) { + const relDir = `src/prototypes/${prototypeId}`; + if (!fs.existsSync(path.join(projectRoot, relDir))) return []; + let raw = ''; + try { + raw = execSync( + `git log --pretty=format:----%n%H%n%ci%n%s%n%b --name-only -- ${relDir}`, + { cwd: projectRoot, encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 }, + ); + } catch { + return []; + } + return parseCommitBlocks(raw); +} + +function formatDateParts(dateLine) { + const match = dateLine.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})/u); + if (!match) { + const date = new Date(dateLine); + if (Number.isNaN(date.getTime())) return { date: '1970-01-01', time: '00:00', iso: null }; + return { + date: date.toISOString().slice(0, 10), + time: `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`, + iso: date.toISOString(), + }; + } + const [, y, m, d, hh, mm] = match; + return { + date: `${y}-${m}-${d}`, + time: `${hh}:${mm}`, + iso: new Date(`${y}-${m}-${d}T${hh}:${mm}:00+08:00`).toISOString(), + }; +} + +function extractBodyLines(body) { + return body + .split('\n') + .map((line) => line.trim()) + .filter((line) => line && !/^Co-authored-by:/iu.test(line)) + .map((line) => line.replace(/^[-*•]\s*/u, '').trim()) + .filter(Boolean); +} + +function strictKeywords(title, prototypeId) { + const keywords = new Set([title, prototypeId]); + for (const part of String(title || '').split(/[·、/()()\s「」]+/u)) { + const trimmed = part.trim(); + if (trimmed.length >= 3) keywords.add(trimmed); + } + if (title.includes('氢费明细')) keywords.add('氢费明细'); + else if (title.includes('氢费')) keywords.add('氢费'); + if (title.includes('原型导航')) keywords.add('原型导航'); + if (title.includes('合同模板')) keywords.add('合同模板'); + if (title.includes('租赁合同')) keywords.add('租赁合同'); + if (title.includes('租赁业务')) keywords.add('租赁业务'); + if (title.includes('收款记录')) keywords.add('收款记录'); + if (title.includes('维修保养')) keywords.add('维修保养'); + if (title.includes('还车应结')) keywords.add('还车应结'); + return [...keywords]; +} + +function lineMatchesPrototype(line, title, prototypeId) { + if (!line) return false; + if (line.includes(title)) return true; + if (line.toLowerCase().includes(prototypeId.toLowerCase())) return true; + return strictKeywords(title, prototypeId).some((keyword) => ( + keyword.length >= 3 && line.includes(keyword) + )); +} + +function commitIsRelevant(commit, title, prototypeId, changedFiles, allChangedFiles, bodyLines) { + if (changedFiles.length > 0) return true; + + const onlyAnnotation = allChangedFiles.length > 0 + && allChangedFiles.every((file) => file === 'annotation-source.json'); + const textMatches = [commit.subject, ...bodyLines].some((line) => ( + lineMatchesPrototype(line, title, prototypeId) + )); + + if (textMatches && onlyAnnotation) return true; + if (textMatches) return true; + return false; +} + +function summarizeFileChanges(files) { + const notes = []; + const prd = files.filter((f) => f.includes('.spec/') && f.endsWith('.md')); + const comments = files.filter((f) => f.includes('prototype-comments') || f.includes('annotation-source')); + const styles = files.filter((f) => f.endsWith('.css')); + const logic = files.filter((f) => /\.(tsx|jsx|ts|js)$/u.test(f) && !f.endsWith('.css')); + const data = files.filter((f) => f.includes('/data/') || f.endsWith('.json')); + + if (logic.length) { + const names = [...new Set(logic.map((f) => path.basename(f)))].slice(0, 4); + notes.push(`页面逻辑与交互:${names.join('、')}${logic.length > names.length ? ' 等' : ''}`); + } + if (styles.length) notes.push(`样式与布局:${[...new Set(styles.map((f) => path.basename(f)))].join('、')}`); + if (prd.length) notes.push(`需求说明与 PRD:${prd.map((f) => path.basename(f)).join('、')}`); + if (comments.length) notes.push('原型标注目录与批注说明同步'); + if (data.length) notes.push(`演示数据与配置:${data.map((f) => path.basename(f)).join('、')}`); + return notes; +} + +function buildSummary(subject, prototypeId, title, bodyLines, changedFiles, onlyAnnotation) { + const matched = bodyLines.filter((line) => lineMatchesPrototype(line, title, prototypeId)); + if (matched.length === 1) return matched[0]; + if (matched.length > 1) return matched[0]; + + const fragments = splitSubjectFragments(subject, prototypeId, title); + if (fragments.length === 1) return fragments[0]; + if (fragments.length > 1) return fragments[0]; + + if (onlyAnnotation && changedFiles.length === 0) return '原型标注目录同步'; + if (changedFiles.length) return summarizeFileChanges(changedFiles)[0] || subject; + if (lineMatchesPrototype(subject, title, prototypeId)) return subject; + + return subject; +} + +function dedupeDetails(summary, details) { + return [...new Set(details)].filter((line) => ( + line + && line !== summary + && !summary.includes(line) + && line.length > 2 + )); +} + +function splitSubjectFragments(subject, prototypeId, title) { + const segments = subject + .split(/[;;]/u) + .map((part) => part.trim()) + .filter(Boolean); + const matched = segments.filter((segment) => lineMatchesPrototype(segment, title, prototypeId)); + if (!matched.length) return []; + + const fragments = []; + for (const segment of matched) { + const colonIndex = segment.indexOf(':'); + const tail = colonIndex >= 0 ? segment.slice(colonIndex + 1) : segment; + tail.split(/[、,,]/u) + .map((part) => part.trim()) + .filter((part) => part.length >= 4) + .forEach((part) => fragments.push(part)); + } + return [...new Set(fragments)]; +} + +function buildDetails(subject, bodyLines, prototypeId, title, changedFiles) { + const details = []; + const matched = bodyLines.filter((line) => lineMatchesPrototype(line, title, prototypeId)); + details.push(...matched); + + for (const fragment of splitSubjectFragments(subject, prototypeId, title)) { + if (!details.includes(fragment) && fragment !== subject) details.push(fragment); + } + + for (const note of summarizeFileChanges(changedFiles)) { + if (!details.includes(note)) details.push(note); + } + + if (!details.length && changedFiles.length) { + details.push(`变更 ${changedFiles.length} 个文件`); + } + + return details.filter((line) => line !== subject); +} + +function bumpVersion(indexFromOldest) { + return `v1.${indexFromOldest}`; +} + +function buildChangelogFromGit(prototypeId, title) { + const commits = getPrototypeCommits(prototypeId); + if (!commits.length) return []; + + const chronological = [...commits].reverse(); + const entries = []; + + for (const commit of chronological) { + const { date, time } = formatDateParts(commit.dateLine); + const bodyLines = extractBodyLines(commit.body); + const changedFiles = commit.files + .map((file) => normalizePrototypePath(file, prototypeId)) + .filter((file) => file && shouldTrackFile(file)); + const allChangedFiles = commit.files + .map((file) => normalizePrototypePath(file, prototypeId)) + .filter(Boolean); + const onlyAnnotation = allChangedFiles.length > 0 + && allChangedFiles.every((file) => file === 'annotation-source.json'); + + if (!commitIsRelevant(commit, title, prototypeId, changedFiles, allChangedFiles, bodyLines)) { + continue; + } + + const summary = buildSummary(commit.subject, prototypeId, title, bodyLines, changedFiles, onlyAnnotation); + const details = dedupeDetails( + summary, + buildDetails(commit.subject, bodyLines, prototypeId, title, changedFiles), + ); + + entries.push({ + version: '', + date, + time, + summary, + details, + commit: commit.hash.slice(0, 7), + files: [...new Set(changedFiles)].slice(0, 12), + }); + } + + return entries.reverse().map((entry, index, list) => ({ + ...entry, + version: bumpVersion(list.length - index), + })); +} + +function rebuildRecentUpdates(registry) { + const rows = []; + for (const [prototypeId, record] of Object.entries(registry.prototypes || {})) { + const latest = record.changelog?.[0]; + if (!latest) continue; + rows.push({ + prototypeId, + title: record.title || prototypeId, + version: latest.version, + date: latest.date, + time: latest.time, + summary: latest.summary, + details: latest.details || [], + files: latest.files || [], + commit: latest.commit, + _sort: `${latest.date}T${latest.time}`, + }); + } + rows.sort((a, b) => b._sort.localeCompare(a._sort)); + return rows.slice(0, MAX_RECENT).map(({ _sort, ...row }) => row); +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8')); + const prototypeIds = args.prototype + ? [args.prototype] + : Object.keys(registry.prototypes || {}); + + let updated = 0; + for (const prototypeId of prototypeIds) { + const record = registry.prototypes[prototypeId]; + if (!record) continue; + const changelog = buildChangelogFromGit(prototypeId, record.title || prototypeId); + if (!changelog.length) continue; + + record.changelog = changelog.slice(0, MAX_CHANGELOG); + record.revision = changelog.length; + record.version = changelog[0].version; + const latestIso = getPrototypeCommits(prototypeId)[0]; + if (latestIso) { + const { iso } = formatDateParts(latestIso.dateLine); + if (iso) record.lastUpdated = iso; + } + registry.prototypes[prototypeId] = record; + updated += 1; + console.log(`[git] ${prototypeId}: ${changelog.length} entries → ${changelog[0].version}`); + } + + registry.recentUpdates = rebuildRecentUpdates(registry); + registry.updatedAt = new Date().toISOString(); + fs.writeFileSync(REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, 'utf8'); + console.log(`完成:补全 ${updated} 个原型的 Git 变更日志`); +} + +main(); diff --git a/scripts/migrate-prototype-annotation-host.mjs b/scripts/migrate-prototype-annotation-host.mjs new file mode 100644 index 0000000..3f9f44b --- /dev/null +++ b/scripts/migrate-prototype-annotation-host.mjs @@ -0,0 +1,62 @@ +/** + * 将原型 index.tsx 中的 AnnotationViewer 迁移为 PrototypeAnnotationHost。 + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, '..'); +const prototypesRoot = path.join(projectRoot, 'src/prototypes'); +const hostImport = "import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host';"; + +function listIndexFiles() { + return fs.readdirSync(prototypesRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => path.join(prototypesRoot, entry.name, 'index.tsx')) + .filter((filePath) => fs.existsSync(filePath)) + .filter((filePath) => fs.readFileSync(filePath, 'utf8').includes('AnnotationViewer')); +} + +function migrateFile(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + if (!content.includes('AnnotationViewer')) return false; + if (content.includes('PrototypeAnnotationHost')) { + console.log(`[skip] ${path.relative(projectRoot, filePath)}`); + return false; + } + + content = content.replace( + /import\s*\{([^}]*)\}\s*from\s*'@axhub\/annotation';/u, + (match, importsBlock) => { + const cleaned = importsBlock + .split(',') + .map((item) => item.trim()) + .filter((item) => item && item !== 'AnnotationViewer') + .join(',\n '); + return `import {\n ${cleaned}\n} from '@axhub/annotation';`; + }, + ); + + if (!content.includes(hostImport)) { + content = content.replace( + /\} from '@axhub\/annotation';/u, + `} from '@axhub/annotation';\n${hostImport}`, + ); + } + + content = content + .replace(//gu, '') + .replace(/React\.createElement\(AnnotationViewer,/gu, 'React.createElement(PrototypeAnnotationHost,'); + + fs.writeFileSync(filePath, content, 'utf8'); + console.log(`[migrate] ${path.relative(projectRoot, filePath)}`); + return true; +} + +let changed = 0; +for (const filePath of listIndexFiles()) { + if (migrateFile(filePath)) changed += 1; +} +console.log(`完成:迁移 ${changed} 个 index.tsx`); diff --git a/scripts/publish-all-to-s3.mjs b/scripts/publish-all-to-s3.mjs index 697a540..bbc30fa 100644 --- a/scripts/publish-all-to-s3.mjs +++ b/scripts/publish-all-to-s3.mjs @@ -66,7 +66,7 @@ function toPublishPath(resource) { function resolveObjectPrefix(entry) { const normalized = entry.path.replace(/^\/+|\/+$/gu, ''); const prototypeMatch = normalized.match(/^(?:src\/)?prototypes\/(.+)$/u); - if (prototypeMatch?.[1]) return prototypeMatch[1]; + if (prototypeMatch?.[1]) return `prototypes/${prototypeMatch[1]}`; const themeMatch = normalized.match(/^(?:src\/)?themes\/(.+)$/u); if (themeMatch?.[1]) return `themes/${themeMatch[1]}`; return entry.id; diff --git a/scripts/remove-breadcrumbs-pass2.mjs b/scripts/remove-breadcrumbs-pass2.mjs new file mode 100644 index 0000000..9271657 --- /dev/null +++ b/scripts/remove-breadcrumbs-pass2.mjs @@ -0,0 +1,112 @@ +#!/usr/bin/env node +import fs from 'fs'; +import path from 'path'; + +const ROOT = path.resolve('src/prototypes'); + +function walk(dir, out = []) { + for (const name of fs.readdirSync(dir)) { + const full = path.join(dir, name); + if (fs.statSync(full).isDirectory()) { + if (name === 'node_modules' || name === '.spec') continue; + walk(full, out); + } else if (/\.(jsx|tsx)$/.test(name)) out.push(full); + } + return out; +} + +function cleanup(source) { + let s = source; + + // Fix corrupted fragment from partial breadcrumb removal (37-新增后装设备) + s = s.replace( + /\{ style: styles\.page \},\s*\{ e\.preventDefault\(\); \} \}, '运维管理'\),[\s\S]*?React\.createElement\('span', \{ style: styles\.breadcrumbCurrent \}, '[^']+'\)\s*\),/g, + '{ style: styles.page },' + ); + + // Top bar: breadcrumb spans + req button -> req button only + s = s.replace( + /React\.createElement\('div', \{ style: \{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 \} \},\s*React\.createElement\('span'[\s\S]*?\),\s*(React\.createElement\(Button, \{ type: 'link'[\s\S]*?'查看需求说明'\)\s*)\),/g, + `React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, $1),` + ); + + // Lease contract rows with broken partial breadcrumb + s = s.replace( + /React\.createElement\('div', \{ style: \{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 \} \},\s*React\.createElement\('span'[\s\S]*?\),\s*(React\.createElement\('(?:button|span)'[\s\S]*?'查看需求说明'\)[^)]*\))/g, + `React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, $1)` + ); + + // Lease contract row - only breadcrumb spans ending with )), no req + s = s.replace( + /React\.createElement\('div', \{ style: \{ marginBottom: 16 \} \},\s*React\.createElement\('span'[\s\S]*?\)\),/g, + '' + ); + + // 后装设备 list page partial breadcrumb block inside createElement + s = s.replace( + /React\.createElement\('div', \{ style: styles\.breadcrumbLeft \},[\s\S]*?React\.createElement\('span', \{ style: styles\.breadcrumbCurrent \}, '[^']+'\)\s*\),/g, + '' + ); + + // Orphan breadcrumb link lines after broken removal + s = s.replace( + /\n\t\t\tReact\.createElement\('a', \{ href: '#', style: styles\.breadcrumbLink[\s\S]*?breadcrumbCurrent \}, '[^']+'\)\n\t\t\),/g, + '' + ); + + // Remove breadcrumbItems variable blocks + s = s.replace(/\n\tvar breadcrumbItems = \[[\s\S]*?\];\n/g, '\n'); + s = s.replace(/\n\tvar breadcrumbItems = \[[\s\S]*?\];\n/g, '\n'); + s = s.replace(/\n\t\tbreadcrumbItems\.push\([^)]+\);\n/g, ''); + + // 备车/交车任务 inline breadcrumb rows (partial) + s = s.replace( + /React\.createElement\('div', \{ style: styles\.breadcrumb[^}]*\},[\s\S]*?\),?\n?/g, + '' + ); + s = s.replace( + /React\.createElement\('span', \{ key: '[^']+', style: styles\.breadcrumbSep \}, ' \/ '\),?\n?\s*/g, + '' + ); + + // Arco/fault pages: remove Breadcrumb from Layout header if any remain + s = s.replace(/,\s*breadcrumb:\s*React\.createElement\(Breadcrumb[\s\S]*?\)/g, ''); + + // business pages partial breadcrumb in flex div + s = s.replace( + /React\.createElement\('div', \{ style: \{ display: 'flex', alignItems: 'center', marginBottom: 16 \} \},[\s\S]*?styles\.breadcrumbSep[\s\S]*?\),/g, + '' + ); + + // Fix double spaces in lease contract lines + s = s.replace(/marginBottom: 16 \} \},\s{2}React/g, 'marginBottom: 16 } }, React'); + + // 27-交车管理: broken topbar after breadcrumb removal + s = s.replace( + /React\.createElement\('div', \{ style: \{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 \} \},\s*React\.createElement\(Button,/g, + `React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement(Button,` + ); + + return s; +} + +function fixCollectPageIndent(source) { + return source.replace( + /(\}, '返回'\),\n)\s+React\.createElement\(Button,/g, + `$1\t\t\tReact.createElement(Button,` + ); +} + +const files = walk(ROOT); +let changed = 0; +for (const file of files) { + let source = fs.readFileSync(file, 'utf8'); + if (!/breadcrumb|Breadcrumb|面包屑/.test(source)) continue; + const next = fixCollectPageIndent(cleanup(source)); + if (next !== source) { + fs.writeFileSync(file, next); + changed++; + console.log('fixed:', path.relative(process.cwd(), file)); + } +} +console.log(`Pass 2 done. ${changed} file(s).`); diff --git a/scripts/remove-breadcrumbs-pass3.mjs b/scripts/remove-breadcrumbs-pass3.mjs new file mode 100644 index 0000000..9d93447 --- /dev/null +++ b/scripts/remove-breadcrumbs-pass3.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node +import fs from 'fs'; +import path from 'path'; + +const ROOT = path.resolve('src/prototypes'); + +function walk(dir, out = []) { + for (const name of fs.readdirSync(dir)) { + const full = path.join(dir, name); + if (fs.statSync(full).isDirectory()) { + if (name === 'node_modules' || name === '.spec') continue; + walk(full, out); + } else if (/\.(jsx|tsx)$/.test(name)) out.push(full); + } + return out; +} + +function cleanup(source) { + let s = source; + + // Lease contract topbar: remove span trail, keep req action + s = s.replace( + /React\.createElement\('div', \{ style: \{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 \} \}, React\.createElement\('span'[\s\S]*?\)\),\s*(React\.createElement\('(?:button|span)'[\s\S]*?'查看需求说明'\)[^)]*\)\),/g, + `React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, $1),` + ); + + // Topbar only breadcrumb spans (no req button) + s = s.replace( + /React\.createElement\('div', \{ style: \{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 \} \}, React\.createElement\('span'[\s\S]*?\)\),/g, + '' + ); + + // Unused breadcrumb vars + s = s.replace(/\n\tvar breadcrumbItems = \[[\s\S]*?\];\n/g, '\n'); + s = s.replace(/\n\t\tvar breadcrumbItems = \[[\s\S]*?\];\n/g, '\n'); + s = s.replace(/\n\tvar breadcrumbNodes = \[[\s\S]*?\];\n/g, '\n'); + + // Broken 后装设备 page wrapper + s = s.replace( + /(\{ style: styles\.page \},\n)\s*React\.createElement\('a', \{ href: '#', style: styles\.requirementLink[\s\S]*?'查看需求说明'\)\n\t\t\),/g, + `$1\t\tReact.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } },\n\t\t\tReact.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明')\n\t\t),\n` + ); + + // Empty if blocks left from breadcrumb removal in 业务台账 + s = s.replace(/\n\tif \(view === 'sales' \|\| view === 'project'\) \{\s*\}\n\tif \(view === 'project'\) \{\s*\}\n/g, '\n'); + + return s; +} + +const files = walk(ROOT); +let changed = 0; +for (const file of files) { + let source = fs.readFileSync(file, 'utf8'); + if (!/breadcrumb|Breadcrumb|面包屑/.test(source)) continue; + const next = cleanup(source); + if (next !== source) { + fs.writeFileSync(file, next); + changed++; + console.log('fixed:', path.relative(process.cwd(), file)); + } +} +console.log(`Pass 3 done. ${changed} file(s).`); diff --git a/scripts/remove-breadcrumbs.mjs b/scripts/remove-breadcrumbs.mjs new file mode 100644 index 0000000..05cfc60 --- /dev/null +++ b/scripts/remove-breadcrumbs.mjs @@ -0,0 +1,213 @@ +#!/usr/bin/env node +/** + * Remove breadcrumb UI from prototype pages under src/prototypes. + * Keeps sibling actions such as「查看需求说明」when present in breadcrumbRight. + */ +import fs from 'fs'; +import path from 'path'; + +const ROOT = path.resolve('src/prototypes'); + +function walk(dir, out = []) { + for (const name of fs.readdirSync(dir)) { + const full = path.join(dir, name); + const stat = fs.statSync(full); + if (stat.isDirectory()) { + if (name === 'node_modules' || name === '.spec') continue; + walk(full, out); + } else if (/\.(jsx|tsx)$/.test(name)) { + out.push(full); + } + } + return out; +} + +function removeBalancedFrom(source, openIndex) { + let i = openIndex; + if (source[i] !== '(') return null; + let depth = 0; + let inStr = null; + let escape = false; + for (; i < source.length; i++) { + const ch = source[i]; + if (inStr) { + if (escape) { + escape = false; + continue; + } + if (ch === '\\') { + escape = true; + continue; + } + if (ch === inStr) inStr = null; + continue; + } + if (ch === "'" || ch === '"' || ch === '`') { + inStr = ch; + continue; + } + if (ch === '(') depth++; + else if (ch === ')') { + depth--; + if (depth === 0) return source.slice(openIndex, i + 1); + } + } + return null; +} + +function removeReactBreadcrumbCalls(source) { + const token = 'React.createElement(Breadcrumb,'; + let result = source; + let idx = 0; + while ((idx = result.indexOf(token, idx)) !== -1) { + const open = result.indexOf('(', idx); + const chunk = removeBalancedFrom(result, open); + if (!chunk) break; + let start = idx; + let end = open + chunk.length; + while (start > 0 && /[\t ]/.test(result[start - 1])) start--; + if (result[start - 1] === ',') start--; + if (result[start - 1] === '\n' && result[start - 2] === ',') start--; + while (end < result.length && /[\t ,]/.test(result[end])) end++; + if (result[end] === '\n') end++; + result = result.slice(0, start) + result.slice(end); + idx = start; + } + return result; +} + +function removeBreadcrumbImports(source) { + return source + .replace(/^\s*var Breadcrumb = antd\.Breadcrumb;\n/gm, '') + .replace(/^\s*const Breadcrumb = antd\.Breadcrumb;\n/gm, '') + .replace(/^\s*import\s+\{[^}]*\bBreadcrumb\b[^}]*\}\s+from\s+['"][^'"]+['"];\n/gm, (line) => + line.replace(/\bBreadcrumb,?\s*/g, '').replace(/,\s*,/g, ',').replace(/\{\s*,/g, '{').replace(/,\s*\}/g, '}').replace(/import\s+\{\s*\}\s+from[^;]+;\n/g, '') + ); +} + +function removeNavBreadcrumbs(source) { + return source.replace( + /\n?\s*]*breadcrumb[^>]*>[\s\S]*?<\/nav>\n?/gi, + '\n' + ); +} + +function replaceJsxBreadcrumbBar(source) { + // JSX: breadcrumb row with optional right actions + return source.replace( + /\{\/\*[\s*]*面包屑[\s*]*\*\/\}\s*\n?\s*
[\s\S]*?
([\s\S]*?)<\/div>\s*<\/div>/g, + (_, right) => `\n\t\t\t
${right.trim()}
` + ); +} + +function replaceCreateElementBreadcrumbBar(source) { + // createElement breadcrumb with Left + Right — keep Right only + const pattern = /React\.createElement\(\s*'div',\s*\{ style: styles\.breadcrumb \},[\s\S]*?React\.createElement\(\s*'div',\s*\{ style: styles\.breadcrumbRight \},([\s\S]*?)\)\s*\)/g; + return source.replace( + pattern, + (_, right) => + `React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } }, ${right.trim()})` + ); +} + +function replaceSimpleCreateElementBreadcrumb(source) { + // div with only styles.breadcrumb path segments (no Right sibling) + return source.replace( + /React\.createElement\(\s*'div',\s*\{ style: styles\.breadcrumb[^}]*\},[\s\S]*?\),?\n?/g, + '' + ); +} + +function replaceLeaseContractBreadcrumbRow(source) { + // Long inline breadcrumb + req button row + return source.replace( + /React\.createElement\('div',\s*\{ style: \{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 \} \},\s*React\.createElement\('div',\s*\{ style: styles\.breadcrumb[^}]+\}[\s\S]*?'查看需求说明'\)\),/g, + (match) => { + const req = match.match(/React\.createElement\('button'[\s\S]*?'查看需求说明'\)/); + if (!req) return ''; + return `React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, ${req[0]}),`; + } + ); +} + +function removeEmptyBreadcrumbWrappers(source) { + // div wrapper that only wrapped breadcrumb (now empty) + return source + .replace( + /React\.createElement\('div',\s*\{ style: \{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 \} \},\s*\),?\n?/g, + '' + ) + .replace( + /React\.createElement\('div',\s*\{ style: \{ marginBottom: 16 \} \},\s*\),?\n?/g, + '' + ) + .replace( + /React\.createElement\('div',\s*\{ className: 'vpr-collect-topbar__main' \},\s*\),?\n?/g, + '' + ); +} + +function transformFile(filePath) { + let source = fs.readFileSync(filePath, 'utf8'); + if (!/breadcrumb|Breadcrumb|面包屑/.test(source)) return false; + + const original = source; + source = removeReactBreadcrumbCalls(source); + source = removeBreadcrumbImports(source); + source = removeNavBreadcrumbs(source); + source = replaceJsxBreadcrumbBar(source); + source = replaceCreateElementBreadcrumbBar(source); + source = replaceSimpleCreateElementBreadcrumb(source); + source = replaceLeaseContractBreadcrumbRow(source); + source = removeEmptyBreadcrumbWrappers(source); + + // header with only req link after breadcrumb removed + source = source.replace( + /React\.createElement\(\s*'header',\s*\{ className: 'vr-page-header' \},\s*React\.createElement\(\s*Button,/g, + `React.createElement('header', { className: 'vr-page-header', style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button,` + ); + + // flex topbar: space-between -> flex-end when breadcrumb was first child + source = source.replace( + /justifyContent: 'space-between', marginBottom: 16 \} \},\s*React\.createElement\(Button, \{ type: 'link'/g, + `justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link'` + ); + + if (source !== original) { + fs.writeFileSync(filePath, source); + return true; + } + return false; +} + +const files = walk(ROOT); +let changed = 0; +for (const file of files) { + if (transformFile(file)) { + changed++; + console.log('updated:', path.relative(process.cwd(), file)); + } +} + +// CSS tweak for collect topbar +const vprCss = path.join(ROOT, 'vehicle-pickup-receivable/styles/index.css'); +if (fs.existsSync(vprCss)) { + let css = fs.readFileSync(vprCss, 'utf8'); + const next = css + .replace('/* 办理页顶栏:返回 + 面包屑 */', '/* 办理页顶栏:返回 + 操作 */') + .replace( + /\.vm-page\.vpr-collect-page \.vpr-collect-topbar \{\n display: flex;\n align-items: center;\n gap: 16px;/, + `.vm-page.vpr-collect-page .vpr-collect-topbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px;` + ) + .replace(/\n\.vm-page\.vpr-collect-page \.vpr-collect-topbar__main \{[^}]+\}\n?/g, '\n'); + if (next !== css) { + fs.writeFileSync(vprCss, next); + console.log('updated:', path.relative(process.cwd(), vprCss)); + } +} + +console.log(`\nDone. ${changed} file(s) updated.`); diff --git a/scripts/strip-prototype-nav-from-directory.mjs b/scripts/strip-prototype-nav-from-directory.mjs new file mode 100644 index 0000000..fe25ba0 --- /dev/null +++ b/scripts/strip-prototype-nav-from-directory.mjs @@ -0,0 +1,92 @@ +/** + * 从各原型 annotation-source.json 移除 ONE-OS 原型导航目录节点。 + * + * 用法: + * node scripts/strip-prototype-nav-from-directory.mjs + * node scripts/strip-prototype-nav-from-directory.mjs --prototype lease-contract-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 stripNavNodes(nodes) { + const list = Array.isArray(nodes) ? nodes : []; + return list + .filter((node) => node?.id !== NAV_FOLDER_ID) + .map((node) => { + if (node?.type === 'folder' && Array.isArray(node.children)) { + return { ...node, children: stripNavNodes(node.children) }; + } + return node; + }); +} + +function listPrototypeIds(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 stripPrototype(prototypeId, projectRoot) { + const annotationPath = path.join(projectRoot, 'src/prototypes', prototypeId, 'annotation-source.json'); + if (!fs.existsSync(annotationPath)) return false; + + const annotation = readJson(annotationPath); + const before = JSON.stringify(annotation.directory?.nodes || []); + annotation.directory = { + nodes: stripNavNodes(annotation.directory?.nodes), + }; + const after = JSON.stringify(annotation.directory.nodes || []); + if (before === after) { + console.log(`[skip] ${prototypeId}: 无导航节点`); + return false; + } + + annotation.data = annotation.data || {}; + annotation.data.updatedAt = Date.now(); + writeJson(annotationPath, annotation); + console.log(`[strip] ${prototypeId}: 已移除原型导航目录`); + return true; +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + const projectRoot = args.projectRoot || path.resolve(__dirname, '..'); + const targets = args.prototype ? [args.prototype] : listPrototypeIds(projectRoot); + + let changed = 0; + for (const prototypeId of targets) { + if (stripPrototype(prototypeId, projectRoot)) changed += 1; + } + console.log(`完成:处理 ${targets.length} 个原型,更新 ${changed} 个 annotation-source.json`); +} + +main(); diff --git a/scripts/sync-oneos-web-prototypes.mjs b/scripts/sync-oneos-web-prototypes.mjs index 6bbe39b..ce65f60 100644 --- a/scripts/sync-oneos-web-prototypes.mjs +++ b/scripts/sync-oneos-web-prototypes.mjs @@ -26,6 +26,7 @@ const STANDALONE_MODULES = [ { slug: 'vehicle-maintenance-ledger', title: '车辆维修明细', skipCodegen: true }, { slug: 'oneos-web-h2-station-site', title: '站点信息', skipCodegen: true }, { slug: 'vehicle-return-settlement', title: '还车应结款', skipCodegen: true }, + { slug: 'vehicle-pickup-receivable', title: '提车应收款', skipCodegen: true }, ]; const MODULES = [ @@ -39,7 +40,17 @@ const MODULES = [ title: '财务管理', dir: '财务管理', excludeDirs: ['文档'], - excludeFiles: ['还车应结款.jsx', '还车应结款-查看.jsx', '还车应结款-费用明细.jsx'], + excludeFiles: [ + '还车应结款.jsx', + '还车应结款-查看.jsx', + '还车应结款-费用明细.jsx', + '提车应收款.jsx', + '提车应收款-查看.jsx', + '提车应收款-开票信息.jsx', + '提车应收款-审核.jsx', + '提车应收款-提车收款单.jsx', + '提车收款单-编辑.jsx', + ], }, { slug: 'oneos-web-procurement', title: '采购管理', dir: '采购管理' }, { slug: 'oneos-web-lease-contract', title: '车辆租赁合同', dir: '车辆租赁合同' }, diff --git a/scripts/sync-project-prototype-directory.mjs b/scripts/sync-project-prototype-directory.mjs index 7e35b5d..27fc9a5 100644 --- a/scripts/sync-project-prototype-directory.mjs +++ b/scripts/sync-project-prototype-directory.mjs @@ -1,5 +1,6 @@ /** - * 将 .axhub/make/sidebar-tree.json 左侧菜单同步到各原型 annotation-source.json 的「原型目录」。 + * 将 .axhub/make/sidebar-tree.json 同步到原型导航页 nav-menu.json。 + * 不再向各原型 annotation-source.json 注入「ONE-OS 原型导航」目录。 * * 用法: * node scripts/sync-project-prototype-directory.mjs @@ -33,56 +34,6 @@ 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)) { @@ -102,6 +53,18 @@ function listPrototypeIdsWithAnnotation(projectRoot) { .sort((a, b) => a.localeCompare(b, 'zh-CN')); } +function stripNavNodes(nodes) { + const list = Array.isArray(nodes) ? nodes : []; + return list + .filter((node) => node?.id !== NAV_FOLDER_ID) + .map((node) => { + if (node?.type === 'folder' && Array.isArray(node.children)) { + return { ...node, children: stripNavNodes(node.children) }; + } + return node; + }); +} + function fixPublishedLinks(nodes, prototypeId) { const walk = (items) => { for (const node of items || []) { @@ -127,7 +90,7 @@ function fixPublishedLinks(nodes, prototypeId) { walk(nodes); } -function syncPrototypeDirectory(prototypeId, sidebarItems, projectRoot) { +function cleanupPrototypeDirectory(prototypeId, projectRoot) { const annotationPath = path.join(projectRoot, 'src/prototypes', prototypeId, 'annotation-source.json'); if (!fs.existsSync(annotationPath)) { console.log(`[skip] ${prototypeId}: 无 annotation-source.json`); @@ -135,27 +98,22 @@ function syncPrototypeDirectory(prototypeId, sidebarItems, projectRoot) { } 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); + const cleanedNodes = stripNavNodes(existingNodes); + fixPublishedLinks(cleanedNodes, prototypeId); - annotation.directory = { - nodes: [navFolder, ...preservedNodes], - }; + const before = JSON.stringify(existingNodes); + const after = JSON.stringify(cleanedNodes); + if (before === after) { + console.log(`[ok] ${prototypeId}: 目录已不含原型导航`); + return false; + } + + annotation.directory = { nodes: cleanedNodes }; annotation.data = annotation.data || {}; annotation.data.updatedAt = Date.now(); - writeJson(annotationPath, annotation); - console.log(`[sync] ${prototypeId}: 已写入 ${navChildren.length} 个顶级目录项(含分组)`); + console.log(`[cleanup] ${prototypeId}: 已移除原型导航目录节点`); return true; } @@ -186,11 +144,11 @@ function main() { let changed = 0; for (const prototypeId of targets) { - if (syncPrototypeDirectory(prototypeId, sidebarItems, projectRoot)) { + if (cleanupPrototypeDirectory(prototypeId, projectRoot)) { changed += 1; } } - console.log(`完成:处理 ${targets.length} 个原型,更新 ${changed} 个 annotation-source.json`); + console.log(`完成:处理 ${targets.length} 个原型,清理 ${changed} 个 annotation-source.json`); syncPrototypeNavMenu(projectRoot, sidebarItems); } diff --git a/skills-lock.json b/skills-lock.json index 45635e1..85f6625 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -1,6 +1,12 @@ { "version": 1, "skills": { + "extract-annotation-source": { + "source": "lintendo/Axhub-Skills", + "sourceType": "github", + "skillPath": "skills/extract-annotation-source/SKILL.md", + "computedHash": "github-main-sync-2026-07-09" + }, "to-prd": { "source": "mattpocock/skills", "sourceType": "github", diff --git a/src/common/OperationActions.tsx b/src/common/OperationActions.tsx new file mode 100644 index 0000000..7ece30b --- /dev/null +++ b/src/common/OperationActions.tsx @@ -0,0 +1,263 @@ +import React, { useMemo, useState } from 'react'; +import { Dropdown } from 'antd'; +import type { MenuProps } from 'antd'; +import { + ArrowRightLeft, + Car, + CircleDollarSign, + Eye, + FileText, + MoreHorizontal, + OctagonX, + Pencil, + RefreshCw, + Trash2, + Undo2, + Upload, + Users, + type LucideIcon, +} from 'lucide-react'; + +export type OperationActionItem = { + key: string; + label: string; + onClick: () => void; + danger?: boolean; + disabled?: boolean; + hidden?: boolean; + icon?: LucideIcon; +}; + +const MORE_ACTION_ICONS: Record = { + edit: Pencil, + del: Trash2, + addVehicle: Car, + renew: RefreshCw, + authorized: FileText, + extraFee: CircleDollarSign, + toTripartite: Users, + terminate: OctagonX, + withdraw: Undo2, + toFormal: ArrowRightLeft, + stampSupplement: Upload, + uploadStamped: Upload, +}; + +function renderMenuItemLabel(item: OperationActionItem) { + const Icon = item.icon ?? MORE_ACTION_ICONS[item.key]; + return ( + + {Icon ? ( + + ) : ( + + )} + {item.label} + + ); +} + +export type OperationPrimaryAction = { + onClick: () => void; + label?: string; + hidden?: boolean; + disabled?: boolean; +}; + +export type OperationActionsProps = { + /** 详情:固定外显主操作 */ + view?: OperationPrimaryAction; + /** + * @deprecated 编辑已收入「更多」下拉,请放入 more 数组 + */ + edit?: OperationPrimaryAction; + /** 其余操作(含编辑)收入「更多」下拉 */ + more?: OperationActionItem[]; + /** 更多按钮文案,默认「更多」 */ + moreLabel?: string; + className?: string; + annotationId?: string; +}; + +function ViewIcon() { + return ; +} + +function MoreIcon() { + return ; +} + +const PrimaryActionButton = React.forwardRef< + HTMLButtonElement, + { + icon: 'view' | 'more'; + label: string; + onClick?: (event: React.MouseEvent) => void; + disabled?: boolean; + ariaLabel: string; + ariaHasPopup?: boolean; + className?: string; + } +>(function PrimaryActionButton( + { + icon, + label, + onClick, + disabled, + ariaLabel, + ariaHasPopup, + className, + }, + ref, +) { + return ( + + ); +}); + +function mergeMoreItems( + more: OperationActionItem[], + edit?: OperationPrimaryAction, +): OperationActionItem[] { + const visibleMore = more.filter((item) => !item.hidden); + if (!edit || edit.hidden) return visibleMore; + const hasEdit = visibleMore.some((item) => item.key === 'edit' || item.label === '编辑'); + if (hasEdit) return visibleMore; + return [ + { + key: 'edit', + label: edit.label || '编辑', + onClick: edit.onClick, + disabled: edit.disabled, + }, + ...visibleMore, + ]; +} + +export function OperationActions({ + view, + edit, + more = [], + moreLabel = '更多', + className, + annotationId, +}: OperationActionsProps) { + const [moreOpen, setMoreOpen] = useState(false); + const visibleMore = useMemo( + () => mergeMoreItems(more, edit), + [more, edit], + ); + + const menuItems: MenuProps['items'] = useMemo(() => { + const items: MenuProps['items'] = []; + visibleMore.forEach((item, index) => { + if (item.danger && index > 0 && !visibleMore[index - 1]?.danger) { + items.push({ type: 'divider' }); + } + items.push({ + key: item.key, + label: renderMenuItemLabel(item), + danger: item.danger, + disabled: item.disabled, + className: item.danger ? 'vm-op-dropdown-menu-item--danger' : undefined, + }); + }); + return items; + }, [visibleMore]); + + const showView = !!(view && !view.hidden); + const showMore = visibleMore.length > 0; + + if (!showView && !showMore) { + return -; + } + + const handleMenuClick: MenuProps['onClick'] = ({ key, domEvent }) => { + domEvent.stopPropagation(); + const target = visibleMore.find((item) => item.key === key); + if (target && !target.disabled) target.onClick(); + }; + + return ( +
event.stopPropagation()} + > + {showView ? ( + { + event.stopPropagation(); + if (!view?.disabled) view!.onClick(); + }} + /> + ) : null} + {showMore ? ( + document.body} + overlayClassName="vm-op-dropdown" + open={moreOpen} + onOpenChange={setMoreOpen} + align={{ offset: [0, 4] }} + > + + + ) : null} +
+ ); +} + +/** + * 从扁平操作列表中拆分出 view / more(编辑收入 more)。 + */ +export function splitOperationActions(items: OperationActionItem[]): { + view?: OperationPrimaryAction; + more: OperationActionItem[]; +} { + const visible = items.filter((item) => !item.hidden); + const viewItem = visible.find((item) => item.label === '详情' || item.label === '查看' || item.key === 'view'); + const more = visible.filter((item) => item !== viewItem); + return { + view: viewItem + ? { + onClick: viewItem.onClick, + label: viewItem.label === '查看' ? '详情' : viewItem.label, + disabled: viewItem.disabled, + } + : undefined, + more, + }; +} diff --git a/src/common/format-number.js b/src/common/format-number.js new file mode 100644 index 0000000..b92c21f --- /dev/null +++ b/src/common/format-number.js @@ -0,0 +1,56 @@ +/** + * 全局数字/金额格式化(千分位 + 固定小数位) + * 与 vehicle-management `.vm-expire-date` 数字展示规范配套使用。 + */ + +function toFiniteNumber(value, fallback = 0) { + if (value === null || value === undefined || value === '') return fallback; + const n = typeof value === 'number' ? value : parseFloat(String(value).replace(/,/g, '')); + return Number.isFinite(n) ? n : fallback; +} + +/** + * @param {number|string|null|undefined} value + * @param {{ fractionDigits?: number, empty?: string }} [options] + */ +export function formatNumber(value, options) { + const fractionDigits = options && options.fractionDigits != null ? options.fractionDigits : 2; + const empty = options && options.empty != null ? options.empty : null; + if (value === null || value === undefined || value === '') { + return empty != null ? empty : (fractionDigits > 0 ? (0).toFixed(fractionDigits) : '0'); + } + const n = toFiniteNumber(value, NaN); + if (Number.isNaN(n)) { + return empty != null ? empty : (fractionDigits > 0 ? (0).toFixed(fractionDigits) : '0'); + } + return n.toLocaleString('zh-CN', { + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + }); +} + +/** 金额(默认 2 位小数,无货币符号) */ +export function formatMoney(value, options) { + if (value == null || value === '') { + if (options && options.empty != null) return options.empty; + } + if (typeof value === 'number' && Number.isNaN(value)) { + if (options && options.empty != null) return options.empty; + } + return formatNumber(value, Object.assign({ fractionDigits: 2 }, options || {})); +} + +/** 提车应收款等列表沿用的金额字符串(无「元」后缀) */ +export function formatMoneyYuan(value) { + return formatMoney(value); +} + +/** 金额 + 单位,默认「元」 */ +export function formatMoneyWithUnit(value, unit) { + return formatMoney(value) + (unit != null ? unit : ' 元'); +} + +/** 整数数量(里程、计数等) */ +export function formatInteger(value) { + return formatNumber(value, { fractionDigits: 0 }); +} diff --git a/src/common/ln-numeric.css b/src/common/ln-numeric.css new file mode 100644 index 0000000..08a2b86 --- /dev/null +++ b/src/common/ln-numeric.css @@ -0,0 +1,121 @@ +/** + * 全局数字字体:对齐车辆管理「交强险到期时间」(.vm-expire-date) + * — JetBrains Mono / 系统等宽 + tabular-nums + */ + +.tabular-nums, +.ln-num { + font-family: var( + --vm-font-mono, + ui-monospace, + "JetBrains Mono", + SFMono-Regular, + Menlo, + Consolas, + monospace + ); + font-variant-numeric: tabular-nums; + font-feature-settings: "tnum" 1; +} + +.ln-money, +.ldb-col-money, +.ln-modal-detail-table__money, +.pr-linked-bill-native-table__money, +.vpr-money, +.vpr-money-cell, +.vpr-collect-kpi-card__value, +.vpr-collect-batch-panel__amount, +.vpr-collect-footer__summary strong, +.vpr-collect-td--num { + font-family: var( + --vm-font-mono, + ui-monospace, + "JetBrains Mono", + SFMono-Regular, + Menlo, + Consolas, + monospace + ); + font-variant-numeric: tabular-nums; + font-feature-settings: "tnum" 1; +} + +.vm-kpi-val, +.vm-mileage, +.vm-expire-date, +.vm-handover-situation-line--mile, +.vm-handover-situation-line--time, +.vm-detail-gps-time, +.vm-detail-plate, +.vm-location-map-gps-time, +.vm-pagination-total-num, +.vm-filter-toggle-badge, +.vm-vin, +.vm-stack .code, +.vm-stack .vm-link.code { + font-family: var( + --vm-font-mono, + ui-monospace, + "JetBrains Mono", + SFMono-Regular, + Menlo, + Consolas, + monospace + ); + font-variant-numeric: tabular-nums; + font-feature-settings: "tnum" 1; +} + +/* 链接态「未购买」等文案仍用正文字体 */ +.vm-expire .vm-link.vm-expire-date { + font-family: inherit; + font-variant-numeric: normal; + font-feature-settings: normal; +} + +.vm-page .ant-table .tabular-nums, +.vm-page .ant-table-cell.tabular-nums, +.vm-page .vm-list-table .tabular-nums { + font-family: var( + --vm-font-mono, + ui-monospace, + "JetBrains Mono", + SFMono-Regular, + Menlo, + Consolas, + monospace + ); + font-variant-numeric: tabular-nums; +} + +.vm-page .ant-input.tabular-nums, +.vm-page .ant-input-affix-wrapper.tabular-nums .ant-input, +.lc-form-readonly.tabular-nums, +.vm-stack .sub.tabular-nums { + font-family: var( + --vm-font-mono, + ui-monospace, + "JetBrains Mono", + SFMono-Regular, + Menlo, + Consolas, + monospace + ); + font-variant-numeric: tabular-nums; + font-feature-settings: "tnum" 1; +} + +.vm-page .ant-input-number-input, +.vm-page .ant-input-affix-wrapper .ant-input[data-money="true"] { + font-family: var( + --vm-font-mono, + ui-monospace, + "JetBrains Mono", + SFMono-Regular, + Menlo, + Consolas, + monospace + ); + font-variant-numeric: tabular-nums; +} diff --git a/src/common/oneosWebLegacy/legacyGlobals.ts b/src/common/oneosWebLegacy/legacyGlobals.ts index 8f39cb5..0e039c1 100644 --- a/src/common/oneosWebLegacy/legacyGlobals.ts +++ b/src/common/oneosWebLegacy/legacyGlobals.ts @@ -1,12 +1,24 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import * as antd from 'antd'; +import { + formatMoney, + formatMoneyYuan, + formatMoneyWithUnit, + formatNumber, + formatInteger, +} from '../format-number.js'; declare global { interface Window { React: typeof React; ReactDOM: typeof ReactDOM; antd: typeof antd; + __oneosFormatNumber: typeof formatNumber; + __oneosFormatMoney: typeof formatMoney; + __oneosFormatMoneyYuan: typeof formatMoneyYuan; + __oneosFormatMoneyWithUnit: typeof formatMoneyWithUnit; + __oneosFormatInteger: typeof formatInteger; } } @@ -15,6 +27,11 @@ export function ensureOneosWebLegacyGlobals(): void { window.React = React; window.ReactDOM = ReactDOM; window.antd = antd; + window.__oneosFormatNumber = formatNumber; + window.__oneosFormatMoney = formatMoney; + window.__oneosFormatMoneyYuan = formatMoneyYuan; + window.__oneosFormatMoneyWithUnit = formatMoneyWithUnit; + window.__oneosFormatInteger = formatInteger; } // Legacy jsx 可能在模块顶层使用 React.createElement,须在页面 import 之前完成注入。 diff --git a/src/common/operation-actions-spec.md b/src/common/operation-actions-spec.md new file mode 100644 index 0000000..cafb8ef --- /dev/null +++ b/src/common/operation-actions-spec.md @@ -0,0 +1,48 @@ +# OperationActions · 列表操作列规范 + +> 完整规范见 `src/prototypes/vm-shared/DESIGN.md` · 操作列 OperationActions + +## 组件 + +- 路径:`src/common/OperationActions.tsx` +- 样式:`src/common/vm-operation-actions.css`(经 `vehicle-management/style.css` 全局引入) + +## 布局 + +``` +[👁 详情] [✏️ 编辑] [⋮] +``` + +- **详情 / 编辑**:外显,带 Lucide 图标 + 文案,最小触控 44×44px +- **更多**:仅图标 `MoreHorizontal`,点击 Ant Dropdown 展开其余操作 +- 无详情/编辑时省略对应按钮;无任何操作时显示 `-` + +## 迁移步骤 + +1. 确认页面已 `import '../vehicle-management/style.css'` +2. `import { OperationActions, splitOperationActions } from '../../common/OperationActions'` +3. 将原 `getOperationButtons` / 内联 `Button link` 改为: + +```tsx + ... }} + edit={canEdit ? { onClick: () => ... } : undefined} + more={[...]} +/> +``` + +4. 操作列宽建议 `168`,`fixed: 'right'` +5. 从 `more` 中移除已外显的「详情」「编辑」项 + +## 扁平列表迁移 + +```tsx +const { view, edit, more } = splitOperationActions(allItems); +return ; +``` + +## 禁止 + +- 横排超过 2 个 `Button type="link"` 主操作 +- 文字「更多」链接触发器 +- pipe `|` 分隔操作链接 diff --git a/src/common/prototype-annotation-host.tsx b/src/common/prototype-annotation-host.tsx new file mode 100644 index 0000000..689e8ed --- /dev/null +++ b/src/common/prototype-annotation-host.tsx @@ -0,0 +1,37 @@ +import React, { useMemo } from 'react'; +import { + AnnotationViewer, + type AnnotationSourceDocument, + type AnnotationViewerOptions, +} from '@axhub/annotation'; +import { useAnnotationMermaidRenderer } from './prototype-annotation-mermaid'; +import { buildAnnotationViewerSource } from './prototype-annotation-utils'; + +export interface PrototypeAnnotationHostProps { + source: AnnotationSourceDocument; + options: AnnotationViewerOptions; + children?: React.ReactNode; +} + +/** + * 统一标注壳:PRD 分章节展示在右侧「原型标注」目录面板, + * 不包含跨原型导航与目录内页面跳转节点。 + */ +export function PrototypeAnnotationHost(props: PrototypeAnnotationHostProps) { + const { source, options, children } = props; + useAnnotationMermaidRenderer(); + + const viewerSource = useMemo( + () => buildAnnotationViewerSource(source, options.currentPageId), + [source, options.currentPageId], + ); + + return ( + <> + {children} + + + ); +} + +export default PrototypeAnnotationHost; diff --git a/src/common/prototype-annotation-mermaid.css b/src/common/prototype-annotation-mermaid.css new file mode 100644 index 0000000..041a974 --- /dev/null +++ b/src/common/prototype-annotation-mermaid.css @@ -0,0 +1,19 @@ +.axhub-mermaid-diagram { + display: block; + max-width: 100%; + margin: 8px 0; + overflow-x: auto; + border-radius: 8px; + background: #f8fafc; + padding: 8px; +} + +.axhub-mermaid-diagram svg { + display: block; + max-width: 100%; + height: auto; +} + +.axhub-annotation-directory-reader .axhub-mermaid-diagram { + padding: 12px; +} diff --git a/src/common/prototype-annotation-mermaid.tsx b/src/common/prototype-annotation-mermaid.tsx new file mode 100644 index 0000000..2e54574 --- /dev/null +++ b/src/common/prototype-annotation-mermaid.tsx @@ -0,0 +1,111 @@ +import { useEffect } from 'react'; +import './prototype-annotation-mermaid.css'; + +const MERMAID_SELECTOR = 'pre code.language-mermaid'; + +let mermaidReady = false; +let renderQueue = Promise.resolve(); + +function collectShadowRoots(root: ParentNode): ShadowRoot[] { + const roots: ShadowRoot[] = []; + root.querySelectorAll('*').forEach((element) => { + if (element.shadowRoot) { + roots.push(element.shadowRoot); + roots.push(...collectShadowRoots(element.shadowRoot)); + } + }); + return roots; +} + +function queryAllIncludingShadowRoots(selector: string, root: ParentNode = document): HTMLElement[] { + const results: HTMLElement[] = []; + root.querySelectorAll(selector).forEach((node) => { + if (node instanceof HTMLElement) results.push(node); + }); + collectShadowRoots(root).forEach((shadowRoot) => { + results.push(...queryAllIncludingShadowRoots(selector, shadowRoot)); + }); + return results; +} + +function observeDeepMutations(callback: () => void) { + const observed = new WeakSet(); + + const observeNode = (node: Node) => { + if (!(node instanceof Element) || observed.has(node)) return; + observed.add(node); + const observer = new MutationObserver(callback); + observer.observe(node, { childList: true, subtree: true }); + collectShadowRoots(node).forEach((shadowRoot) => observeNode(shadowRoot)); + node.querySelectorAll('*').forEach((child) => { + if (child.shadowRoot) observeNode(child.shadowRoot); + }); + }; + + observeNode(document.body); + return () => { + // MutationObserver instances are GC'd with observed nodes on unmount. + }; +} + +function scheduleMermaidRender() { + renderQueue = renderQueue.then(async () => { + const codes = queryAllIncludingShadowRoots(MERMAID_SELECTOR); + if (codes.length === 0) return; + + const mermaid = (await import('mermaid')).default; + if (!mermaidReady) { + mermaid.initialize({ + startOnLoad: false, + theme: 'neutral', + securityLevel: 'strict', + fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif', + }); + mermaidReady = true; + } + + for (const code of codes) { + const pre = code.parentElement; + if (!pre || pre.dataset.mermaidRendered === 'true') continue; + + const graphDefinition = code.textContent?.trim(); + if (!graphDefinition) continue; + + pre.dataset.mermaidRendered = 'pending'; + const id = `axhub-mermaid-${Math.random().toString(36).slice(2, 10)}`; + + try { + const { svg } = await mermaid.render(id, graphDefinition); + const ownerDocument = pre.ownerDocument ?? document; + const container = ownerDocument.createElement('div'); + container.className = 'axhub-mermaid-diagram'; + container.setAttribute('role', 'img'); + container.setAttribute('aria-label', '流程图'); + container.innerHTML = svg; + pre.replaceWith(container); + } catch (error) { + pre.dataset.mermaidRendered = 'error'; + console.warn('[axhub-mermaid] render failed:', error); + } + } + }); +} + +/** + * 将标注面板 Markdown 中的 ```mermaid 代码块渲染为 SVG 流程图。 + */ +export function useAnnotationMermaidRenderer() { + useEffect(() => { + scheduleMermaidRender(); + + const onMutate = () => { + scheduleMermaidRender(); + }; + + const observer = new MutationObserver(onMutate); + observer.observe(document.body, { childList: true, subtree: true }); + observeDeepMutations(onMutate); + + return () => observer.disconnect(); + }, []); +} diff --git a/src/common/prototype-annotation-utils.ts b/src/common/prototype-annotation-utils.ts new file mode 100644 index 0000000..06b877a --- /dev/null +++ b/src/common/prototype-annotation-utils.ts @@ -0,0 +1,104 @@ +import type { AnnotationSourceDocument } from '@axhub/annotation'; + +export const PROTOTYPE_NAV_FOLDER_ID = 'oneos-project-nav'; + +type DirectoryNode = { + id?: string; + type?: string; + title?: string; + markdown?: string; + markdownId?: string; + markdownPath?: string; + children?: DirectoryNode[]; + payload?: { pageId?: string }; +}; + +function isNavFolder(node: DirectoryNode | undefined): boolean { + return node?.type === 'folder' && node.id === PROTOTYPE_NAV_FOLDER_ID; +} + +function isDirectoryJumpNode(node: DirectoryNode | undefined): boolean { + return node?.type === 'link' || node?.type === 'route'; +} + +function collectRoutePageIds(nodes: DirectoryNode[] | undefined): string[] { + const pageIds: string[] = []; + const walk = (items: DirectoryNode[] | undefined) => { + for (const node of items || []) { + if (node.type === 'route') { + const pageId = String(node.payload?.pageId || '').trim(); + if (pageId) pageIds.push(pageId); + } + if (node.type === 'folder' && Array.isArray(node.children)) { + walk(node.children); + } + } + }; + walk(nodes); + return pageIds; +} + +function folderMatchesPage(folder: DirectoryNode, currentPageId?: string): boolean { + if (!currentPageId) return true; + const pageIds = collectRoutePageIds(folder.children); + if (!pageIds.length) return true; + return pageIds.includes(currentPageId); +} + +function filterDirectoryNodesForAnnotationPanel( + nodes: DirectoryNode[] | undefined, + currentPageId?: string, +): DirectoryNode[] { + const result: DirectoryNode[] = []; + + for (const node of nodes || []) { + if (isNavFolder(node) || isDirectoryJumpNode(node)) continue; + + if (node.type === 'markdown') { + result.push(node); + continue; + } + + if (node.type === 'folder') { + if (!folderMatchesPage(node, currentPageId)) continue; + const children = filterDirectoryNodesForAnnotationPanel(node.children, currentPageId); + if (!children.length) continue; + result.push({ + ...node, + children, + }); + } + } + + return result; +} + +/** 供右侧「原型标注」目录使用:保留 PRD 分节,去除导航与页面跳转节点 */ +export function buildAnnotationViewerSource( + source: AnnotationSourceDocument, + currentPageId?: string, +): AnnotationSourceDocument { + const nodes = filterDirectoryNodesForAnnotationPanel( + source.directory?.nodes as DirectoryNode[] | undefined, + currentPageId, + ); + + return { + ...source, + directory: { nodes }, + }; +} + +export function stripPrototypeNavFromDirectoryNodes( + nodes: DirectoryNode[] | undefined, +): DirectoryNode[] { + return (nodes || []) + .filter((node) => !isNavFolder(node)) + .map((node) => { + if (node.type !== 'folder' || !Array.isArray(node.children)) return node; + return { + ...node, + children: stripPrototypeNavFromDirectoryNodes(node.children), + }; + }); +} diff --git a/src/common/vm-button-icons.css b/src/common/vm-button-icons.css new file mode 100644 index 0000000..783396d --- /dev/null +++ b/src/common/vm-button-icons.css @@ -0,0 +1,111 @@ +/** + * ONE-OS vm-btn 图标 — 通过 data-vm-icon 统一左侧图标 + * 规范:所有 vm-btn(除 link / back)均应带图标 + */ + +.vm-btn[data-vm-icon]::before { + content: ''; + width: 16px; + height: 16px; + flex-shrink: 0; + background-color: currentColor; + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + -webkit-mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; +} + +.vm-btn[data-vm-icon='search']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='rotate-ccw']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8'/%3E%3Cpath d='M3 3v5h5'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8'/%3E%3Cpath d='M3 3v5h5'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='download']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='7 10 12 15 17 10'/%3E%3Cline x1='12' x2='12' y1='15' y2='3'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='7 10 12 15 17 10'/%3E%3Cline x1='12' x2='12' y1='15' y2='3'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='plus']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='M12 5v14'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='M12 5v14'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='x']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='save']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z'/%3E%3Cpath d='M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7'/%3E%3Cpath d='M7 3v4a1 1 0 0 0 1 1h7'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z'/%3E%3Cpath d='M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7'/%3E%3Cpath d='M7 3v4a1 1 0 0 0 1 1h7'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='send']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m22 2-7 20-4-9-9-4z'/%3E%3Cpath d='M22 2 11 13'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m22 2-7 20-4-9-9-4z'/%3E%3Cpath d='M22 2 11 13'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='filter']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 3H2l8 9.46V19l4 2v-8.54z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M22 3H2l8 9.46V19l4 2v-8.54z'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='chevron-up']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m18 15-6-6-6 6'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m18 15-6-6-6 6'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='chevron-left']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m15 18-6-6 6-6'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m15 18-6-6 6-6'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='chevron-right']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='upload']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='17 8 12 3 7 8'/%3E%3Cline x1='12' x2='12' y1='3' y2='15'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3Cpolyline points='17 8 12 3 7 8'/%3E%3Cline x1='12' x2='12' y1='3' y2='15'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='eye']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='trash']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6h18'/%3E%3Cpath d='M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6'/%3E%3Cpath d='M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6h18'/%3E%3Cpath d='M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6'/%3E%3Cpath d='M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='import']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='m8 11 4 4 4-4'/%3E%3Cpath d='M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12'/%3E%3Cpath d='m8 11 4 4 4-4'/%3E%3Cpath d='M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4'/%3E%3C/svg%3E"); +} + +.vm-btn[data-vm-icon='settings']::before { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E"); +} + +/* 行内 SVG 图标统一尺寸(迁移前兼容) */ +.vm-btn > svg { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +/* 预览翻页等紧凑按钮 */ +.vm-btn.ct-preview-pager__btn { + min-width: 0; + padding: 0 10px; +} diff --git a/src/common/vm-operation-actions.css b/src/common/vm-operation-actions.css new file mode 100644 index 0000000..28aade4 --- /dev/null +++ b/src/common/vm-operation-actions.css @@ -0,0 +1,194 @@ +/** + * 全局列表操作列 — OperationActions + * 规范:vm-shared/DESIGN.md · 操作列 + */ + +.vm-operation-actions { + display: inline-flex; + align-items: center; + gap: 2px; + flex-wrap: nowrap; +} + +.vm-op-action { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + min-width: 44px; + min-height: 44px; + padding: 0 6px; + border: none; + border-radius: var(--ln-radius-control, 6px); + background: transparent; + color: var(--ln-link, var(--ln-primary, #32a06e)); + font-size: 13px; + font-weight: 500; + line-height: 1.2; + cursor: pointer; + transition: color 0.15s ease, background-color 0.15s ease; +} + +.vm-op-action:hover, +.vm-op-action:focus-visible { + color: var(--ln-primary-hover, #278a5c); + background: color-mix(in srgb, var(--ln-primary, #32a06e) 8%, transparent); + outline: none; +} + +.vm-op-action:focus-visible { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary, #32a06e) 35%, transparent); +} + +.vm-op-action__icon { + width: 16px; + height: 16px; + flex-shrink: 0; + color: currentColor; +} + +.vm-op-action__label { + white-space: nowrap; +} + +.vm-op-action--open, +.vm-op-action[aria-expanded="true"] { + color: var(--ln-primary-hover, #278a5c); + background: color-mix(in srgb, var(--ln-primary, #32a06e) 12%, transparent); +} + +.vm-op-more-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 44px; + min-width: 44px; + height: 44px; + padding: 0; + border: none; + border-radius: var(--ln-radius-control, 6px); + background: transparent; + color: var(--ln-muted, #64748b); + cursor: pointer; + transition: color 0.15s ease, background-color 0.15s ease; +} + +.vm-op-more-btn:hover, +.vm-op-more-btn:focus-visible { + color: var(--ln-ink, #0f172a); + background: var(--ln-canvas-soft, #f4f4f5); + outline: none; +} + +.vm-op-more-btn:focus-visible { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary, #32a06e) 35%, transparent); +} + +.vm-op-more-btn__icon { + width: 18px; + height: 18px; + flex-shrink: 0; +} + +/* 更多下拉:挂到 body,使用 overlayClassName 命中 */ +.vm-op-dropdown.ant-dropdown { + z-index: 1060; +} + +.vm-op-dropdown .vm-op-dropdown-menu.ant-dropdown-menu { + min-width: 176px; + padding: 6px; + border: 1px solid color-mix(in srgb, var(--ln-ink, #0f172a) 8%, transparent); + border-radius: 10px; + background: var(--ln-surface-card, #fff); + box-shadow: + 0 12px 28px -8px rgba(15, 23, 42, 0.16), + 0 4px 10px -4px rgba(15, 23, 42, 0.08); +} + +.vm-op-dropdown .ant-dropdown-menu-item { + display: flex; + align-items: center; + min-height: 40px; + margin: 2px 0; + padding: 8px 10px; + border-radius: 6px; + font-size: 14px; + line-height: 1.4; + color: var(--ln-ink, #0f172a); + transition: background-color 0.15s ease, color 0.15s ease; +} + +.vm-op-dropdown .ant-dropdown-menu-item .ant-dropdown-menu-title-content { + flex: 1; + min-width: 0; +} + +.vm-op-dropdown .ant-dropdown-menu-item:not(.ant-dropdown-menu-item-disabled):hover, +.vm-op-dropdown .ant-dropdown-menu-item:not(.ant-dropdown-menu-item-disabled).ant-dropdown-menu-item-active { + background: var(--ln-canvas-soft, #f4f4f5); +} + +.vm-op-dropdown .ant-dropdown-menu-item-divider { + margin: 4px 8px; + background: color-mix(in srgb, var(--ln-ink, #0f172a) 8%, transparent); +} + +.vm-op-menu-item { + display: inline-flex; + align-items: center; + gap: 8px; + width: 100%; +} + +.vm-op-menu-item__icon, +.vm-op-menu-item__icon-spacer { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.vm-op-menu-item__icon { + color: var(--ln-muted, #64748b); +} + +.vm-op-menu-item__label { + flex: 1; + white-space: nowrap; +} + +.vm-op-dropdown .vm-op-dropdown-menu-item--danger:not(.ant-dropdown-menu-item-disabled) .vm-op-menu-item__label, +.vm-op-dropdown .ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled) .vm-op-menu-item__label { + color: var(--ln-error, #e5484d); + font-weight: 500; +} + +.vm-op-dropdown .vm-op-dropdown-menu-item--danger:not(.ant-dropdown-menu-item-disabled) .vm-op-menu-item__icon, +.vm-op-dropdown .ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled) .vm-op-menu-item__icon { + color: var(--ln-error, #e5484d); +} + +.vm-op-dropdown .vm-op-dropdown-menu-item--danger:not(.ant-dropdown-menu-item-disabled):hover, +.vm-op-dropdown .vm-op-dropdown-menu-item--danger:not(.ant-dropdown-menu-item-disabled).ant-dropdown-menu-item-active, +.vm-op-dropdown .ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover, +.vm-op-dropdown .ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled).ant-dropdown-menu-item-active { + background: color-mix(in srgb, var(--ln-error, #e5484d) 8%, transparent); +} + +.vm-op-dropdown .ant-dropdown-menu-item-disabled { + opacity: 0.45; + cursor: not-allowed; +} + +.vm-operation-actions .ant-dropdown-menu-item-danger, +.vm-operation-actions .ant-dropdown-menu-item-danger > span { + color: var(--ln-error, #e5484d) !important; +} + +@media (prefers-reduced-motion: reduce) { + .vm-op-action, + .vm-op-more-btn, + .vm-op-dropdown .ant-dropdown-menu-item { + transition: none; + } +} diff --git a/src/prototypes/business-dept-ledger/styles/index.css b/src/prototypes/business-dept-ledger/styles/index.css index a9d5e48..0a4b8fe 100644 --- a/src/prototypes/business-dept-ledger/styles/index.css +++ b/src/prototypes/business-dept-ledger/styles/index.css @@ -39,7 +39,7 @@ min-height: var(--vm-control-height); height: var(--vm-control-height); border-radius: var(--ln-radius-sm); - border-color: var(--ln-border); + border-color: var(--ln-hairline-strong); font-size: 13px; } @@ -113,7 +113,7 @@ .vm-page.bdl-page .bdl-report-hint { margin: 0; font-size: 12px; - color: var(--ln-subtle); + color: var(--ln-muted-soft); line-height: 1.45; } @@ -126,8 +126,9 @@ overflow: hidden; max-height: none; min-width: 0; + border: 1px solid var(--ln-hairline); border-radius: var(--ln-radius-card); - box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.06); + background: var(--ln-surface-card); } .vm-page.bdl-page .bdl-list-table.ant-table-wrapper, @@ -136,55 +137,78 @@ max-width: 100%; } +/* —— 表格边框与表头(使用 Linear token,避免无效变量) —— */ .vm-page.bdl-page .bdl-list-table .ant-table-container { border-radius: var(--ln-radius-card); + border: none; +} + +.vm-page.bdl-page .bdl-list-table.ant-table-bordered .ant-table-container { + border: none; } .vm-page.bdl-page .bdl-list-table .ant-table-thead > tr > th, -.vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td { +.vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td, +.vm-page.bdl-page .bdl-list-table .ant-table-summary > tr > td { white-space: nowrap; font-variant-numeric: tabular-nums; - padding: 0 10px !important; - height: 38px !important; + padding: 8px 12px !important; font-size: 13px; + border-color: var(--ln-hairline) !important; + box-sizing: border-box; + overflow: visible; + text-overflow: clip; } -.vm-page.bdl-page .bdl-list-table .ant-table-thead > tr > th { +.vm-page.bdl-page .bdl-list-table.ant-table-bordered .ant-table-thead > tr > th, +.vm-page.bdl-page .bdl-list-table.ant-table-bordered .ant-table-tbody > tr:not(.ant-table-measure-row) > td, +.vm-page.bdl-page .bdl-list-table.ant-table-bordered .ant-table-summary > tr > td { + border-inline-end: 1px solid var(--ln-hairline) !important; + border-bottom: 1px solid var(--ln-hairline) !important; +} + +/* 分组表:第一行业务板块 */ +.vm-page.bdl-page .bdl-list-table--grouped .ant-table-thead > tr:first-child > th { + background: var(--ln-surface-strong) !important; color: var(--ln-ink) !important; + font-size: 13px !important; font-weight: 600 !important; - background: var(--ln-surface-2) !important; - border-bottom: 1px solid var(--ln-border) !important; - border-inline-end: 1px solid var(--ln-border-subtle) !important; + text-align: center; + border-bottom: 2px solid var(--ln-hairline-strong) !important; } -.vm-page.bdl-page .bdl-list-table .ant-table-thead > tr:first-child > th { +/* 分组表:第二行 业绩/成本/盈亏 */ +.vm-page.bdl-page .bdl-list-table--grouped .ant-table-thead > tr:nth-child(2) > th { + background: var(--ln-canvas-soft) !important; + color: var(--ln-muted) !important; + font-size: 12px !important; + font-weight: 500 !important; text-align: center; - background: var(--ln-surface-3) !important; - border-bottom: 2px solid var(--ln-border) !important; +} + +/* 单层表头(按客户视图等) */ +.vm-page.bdl-page .bdl-list-table:not(.bdl-list-table--grouped) .ant-table-thead > tr > th { + background: var(--ln-canvas-soft) !important; + color: var(--ln-muted) !important; + font-weight: 500 !important; + font-size: 12px !important; } .vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td { color: var(--ln-body); - border-bottom: 1px solid var(--ln-border-subtle) !important; - border-inline-end: 1px solid var(--ln-border-subtle) !important; + background: var(--ln-surface-card); } .vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr.bdl-row-month:hover > td { - background: var(--ln-accent-soft) !important; -} - -.vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr.bdl-row-total > td { - font-weight: 700; - background: var(--ln-surface-2) !important; - color: var(--ln-ink) !important; - border-top: 2px solid var(--ln-border) !important; + background: var(--ln-canvas-soft) !important; } +.vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr.bdl-row-total > td, .vm-page.bdl-page .bdl-list-table .ant-table-summary > tr > td { font-weight: 700; - background: var(--ln-surface-2) !important; + background: var(--ln-surface-strong) !important; color: var(--ln-ink) !important; - border-top: 2px solid var(--ln-border) !important; + border-top: 2px solid var(--ln-hairline-strong) !important; } /* 冻结列(对齐 lc-list-table--nested) */ @@ -214,7 +238,7 @@ .vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr.bdl-row-total > td.ant-table-cell-fix-left, .vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr.bdl-row-total > td.ant-table-cell-fix-end, .vm-page.bdl-page .bdl-list-table .ant-table-tbody > tr.bdl-row-total > td.ant-table-cell-fix-right { - background: var(--ln-surface-2) !important; + background: var(--ln-surface-strong) !important; } .vm-page.bdl-page .bdl-list-table .ant-table-cell-fix-end:not(.ant-table-cell-scrollbar), @@ -248,21 +272,100 @@ box-shadow: inset -10px 0 8px -8px rgba(15, 23, 42, 0.12); } +/* 纵向滚动条占位缝:防止表头/表体右侧冻结列错位(对齐 lc-list-table--nested) */ +.vm-page.bdl-page .bdl-list-table--nested .ant-table-thead > tr > th.ant-table-cell-fix-end:not(.ant-table-cell-scrollbar)::before, +.vm-page.bdl-page .bdl-list-table--nested .ant-table-tbody > tr > td.ant-table-cell-fix-end:not(.ant-table-cell-scrollbar)::before, +.vm-page.bdl-page .bdl-list-table--nested .ant-table-thead > tr > th.ant-table-cell-fix-right:not(.ant-table-cell-scrollbar)::before, +.vm-page.bdl-page .bdl-list-table--nested .ant-table-tbody > tr > td.ant-table-cell-fix-right:not(.ant-table-cell-scrollbar)::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + right: -17px; + width: 17px; + background: inherit; + pointer-events: none; +} + +.vm-page.bdl-page .bdl-list-table--nested .ant-table-cell-scrollbar { + width: 0 !important; + min-width: 0 !important; + max-width: 0 !important; + padding: 0 !important; + border: none !important; + visibility: hidden; +} + +.vm-page.bdl-page .bdl-list-table--nested .ant-table-container { + overflow: hidden; +} + +.vm-page.bdl-page .bdl-list-table--nested .ant-table-content { + overflow: auto hidden; +} + +/* 金额列最小宽度,避免合计行大额被裁切 */ +.vm-page.bdl-page .bdl-list-table .col-amount, +.vm-page.bdl-page .bdl-list-table .col-summary { + min-width: 128px; +} + +.vm-page.bdl-page .bdl-list-table .col-summary { + min-width: 148px; +} + +.vm-page.bdl-page .bdl-list-table .bdl-amount-cell { + display: inline-block; + max-width: 100%; + text-align: right; +} + +.vm-page.bdl-page .bdl-list-table .biz-standbook-perf-link, .vm-page.bdl-page .biz-standbook-perf-link { cursor: pointer; color: var(--ln-primary); - padding: 4px 8px; - margin: -4px -8px; + padding: 0; + margin: 0; border: none; background: transparent; font: inherit; - border-radius: 6px; + border-radius: 4px; transition: background 0.2s, color 0.2s; + max-width: none; + overflow: visible; + text-align: inherit; + white-space: nowrap; +} + +/* 钻取子页表格:复用主表边框规范 */ +.vm-page.bdl-page .bdl-drill-card .biz-standbook-table.ant-table-bordered .ant-table-thead > tr > th, +.vm-page.bdl-page .bdl-drill-card .biz-standbook-table.ant-table-bordered .ant-table-tbody > tr:not(.ant-table-measure-row) > td, +.vm-page.bdl-page .bdl-drill-card .biz-standbook-table.ant-table-bordered .ant-table-summary > tr > td { + border-color: var(--ln-hairline) !important; + border-inline-end: 1px solid var(--ln-hairline) !important; + border-bottom: 1px solid var(--ln-hairline) !important; + padding: 8px 10px !important; + font-size: 13px; + white-space: nowrap; + font-variant-numeric: tabular-nums; +} + +.vm-page.bdl-page .bdl-drill-card .biz-standbook-table .ant-table-thead > tr > th { + background: var(--ln-canvas-soft) !important; + color: var(--ln-muted) !important; + font-weight: 500 !important; +} + +.vm-page.bdl-page .bdl-drill-card .biz-standbook-table-wrap, +.vm-page.bdl-page .bdl-drill-card .biz-standbook-table { + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-card); + overflow: hidden; } .vm-page.bdl-page .biz-standbook-perf-link:hover { - color: var(--ln-primary-strong); - background: var(--ln-accent-soft); + color: var(--ln-primary-focus); + background: var(--ln-primary-soft); } .vm-page.bdl-page .biz-standbook-perf-link:focus { diff --git a/src/prototypes/contract-template-management/ContractTemplate.jsx b/src/prototypes/contract-template-management/ContractTemplate.jsx index 82d2af6..28b88a1 100644 --- a/src/prototypes/contract-template-management/ContractTemplate.jsx +++ b/src/prototypes/contract-template-management/ContractTemplate.jsx @@ -48,6 +48,7 @@ import { splitFilterFields, VM_FILTER_PRIMARY_VISIBLE_COUNT, } from '../../common/vm-filter-panel.ts'; +import { OperationActions } from '../../common/OperationActions'; var DEFAULT_LESSOR_COMPANIES = [ @@ -2593,9 +2594,11 @@ const Component = function (props) { } function createPreviewPagerBtn(label, opts) { + var icon = label === '上一页' ? 'chevron-left' : (label === '下一页' ? 'chevron-right' : null); return React.createElement(Button, Object.assign({ size: 'small', - className: 'vm-btn vm-btn-secondary ct-preview-pager__btn' + className: 'vm-btn vm-btn-secondary ct-preview-pager__btn', + 'data-vm-icon': icon, }, opts), label); } @@ -4747,66 +4750,45 @@ const Component = function (props) { function renderTemplateRowActions(record) { if (isPrototypeReadOnly()) { - return React.createElement('div', { className: 'ct-row-actions' }, - React.createElement(Button, { - type: 'link', size: 'small', className: 'ct-action-btn', key: 'view', - onClick: function () { openEdit(record); } - }, '查看') - ); + return React.createElement(OperationActions, { + view: { onClick: function () { openEdit(record); } } + }); } var published = isPublishedTemplate(record); var enabled = isTemplateEnabled(record); var actionLocked = published && enabled; - var lockTitle = '启用中的模板不可操作,请先停用'; - var menuItems = []; + var more = []; if (published) { - menuItems.push({ - key: 'version-log', - label: '版本日志' - }); + if (enabled) { + more.push({ + key: 'disable', + label: '停用', + onClick: function () { confirmDisableTemplate(record); } + }); + } else { + more.push({ + key: 'enable', + label: '启用', + onClick: function () { toggleTemplateEnabled(record, true); } + }); + } + more.push({ key: 'version-log', label: '版本日志', onClick: function () { openVersionLog(record); } }); } - menuItems.push({ + more.push({ key: 'delete', label: '删除', disabled: actionLocked, - danger: true + danger: true, + onClick: actionLocked ? undefined : function () { confirmDelete(record); } }); - function onMenuClick(info) { - if (info.key === 'version-log') openVersionLog(record); - if (info.key === 'delete' && !actionLocked) confirmDelete(record); - } - - return React.createElement('div', { className: 'ct-row-actions' }, - published - ? (enabled - ? React.createElement(Button, { - type: 'link', size: 'small', className: 'ct-action-btn', key: 'disable', - onClick: function () { confirmDisableTemplate(record); } - }, '停用') - : React.createElement(Button, { - type: 'link', size: 'small', className: 'ct-action-btn', key: 'enable', - onClick: function () { toggleTemplateEnabled(record, true); } - }, '启用')) - : null, - React.createElement(Button, { - type: 'link', size: 'small', className: 'ct-action-btn', key: 'edit', - disabled: actionLocked, - title: actionLocked ? lockTitle : undefined, - onClick: actionLocked ? undefined : function () { openEdit(record); } - }, '编辑'), - menuItems.length - ? React.createElement(Dropdown, { - key: 'more', - trigger: ['click'], - menu: { items: menuItems, onClick: onMenuClick } - }, React.createElement(Button, { - type: 'link', size: 'small', className: 'ct-action-btn ct-row-actions__more', - 'aria-label': '更多操作' - }, '更多')) - : null - ); + return React.createElement(OperationActions, { + edit: actionLocked + ? { disabled: true, onClick: function () {} } + : { onClick: function () { openEdit(record); } }, + more: more + }); } function getListColumns() { @@ -4836,7 +4818,7 @@ const Component = function (props) { { title: '最后更新时间', dataIndex: 'updateTime', key: 'updateTime', width: 140, className: 'ct-col-optional' }, { title: '更新人', dataIndex: 'updater', key: 'updater', width: 100, className: 'ct-col-optional', render: formatPersonDisplayName }, { - title: '操作', key: 'action', width: 200, fixed: 'right', + title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, record) { return renderTemplateRowActions(record); } } ]); @@ -5014,6 +4996,7 @@ const Component = function (props) { ? React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-link vm-filter-toggle', + 'data-vm-icon': listFilterExpanded ? 'chevron-up' : 'filter', onClick: function () { setListFilterExpanded(!listFilterExpanded); }, 'aria-expanded': listFilterExpanded, 'aria-controls': expandPanelId @@ -5024,12 +5007,11 @@ const Component = function (props) { className: 'vm-filter-toggle-badge', 'aria-label': '已设置 ' + extraActiveCount + ' 项扩展筛选' }, String(extraActiveCount)) - : null, - renderFilterChevronIcon(listFilterExpanded) + : null ) : null, - React.createElement(Button, { className: 'vm-btn vm-btn-ghost', onClick: resetListFilters }, '重置'), - React.createElement(Button, { type: 'primary', className: 'ct-btn-cta', onClick: applyListFilters }, '查询') + React.createElement(Button, { 'data-vm-icon': 'rotate-ccw', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: resetListFilters }, '重置'), + React.createElement(Button, { 'data-vm-icon': 'search', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: applyListFilters }, '查询') ) ); } @@ -5139,15 +5121,17 @@ const Component = function (props) { ) : null, React.createElement('div', { className: 'ct-editor-page__topbar vm-detail-topbar', 'data-annotation-id': 'ct-editor-topbar' }, - React.createElement(Button, { className: 'ct-editor-page__back-btn vm-btn vm-btn-back', onClick: requestBackToList }, '← 返回列表'), + React.createElement(Button, { className: 'vm-btn vm-btn-back', onClick: requestBackToList }, '返回列表'), readOnly ? null : React.createElement('div', { className: 'ct-editor-page__topbar-actions' }, React.createElement(Button, { + 'data-vm-icon': 'save', className: 'vm-btn vm-btn-secondary', onClick: function () { handleSaveTemplate(false); } }, '保存'), React.createElement(Button, { + 'data-vm-icon': 'send', type: 'primary', className: 'vm-btn vm-btn-primary ct-btn-cta', onClick: function () { handleSaveTemplate(true); } diff --git a/src/prototypes/contract-template-management/annotation-source.json b/src/prototypes/contract-template-management/annotation-source.json index d0b4565..6012913 100644 --- a/src/prototypes/contract-template-management/annotation-source.json +++ b/src/prototypes/contract-template-management/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "contract-template-management", "pageId": "list", - "updatedAt": 1783589987162, + "updatedAt": 1783800844961, "nodes": [ { "id": "ct-list-filter", @@ -328,36 +328,6 @@ "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "ct-doc-root", diff --git a/src/prototypes/contract-template-management/contract-template-catalog.js b/src/prototypes/contract-template-management/contract-template-catalog.js index 10954ab..92affb4 100644 --- a/src/prototypes/contract-template-management/contract-template-catalog.js +++ b/src/prototypes/contract-template-management/contract-template-catalog.js @@ -71,5 +71,5 @@ export function getDefaultContractTemplateId(contractType, category) { export function getContractTemplatePreviewBadge(templateId) { var option = getContractTemplateOption(templateId); if (!option) return '请先选择合同模板'; - return [option.contractTypeLabel, option.fileName || option.title].filter(Boolean).join(' · '); + return option.contractTypeLabel || option.title || option.kind || '—'; } diff --git a/src/prototypes/contract-template-management/contract-template-vars.js b/src/prototypes/contract-template-management/contract-template-vars.js index 915e2ef..ea0ff6f 100644 --- a/src/prototypes/contract-template-management/contract-template-vars.js +++ b/src/prototypes/contract-template-management/contract-template-vars.js @@ -207,6 +207,7 @@ export var MILEAGE_PERIOD_OPTIONS = [ export var CONTRACT_CODE_PREFIX = 'LNZLHT'; export var DEFAULT_FEE_INFO = { + paymentMethod: 'advance', paymentPeriod: 1, hydrogenPaymentMethod: 'self', prepayAmount: null, @@ -225,16 +226,21 @@ export function generateAutoContractCodeSuffix() { } export var PAYMENT_PERIOD_OPTIONS = [ - { value: 1, label: '1个月' }, - { value: 3, label: '3个月' }, - { value: 6, label: '6个月' }, - { value: 12, label: '12个月' }, + { value: 1, label: '月付' }, + { value: 3, label: '季付' }, + { value: 6, label: '半年付' }, + { value: 12, label: '年付' }, +]; + +export var PAYMENT_METHOD_OPTIONS = [ + { value: 'advance', label: '先付后用' }, + { value: 'postpay', label: '先用后付' }, ]; export var HYDROGEN_PAYMENT_METHOD_OPTIONS = [ { value: 'self', label: '自行解决' }, { value: 'prepay', label: '预付款' }, - { value: 'month', label: '按月结算(原型占位,条款待法务确认)' }, + { value: 'month', label: '按月结算' }, ]; export function getLessorCompanies() { diff --git a/src/prototypes/contract-template-management/index.tsx b/src/prototypes/contract-template-management/index.tsx index 6d765d5..4084707 100644 --- a/src/prototypes/contract-template-management/index.tsx +++ b/src/prototypes/contract-template-management/index.tsx @@ -6,11 +6,11 @@ import { clearHostPrototypeRouteInfo } from '../../common/useHashPage'; import * as antd from 'antd'; import { ConfigProvider } from 'antd'; import { - AnnotationViewer, type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import 'antd/dist/reset.css'; import '../vehicle-management/style.css'; import './styles/index.css'; @@ -109,7 +109,7 @@ export function ContractTemplateManagementContent({ embedded = false }: { embedd navigationRef={navigationRef} onPageViewChange={setAnnotationPageId} /> - diff --git a/src/prototypes/contract-template-management/styles/contract-template.css b/src/prototypes/contract-template-management/styles/contract-template.css index 215c301..7a3a993 100644 --- a/src/prototypes/contract-template-management/styles/contract-template.css +++ b/src/prototypes/contract-template-management/styles/contract-template.css @@ -803,21 +803,6 @@ box-shadow: inset 0 0 0 1px var(--ln-primary); } -.vm-page.ct-page .ct-editor-page__back-btn.ant-btn { - padding: 4px 12px; - height: var(--vm-control-height); - font-size: 0.875rem; - color: var(--ln-muted); - border-color: var(--ln-hairline-strong); - background: var(--ln-surface-card); -} - -.vm-page.ct-page .ct-editor-page__back-btn.ant-btn:hover { - color: var(--ln-primary); - border-color: var(--ln-primary); - background: var(--ln-primary-soft); -} - .vm-page.ct-page .ct-editor-page__sections { flex-shrink: 0; margin-bottom: 12px; diff --git a/src/prototypes/customer-management/annotation-source.json b/src/prototypes/customer-management/annotation-source.json index 848c843..e3c7252 100644 --- a/src/prototypes/customer-management/annotation-source.json +++ b/src/prototypes/customer-management/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "customer-management", "pageId": "list", - "updatedAt": 1783589987164, + "updatedAt": 1783800844964, "nodes": [ { "id": "cm-list-filter", @@ -245,36 +245,6 @@ "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "cm-doc-root", diff --git a/src/prototypes/customer-management/components/BatchRiskTagModal.tsx b/src/prototypes/customer-management/components/BatchRiskTagModal.tsx index 231479c..9bc5c26 100644 --- a/src/prototypes/customer-management/components/BatchRiskTagModal.tsx +++ b/src/prototypes/customer-management/components/BatchRiskTagModal.tsx @@ -140,8 +140,8 @@ export function BatchRiskTagModal({
- - +
diff --git a/src/prototypes/customer-management/components/CustomerTable.tsx b/src/prototypes/customer-management/components/CustomerTable.tsx index b304d19..33ec90e 100644 --- a/src/prototypes/customer-management/components/CustomerTable.tsx +++ b/src/prototypes/customer-management/components/CustomerTable.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { OperationActions } from '../../../common/OperationActions'; import type { CustomerRecord } from '../types'; import type { CustomerRiskTag, MockUser } from '../types/riskTag'; import { RiskLabelTags } from './RiskLabelTags'; @@ -110,19 +111,25 @@ export function CustomerTable({ {record.address} {record.createTime} -
- - | - - {canManage && ( - <> - | - - - )} - | - -
+ onView(record) }} + edit={{ onClick: () => onEdit(record) }} + more={[ + ...(canManage + ? [{ + key: 'tags', + label: '管理标签', + onClick: () => onManageTags(record), + }] + : []), + { + key: 'delete', + label: '删除', + danger: true, + onClick: () => onDelete(record), + }, + ]} + /> ))} diff --git a/src/prototypes/customer-management/components/FilterPanel.tsx b/src/prototypes/customer-management/components/FilterPanel.tsx index 88edc2a..f29a9ca 100644 --- a/src/prototypes/customer-management/components/FilterPanel.tsx +++ b/src/prototypes/customer-management/components/FilterPanel.tsx @@ -191,19 +191,12 @@ export function FilterPanel({ type="button" className="vm-btn vm-btn-link" onClick={() => setExpanded(!expanded)} - > + data-vm-icon={expanded ? 'chevron-up' : 'filter'}> {expanded ? '收起' : '更多筛选'} - - + )} - - +
diff --git a/src/prototypes/customer-management/components/RiskTagDrawer.tsx b/src/prototypes/customer-management/components/RiskTagDrawer.tsx index 27de564..ba1f17f 100644 --- a/src/prototypes/customer-management/components/RiskTagDrawer.tsx +++ b/src/prototypes/customer-management/components/RiskTagDrawer.tsx @@ -252,11 +252,11 @@ export function RiskTagDrawer({
{tab === 'add' ? ( <> - - + + ) : ( - + )}
@@ -278,8 +278,8 @@ export function RiskTagDrawer({ onChange={(e) => setRemoveReason(e.target.value)} />
- - + +
)} @@ -296,9 +296,9 @@ export function RiskTagDrawer({ onChange={(e) => setRejectReason(e.target.value)} />
- - - + + +
)} diff --git a/src/prototypes/customer-management/index.tsx b/src/prototypes/customer-management/index.tsx index 691fd34..3031737 100644 --- a/src/prototypes/customer-management/index.tsx +++ b/src/prototypes/customer-management/index.tsx @@ -7,11 +7,11 @@ import './styles/index.css'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Download, FileUp, Plus, Tags } from 'lucide-react'; import { - AnnotationViewer, type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import seedCustomers from './data/customers.json'; import { FilterPanel } from './components/FilterPanel'; import { CustomerTable } from './components/CustomerTable'; @@ -330,9 +330,6 @@ export default function CustomerManagementApp() { /> ) : ( <> -
当前操作角色(原型演示): @@ -366,36 +363,34 @@ export default function CustomerManagementApp() {
- {canBatchTag(currentUser) && selectedIds.size > 0 && ( )}
@@ -452,7 +447,7 @@ export default function CustomerManagementApp() { onSubmit={handleBatchApply} /> - diff --git a/src/prototypes/customer-management/pages/CreatePage.tsx b/src/prototypes/customer-management/pages/CreatePage.tsx index bd8a994..0e16446 100644 --- a/src/prototypes/customer-management/pages/CreatePage.tsx +++ b/src/prototypes/customer-management/pages/CreatePage.tsx @@ -68,13 +68,9 @@ export function CreatePage({ return (
- +
+ +

客户信息

@@ -273,8 +269,8 @@ export function CreatePage({
- - + +
); diff --git a/src/prototypes/insurance-procurement/InsuranceProcurement.jsx b/src/prototypes/insurance-procurement/InsuranceProcurement.jsx index dcf4ec0..a49de8a 100644 --- a/src/prototypes/insurance-procurement/InsuranceProcurement.jsx +++ b/src/prototypes/insurance-procurement/InsuranceProcurement.jsx @@ -5,6 +5,7 @@ // 与车辆管理「保险状态」联动:交强险 + 商业险均存在且在有效期内为正常,否则异常(禁止交车) import React, { useState, useMemo, useCallback, useRef } from 'react'; +import { OperationActions } from '../../common/OperationActions'; import dayjs from 'dayjs'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import { @@ -4434,37 +4435,20 @@ const Component = function () { { title: '操作', key: 'action', - width: 188, + width: 168, fixed: 'right', render: (_, record) => ( -
- - - - { - domEvent.stopPropagation(); - handlePolicyMoreMenuClick(record, key); - }, - }} - > - - e.stopPropagation()} - > - {ICONS.more} - - - -
+ openVehicleInsHistoryEdit(record) }} + more={[ + { key: 'preview', label: '预览', onClick: () => handleInsuranceRecordPreview(record) }, + { key: 'download', label: '下载', onClick: () => handleInsuranceRecordDownload(record) }, + ...getPolicyMoreMenuItems(record).map((item) => ({ + ...item, + onClick: item.key ? () => handlePolicyMoreMenuClick(record, item.key) : undefined, + })), + ]} + /> ), }, ]; @@ -5937,33 +5921,42 @@ const Component = function () { { title: '操作', key: 'action', - width: 96, + width: 168, fixed: 'right', onHeaderCell: () => ({ className: 'lc-compare-th-edit' }), render: (_, row) => ( -
- { - setCopyPopoverRowId(open ? row.id : null); - if (!open) setCopyCountDraft(1); - }} - content={( -
-
复制条数
- setCopyCountDraft(v || 1)} style={{ width: '100%' }} /> -
- - -
+ { + setCopyPopoverRowId(open ? row.id : null); + if (!open) setCopyCountDraft(1); + }} + content={( +
+
复制条数
+ setCopyCountDraft(v || 1)} style={{ width: '100%' }} /> +
+ +
- )} - > - - - -
+
+ )} + > + { + setCopyPopoverRowId(row.id); + setCopyCountDraft(1); + }, + }, + { key: 'delete', label: '删除', danger: true, onClick: () => handleDeleteCompareRow(row.id) }, + ]} + /> +
), }, ]; @@ -6501,17 +6494,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 64, + width: 168, onHeaderCell: listColumnHeaderCell, render: (record) => ( - + openVehicleInsuranceMgmt(record) }} + /> ), }, ]; @@ -7658,17 +7646,13 @@ const Component = function () { { title: '操作', key: 'action', - width: 120, + width: 168, fixed: 'right', render: (_, record) => ( - - - - + openCompareEditor(record) }} + more={[{ key: 'delete', label: '删除', danger: true, onClick: () => handleDeleteCompareSheet(record) }]} + /> ), }, ]} @@ -8109,31 +8093,29 @@ const Component = function () { { title: '操作', key: 'action', - width: 120, + width: 168, fixed: 'right', render: (_, record) => { const recognizing = isPolicyRecognTaskRecognizing(record); const noSuccess = !(record.recognSuccessCount > 0); const disabled = recognizing || noSuccess; - const btn = ( - + const action = ( + openPolicyRecognTaskRecord(record), + }} + /> ); if (recognizing) { return ( - {btn} + {action} ); } - return btn; + return action; }, }, ]} diff --git a/src/prototypes/insurance-procurement/annotation-source.json b/src/prototypes/insurance-procurement/annotation-source.json index 4fd877d..08a56a3 100644 --- a/src/prototypes/insurance-procurement/annotation-source.json +++ b/src/prototypes/insurance-procurement/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "insurance-procurement", "pageId": "list", - "updatedAt": 1783589987165, + "updatedAt": 1783800844965, "nodes": [ { "id": "ipc-header", @@ -496,36 +496,6 @@ "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "ipc-doc-root", diff --git a/src/prototypes/insurance-procurement/index.tsx b/src/prototypes/insurance-procurement/index.tsx index 0937438..673d400 100644 --- a/src/prototypes/insurance-procurement/index.tsx +++ b/src/prototypes/insurance-procurement/index.tsx @@ -5,10 +5,10 @@ import React, { useMemo } from 'react'; import { ConfigProvider } from 'antd'; import { - AnnotationViewer, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import 'antd/dist/reset.css'; import './styles/index.css'; import InsuranceProcurement from './InsuranceProcurement.jsx'; @@ -82,7 +82,7 @@ export default function InsuranceProcurementPage() { { theme: ipcTheme }, React.createElement(InsuranceProcurement), ), - React.createElement(AnnotationViewer, { + React.createElement(PrototypeAnnotationHost, { source: annotationSourceDocument as AnnotationSourceDocument, options: annotationOptions, }), diff --git a/src/prototypes/lease-business-detail/.spec/prototype-comment-assets/c5c71486-f696-4b0f-8968-b2627d76148f.png b/src/prototypes/lease-business-detail/.spec/prototype-comment-assets/c5c71486-f696-4b0f-8968-b2627d76148f.png new file mode 100644 index 0000000..efa59db Binary files /dev/null and b/src/prototypes/lease-business-detail/.spec/prototype-comment-assets/c5c71486-f696-4b0f-8968-b2627d76148f.png differ diff --git a/src/prototypes/lease-business-detail/.spec/prototype-comment-assets/c8045d0a-6e00-4274-bdb0-07b8d983b41a.png b/src/prototypes/lease-business-detail/.spec/prototype-comment-assets/c8045d0a-6e00-4274-bdb0-07b8d983b41a.png new file mode 100644 index 0000000..5aeb1b7 Binary files /dev/null and b/src/prototypes/lease-business-detail/.spec/prototype-comment-assets/c8045d0a-6e00-4274-bdb0-07b8d983b41a.png differ diff --git a/src/prototypes/lease-business-detail/.spec/requirements-prd.md b/src/prototypes/lease-business-detail/.spec/requirements-prd.md new file mode 100644 index 0000000..70660f5 --- /dev/null +++ b/src/prototypes/lease-business-detail/.spec/requirements-prd.md @@ -0,0 +1,165 @@ +# 租赁业务明细 · 产品需求说明(PRD) + +> 对齐 Excel《2026年业务二部运营台账总表》「2.租赁车辆收入明细表」;业管人员按月份维护运营明细,公式列系统自动计算。 + +--- + +## 1. 模块定位 + +| 项 | 说明 | +|---|---| +| 模块名称 | 台账数据 — 租赁业务明细 | +| 原型路径 | `src/prototypes/lease-business-detail` | +| 预览地址 | `/prototypes/lease-business-detail` | +| 目标用户 | 业务二部业管 / 运营人员 | +| 核心任务 | 按 Excel 样表维护租赁车辆收入明细、查询汇总、批量导入导出 | +| 页面结构 | 筛选区 + 7 项 KPI + 工具栏 + 47 列宽表(表尾 23 项汇总)+ 分页 | +| 设计基底 | `vm-page` + `ldb-page` + `lc-page`(见 `ledger-shared/DESIGN.md`) | + +### 与「租赁业务台账」的分工 + +| 模块 | 定位 | +|---|---| +| **租赁业务明细**(本模块) | 对齐业管 Excel 运营台账宽表;手工列导入维护;6 个公式列自动计算;**不**做收款关联与账单状态 | +| **租赁业务台账** | 账单维度维护、关联收款记录、收款状态(未收/部分/已收)、归档与角色数据范围 | + +下游 **业务部台账 / 业务汇总分析** 中「租赁业绩」数据来源为本模块明细(见 `oneos-web-data-analysis/05-业务部台账`)。 + +### 不做范围(当前原型) + +- 「租赁业务盈亏月度汇总」独立 Tab(Legacy 数据分析页有,本原型待规划) +- 行内编辑、删除、服务端持久化 +- 关联收款记录自动回写实收 +- 员工/主管数据权限隔离 + +--- + +## 2. 筛选区 + +默认展示 4 项(一行),「更多筛选」展开车牌多选。 + +| 筛选项 | 说明 | +|---|---| +| 统计月份 | `YYYY-MM` 格式,精确匹配年份+月份 | +| 业务部门 | 精确匹配 | +| 业务员 | 精确匹配 | +| 客户名称 | 模糊包含 | +| 车牌号码 | 多选精确匹配(更多筛选) | + +查询按钮触发筛选;KPI 与表尾汇总基于**全量筛选结果**(非当前页)。 + +--- + +## 3. KPI 统计卡片(7 张) + +顶部展示关键指标;完整 23 项金额合计见列表底部汇总行。 + +| KPI | 汇总字段 | +|---|---| +| 应收总计 | `receivableTotal` | +| 押金总计 | `deposit`(仅数值型) | +| 实收总计 | `receivedAmount` | +| 里程减免总计 | `mileageDeduction` | +| 其他减免总计 | `otherDeduction` | +| 未收总计 | `outstanding`(>0 标红) | +| 盈亏总计 | `profitLoss`(正负着色) | + +--- + +## 4. 列表工具栏 + +| 按钮 | 行为 | +|---|---| +| 批量导入 | 下载模板(仅手工录入列)→ 上传 Excel → 公式列自动计算 | +| 批量导出 | 导出当前筛选结果(完整 47 列,含公式列结果) | + +导入必填:**年份、月份、车牌号码**。 + +--- + +## 5. 明细列表(47 列) + +表头顺序严格对齐 Excel「2.租赁车辆收入明细表」: + +年份、月份、业务部门、业务员、付款日期、车牌号码、车型、客户名称、增值服务、享免政策、提车日期、合同生效日期、合同到期日期、起始日期、退车日期、平均天数、押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、开票日期、实际付款日期、付款方式、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏、GPS里程、其他减免金额明细内容、资产归属、签约公司、备注。 + +### 交互 + +- 横向滚动;左冻结:**年份 + 车牌号码** +- 仅**日期类列**表头支持正序/倒序排序 +- 公式列(`calculated`)浅灰底色,表示系统计算、导入模板不含 +- 表尾**汇总行**:23 个金额列 SUBTOTAL,首列显示「汇总」 +- 未收 > 0 单元格标红;盈亏正负着色 +- 底部分页:10 / 20 / 50 / 100 + +### 表尾汇总(23 项) + +押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏。 + +--- + +## 6. 公式列(系统自动计算) + +对齐 Excel 表内公式;实现见 `utils/calc-detail.ts`。 + +| 字段 | 计算方式 | +|---|---| +| **应收合计** | 应收租金 + 维保包干收入 + 保险上浮费 + 运维费(收入) + 其他收入 + 里程减免 + 其他减免 | +| **月度收入** | 月度租金 + 同上调整项 | +| **未收** | 应收合计 − 实收金额 | +| **车辆实际成本** | 车辆标准成本 × 平均天数 | +| **总成本** | 车辆实际成本 + 保险费 + 氢费 + 运维费(成本) + 居间费 + 其他 | +| **盈亏** | 月度收入 − 总成本 | + +减免金额在 Excel 中以负数录入;导入时填负数即可。 + +### 导入模板 + +- **含**:41 列手工录入字段(不含上述 6 列) +- **不含**:应收合计、月度收入、未收、车辆实际成本、总成本、盈亏 +- 导入后调用 `applyDetailCalculations` 统一重算 + +--- + +## 7. 样例数据 + +- 文件:`data/rows.json` +- 来源:Excel《2026年业务二部运营台账总表6.17 - 新版》2026 年 1–6 月明细 +- 条数:553 条;汇总与 Excel SUBTOTAL 行一致 + +--- + +## 8. 源码入口 + +| 类型 | 路径 | +|---|---| +| 主入口 | `index.tsx` | +| 标注源 | `annotation-source.json` | +| 列定义 | `components/tableColumns.ts` | +| 筛选 / KPI 汇总 | `utils/detail.ts` | +| 公式计算 | `utils/calc-detail.ts` | +| 导入导出 | `utils/batch-io.ts` | +| 类型 | `types.ts` | +| 组件 | `components/FilterPanel.tsx`、`DetailKpiRow.tsx`、`DetailTable.tsx`、`DetailImportModal.tsx` | + +--- + +## 9. 验收重点 + +1. 表头 47 列顺序与 Excel 样表一致;双「运维费」列位置正确 +2. 筛选后 KPI、表尾汇总与明细行合计一致 +3. 下载导入模板无 6 个公式列;导入后公式列自动计算且与 Excel 同口径 +4. 导出为完整 47 列 +5. 横向滚动与左冻结(年份、车牌)正常 +6. 未收 > 0 标红;盈亏正负着色 + +--- + +## 10. 待规划项(评审记录) + +详见 `.spec/prototype-review.md`: + +- P1:盈亏月度汇总 Tab +- P1:与租赁业务台账 / 收款模块集成口径 +- P2:导入重复键(年+月+车牌)处理策略 +- P2:导入行级校验与实收 ≤ 应收校验 diff --git a/src/prototypes/lease-business-detail/annotation-source.json b/src/prototypes/lease-business-detail/annotation-source.json index deee134..d465132 100644 --- a/src/prototypes/lease-business-detail/annotation-source.json +++ b/src/prototypes/lease-business-detail/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "lease-business-detail", "pageId": "list", - "updatedAt": 1783589987165, + "updatedAt": 1783800844965, "nodes": [ { "id": "lbd-filter", @@ -19,12 +19,14 @@ "fingerprint": "lbd-filter", "path": [] }, - "annotationText": "统计月份、业务部门、业务员、客户、车牌筛选;业管人员按月份维护明细。", - "hasMarkdown": false, + "aiPrompt": "统计月份、部门、业务员、客户、状态、车牌筛选。", + "annotationText": "## 2. 筛选区\n\n默认展示 5 项(一行),「更多筛选」展开车牌多选。\n\n| 筛选项 | 说明 |\n|---|---|\n| 统计月份 | `YYYY-MM` 格式,精确匹配年份+月份 |\n| 业务部门 | 精确匹配 |\n| 业务员 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 状态 | 已结清 / 部分收款 / 未收款(按实收与应收合计计算) |\n| 车牌号码 | 多选精确匹配(更多筛选) |\n\n查询按钮触发筛选;KPI 与表尾汇总基于**全量筛选结果**(非当前页)。\n\n---", + "hasMarkdown": true, "color": "#2563eb", "images": [], "createdAt": 1783488000000, - "updatedAt": 1783488000000 + "updatedAt": 1783612156632, + "controls": [] }, { "id": "lbd-kpi", @@ -38,12 +40,14 @@ "fingerprint": "lbd-kpi", "path": [] }, - "annotationText": "7 项关键指标:应收/押金/实收/里程减免/其他减免/未收/盈亏。完整 23 项汇总见列表底部汇总行。", - "hasMarkdown": false, + "aiPrompt": "7 项关键指标口径。", + "annotationText": "## 3. KPI 统计卡片(7 张)\n\n顶部展示关键指标;完整 23 项金额合计见列表底部汇总行。\n\n| KPI | 汇总字段 |\n|---|---|\n| 应收总计 | receivableTotal |\n| 押金总计 | deposit(仅数值型) |\n| 实收总计 | receivedAmount |\n| 里程减免总计 | mileageDeduction |\n| 其他减免总计 | otherDeduction |\n| 未收总计 | outstanding(>0 标红) |\n| 盈亏总计 | profitLoss(正负着色) |\n\n---", + "hasMarkdown": true, "color": "#059669", "images": [], "createdAt": 1783488000000, - "updatedAt": 1783488000000 + "updatedAt": 1783612156632, + "controls": [] }, { "id": "lbd-toolbar", @@ -57,12 +61,14 @@ "fingerprint": "lbd-toolbar", "path": [] }, - "annotationText": "下载模板仅含手工录入列;导入后自动计算应收合计、月度收入、未收、车辆实际成本、总成本、盈亏。导出含完整列。", - "hasMarkdown": false, + "aiPrompt": "导入模板与导出列范围。", + "annotationText": "## 4. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板(36 列手工录入)→ 上传 Excel → 公式列自动计算 |\n| 批量导出 | 导出当前筛选结果(完整 47 列,含公式列结果) |\n\n导入必填见模板说明。\n\n### 权限\n\n- 默认业管人员可编辑、删除明细记录\n- 主管预览:URL 追加 `?role=supervisor`(可修改已锁定记录)\n\n---", + "hasMarkdown": true, "color": "#7c3aed", "images": [], "createdAt": 1783488000000, - "updatedAt": 1783488000000 + "updatedAt": 1783612156632, + "controls": [] }, { "id": "lbd-table", @@ -76,12 +82,182 @@ "fingerprint": "lbd-table", "path": [] }, - "annotationText": "宽表 47 列;底部汇总行展示 23 项金额合计(对齐 Excel SUBTOTAL);未收>0 标红。", - "hasMarkdown": false, + "aiPrompt": "48 列宽表、状态列、汇总行与交互。", + "annotationText": "## 5. 明细列表(48 列)\n\n表头顺序严格对齐 Excel「2.租赁车辆收入明细表」,并在月份列左侧增加「状态」列。\n\n### 交互\n\n- 横向滚动;左冻结:**状态 + 月份 + 车牌号码**\n- 仅**日期类列**表头支持正序/倒序排序\n- 公式列(calculated)浅灰底色,导入模板不含\n- 表尾**汇总行**:23 个金额列 SUBTOTAL,月份列显示「汇总」\n- 未收 > 0 单元格标红;盈亏正负着色\n- 底部分页:10 / 20 / 50 / 100\n\n### 收款状态(状态列)\n\n| 状态 | 判定规则 |\n|---|---|\n| 未收款 | 实收金额 = 0 或未录入 |\n| 部分收款 | 实收金额 > 0 且 < 应收合计 |\n| 已结清 | 实收金额 ≥ 应收合计 |\n\n### 表尾汇总(23 项)\n\n押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏。\n\n---", + "hasMarkdown": true, "color": "#ea580c", "images": [], "createdAt": 1783488000000, - "updatedAt": 1783488000000 + "updatedAt": 1783612156632, + "controls": [] + }, + { + "id": "lbd-col-receivable-total", + "index": 5, + "title": "应收合计", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-col-receivable-total\"]" + ], + "fingerprint": "lbd-col-receivable-total", + "path": [] + }, + "aiPrompt": "应收合计字段计算口径。", + "annotationText": "## 应收合计\n\n**计算方式**:维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免金额(负数)+ 其他减免金额(负数)。\n**录入方式**:系统自动计算,只读。\n\n---", + "hasMarkdown": true, + "color": "#64748b", + "images": [], + "createdAt": 1783612752344, + "updatedAt": 1783612752344, + "controls": [] + }, + { + "id": "lbd-col-outstanding", + "index": 6, + "title": "未收", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-col-outstanding\"]" + ], + "fingerprint": "lbd-col-outstanding", + "path": [] + }, + "aiPrompt": "未收字段计算口径。", + "annotationText": "## 未收\n\n**计算方式**:应收合计 − 实收金额。\n**录入方式**:系统自动计算,只读。\n\n---", + "hasMarkdown": true, + "color": "#64748b", + "images": [], + "createdAt": 1783612752344, + "updatedAt": 1783612752344, + "controls": [] + }, + { + "id": "lbd-col-vehicle-actual-cost", + "index": 7, + "title": "车辆实际成本", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-col-vehicle-actual-cost\"]" + ], + "fingerprint": "lbd-col-vehicle-actual-cost", + "path": [] + }, + "aiPrompt": "车辆实际成本字段计算口径。", + "annotationText": "## 车辆实际成本\n\n**计算方式**:车辆实际成本 = 车辆标准成本 × 平均天数。\n**录入方式**:系统自动计算,只读。\n\n---", + "hasMarkdown": true, + "color": "#64748b", + "images": [], + "createdAt": 1783612752344, + "updatedAt": 1783612752344, + "controls": [] + }, + { + "id": "lbd-col-total-cost", + "index": 8, + "title": "总成本", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-col-total-cost\"]" + ], + "fingerprint": "lbd-col-total-cost", + "path": [] + }, + "aiPrompt": "总成本字段计算口径。", + "annotationText": "## 总成本\n\n**计算方式**:总成本 = 车辆实际成本 + 保险费 + 氢费 + 运维费(成本)+ 居间费 + 其他。\n**录入方式**:系统自动计算,只读。\n\n---", + "hasMarkdown": true, + "color": "#64748b", + "images": [], + "createdAt": 1783612752344, + "updatedAt": 1783612752344, + "controls": [] + }, + { + "id": "lbd-col-profit-loss", + "index": 9, + "title": "盈亏", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-col-profit-loss\"]" + ], + "fingerprint": "lbd-col-profit-loss", + "path": [] + }, + "aiPrompt": "盈亏字段计算口径。", + "annotationText": "## 盈亏\n\n**计算方式**:盈亏 = 月度收入 − 总成本。\n**录入方式**:系统自动计算,只读。\n\n---", + "hasMarkdown": true, + "color": "#64748b", + "images": [], + "createdAt": 1783612752344, + "updatedAt": 1783612752344, + "controls": [] + }, + { + "id": "lbd-col-hydrogen-fee", + "index": 10, + "title": "氢费", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-col-hydrogen-fee\"]" + ], + "fingerprint": "lbd-col-hydrogen-fee", + "path": [] + }, + "aiPrompt": "氢费字段汇总口径与明细弹窗。", + "annotationText": "## 氢费\n\n**计算方式**:车辆氢费明细中,该车牌在「提车日期」与「退车日期」之间、承担方式为「我司承担」的加氢记录「加氢总价(元)」求和。\n**表头提示**:显示该车牌号码对应客户名称,所有加氢记录,承担方式为我司承担的记录「加氢总价(元)」总和。\n**交互**:金额不为零时可点击,弹出加氢明细列表。\n**录入方式**:系统自动汇总,只读。\n\n---", + "hasMarkdown": true, + "color": "#64748b", + "images": [], + "createdAt": 1783612752344, + "updatedAt": 1783612752344, + "controls": [] + }, + { + "id": "lbd-col-status", + "index": 12, + "title": "状态", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-col-status\"]" + ], + "fingerprint": "lbd-col-status", + "path": [] + }, + "aiPrompt": "收款状态列:未收款 / 部分收款 / 已结清。", + "annotationText": "## 状态\n\n按**实收金额**与**应收合计**自动计算,只读展示。\n\n| 状态 | 判定规则 |\n|---|---|\n| **未收款** | 实收金额 = 0 或未录入 |\n| **部分收款** | 实收金额 > 0 且 < 应收合计 |\n| **已结清** | 实收金额 ≥ 应收合计 |\n\n列位置:月份列左侧;左冻结。\n\n---", + "hasMarkdown": true, + "color": "#0d9488", + "images": [], + "createdAt": 1783656452224, + "updatedAt": 1783656452224, + "controls": [] + }, + { + "id": "lbd-changelog-modal", + "index": 13, + "title": "变更日志", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"lbd-changelog-modal\"]" + ], + "fingerprint": "lbd-changelog-modal", + "path": [] + }, + "aiPrompt": "变更日志弹窗标题与列表字段。", + "annotationText": "## 变更日志\n\n弹窗标题仅显示「变更日志」;表格上方展示记录条数,列表字段为操作时间、操作人、字段、修改前、修改后。\n\n---", + "hasMarkdown": true, + "color": "#64748b", + "images": [], + "createdAt": 1783612752344, + "updatedAt": 1783612752344, + "controls": [] } ], "directory": { @@ -101,33 +277,143 @@ "markdownNodes": [] } }, + "markdownMap": { + "lbd-filter": "## 2. 筛选区\n\n默认展示 5 项(一行),「更多筛选」展开车牌多选。\n\n| 筛选项 | 说明 |\n|---|---|\n| 统计月份 | `YYYY-MM` 格式,精确匹配年份+月份 |\n| 业务部门 | 精确匹配 |\n| 业务员 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 状态 | 已结清 / 部分收款 / 未收款(按实收与应收合计计算) |\n| 车牌号码 | 多选精确匹配(更多筛选) |\n\n查询按钮触发筛选;KPI 与表尾汇总基于**全量筛选结果**(非当前页)。\n\n---", + "lbd-kpi": "## 3. KPI 统计卡片(7 张)\n\n顶部展示关键指标;完整 23 项金额合计见列表底部汇总行。\n\n| KPI | 汇总字段 |\n|---|---|\n| 应收总计 | receivableTotal |\n| 押金总计 | deposit(仅数值型) |\n| 实收总计 | receivedAmount |\n| 里程减免总计 | mileageDeduction |\n| 其他减免总计 | otherDeduction |\n| 未收总计 | outstanding(>0 标红) |\n| 盈亏总计 | profitLoss(正负着色) |\n\n---", + "lbd-toolbar": "## 4. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板(36 列手工录入)→ 上传 Excel → 公式列自动计算 |\n| 批量导出 | 导出当前筛选结果(完整 47 列,含公式列结果) |\n\n导入必填见模板说明。\n\n### 权限\n\n- 默认业管人员可编辑、删除明细记录\n- 主管预览:URL 追加 `?role=supervisor`(可修改已锁定记录)\n\n---", + "lbd-table": "## 5. 明细列表(48 列)\n\n表头顺序严格对齐 Excel「2.租赁车辆收入明细表」,并在月份列左侧增加「状态」列。\n\n### 交互\n\n- 横向滚动;左冻结:**状态 + 月份 + 车牌号码**\n- 仅**日期类列**表头支持正序/倒序排序\n- 公式列(calculated)浅灰底色,导入模板不含\n- 表尾**汇总行**:23 个金额列 SUBTOTAL,月份列显示「汇总」\n- 未收 > 0 单元格标红;盈亏正负着色\n- 底部分页:10 / 20 / 50 / 100\n\n### 收款状态(状态列)\n\n| 状态 | 判定规则 |\n|---|---|\n| 未收款 | 实收金额 = 0 或未录入 |\n| 部分收款 | 实收金额 > 0 且 < 应收合计 |\n| 已结清 | 实收金额 ≥ 应收合计 |\n\n### 表尾汇总(23 项)\n\n押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏。\n\n---", + "lbd-doc-formulas": "## 6. 公式列(系统自动计算)\n\n对齐 Excel 表内公式;实现见 `utils/calc-detail.ts`。\n\n| 字段 | 计算方式 |\n|---|---|\n| **应收合计** | 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免金额(负数)+ 其他减免金额(负数) |\n| **月度收入** | 月度租金 + 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免 + 其他减免 |\n| **未收** | 应收合计 − 实收金额 |\n| **车辆实际成本** | 车辆标准成本 × 平均天数 |\n| **总成本** | 车辆实际成本 + 保险费 + 氢费 + 运维费(成本)+ 居间费 + 其他 |\n| **盈亏** | 月度收入 − 总成本 |\n\n减免金额在 Excel 中以负数录入;导入时填负数即可。\n\n---", + "lbd-doc-boundary": "## 模块边界\n\n### 与「租赁业务台账」的分工\n\n| 模块 | 定位 |\n|---|---|\n| **租赁业务明细**(本模块) | 对齐业管 Excel 运营台账宽表;手工列导入维护;6 个公式列自动计算;**不**做收款关联与账单状态 |\n| **租赁业务台账** | 账单维度维护、关联收款记录、收款状态(未收/部分/已收)、归档与角色数据范围 |\n\n下游 **业务部台账 / 业务汇总分析** 中「租赁业绩」数据来源为本模块明细。\n\n### 不做范围(当前原型)\n\n- 「租赁业务盈亏月度汇总」独立 Tab\n- 行内编辑、删除、服务端持久化\n- 关联收款记录自动回写实收\n- 员工/主管数据权限隔离\n\n---", + "lbd-doc-overview": "## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 台账数据 — 租赁业务明细 |\n| 目标用户 | 业务二部业管 / 运营人员 |\n| 核心任务 | 按 Excel 样表维护租赁车辆收入明细、查询汇总、批量导入导出 |\n| 页面结构 | 筛选区 + 7 项 KPI + 工具栏 + 47 列宽表(表尾 23 项汇总)+ 分页 |\n| 设计基底 | vm-page + ldb-page + lc-page |\n\n对齐 Excel《2026年业务二部运营台账总表》「2.租赁车辆收入明细表」。\n\n---", + "lbd-col-receivable-total": "## 应收合计\n\n**计算方式**:维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免金额(负数)+ 其他减免金额(负数)。\n**录入方式**:系统自动计算,只读。\n\n---", + "lbd-col-outstanding": "## 未收\n\n**计算方式**:应收合计 − 实收金额。\n**录入方式**:系统自动计算,只读。\n\n---", + "lbd-col-vehicle-actual-cost": "## 车辆实际成本\n\n**计算方式**:车辆实际成本 = 车辆标准成本 × 平均天数。\n**录入方式**:系统自动计算,只读。\n\n---", + "lbd-col-total-cost": "## 总成本\n\n**计算方式**:总成本 = 车辆实际成本 + 保险费 + 氢费 + 运维费(成本)+ 居间费 + 其他。\n**录入方式**:系统自动计算,只读。\n\n---", + "lbd-col-profit-loss": "## 盈亏\n\n**计算方式**:盈亏 = 月度收入 − 总成本。\n**录入方式**:系统自动计算,只读。\n\n---", + "lbd-col-hydrogen-fee": "## 氢费\n\n**计算方式**:车辆氢费明细中,该车牌在「提车日期」与「退车日期」之间、承担方式为「我司承担」的加氢记录「加氢总价(元)」求和。\n**表头提示**:显示该车牌号码对应客户名称,所有加氢记录,承担方式为我司承担的记录「加氢总价(元)」总和。\n**交互**:金额不为零时可点击,弹出加氢明细列表(加氢时间、加氢站名称、客户名称、车牌号码、加氢量、加氢单价、加氢总价)。\n**录入方式**:系统自动汇总,只读。\n\n---", + "lbd-col-status": "## 状态\n\n按**实收金额**与**应收合计**自动计算,只读展示。\n\n| 状态 | 判定规则 |\n|---|---|\n| **未收款** | 实收金额 = 0 或未录入 |\n| **部分收款** | 实收金额 > 0 且 < 应收合计 |\n| **已结清** | 实收金额 ≥ 应收合计 |\n\n列位置:月份列左侧;左冻结。\n\n---", + "lbd-changelog-modal": "## 变更日志\n\n弹窗标题仅显示「变更日志」;表格上方展示记录条数,列表字段为操作时间、操作人、字段、修改前、修改后。\n\n---" + }, + "assetMap": {}, "directory": { "nodes": [ { "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", + "id": "lbd-doc-root", + "title": "租赁业务明细说明", "defaultExpanded": true, "children": [ + { + "type": "markdown", + "id": "lbd-doc-overview", + "title": "模块总览", + "markdown": "## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 台账数据 — 租赁业务明细 |\n| 目标用户 | 业务二部业管 / 运营人员 |\n| 核心任务 | 按 Excel 样表维护租赁车辆收入明细、查询汇总、批量导入导出 |\n| 页面结构 | 筛选区 + 7 项 KPI + 工具栏 + 47 列宽表(表尾 23 项汇总)+ 分页 |\n| 设计基底 | vm-page + ldb-page + lc-page |\n\n对齐 Excel《2026年业务二部运营台账总表》「2.租赁车辆收入明细表」。\n\n---" + }, { "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", + "id": "lbd-link-self", + "title": "租赁业务明细原型", + "href": "./index.html", "target": "self" }, + { + "type": "markdown", + "id": "lbd-doc-prd", + "title": "PRD 全文", + "markdown": "# 租赁业务明细 · 产品需求说明(PRD)\n\n> 对齐 Excel《2026年业务二部运营台账总表》「2.租赁车辆收入明细表」;业管人员按月份维护运营明细,公式列系统自动计算。\n\n---\n\n## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 台账数据 — 租赁业务明细 |\n| 原型路径 | `src/prototypes/lease-business-detail` |\n| 预览地址 | `/prototypes/lease-business-detail` |\n| 目标用户 | 业务二部业管 / 运营人员 |\n| 核心任务 | 按 Excel 样表维护租赁车辆收入明细、查询汇总、批量导入导出 |\n| 页面结构 | 筛选区 + 7 项 KPI + 工具栏 + 47 列宽表(表尾 23 项汇总)+ 分页 |\n| 设计基底 | `vm-page` + `ldb-page` + `lc-page`(见 `ledger-shared/DESIGN.md`) |\n\n### 与「租赁业务台账」的分工\n\n| 模块 | 定位 |\n|---|---|\n| **租赁业务明细**(本模块) | 对齐业管 Excel 运营台账宽表;手工列导入维护;6 个公式列自动计算;**不**做收款关联与账单状态 |\n| **租赁业务台账** | 账单维度维护、关联收款记录、收款状态(未收/部分/已收)、归档与角色数据范围 |\n\n下游 **业务部台账 / 业务汇总分析** 中「租赁业绩」数据来源为本模块明细(见 `oneos-web-data-analysis/05-业务部台账`)。\n\n### 不做范围(当前原型)\n\n- 「租赁业务盈亏月度汇总」独立 Tab(Legacy 数据分析页有,本原型待规划)\n- 行内编辑、删除、服务端持久化\n- 关联收款记录自动回写实收\n- 员工/主管数据权限隔离\n\n---\n\n## 2. 筛选区\n\n默认展示 4 项(一行),「更多筛选」展开车牌多选。\n\n| 筛选项 | 说明 |\n|---|---|\n| 统计月份 | `YYYY-MM` 格式,精确匹配年份+月份 |\n| 业务部门 | 精确匹配 |\n| 业务员 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 车牌号码 | 多选精确匹配(更多筛选) |\n\n查询按钮触发筛选;KPI 与表尾汇总基于**全量筛选结果**(非当前页)。\n\n---\n\n## 3. KPI 统计卡片(7 张)\n\n顶部展示关键指标;完整 23 项金额合计见列表底部汇总行。\n\n| KPI | 汇总字段 |\n|---|---|\n| 应收总计 | `receivableTotal` |\n| 押金总计 | `deposit`(仅数值型) |\n| 实收总计 | `receivedAmount` |\n| 里程减免总计 | `mileageDeduction` |\n| 其他减免总计 | `otherDeduction` |\n| 未收总计 | `outstanding`(>0 标红) |\n| 盈亏总计 | `profitLoss`(正负着色) |\n\n---\n\n## 4. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板(仅手工录入列)→ 上传 Excel → 公式列自动计算 |\n| 批量导出 | 导出当前筛选结果(完整 47 列,含公式列结果) |\n\n导入必填:**年份、月份、车牌号码**。\n\n---\n\n## 5. 明细列表(47 列)\n\n表头顺序严格对齐 Excel「2.租赁车辆收入明细表」:\n\n年份、月份、业务部门、业务员、付款日期、车牌号码、车型、客户名称、增值服务、享免政策、提车日期、合同生效日期、合同到期日期、起始日期、退车日期、平均天数、押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、开票日期、实际付款日期、付款方式、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏、GPS里程、其他减免金额明细内容、资产归属、签约公司、备注。\n\n### 交互\n\n- 横向滚动;左冻结:**年份 + 车牌号码**\n- 仅**日期类列**表头支持正序/倒序排序\n- 公式列(`calculated`)浅灰底色,表示系统计算、导入模板不含\n- 表尾**汇总行**:23 个金额列 SUBTOTAL,首列显示「汇总」\n- 未收 > 0 单元格标红;盈亏正负着色\n- 底部分页:10 / 20 / 50 / 100\n\n### 表尾汇总(23 项)\n\n押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏。\n\n---\n\n## 6. 公式列(系统自动计算)\n\n对齐 Excel 表内公式;实现见 `utils/calc-detail.ts`。\n\n| 字段 | 计算方式 |\n|---|---|\n| **应收合计** | 应收租金 + 维保包干收入 + 保险上浮费 + 运维费(收入) + 其他收入 + 里程减免 + 其他减免 |\n| **月度收入** | 月度租金 + 同上调整项 |\n| **未收** | 应收合计 − 实收金额 |\n| **车辆实际成本** | 车辆标准成本 × 平均天数 |\n| **总成本** | 车辆实际成本 + 保险费 + 氢费 + 运维费(成本) + 居间费 + 其他 |\n| **盈亏** | 月度收入 − 总成本 |\n\n减免金额在 Excel 中以负数录入;导入时填负数即可。\n\n### 导入模板\n\n- **含**:41 列手工录入字段(不含上述 6 列)\n- **不含**:应收合计、月度收入、未收、车辆实际成本、总成本、盈亏\n- 导入后调用 `applyDetailCalculations` 统一重算\n\n---\n\n## 7. 样例数据\n\n- 文件:`data/rows.json`\n- 来源:Excel《2026年业务二部运营台账总表6.17 - 新版》2026 年 1–6 月明细\n- 条数:553 条;汇总与 Excel SUBTOTAL 行一致\n\n---\n\n## 8. 源码入口\n\n| 类型 | 路径 |\n|---|---|\n| 主入口 | `index.tsx` |\n| 标注源 | `annotation-source.json` |\n| 列定义 | `components/tableColumns.ts` |\n| 筛选 / KPI 汇总 | `utils/detail.ts` |\n| 公式计算 | `utils/calc-detail.ts` |\n| 导入导出 | `utils/batch-io.ts` |\n| 类型 | `types.ts` |\n| 组件 | `components/FilterPanel.tsx`、`DetailKpiRow.tsx`、`DetailTable.tsx`、`DetailImportModal.tsx` |\n\n---\n\n## 9. 验收重点\n\n1. 表头 47 列顺序与 Excel 样表一致;双「运维费」列位置正确\n2. 筛选后 KPI、表尾汇总与明细行合计一致\n3. 下载导入模板无 6 个公式列;导入后公式列自动计算且与 Excel 同口径\n4. 导出为完整 47 列\n5. 横向滚动与左冻结(年份、车牌)正常\n6. 未收 > 0 标红;盈亏正负着色\n\n---\n\n## 10. 待规划项(评审记录)\n\n详见 `.spec/prototype-review.md`:\n\n- P1:盈亏月度汇总 Tab\n- P1:与租赁业务台账 / 收款模块集成口径\n- P2:导入重复键(年+月+车牌)处理策略\n- P2:导入行级校验与实收 ≤ 应收校验\n", + "markdownPath": "src/prototypes/lease-business-detail/.spec/requirements-prd.md" + }, { "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", + "id": "lbd-doc-page-annotations", + "title": "页面标注", "defaultExpanded": true, "children": [ { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" + "type": "markdown", + "id": "lbd-doc-lbd-filter", + "title": "筛选条件", + "markdown": "## 2. 筛选区\n\n默认展示 5 项(一行),「更多筛选」展开车牌多选。\n\n| 筛选项 | 说明 |\n|---|---|\n| 统计月份 | `YYYY-MM` 格式,精确匹配年份+月份 |\n| 业务部门 | 精确匹配 |\n| 业务员 | 精确匹配 |\n| 客户名称 | 模糊包含 |\n| 状态 | 已结清 / 部分收款 / 未收款(按实收与应收合计计算) |\n| 车牌号码 | 多选精确匹配(更多筛选) |\n\n查询按钮触发筛选;KPI 与表尾汇总基于**全量筛选结果**(非当前页)。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-kpi", + "title": "KPI 汇总", + "markdown": "## 3. KPI 统计卡片(7 张)\n\n顶部展示关键指标;完整 23 项金额合计见列表底部汇总行。\n\n| KPI | 汇总字段 |\n|---|---|\n| 应收总计 | receivableTotal |\n| 押金总计 | deposit(仅数值型) |\n| 实收总计 | receivedAmount |\n| 里程减免总计 | mileageDeduction |\n| 其他减免总计 | otherDeduction |\n| 未收总计 | outstanding(>0 标红) |\n| 盈亏总计 | profitLoss(正负着色) |\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-toolbar", + "title": "批量导入导出", + "markdown": "## 4. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 批量导入 | 下载模板(仅 41 列手工录入)→ 上传 Excel → 公式列自动计算 |\n| 批量导出 | 导出当前筛选结果(完整 47 列,含公式列结果) |\n\n导入必填:**年份、月份、车牌号码**。\n\n### 导入模板列范围\n\n- **含**:41 列手工录入字段\n- **不含**:应收合计、月度收入、未收、车辆实际成本、总成本、盈亏(6 个公式列)\n- 导入后调用 `applyDetailCalculations` 统一重算\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-table", + "title": "明细列表", + "markdown": "## 5. 明细列表(47 列)\n\n表头顺序严格对齐 Excel「2.租赁车辆收入明细表」。\n\n### 交互\n\n- 横向滚动;左冻结:**年份 + 车牌号码**\n- 仅**日期类列**表头支持正序/倒序排序\n- 公式列(calculated)浅灰底色,导入模板不含\n- 表尾**汇总行**:23 个金额列 SUBTOTAL,首列显示「汇总」\n- 未收 > 0 单元格标红;盈亏正负着色\n- 底部分页:10 / 20 / 50 / 100\n\n### 表尾汇总(23 项)\n\n押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏。\n\n---" + } + ] + }, + { + "type": "folder", + "id": "lbd-doc-specs", + "title": "规格说明", + "defaultExpanded": false, + "children": [ + { + "type": "markdown", + "id": "lbd-doc-formulas", + "title": "公式列", + "markdown": "## 6. 公式列(系统自动计算)\n\n对齐 Excel 表内公式;实现见 `utils/calc-detail.ts`。\n\n| 字段 | 计算方式 |\n|---|---|\n| **应收合计** | 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免金额(负数)+ 其他减免金额(负数) |\n| **月度收入** | 月度租金 + 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免 + 其他减免 |\n| **未收** | 应收合计 − 实收金额 |\n| **车辆实际成本** | 车辆标准成本 × 平均天数 |\n| **总成本** | 车辆实际成本 + 保险费 + 氢费 + 运维费(成本)+ 居间费 + 其他 |\n| **盈亏** | 月度收入 − 总成本 |\n\n减免金额在 Excel 中以负数录入;导入时填负数即可。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-boundary", + "title": "模块边界", + "markdown": "## 模块边界\n\n### 与「租赁业务台账」的分工\n\n| 模块 | 定位 |\n|---|---|\n| **租赁业务明细**(本模块) | 对齐业管 Excel 运营台账宽表;手工列导入维护;6 个公式列自动计算;**不**做收款关联与账单状态 |\n| **租赁业务台账** | 账单维度维护、关联收款记录、收款状态(未收/部分/已收)、归档与角色数据范围 |\n\n下游 **业务部台账 / 业务汇总分析** 中「租赁业绩」数据来源为本模块明细。\n\n### 不做范围(当前原型)\n\n- 「租赁业务盈亏月度汇总」独立 Tab\n- 行内编辑、删除、服务端持久化\n- 关联收款记录自动回写实收\n- 员工/主管数据权限隔离\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-col-receivable-total", + "title": "应收合计", + "markdown": "## 应收合计\n\n**计算方式**:维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免金额(负数)+ 其他减免金额(负数)。\n**录入方式**:系统自动计算,只读。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-col-outstanding", + "title": "未收", + "markdown": "## 未收\n\n**计算方式**:应收合计 − 实收金额。\n**录入方式**:系统自动计算,只读。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-col-vehicle-actual-cost", + "title": "车辆实际成本", + "markdown": "## 车辆实际成本\n\n**计算方式**:车辆实际成本 = 车辆标准成本 × 平均天数。\n**录入方式**:系统自动计算,只读。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-col-total-cost", + "title": "总成本", + "markdown": "## 总成本\n\n**计算方式**:总成本 = 车辆实际成本 + 保险费 + 氢费 + 运维费(成本)+ 居间费 + 其他。\n**录入方式**:系统自动计算,只读。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-col-profit-loss", + "title": "盈亏", + "markdown": "## 盈亏\n\n**计算方式**:盈亏 = 月度收入 − 总成本。\n**录入方式**:系统自动计算,只读。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-col-status", + "title": "状态", + "markdown": "## 状态\n\n按**实收金额**与**应收合计**自动计算,只读展示。\n\n| 状态 | 判定规则 |\n|---|---|\n| **未收款** | 实收金额 = 0 或未录入 |\n| **部分收款** | 实收金额 > 0 且 < 应收合计 |\n| **已结清** | 实收金额 ≥ 应收合计 |\n\n列位置:月份列左侧;左冻结。\n\n---" + }, + { + "type": "markdown", + "id": "lbd-doc-lbd-col-hydrogen-fee", + "title": "氢费", + "markdown": "## 氢费\n\n**计算方式**:车辆氢费明细中,该车牌在「提车日期」与「退车日期」(未退车取账单月末)之间、承担方式为「我司承担」的加氢记录「加氢总价(元)」求和。\n**交互**:金额不为零时可点击,弹出加氢明细列表。\n**录入方式**:系统自动汇总,只读。\n\n---" } ] } diff --git a/src/prototypes/lease-business-detail/columnHeaderTips.ts b/src/prototypes/lease-business-detail/columnHeaderTips.ts new file mode 100644 index 0000000..b9bc9e5 --- /dev/null +++ b/src/prototypes/lease-business-detail/columnHeaderTips.ts @@ -0,0 +1,39 @@ +/** 表头悬浮说明 */ +export const COLUMN_HEADER_TIPS: Record = { + 'lbd-col-status': '按实收金额与应收合计自动计算:未收款 / 部分收款 / 已结清。', + 'lbd-col-year-month': '展示合并后的统计月份(YYYY-MM);导入模板仍使用「年份」「月份」两列。', + 'lbd-col-plate': '车牌号码与系统「车辆管理」匹配;不存在的车牌导入失败。', + 'lbd-col-vehicle-type': '根据车牌号,显示系统「品牌·型号」。', + 'lbd-col-business-dept': '根据车牌对应租赁合同显示业务部门与业务员;展示时合并为两行。', + 'lbd-col-customer': '导入后与客户管理比对;不一致时提示正确客户并支持一键替换。', + 'lbd-col-pickup-date': '对照交车管理校验提车日期;不一致时显示警告。', + 'lbd-col-return-date': '对照还车记录校验退车日期;不一致时显示警告。', + 'lbd-col-avg-days': '平均天数 = 本月在租天数 ÷ 当月自然日天数,保留两位小数。', + 'lbd-col-deposit': '与租赁合同保证金比对;不一致时提示正确金额。', + 'lbd-col-contract-rent': '与租赁合同租金比对;不一致时提示正确金额。', + 'lbd-col-receivable-total': + '应收合计 = 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免金额(负数)+ 其他减免金额(负数)。', + 'lbd-col-receivable-rent': + '应收租金 = 合同标的租金 × 租赁合同结算周期月份数;与导入值不一致时警告。', + 'lbd-col-monthly-income': + '月度收入 = 月度租金 + 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免 + 其他减免。', + 'lbd-col-monthly-rent': + '月度租金 = 合同标的租金 ÷ 付款周期月数,四舍五入保留两位小数。', + 'lbd-col-mileage-deduction': '里程减免金额以负数显示;录入正数时警告。', + 'lbd-col-other-deduction': '其他减免金额以负数显示;录入正数时警告。', + 'lbd-col-outstanding': '未收 = 应收合计 − 实收金额;差额为零时显示 0。', + 'lbd-col-payment-method': '根据合同付款间隔翻译:1→月度、3→季度、6→半年、12→年度,并附加预付/后付。', + 'lbd-col-vehicle-actual-cost': '车辆实际成本 = 车辆标准成本 × 平均天数。', + 'lbd-col-total-cost': '总成本 = 车辆实际成本 + 保险费 + 氢费 + 运维费(成本)+ 居间费 + 其他。', + 'lbd-col-hydrogen-fee': + '显示该车牌号码对应客户名称,所有加氢记录,承担方式为我司承担的记录「加氢总价(元)」总和', + 'lbd-col-profit-loss': '盈亏 = 月度收入 − 总成本;红色为正、绿色为负(股市配色)。', + 'lbd-col-asset-owner': '等同于车辆「登记所有权」;与车辆管理不一致时警告。', + 'lbd-col-signing-company': '显示与客户签订租赁合同的签约公司。', +}; + +export function resolveColumnHeaderTip(annotationId?: string, headerTip?: string): string | undefined { + if (headerTip) return headerTip; + if (annotationId && COLUMN_HEADER_TIPS[annotationId]) return COLUMN_HEADER_TIPS[annotationId]; + return undefined; +} diff --git a/src/prototypes/lease-business-detail/components/DetailChangeLogModal.tsx b/src/prototypes/lease-business-detail/components/DetailChangeLogModal.tsx new file mode 100644 index 0000000..31462f4 --- /dev/null +++ b/src/prototypes/lease-business-detail/components/DetailChangeLogModal.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { X } from 'lucide-react'; +import type { DetailChangeLogEntry } from '../utils/change-log'; +import { displayText } from '../utils/detail'; + +interface DetailChangeLogModalProps { + open: boolean; + rowLabel: string; + entries: DetailChangeLogEntry[]; + onClose: () => void; +} + +function renderDiffValue(value: string, tone: 'before' | 'after') { + const text = displayText(value); + if (!text) { + return ; + } + return ( + + {text} + + ); +} + +export function DetailChangeLogModal({ + open, + rowLabel: _rowLabel, + entries, + onClose, +}: DetailChangeLogModalProps) { + if (!open) return null; + + return ( +
+
event.stopPropagation()} + > +
+

变更日志

+ +
+
+ {entries.length === 0 ? ( +

暂无变更记录

+ ) : ( + <> +

共 {entries.length} 条变更记录

+
+ + + + + + + + + + + + + + + + + + + {entries.map((entry) => ( + + + + + + + + ))} + +
操作时间操作人字段修改前修改后
{displayText(entry.operatedAt)}{displayText(entry.operatorName)} + + {displayText(entry.fieldLabel) || '—'} + + {renderDiffValue(entry.before, 'before')}{renderDiffValue(entry.after, 'after')}
+
+ + )} +
+
+ +
+
+
+ ); +} diff --git a/src/prototypes/lease-business-detail/components/DetailDeleteConfirmModal.tsx b/src/prototypes/lease-business-detail/components/DetailDeleteConfirmModal.tsx new file mode 100644 index 0000000..b2796a6 --- /dev/null +++ b/src/prototypes/lease-business-detail/components/DetailDeleteConfirmModal.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { X } from 'lucide-react'; + +interface DetailDeleteConfirmModalProps { + open: boolean; + rowLabel?: string; + onCancel: () => void; + onConfirm: () => void; +} + +export function DetailDeleteConfirmModal({ + open, + rowLabel, + onCancel, + onConfirm, +}: DetailDeleteConfirmModalProps) { + if (!open) return null; + + return ( +
+
event.stopPropagation()} + > +
+

确认删除

+ +
+
+

删除后该条明细将无法恢复,是否确认删除?

+ {rowLabel &&

{rowLabel}

} +
+
+ + +
+
+
+ ); +} diff --git a/src/prototypes/lease-business-detail/components/DetailEditModal.tsx b/src/prototypes/lease-business-detail/components/DetailEditModal.tsx new file mode 100644 index 0000000..55694d9 --- /dev/null +++ b/src/prototypes/lease-business-detail/components/DetailEditModal.tsx @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from 'react'; +import { X } from 'lucide-react'; +import type { LeaseBusinessDetailRow } from '../types'; +import { detailRowYearMonth } from '../utils/detail'; +import { DETAIL_EDIT_FIELDS, draftFromRow, parseDraftToPatch } from '../utils/edit-fields'; + +interface DetailEditModalProps { + open: boolean; + row: LeaseBusinessDetailRow | null; + onClose: () => void; + onSave: (patch: Partial) => void; +} + +export function DetailEditModal({ open, row, onClose, onSave }: DetailEditModalProps) { + const [draft, setDraft] = useState>({}); + + useEffect(() => { + if (open && row) setDraft(draftFromRow(row)); + }, [open, row]); + + if (!open || !row) return null; + + const rowLabel = `${detailRowYearMonth(row)} · ${row.plateNo}`; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + onSave(parseDraftToPatch(draft)); + }; + + return ( +
+
event.stopPropagation()} + > +
+

编辑明细 · {rowLabel}

+ +
+
+

公式列(应收合计、月度收入、未收、车辆实际成本、总成本、盈亏、氢费等)保存后由系统自动重算。

+
+ {DETAIL_EDIT_FIELDS.map((field) => ( + + ))} +
+
+
+ + +
+
+
+ ); +} diff --git a/src/prototypes/lease-business-detail/components/DetailImportModal.tsx b/src/prototypes/lease-business-detail/components/DetailImportModal.tsx index c3d27c2..0cc0ecf 100644 --- a/src/prototypes/lease-business-detail/components/DetailImportModal.tsx +++ b/src/prototypes/lease-business-detail/components/DetailImportModal.tsx @@ -6,6 +6,8 @@ interface DetailImportModalProps { onClose: () => void; onDownloadTemplate: () => void; onImport: (file: File) => void; + failedCount?: number; + onDownloadErrorLog?: () => void; } export function DetailImportModal({ @@ -13,6 +15,8 @@ export function DetailImportModal({ onClose, onDownloadTemplate, onImport, + failedCount = 0, + onDownloadErrorLog, }: DetailImportModalProps) { const inputRef = useRef(null); if (!open) return null; @@ -36,11 +40,9 @@ export function DetailImportModal({
1 - 下载导入模板(仅含手工录入列,公式列由系统自动计算) + 下载导入模板
-
@@ -55,7 +57,6 @@ export function DetailImportModal({ >

点击选择文件上传

-

支持 .xls、.xlsx;必填:年份、月份、车牌号码。应收合计、月度收入、未收、车辆实际成本、总成本、盈亏由系统自动计算

+ {failedCount > 0 && onDownloadErrorLog ? ( +
+

+ 本次有 {failedCount} 条记录导入失败(如车牌号不存在),可下载错误日志核对。 +

+ +
+ ) : null}
- +
diff --git a/src/prototypes/lease-business-detail/components/DetailTable.tsx b/src/prototypes/lease-business-detail/components/DetailTable.tsx index c05bed6..7128a69 100644 --- a/src/prototypes/lease-business-detail/components/DetailTable.tsx +++ b/src/prototypes/lease-business-detail/components/DetailTable.tsx @@ -1,31 +1,55 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'; +import { OperationActions } from '../../../common/OperationActions'; import type { LeaseBusinessDetailRow } from '../types'; import { DEFAULT_DETAIL_COLUMN_WIDTHS, - DETAIL_SUMMARY_COLUMN_KEYS, DETAIL_TABLE_COLUMNS, DETAIL_TABLE_MIN_WIDTH, type DetailTableColumn, } from './tableColumns'; import type { LeaseDetailKpiSummary } from '../types'; import { + detailRowYearMonth, displayDeposit, displayMoney, displayOutstanding, displayText, formatMoney, } from '../utils/detail'; +import { resolveColumnHeaderTip } from '../columnHeaderTips'; +import { HeaderWithTip } from './TableHeaderTip'; +import { getFieldCheck, normalizeDeductionForDisplay } from '../utils/field-checks'; +import { wrapFieldCell } from './FieldCheckIcon'; +import { getVehicleRef, resolvePaymentMethodLabel } from '../utils/system-ref'; +import { getHydrogenRefuelRecords } from '../utils/hydrogen-fee'; +import { + DETAIL_PAYMENT_STATUS_CLASS, + paymentStatusSortValue, + resolveDetailPaymentStatus, +} from '../utils/payment-status'; +import { canEditDetailRow } from '../utils/change-log'; +import { + LedgerDetailPopover, + LedgerDetailTable, +} from '../../lease-business-ledger/components/LedgerDetailPopover'; interface DetailTableProps { records: LeaseBusinessDetailRow[]; summary: LeaseDetailKpiSummary; loading?: boolean; + isSupervisor: boolean; + onEdit: (row: LeaseBusinessDetailRow) => void; + onDelete: (row: LeaseBusinessDetailRow) => void; + onChangeLog: (row: LeaseBusinessDetailRow) => void; + onRowPatch?: (id: string, patch: Partial) => void; } type SortDir = 'asc' | 'desc'; -const STICKY_LEFT_KEYS = ['year', 'plateNo'] as const; +const ACTION_COL_WIDTH = 168; + +const STICKY_LEFT_KEYS = ['paymentStatus', 'yearMonth', 'plateNo'] as const; function buildStickyLayout(widths: Record) { let left = 0; @@ -59,36 +83,85 @@ function stickyStyle( }; } -function HeaderSort({ - title, - sortActive, - sortDir, - onSort, -}: { - title: string; - sortActive?: boolean; - sortDir?: SortDir; - onSort: () => void; -}) { - return ( - - ); +function getSortValue(row: LeaseBusinessDetailRow, key: string): string | number { + if (key === 'paymentStatus') { + return paymentStatusSortValue(row.receivableTotal, row.receivedAmount); + } + if (key === 'yearMonth') return row.year * 100 + row.month; + return row[key as keyof LeaseBusinessDetailRow] as string | number; } -function renderCell(row: LeaseBusinessDetailRow, col: DetailTableColumn) { +function renderCellContent( + row: LeaseBusinessDetailRow, + col: DetailTableColumn, +): React.ReactNode { + switch (col.render) { + case 'payment-status': { + const label = resolveDetailPaymentStatus(row.receivableTotal, row.receivedAmount); + const statusClass = DETAIL_PAYMENT_STATUS_CLASS[label]; + return {label}; + } + case 'year-month': + return {detailRowYearMonth(row)}; + case 'stack-dept': + return ( +
+ {displayText(row.businessDept)} + {displayText(row.salesperson)} +
+ ); + case 'vehicle-type': { + const vehicle = getVehicleRef(row.plateNo); + const label = vehicle?.brandModel || row.vehicleType; + return {displayText(label)}; + } + case 'payment-method': + return displayText(resolvePaymentMethodLabel(row)); + case 'hydrogen-fee': { + const amount = row.hydrogenFee; + const text = displayMoney(amount, true); + if (!text) return ''; + const records = getHydrogenRefuelRecords(row); + const tableRows = records.map((item) => ({ + hydrogenTime: item.hydrogenTime, + stationName: item.stationName, + customerName: item.customerName, + plateNo: item.plateNo, + hydrogenKg: String(item.hydrogenKg), + unitPrice: formatMoney(item.unitPrice), + totalPrice: formatMoney(item.totalPrice), + })); + return ( + + + + ); + } + default: + break; + } + if (col.key === 'outstanding') { const { text, warn } = displayOutstanding(row.outstanding); if (!text) return ''; return warn ? {text} - : text; + : {text}; } if (col.key === 'deposit') { return {displayDeposit(row.deposit)}; @@ -102,37 +175,63 @@ function renderCell(row: LeaseBusinessDetailRow, col: DetailTableColumn) { if (col.key === 'customerName') { return {displayText(row.customerName)}; } + if (col.key === 'mileageDeduction' || col.key === 'otherDeduction') { + const normalized = normalizeDeductionForDisplay(row[col.key]); + if (normalized === 0) return ''; + return {displayMoney(normalized, false)}; + } if (col.key === 'otherDeductionDetail' || col.key === 'remark') { const text = displayText(row[col.key]); return text ? {text} : ''; } const raw = row[col.key as keyof LeaseBusinessDetailRow]; if (col.money && typeof raw === 'number') return displayMoney(raw); - if (col.key === 'year' || col.key === 'month' || col.key === 'avgDays') { - return raw === 0 ? '' : String(raw); + if (col.key === 'avgDays') { + return raw === 0 ? '' : Number(raw).toFixed(2); } return displayText(raw); } +function renderCell( + row: LeaseBusinessDetailRow, + col: DetailTableColumn, + onRowPatch?: (id: string, patch: Partial) => void, +) { + const content = renderCellContent(row, col); + if (!col.checkField) return content; + + const check = getFieldCheck(row, col.checkField); + return wrapFieldCell( + content, + check, + check.replaceAction && onRowPatch + ? () => onRowPatch(row.id, check.replaceAction!) + : undefined, + ); +} + function renderSummaryCell(col: DetailTableColumn, summary: LeaseDetailKpiSummary) { - if (col.key === 'year') { + if (col.key === 'yearMonth') { return 汇总; } - if (!DETAIL_SUMMARY_COLUMN_KEYS.has(col.key)) return ''; + if (!col.summaryKey) return ''; - const value = summary[col.key as keyof LeaseDetailKpiSummary]; - if (col.key === 'outstanding') { + const value = summary[col.summaryKey]; + if (col.summaryKey === 'outstanding') { const { text, warn } = displayOutstanding(value); - if (!text) return ''; return warn ? {text} - : text; + : {text}; } - if (col.key === 'profitLoss') { + if (col.summaryKey === 'profitLoss') { const text = formatMoney(value); const cls = value >= 0 ? 'lbd-profit-pos' : 'lbd-profit-neg'; return {text}; } + if (col.summaryKey === 'mileageDeduction' || col.summaryKey === 'otherDeduction') { + const normalized = normalizeDeductionForDisplay(value); + return normalized === 0 ? '' : displayMoney(normalized, false); + } return displayMoney(value, false); } @@ -146,7 +245,16 @@ function TableSkeleton() { ); } -export function DetailTable({ records, summary, loading = false }: DetailTableProps) { +export function DetailTable({ + records, + summary, + loading = false, + isSupervisor, + onEdit, + onDelete, + onChangeLog, + onRowPatch, +}: DetailTableProps) { const [sortKey, setSortKey] = useState(null); const [sortDir, setSortDir] = useState('asc'); const [columnWidths, setColumnWidths] = useState>(() => ({ ...DEFAULT_DETAIL_COLUMN_WIDTHS })); @@ -158,8 +266,8 @@ export function DetailTable({ records, summary, loading = false }: DetailTablePr if (!sortKey) return records; const dir = sortDir === 'asc' ? 1 : -1; return [...records].sort((a, b) => { - const av = a[sortKey as keyof LeaseBusinessDetailRow]; - const bv = b[sortKey as keyof LeaseBusinessDetailRow]; + const av = getSortValue(a, sortKey); + const bv = getSortValue(b, sortKey); if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir; return String(av ?? '').localeCompare(String(bv ?? ''), 'zh-CN') * dir; }); @@ -206,7 +314,7 @@ export function DetailTable({ records, summary, loading = false }: DetailTablePr const tableMinWidth = Math.max( DETAIL_TABLE_MIN_WIDTH, - DETAIL_TABLE_COLUMNS.reduce( + ACTION_COL_WIDTH + DETAIL_TABLE_COLUMNS.reduce( (sum, col) => sum + (columnWidths[col.key] ?? DEFAULT_DETAIL_COLUMN_WIDTHS[col.key] ?? 100), 0, ), @@ -236,59 +344,89 @@ export function DetailTable({ records, summary, loading = false }: DetailTablePr {DETAIL_TABLE_COLUMNS.map((col) => ( ))} + - {DETAIL_TABLE_COLUMNS.map((col) => ( - - {col.dateSort ? ( - handleSort(col.key)} - /> - ) : ( - col.title - )} - handleResizeStart(col.key, event)} - /> - - ))} - - - - {sortedRecords.map((row) => ( - - {DETAIL_TABLE_COLUMNS.map((col) => ( - { + const headerTip = resolveColumnHeaderTip(col.annotationId, col.headerTip); + return ( + - {renderCell(row, col)} +
+ handleSort(col.key) : undefined} + /> + handleResizeStart(col.key, event)} + /> +
+ + ); + })} + 操作 + + + + {sortedRecords.map((row) => { + const canEdit = canEditDetailRow(row, isSupervisor); + const archived = row.archiveStatus === 'archived'; + return ( + + {DETAIL_TABLE_COLUMNS.map((col) => ( + + {renderCell(row, col, onRowPatch)} + + ))} + + onEdit(row) } : undefined} + more={[ + ...(canEdit + ? [{ + key: 'delete', + label: '删除', + danger: true, + onClick: () => onDelete(row), + }] + : []), + { + key: 'changeLog', + label: '变更日志', + onClick: () => onChangeLog(row), + }, + ]} + /> - ))} - - ))} + + ); + })} @@ -306,6 +444,7 @@ export function DetailTable({ records, summary, loading = false }: DetailTablePr {renderSummaryCell(col, summary)} ))} + diff --git a/src/prototypes/lease-business-detail/components/FieldCheckIcon.tsx b/src/prototypes/lease-business-detail/components/FieldCheckIcon.tsx new file mode 100644 index 0000000..d55dfc2 --- /dev/null +++ b/src/prototypes/lease-business-detail/components/FieldCheckIcon.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +import { Check, AlertTriangle } from 'lucide-react'; +import type { FieldCheckInfo } from '../utils/field-checks'; + +interface FieldCheckIconProps { + info: FieldCheckInfo; + onReplace?: () => void; +} + +function PopoverPanel({ + info, + onReplace, + onClose, +}: { + info: FieldCheckInfo; + onReplace?: () => void; + onClose: () => void; +}) { + return ( +
+ {info.title ?
{info.title}
: null} + {info.details?.map((line) => ( +
{line}
+ ))} + {info.replaceAction && onReplace ? ( +
+ +
+ ) : null} +
+ ); +} + +export function FieldCheckIcon({ info, onReplace }: FieldCheckIconProps) { + const [open, setOpen] = useState(false); + if (info.status === 'none') return null; + + const isOk = info.status === 'ok'; + const icon = isOk + ? + : ; + + return ( + + + {open && !isOk ? ( + setOpen(false)} /> + ) : null} + {open && isOk && info.details?.length ? ( +
+ {info.details.map((line) => ( +
{line}
+ ))} +
+ ) : null} +
+ ); +} + +export function wrapFieldCell( + main: React.ReactNode, + info: FieldCheckInfo, + onReplace?: () => void, +): React.ReactNode { + if (info.status === 'none') return main; + return ( + + {main} + + + ); +} diff --git a/src/prototypes/lease-business-detail/components/FilterPanel.tsx b/src/prototypes/lease-business-detail/components/FilterPanel.tsx index 9137931..5bff6aa 100644 --- a/src/prototypes/lease-business-detail/components/FilterPanel.tsx +++ b/src/prototypes/lease-business-detail/components/FilterPanel.tsx @@ -4,8 +4,9 @@ import type { LeaseBusinessDetailRow, LeaseDetailFilters } from '../types'; import { FilterPickerField } from '../../vehicle-management/components/FilterPickerField'; import { MultiPlateFilterField } from '../../vehicle-management/components/MultiPlateFilterField'; import { buildDetailMonthOptions, buildDetailOptions } from '../utils/detail'; +import { DETAIL_PAYMENT_STATUS_OPTIONS } from '../utils/payment-status'; -const PRIMARY_KEYS = ['statMonth', 'businessDept', 'salesperson', 'customerName'] as const; +const PRIMARY_KEYS = ['statMonth', 'businessDept', 'salesperson', 'customerName', 'paymentStatus'] as const; const EXTRA_KEYS = ['plateNos'] as const; type FieldKey = (typeof PRIMARY_KEYS)[number] | (typeof EXTRA_KEYS)[number]; @@ -98,6 +99,19 @@ export function FilterPanel({ /> ), }, + paymentStatus: { + key: 'paymentStatus', + label: '状态', + control: ( + onChange({ paymentStatus })} + /> + ), + }, plateNos: { key: 'plateNos', label: '车牌号码', @@ -158,21 +172,19 @@ export function FilterPanel({ onClick={() => setExpanded(!expanded)} aria-expanded={expanded} aria-controls="lbd-filter-expand-panel" - > + + data-vm-icon={expanded ? 'chevron-up' : 'filter'}> {expanded ? '收起' : '更多筛选'} {extraActiveCount > 0 && !expanded ? ( {extraActiveCount} ) : null} - - - + - diff --git a/src/prototypes/lease-business-detail/components/TableHeaderTip.tsx b/src/prototypes/lease-business-detail/components/TableHeaderTip.tsx new file mode 100644 index 0000000..3ea1ef3 --- /dev/null +++ b/src/prototypes/lease-business-detail/components/TableHeaderTip.tsx @@ -0,0 +1,100 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { ArrowDown, ArrowUp, ArrowUpDown, CircleHelp } from 'lucide-react'; + +const HEADER_TIP_MAX_WIDTH = 300; +const HEADER_TIP_VIEWPORT_MARGIN = 12; + +export function TableHeaderTip({ tip, title }: { tip: string; title: string }) { + const anchorRef = useRef(null); + const [visible, setVisible] = useState(false); + const [style, setStyle] = useState({}); + + const reposition = useCallback(() => { + const el = anchorRef.current; + if (!el) return; + const rect = el.getBoundingClientRect(); + const maxWidth = Math.min(HEADER_TIP_MAX_WIDTH, window.innerWidth - HEADER_TIP_VIEWPORT_MARGIN * 2); + let left = rect.left; + left = Math.max( + HEADER_TIP_VIEWPORT_MARGIN, + Math.min(left, window.innerWidth - HEADER_TIP_VIEWPORT_MARGIN - maxWidth), + ); + setStyle({ top: rect.bottom + 6, left, maxWidth }); + }, []); + + useEffect(() => { + if (!visible) return undefined; + reposition(); + window.addEventListener('scroll', reposition, true); + window.addEventListener('resize', reposition); + return () => { + window.removeEventListener('scroll', reposition, true); + window.removeEventListener('resize', reposition); + }; + }, [visible, reposition]); + + return ( + <> + e.stopPropagation()} + onMouseEnter={() => { reposition(); setVisible(true); }} + onMouseLeave={() => setVisible(false)} + onFocus={() => { reposition(); setVisible(true); }} + onBlur={() => setVisible(false)} + > + + + {visible && createPortal( +
+ {tip} +
, + document.body, + )} + + ); +} + +export function HeaderWithTip({ + title, + tip, + sortable, + sortActive, + sortDir, + onSort, +}: { + title: string; + tip?: string; + sortable?: boolean; + sortActive?: boolean; + sortDir?: 'asc' | 'desc'; + onSort?: () => void; +}) { + const label = ( + + {title} + {tip ? : null} + + ); + + if (!sortable || !onSort) return label; + + return ( + + ); +} diff --git a/src/prototypes/lease-business-detail/components/tableColumns.ts b/src/prototypes/lease-business-detail/components/tableColumns.ts index a5b88d4..e59613b 100644 --- a/src/prototypes/lease-business-detail/components/tableColumns.ts +++ b/src/prototypes/lease-business-detail/components/tableColumns.ts @@ -1,77 +1,325 @@ +import type { LeaseBusinessDetailRow, LeaseDetailKpiSummary } from '../types'; + +export type DetailColumnRender = + | 'payment-status' + | 'year-month' + | 'stack-dept' + | 'vehicle-type' + | 'payment-method' + | 'hydrogen-fee'; + export interface DetailTableColumn { key: string; title: string; + annotationId?: string; + headerTip?: string; money?: boolean; className?: string; sticky?: 'left' | 'right'; defaultWidth?: number; - /** 日期类列,支持表头正序/倒序 */ dateSort?: boolean; - /** Excel 公式列,系统计算、导入模板不含 */ calculated?: boolean; + render?: DetailColumnRender; + /** 表尾汇总对应字段 */ + summaryKey?: keyof LeaseDetailKpiSummary; + /** 单元格校验字段 */ + checkField?: + | 'customerName' + | 'pickupDate' + | 'returnDate' + | 'deposit' + | 'contractRent' + | 'receivableRent' + | 'mileageDeduction' + | 'otherDeduction' + | 'assetOwner'; } -/** 列表表头列(严格对齐业务明细 Excel 样表) */ +/** 页面展示列(年月、部门+业务员已合并) */ export const DETAIL_TABLE_COLUMNS: DetailTableColumn[] = [ - { key: 'year', title: '年份', defaultWidth: 64, sticky: 'left', className: 'tabular-nums' }, - { key: 'month', title: '月份', defaultWidth: 56, className: 'tabular-nums' }, - { key: 'businessDept', title: '业务部门', defaultWidth: 96 }, - { key: 'salesperson', title: '业务员', defaultWidth: 88 }, + { + key: 'paymentStatus', + title: '状态', + render: 'payment-status', + sticky: 'left', + defaultWidth: 96, + annotationId: 'lbd-col-status', + }, + { + key: 'yearMonth', + title: '月份', + render: 'year-month', + sticky: 'left', + className: 'tabular-nums', + defaultWidth: 88, + annotationId: 'lbd-col-year-month', + dateSort: true, + }, + { + key: 'businessAssignment', + title: '业务部门', + render: 'stack-dept', + defaultWidth: 120, + annotationId: 'lbd-col-business-dept', + }, { key: 'paymentDate', title: '付款日期', defaultWidth: 108, dateSort: true }, - { key: 'plateNo', title: '车牌号码', defaultWidth: 110, sticky: 'left', className: 'lbd-col-plate' }, - { key: 'vehicleType', title: '车型', defaultWidth: 72 }, - { key: 'customerName', title: '客户名称', className: 'ldb-col-name', defaultWidth: 200 }, + { + key: 'plateNo', + title: '车牌号码', + defaultWidth: 110, + sticky: 'left', + className: 'lbd-col-plate', + annotationId: 'lbd-col-plate', + }, + { + key: 'vehicleType', + title: '车型', + render: 'vehicle-type', + defaultWidth: 120, + annotationId: 'lbd-col-vehicle-type', + }, + { + key: 'customerName', + title: '客户名称', + className: 'ldb-col-name', + defaultWidth: 200, + annotationId: 'lbd-col-customer', + checkField: 'customerName', + }, { key: 'valueAddedService', title: '增值服务', defaultWidth: 96 }, { key: 'exemptionPolicy', title: '享免政策', defaultWidth: 120 }, - { key: 'pickupDate', title: '提车日期', defaultWidth: 108, dateSort: true }, + { + key: 'pickupDate', + title: '提车日期', + defaultWidth: 108, + dateSort: true, + annotationId: 'lbd-col-pickup-date', + checkField: 'pickupDate', + }, { key: 'contractStartDate', title: '合同生效日期', defaultWidth: 118, dateSort: true }, { key: 'contractEndDate', title: '合同到期日期', defaultWidth: 118, dateSort: true }, { key: 'periodStartDate', title: '起始日期', defaultWidth: 108, dateSort: true }, - { key: 'returnDate', title: '退车日期', defaultWidth: 108, dateSort: true }, - { key: 'avgDays', title: '平均天数', defaultWidth: 88, className: 'tabular-nums' }, - { key: 'deposit', title: '押金', money: true, defaultWidth: 100 }, - { key: 'contractRent', title: '合同标的租金', money: true, defaultWidth: 110 }, - { key: 'receivableTotal', title: '应收合计', money: true, defaultWidth: 100, calculated: true }, - { key: 'receivableRent', title: '应收租金', money: true, defaultWidth: 100 }, - { key: 'monthlyIncome', title: '月度收入', money: true, defaultWidth: 100, calculated: true }, - { key: 'monthlyRent', title: '月度租金', money: true, defaultWidth: 100 }, - { key: 'maintenanceIncome', title: '维保包干收入', money: true, defaultWidth: 110 }, - { key: 'insuranceSurcharge', title: '保险上浮费', money: true, defaultWidth: 100 }, - { key: 'opsFeeIncome', title: '运维费', money: true, defaultWidth: 90 }, - { key: 'otherIncome', title: '其他收入', money: true, defaultWidth: 100 }, - { key: 'mileageDeduction', title: '里程减免金额', money: true, defaultWidth: 110 }, - { key: 'otherDeduction', title: '其他减免金额', money: true, defaultWidth: 110 }, - { key: 'receivedAmount', title: '实收金额', money: true, defaultWidth: 100 }, - { key: 'outstanding', title: '未收', money: true, defaultWidth: 90, calculated: true }, + { + key: 'returnDate', + title: '退车日期', + defaultWidth: 108, + dateSort: true, + annotationId: 'lbd-col-return-date', + checkField: 'returnDate', + }, + { + key: 'avgDays', + title: '平均天数', + defaultWidth: 88, + className: 'tabular-nums', + annotationId: 'lbd-col-avg-days', + }, + { + key: 'deposit', + title: '押金', + money: true, + defaultWidth: 100, + summaryKey: 'deposit', + annotationId: 'lbd-col-deposit', + checkField: 'deposit', + }, + { + key: 'contractRent', + title: '合同标的租金', + money: true, + defaultWidth: 110, + summaryKey: 'contractRent', + annotationId: 'lbd-col-contract-rent', + checkField: 'contractRent', + }, + { + key: 'receivableTotal', + title: '应收合计', + money: true, + defaultWidth: 100, + calculated: true, + summaryKey: 'receivableTotal', + annotationId: 'lbd-col-receivable-total', + }, + { + key: 'receivableRent', + title: '应收租金', + money: true, + defaultWidth: 100, + summaryKey: 'receivableRent', + annotationId: 'lbd-col-receivable-rent', + checkField: 'receivableRent', + }, + { + key: 'monthlyIncome', + title: '月度收入', + money: true, + defaultWidth: 100, + calculated: true, + summaryKey: 'monthlyIncome', + annotationId: 'lbd-col-monthly-income', + }, + { + key: 'monthlyRent', + title: '月度租金', + money: true, + defaultWidth: 100, + summaryKey: 'monthlyRent', + annotationId: 'lbd-col-monthly-rent', + }, + { key: 'maintenanceIncome', title: '维保包干收入', money: true, defaultWidth: 110, summaryKey: 'maintenanceIncome' }, + { key: 'insuranceSurcharge', title: '保险上浮费', money: true, defaultWidth: 100, summaryKey: 'insuranceSurcharge' }, + { key: 'opsFeeIncome', title: '运维费', money: true, defaultWidth: 90, summaryKey: 'opsFeeIncome' }, + { key: 'otherIncome', title: '其他收入', money: true, defaultWidth: 100, summaryKey: 'otherIncome' }, + { + key: 'mileageDeduction', + title: '里程减免金额', + money: true, + defaultWidth: 110, + summaryKey: 'mileageDeduction', + annotationId: 'lbd-col-mileage-deduction', + checkField: 'mileageDeduction', + }, + { + key: 'otherDeduction', + title: '其他减免金额', + money: true, + defaultWidth: 110, + summaryKey: 'otherDeduction', + annotationId: 'lbd-col-other-deduction', + checkField: 'otherDeduction', + }, + { key: 'receivedAmount', title: '实收金额', money: true, defaultWidth: 100, summaryKey: 'receivedAmount' }, + { + key: 'outstanding', + title: '未收', + money: true, + defaultWidth: 90, + calculated: true, + summaryKey: 'outstanding', + annotationId: 'lbd-col-outstanding', + }, { key: 'invoiceDate', title: '开票日期', defaultWidth: 108, dateSort: true }, { key: 'actualPaymentDate', title: '实际付款日期', defaultWidth: 118, dateSort: true }, - { key: 'paymentMethod', title: '付款方式', defaultWidth: 100 }, - { key: 'vehicleStandardCost', title: '车辆标准成本', money: true, defaultWidth: 110 }, - { key: 'vehicleActualCost', title: '车辆实际成本', money: true, defaultWidth: 110, calculated: true }, - { key: 'insuranceFee', title: '保险费', money: true, defaultWidth: 90 }, - { key: 'hydrogenFee', title: '氢费', money: true, defaultWidth: 90 }, - { key: 'opsFeeCost', title: '运维费', money: true, defaultWidth: 90 }, - { key: 'brokerageFee', title: '居间费', money: true, defaultWidth: 90 }, - { key: 'otherCost', title: '其他', money: true, defaultWidth: 90 }, - { key: 'totalCost', title: '总成本', money: true, defaultWidth: 100, calculated: true }, - { key: 'profitLoss', title: '盈亏', money: true, defaultWidth: 90, calculated: true }, + { + key: 'paymentMethod', + title: '付款方式', + render: 'payment-method', + defaultWidth: 108, + annotationId: 'lbd-col-payment-method', + }, + { key: 'vehicleStandardCost', title: '车辆标准成本', money: true, defaultWidth: 110, summaryKey: 'vehicleStandardCost' }, + { + key: 'vehicleActualCost', + title: '车辆实际成本', + money: true, + defaultWidth: 110, + calculated: true, + summaryKey: 'vehicleActualCost', + annotationId: 'lbd-col-vehicle-actual-cost', + }, + { key: 'insuranceFee', title: '保险费', money: true, defaultWidth: 90, summaryKey: 'insuranceFee' }, + { + key: 'hydrogenFee', + title: '氢费', + money: true, + defaultWidth: 90, + summaryKey: 'hydrogenFee', + render: 'hydrogen-fee', + annotationId: 'lbd-col-hydrogen-fee', + }, + { key: 'opsFeeCost', title: '运维费', money: true, defaultWidth: 90, summaryKey: 'opsFeeCost' }, + { key: 'brokerageFee', title: '居间费', money: true, defaultWidth: 90, summaryKey: 'brokerageFee' }, + { key: 'otherCost', title: '其他', money: true, defaultWidth: 90, summaryKey: 'otherCost' }, + { + key: 'totalCost', + title: '总成本', + money: true, + defaultWidth: 100, + calculated: true, + summaryKey: 'totalCost', + annotationId: 'lbd-col-total-cost', + }, + { + key: 'profitLoss', + title: '盈亏', + money: true, + defaultWidth: 90, + calculated: true, + summaryKey: 'profitLoss', + annotationId: 'lbd-col-profit-loss', + }, { key: 'gpsMileage', title: 'GPS里程', defaultWidth: 96, className: 'tabular-nums' }, { key: 'otherDeductionDetail', title: '其他减免金额明细内容', className: 'ldb-col-remark', defaultWidth: 160 }, - { key: 'assetOwner', title: '资产归属', defaultWidth: 140 }, - { key: 'signingCompany', title: '签约公司', className: 'ldb-col-name', defaultWidth: 180 }, + { + key: 'assetOwner', + title: '资产归属', + defaultWidth: 140, + annotationId: 'lbd-col-asset-owner', + checkField: 'assetOwner', + }, + { + key: 'signingCompany', + title: '签约公司', + className: 'ldb-col-name', + defaultWidth: 180, + annotationId: 'lbd-col-signing-company', + }, { key: 'remark', title: '备注', className: 'ldb-col-remark', defaultWidth: 140 }, ]; -/** 列表导出表头(含公式列) */ -export const DETAIL_EXPORT_HEADERS = DETAIL_TABLE_COLUMNS.map((col) => col.title); +/** Excel 导入/导出列规格(47 列,含独立年份/月份/业务员) */ +export const DETAIL_EXPORT_SPECS: { key: keyof LeaseBusinessDetailRow; title: string }[] = [ + { key: 'year', title: '年份' }, + { key: 'month', title: '月份' }, + { key: 'businessDept', title: '业务部门' }, + { key: 'salesperson', title: '业务员' }, + { key: 'paymentDate', title: '付款日期' }, + { key: 'plateNo', title: '车牌号码' }, + { key: 'vehicleType', title: '车型' }, + { key: 'customerName', title: '客户名称' }, + { key: 'valueAddedService', title: '增值服务' }, + { key: 'exemptionPolicy', title: '享免政策' }, + { key: 'pickupDate', title: '提车日期' }, + { key: 'contractStartDate', title: '合同生效日期' }, + { key: 'contractEndDate', title: '合同到期日期' }, + { key: 'periodStartDate', title: '起始日期' }, + { key: 'returnDate', title: '退车日期' }, + { key: 'avgDays', title: '平均天数' }, + { key: 'deposit', title: '押金' }, + { key: 'contractRent', title: '合同标的租金' }, + { key: 'receivableTotal', title: '应收合计' }, + { key: 'receivableRent', title: '应收租金' }, + { key: 'monthlyIncome', title: '月度收入' }, + { key: 'monthlyRent', title: '月度租金' }, + { key: 'maintenanceIncome', title: '维保包干收入' }, + { key: 'insuranceSurcharge', title: '保险上浮费' }, + { key: 'opsFeeIncome', title: '运维费' }, + { key: 'otherIncome', title: '其他收入' }, + { key: 'mileageDeduction', title: '里程减免金额' }, + { key: 'otherDeduction', title: '其他减免金额' }, + { key: 'receivedAmount', title: '实收金额' }, + { key: 'outstanding', title: '未收' }, + { key: 'invoiceDate', title: '开票日期' }, + { key: 'actualPaymentDate', title: '实际付款日期' }, + { key: 'paymentMethod', title: '付款方式' }, + { key: 'vehicleStandardCost', title: '车辆标准成本' }, + { key: 'vehicleActualCost', title: '车辆实际成本' }, + { key: 'insuranceFee', title: '保险费' }, + { key: 'hydrogenFee', title: '氢费' }, + { key: 'opsFeeCost', title: '运维费' }, + { key: 'brokerageFee', title: '居间费' }, + { key: 'otherCost', title: '其他' }, + { key: 'totalCost', title: '总成本' }, + { key: 'profitLoss', title: '盈亏' }, + { key: 'gpsMileage', title: 'GPS里程' }, + { key: 'otherDeductionDetail', title: '其他减免金额明细内容' }, + { key: 'assetOwner', title: '资产归属' }, + { key: 'signingCompany', title: '签约公司' }, + { key: 'remark', title: '备注' }, +]; -/** 导入模板表头(仅手工录入列,不含公式列) */ -export const DETAIL_IMPORT_TEMPLATE_HEADERS = DETAIL_TABLE_COLUMNS - .filter((col) => !col.calculated) - .map((col) => col.title); - -/** @deprecated 使用 DETAIL_EXPORT_HEADERS / DETAIL_IMPORT_TEMPLATE_HEADERS */ -export const DETAIL_IMPORT_HEADERS = DETAIL_EXPORT_HEADERS; +export const DETAIL_EXPORT_HEADERS = DETAIL_EXPORT_SPECS.map((s) => s.title); export const DEFAULT_DETAIL_COLUMN_WIDTHS: Record = Object.fromEntries( DETAIL_TABLE_COLUMNS.map((col) => [col.key, col.defaultWidth ?? 100]), @@ -82,29 +330,6 @@ export const DETAIL_TABLE_MIN_WIDTH = DETAIL_TABLE_COLUMNS.reduce( 0, ); -/** 表尾汇总行:对齐 Excel SUBTOTAL 的 23 个金额列 */ -export const DETAIL_SUMMARY_COLUMN_KEYS = new Set([ - 'deposit', - 'contractRent', - 'receivableTotal', - 'receivableRent', - 'monthlyIncome', - 'monthlyRent', - 'maintenanceIncome', - 'insuranceSurcharge', - 'opsFeeIncome', - 'otherIncome', - 'mileageDeduction', - 'otherDeduction', - 'receivedAmount', - 'outstanding', - 'vehicleStandardCost', - 'vehicleActualCost', - 'insuranceFee', - 'hydrogenFee', - 'opsFeeCost', - 'brokerageFee', - 'otherCost', - 'totalCost', - 'profitLoss', -]); +export const DETAIL_SUMMARY_COLUMN_KEYS = new Set( + DETAIL_TABLE_COLUMNS.filter((c) => c.summaryKey).map((c) => c.summaryKey as string), +); diff --git a/src/prototypes/lease-business-detail/data/change-log-seed.json b/src/prototypes/lease-business-detail/data/change-log-seed.json new file mode 100644 index 0000000..9a1c027 --- /dev/null +++ b/src/prototypes/lease-business-detail/data/change-log-seed.json @@ -0,0 +1,178 @@ +{ + "lbd-1": [ + { + "id": "lbd-clog-seed-1-1", + "operatedAt": "2026-01-05 09:12:08", + "operatorId": "staff-001", + "operatorName": "刘念念", + "field": "_import", + "fieldLabel": "批量导入", + "before": "—", + "after": "新增明细" + }, + { + "id": "lbd-clog-seed-1-2", + "operatedAt": "2026-01-06 14:25:33", + "operatorId": "staff-001", + "operatorName": "刘念念", + "field": "receivedAmount", + "fieldLabel": "实收金额", + "before": "0.00", + "after": "4,500.00" + }, + { + "id": "lbd-clog-seed-1-3", + "operatedAt": "2026-01-07 10:08:19", + "operatorId": "staff-001", + "operatorName": "刘念念", + "field": "customerName", + "fieldLabel": "客户名称", + "before": "安徽驰远供应链", + "after": "安徽驰远供应链管理有限公司" + }, + { + "id": "lbd-clog-seed-1-4", + "operatedAt": "2026-01-08 16:42:05", + "operatorId": "staff-001", + "operatorName": "刘念念", + "field": "actualPaymentDate", + "fieldLabel": "实际付款日期", + "before": "—", + "after": "2026-01-19" + }, + { + "id": "lbd-clog-seed-1-5", + "operatedAt": "2026-01-10 11:30:00", + "operatorId": "staff-001", + "operatorName": "刘念念", + "field": "remark", + "fieldLabel": "备注", + "before": "—", + "after": "1月租金已收齐" + } + ], + "lbd-33": [ + { + "id": "lbd-clog-seed-33-1", + "operatedAt": "2026-01-12 08:55:12", + "operatorId": "staff-002", + "operatorName": "尚建华", + "field": "_import", + "fieldLabel": "批量导入", + "before": "—", + "after": "新增明细" + }, + { + "id": "lbd-clog-seed-33-2", + "operatedAt": "2026-01-13 15:20:44", + "operatorId": "staff-002", + "operatorName": "尚建华", + "field": "contractRent", + "fieldLabel": "合同标的租金", + "before": "3,800.00", + "after": "4,000.00" + }, + { + "id": "lbd-clog-seed-33-3", + "operatedAt": "2026-01-14 09:18:27", + "operatorId": "staff-002", + "operatorName": "尚建华", + "field": "receivedAmount", + "fieldLabel": "实收金额", + "before": "0.00", + "after": "4,000.00" + }, + { + "id": "lbd-clog-seed-33-4", + "operatedAt": "2026-01-15 17:05:51", + "operatorId": "staff-002", + "operatorName": "尚建华", + "field": "valueAddedService", + "fieldLabel": "增值服务", + "before": "—", + "after": "含1000元氢费" + }, + { + "id": "lbd-clog-seed-33-5", + "operatedAt": "2026-01-18 10:22:36", + "operatorId": "staff-002", + "operatorName": "尚建华", + "field": "_reconcile", + "fieldLabel": "完成对账", + "before": "未对账", + "after": "已对账(2026-01-18)" + }, + { + "id": "lbd-clog-seed-33-6", + "operatedAt": "2026-01-18 10:22:36", + "operatorId": "staff-002", + "operatorName": "尚建华", + "field": "archiveStatus", + "fieldLabel": "对账状态", + "before": "未对账", + "after": "已对账" + }, + { + "id": "lbd-clog-seed-33-7", + "operatedAt": "2026-01-20 14:11:08", + "operatorId": "supervisor-001", + "operatorName": "王主管", + "field": "remark", + "fieldLabel": "备注", + "before": "—", + "after": "主管复核:实收与合同一致,归档确认" + } + ], + "lbd-280": [ + { + "id": "lbd-clog-seed-280-1", + "operatedAt": "2026-04-10 09:40:15", + "operatorId": "staff-003", + "operatorName": "赵连飞", + "field": "_import", + "fieldLabel": "批量导入", + "before": "—", + "after": "新增明细" + }, + { + "id": "lbd-clog-seed-280-2", + "operatedAt": "2026-04-11 11:05:22", + "operatorId": "staff-003", + "operatorName": "赵连飞", + "field": "vehicleStandardCost", + "fieldLabel": "车辆标准成本", + "before": "2,800.00", + "after": "3,000.00" + }, + { + "id": "lbd-clog-seed-280-3", + "operatedAt": "2026-04-12 16:33:49", + "operatorId": "staff-003", + "operatorName": "赵连飞", + "field": "totalCost", + "fieldLabel": "总成本", + "before": "10,200.00", + "after": "11,190.22" + }, + { + "id": "lbd-clog-seed-280-4", + "operatedAt": "2026-04-14 08:50:03", + "operatorId": "staff-003", + "operatorName": "赵连飞", + "field": "hydrogenFee", + "fieldLabel": "氢费", + "before": "0.00", + "after": "8,190.22" + }, + { + "id": "lbd-clog-seed-280-5", + "operatedAt": "2026-04-15 13:28:17", + "operatorId": "supervisor-001", + "operatorName": "王主管", + "field": "profitLoss", + "fieldLabel": "盈亏", + "before": "-2,400.22", + "after": "-3,390.22" + } + ] +} diff --git a/src/prototypes/lease-business-detail/data/hydrogen-refuel-records.json b/src/prototypes/lease-business-detail/data/hydrogen-refuel-records.json new file mode 100644 index 0000000..0a520d1 --- /dev/null +++ b/src/prototypes/lease-business-detail/data/hydrogen-refuel-records.json @@ -0,0 +1,162 @@ +[ + { + "hydrogenTime": "2026-01-08 09:15:00", + "stationName": "上海嘉定氢站", + "customerName": "安徽驰远供应链管理有限公司", + "plateNo": "沪A32896F", + "hydrogenKg": 42.5, + "unitPrice": 30.13, + "totalPrice": 1280.5, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-01-22 11:40:00", + "stationName": "上海嘉定氢站", + "customerName": "安徽驰远供应链管理有限公司", + "plateNo": "沪A32896F", + "hydrogenKg": 20, + "unitPrice": 31, + "totalPrice": 620, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-01-11 08:30:00", + "stationName": "嘉兴南湖氢站", + "customerName": "上海利合供应链管理有限公司", + "plateNo": "浙F08991F", + "hydrogenKg": 24, + "unitPrice": 30, + "totalPrice": 720, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-01-12 10:30:00", + "stationName": "乌鲁木齐米东加氢站", + "customerName": "新疆佳淇信息科技有限公司", + "plateNo": "粤AGR5098", + "hydrogenKg": 11.8, + "unitPrice": 45, + "totalPrice": 531.25, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-01-25 14:20:00", + "stationName": "乌鲁木齐米东加氢站", + "customerName": "新疆佳淇信息科技有限公司", + "plateNo": "粤AGR5098", + "hydrogenKg": 9.72, + "unitPrice": 45, + "totalPrice": 437.5, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-04-05 09:10:00", + "stationName": "嘉兴港区加氢站", + "customerName": "嘉兴市飞宇物流有限公司", + "plateNo": "粤AGP9713", + "hydrogenKg": 45.5, + "unitPrice": 45, + "totalPrice": 2047.56, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-04-12 15:22:00", + "stationName": "嘉兴港区加氢站", + "customerName": "嘉兴市飞宇物流有限公司", + "plateNo": "粤AGP9713", + "hydrogenKg": 45.5, + "unitPrice": 45, + "totalPrice": 2047.55, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-04-18 10:05:00", + "stationName": "嘉兴港区加氢站", + "customerName": "嘉兴市飞宇物流有限公司", + "plateNo": "粤AGP9713", + "hydrogenKg": 45.5, + "unitPrice": 45, + "totalPrice": 2047.55, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-04-25 16:48:00", + "stationName": "嘉兴港区加氢站", + "customerName": "嘉兴市飞宇物流有限公司", + "plateNo": "粤AGP9713", + "hydrogenKg": 45.5, + "unitPrice": 45, + "totalPrice": 2047.56, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-05-03 08:55:00", + "stationName": "乌鲁木齐头屯河加氢站", + "customerName": "黑龙江圣合商贸有限公司新疆分公司", + "plateNo": "粤AGP5711", + "hydrogenKg": 53.17, + "unitPrice": 45, + "totalPrice": 2392.58, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-05-18 13:30:00", + "stationName": "乌鲁木齐头屯河加氢站", + "customerName": "黑龙江圣合商贸有限公司新疆分公司", + "plateNo": "粤AGP5711", + "hydrogenKg": 53.17, + "unitPrice": 45, + "totalPrice": 2392.58, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-05-05 07:40:00", + "stationName": "嘉兴港区加氢站", + "customerName": "嘉兴市飞宇物流有限公司", + "plateNo": "粤AGP9713", + "hydrogenKg": 72.4, + "unitPrice": 45, + "totalPrice": 3258.5, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-05-15 18:15:00", + "stationName": "嘉兴港区加氢站", + "customerName": "嘉兴市飞宇物流有限公司", + "plateNo": "粤AGP9713", + "hydrogenKg": 72.4, + "unitPrice": 45, + "totalPrice": 3258.5, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-05-26 14:08:33", + "stationName": "嘉兴加氢站(一期)", + "customerName": "浙江绿运物流有限公司", + "plateNo": "浙A67890F", + "hydrogenKg": 10, + "unitPrice": 45, + "totalPrice": 450, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-05-30 09:30:22", + "stationName": "杭州临平加氢站", + "customerName": "杭州临平城配中心", + "plateNo": "浙B23456F", + "hydrogenKg": 15.3, + "unitPrice": 46, + "totalPrice": 703.8, + "borneBy": "我司承担" + }, + { + "hydrogenTime": "2026-01-15 16:45:00", + "stationName": "嘉兴港区加氢站", + "customerName": "演示客户A", + "plateNo": "沪A65889F", + "hydrogenKg": 32, + "unitPrice": 30, + "totalPrice": 960, + "borneBy": "客户承担" + } +] diff --git a/src/prototypes/lease-business-detail/index.tsx b/src/prototypes/lease-business-detail/index.tsx index 54a209b..5426232 100644 --- a/src/prototypes/lease-business-detail/index.tsx +++ b/src/prototypes/lease-business-detail/index.tsx @@ -8,34 +8,59 @@ import './styles/index.css'; import React, { useCallback, useMemo, useState } from 'react'; import { Download, FileUp } from 'lucide-react'; import { - AnnotationViewer, type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import seedRows from './data/rows.json'; +import changeLogSeed from './data/change-log-seed.json'; import { FilterPanel } from './components/FilterPanel'; import { DetailKpiRow } from './components/DetailKpiRow'; import { DetailTable } from './components/DetailTable'; import { DetailImportModal } from './components/DetailImportModal'; +import { DetailEditModal } from './components/DetailEditModal'; +import { DetailChangeLogModal } from './components/DetailChangeLogModal'; +import { DetailDeleteConfirmModal } from './components/DetailDeleteConfirmModal'; import { TablePagination, DEFAULT_PAGE_SIZE } from '../../common/TablePagination'; import { EMPTY_LEASE_DETAIL_FILTERS, type LeaseBusinessDetailRow, type LeaseDetailFilters, } from './types'; -import { applyDetailFilters, sumDetailKpi } from './utils/detail'; +import { applyDetailFilters, detailRowYearMonth, sumDetailKpi } from './utils/detail'; import { applyDetailCalculations } from './utils/calc-detail'; import { downloadDetailImportTemplate, + downloadDetailImportErrorLog, exportDetailRows, parseDetailImportFile, + type DetailImportErrorRow, } from './utils/batch-io'; +import { + appendChangeLogs, + collectRowChangeLogs, + createChangeLogEntry, + type DetailChangeLogsByRowId, +} from './utils/change-log'; +import { isDetailSupervisor, resolveDetailOperator } from './utils/role'; import annotationSourceDocument from './annotation-source.json'; +const ARCHIVED_DEMO_ROW_IDS = new Set(['lbd-33']); + export default function LeaseBusinessDetailApp() { + const operator = useMemo(() => resolveDetailOperator(), []); + const isSupervisor = isDetailSupervisor(operator); + const [records, setRecords] = useState(() => - (seedRows as LeaseBusinessDetailRow[]).map((row) => applyDetailCalculations(row)), + (seedRows as LeaseBusinessDetailRow[]).map((row) => applyDetailCalculations({ + ...row, + archiveStatus: ARCHIVED_DEMO_ROW_IDS.has(row.id) ? 'archived' : (row.archiveStatus ?? 'active'), + reconciledAt: ARCHIVED_DEMO_ROW_IDS.has(row.id) ? '2026-01-18' : (row.reconciledAt ?? ''), + })), + ); + const [changeLogs, setChangeLogs] = useState( + () => ({ ...(changeLogSeed as DetailChangeLogsByRowId) }), ); const [pendingFilters, setPendingFilters] = useState(EMPTY_LEASE_DETAIL_FILTERS); const [appliedFilters, setAppliedFilters] = useState(EMPTY_LEASE_DETAIL_FILTERS); @@ -43,7 +68,12 @@ export default function LeaseBusinessDetailApp() { const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); const [tableLoading, setTableLoading] = useState(false); const [importOpen, setImportOpen] = useState(false); + const [importErrors, setImportErrors] = useState([]); + const [importErrorHeaders, setImportErrorHeaders] = useState([]); const [toast, setToast] = useState(''); + const [editRow, setEditRow] = useState(null); + const [deleteRow, setDeleteRow] = useState(null); + const [changeLogRow, setChangeLogRow] = useState(null); const showToast = useCallback((msg: string) => { setToast(msg); @@ -61,6 +91,32 @@ export default function LeaseBusinessDetailApp() { return filtered.slice(start, start + pageSize); }, [filtered, page, pageSize]); + const updateRow = useCallback(( + id: string, + patch: Partial, + logMeta?: { forceField?: string; fieldLabel?: string }, + ) => { + setRecords((prev) => { + const before = prev.find((row) => row.id === id); + if (!before) return prev; + const after = applyDetailCalculations({ ...before, ...patch }); + const logs = collectRowChangeLogs(operator, before, after); + if (logs.length) { + setChangeLogs((prevLogs) => appendChangeLogs(prevLogs, id, logs)); + } else if (logMeta?.forceField) { + const entry = createChangeLogEntry( + operator, + logMeta.forceField, + before[logMeta.forceField as keyof LeaseBusinessDetailRow], + after[logMeta.forceField as keyof LeaseBusinessDetailRow], + { fieldLabel: logMeta.fieldLabel, force: true }, + ); + if (entry) setChangeLogs((prevLogs) => appendChangeLogs(prevLogs, id, [entry])); + } + return prev.map((row) => (row.id === id ? after : row)); + }); + }, [operator]); + const handleSearch = () => { setTableLoading(true); window.setTimeout(() => { @@ -78,20 +134,70 @@ export default function LeaseBusinessDetailApp() { const handleImport = async (file: File) => { try { - const imported = await parseDetailImportFile(file); - if (imported.length === 0) { - showToast('未解析到有效数据,请检查模板与必填列(年份、月份、车牌号码)'); + const result = await parseDetailImportFile(file); + setImportErrors(result.errors); + setImportErrorHeaders(result.fileHeaders); + + if (result.success.length === 0 && result.errors.length === 0) { + showToast('未解析到有效数据,请检查模板与必填列'); return; } - setRecords((prev) => [...imported, ...prev]); - setImportOpen(false); - setPage(1); - showToast(`已导入 ${imported.length} 条明细,公式列已自动计算`); + + if (result.success.length > 0) { + const imported = result.success.map((row) => applyDetailCalculations({ + ...row, + archiveStatus: 'active', + reconciledAt: '', + })); + setRecords((prev) => [...imported, ...prev]); + setChangeLogs((prev) => { + let next = { ...prev }; + imported.forEach((row) => { + const entry = createChangeLogEntry(operator, '_import', null, null, { + fieldLabel: '批量导入', + beforeText: '—', + afterText: '新增明细', + force: true, + }); + if (entry) next = appendChangeLogs(next, row.id, [entry]); + }); + return next; + }); + setPage(1); + } + + if (result.errors.length > 0 && result.success.length > 0) { + showToast(`已导入 ${result.success.length} 条;${result.errors.length} 条失败,可下载错误日志`); + } else if (result.errors.length > 0) { + showToast(`${result.errors.length} 条导入失败,请下载错误日志查看原因`); + } else { + setImportOpen(false); + showToast(`已导入 ${result.success.length} 条明细,公式列已自动计算`); + } } catch { showToast('导入失败,请检查文件格式'); } }; + const handleDeleteConfirm = () => { + if (!deleteRow) return; + const entry = createChangeLogEntry(operator, '_delete', null, null, { + fieldLabel: '删除记录', + beforeText: `${detailRowYearMonth(deleteRow)} · ${deleteRow.plateNo}`, + afterText: '已删除', + force: true, + }); + if (entry) setChangeLogs((prev) => appendChangeLogs(prev, deleteRow.id, [entry])); + setRecords((prev) => prev.filter((row) => row.id !== deleteRow.id)); + setDeleteRow(null); + showToast('已删除该条明细'); + }; + + const changeLogEntries = changeLogRow ? (changeLogs[changeLogRow.id] ?? []) : []; + const changeLogLabel = changeLogRow + ? `${detailRowYearMonth(changeLogRow)} · ${changeLogRow.plateNo}` + : ''; + const annotationOptions = useMemo( () => ({ showToolbar: true, @@ -108,6 +214,16 @@ export default function LeaseBusinessDetailApp() { return ( <>
+
+ + 当前视角: + {isSupervisor ? '业务管理部主管(可修改已锁定记录)' : '业管人员'} + + {!isSupervisor && ( + 主管预览:URL 追加 ?role=supervisor + )} +
+ setImportOpen(true)} + data-vm-icon="import" + onClick={() => { + setImportErrors([]); + setImportErrorHeaders([]); + setImportOpen(true); + }} > - 批量导入
- + updateRow(id, patch)} + />
setImportOpen(false)} onDownloadTemplate={downloadDetailImportTemplate} onImport={handleImport} + failedCount={importErrors.length} + onDownloadErrorLog={ + importErrors.length > 0 + ? () => downloadDetailImportErrorLog(importErrors, importErrorHeaders) + : undefined + } /> - + setEditRow(null)} + onSave={(patch) => { + if (!editRow) return; + updateRow(editRow.id, patch); + setEditRow(null); + showToast('明细已保存,公式列已自动重算'); + }} + /> + + setDeleteRow(null)} + onConfirm={handleDeleteConfirm} + /> + + setChangeLogRow(null)} + /> + + ); } diff --git a/src/prototypes/lease-business-detail/styles/index.css b/src/prototypes/lease-business-detail/styles/index.css index c7dee0f..3c614b8 100644 --- a/src/prototypes/lease-business-detail/styles/index.css +++ b/src/prototypes/lease-business-detail/styles/index.css @@ -324,15 +324,98 @@ } .lbd-profit-pos { - color: var(--ln-success, #16a34a); + color: var(--ln-error, #dc2626); font-weight: 600; } .lbd-profit-neg { - color: var(--ln-error, #dc2626); + color: var(--ln-success, #16a34a); font-weight: 600; } +.lbd-field-check-cell { + display: inline-flex; + align-items: center; + gap: 4px; + max-width: 100%; +} + +.lbd-field-check-wrap { + position: relative; + display: inline-flex; + flex-shrink: 0; +} + +.lbd-field-check-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + padding: 0; + border: none; + border-radius: 999px; + cursor: pointer; +} + +.lbd-field-check-icon.is-ok { + background: color-mix(in srgb, var(--ln-success, #16a34a) 14%, transparent); + color: var(--ln-success, #16a34a); +} + +.lbd-field-check-icon.is-warn { + background: color-mix(in srgb, var(--ln-error, #dc2626) 12%, transparent); + color: var(--ln-error, #dc2626); +} + +.lbd-field-check-pop { + position: absolute; + top: calc(100% + 6px); + right: 0; + z-index: 30; + min-width: 200px; + max-width: 280px; + padding: 8px 10px; + border-radius: var(--ln-radius-control); + background: var(--ln-ink); + color: #fff; + font-size: 0.75rem; + line-height: 1.45; + box-shadow: var(--ln-shadow-hover); +} + +.lbd-field-check-pop--ok { + background: var(--ln-surface-card); + color: var(--ln-ink); + border: 1px solid var(--ln-hairline); +} + +.lbd-field-check-pop__title { + font-weight: 600; + margin-bottom: 4px; +} + +.lbd-field-check-pop__line + .lbd-field-check-pop__line { + margin-top: 2px; +} + +.lbd-field-check-pop__actions { + margin-top: 8px; +} + +.lbd-detail-table .ldb-th-inner { + display: flex; + align-items: center; + gap: 0; + min-height: 100%; +} + +.lbd-detail-table .ldb-th-with-tip { + display: inline-flex; + align-items: center; + gap: 4px; +} + .lbd-cell-remark { display: inline-block; max-width: 140px; @@ -345,6 +428,253 @@ padding: 48px 24px; } +.lbd-import-error-step { + margin-top: 4px; +} + +.lbd-import-error-summary { + margin: 0 0 10px; + font-size: 0.875rem; + color: var(--ln-muted); + line-height: 1.5; +} + +.lbd-import-error-summary strong { + color: var(--ln-error, #dc2626); + font-weight: 600; +} + +.ldb-import-modal .vm-upload-zone { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 120px; +} + +.ldb-import-modal .vm-upload-zone p { + margin: 0; +} + .lbd-page .vm-table-actions { margin-left: auto; } + +.lbd-role-banner { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 16px; + margin-bottom: 10px; + padding: 8px 12px; + border-radius: var(--ln-radius-control); + border: 1px solid var(--ln-hairline); + background: color-mix(in srgb, var(--ln-primary) 6%, var(--ln-surface-card)); + font-size: 0.875rem; + color: var(--ln-text-secondary); +} + +.lbd-role-banner-hint { + color: var(--ln-muted); + font-size: 0.8125rem; +} + +.lbd-detail-table .ldb-col-actions { + min-width: 200px; +} + +.lbd-row-archived td { + background: color-mix(in srgb, var(--ln-muted) 6%, var(--ln-surface-card)); +} + +.lbd-row-archived:hover td { + background: color-mix(in srgb, var(--ln-muted) 10%, var(--ln-surface-card)); +} + +.lbd-changelog-meta { + margin: 0 0 12px; + font-size: 0.8125rem; + color: var(--ln-muted); + line-height: 1.4; +} + +.lbd-changelog-modal.vm-modal-lg { + width: min(920px, calc(100vw - 32px)); +} + +.lbd-changelog-header { + align-items: center; +} + +.lbd-changelog-header-text { + min-width: 0; + flex: 1; +} + +.lbd-changelog-header .vm-modal-title { + line-height: 1.35; +} + +.lbd-changelog-body { + overflow: hidden; + padding: 12px 24px 20px; +} + +.lbd-changelog-footer { + justify-content: flex-end; +} + +.lbd-changelog-table-wrap { + overflow: auto; + max-height: min(440px, 58vh); + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card); + scrollbar-gutter: stable; +} + +.lbd-changelog-table { + width: 100%; + min-width: 800px; + table-layout: fixed; + border-collapse: separate; + border-spacing: 0; + font-size: 0.8125rem; +} + +.lbd-changelog-table th, +.lbd-changelog-table td { + padding: 12px 14px; + border-bottom: 1px solid var(--ln-hairline); + text-align: left; + line-height: 1.5; +} + +.lbd-changelog-table thead th { + position: sticky; + top: 0; + z-index: 2; + font-weight: 600; + font-size: 0.75rem; + letter-spacing: 0.02em; + color: var(--ln-muted); + white-space: nowrap; + vertical-align: middle; + background: color-mix(in srgb, var(--ln-canvas-soft) 92%, var(--ln-surface-card)); + box-shadow: inset 0 -1px 0 var(--ln-hairline); +} + +.lbd-changelog-table tbody tr:nth-child(even) td { + background: color-mix(in srgb, var(--ln-canvas-soft) 45%, transparent); +} + +.lbd-changelog-table tbody tr:hover td { + background: color-mix(in srgb, var(--ln-primary) 5%, var(--ln-surface-card)); +} + +.lbd-changelog-table tbody tr:last-child td { + border-bottom: none; +} + +.lbd-changelog-cell-meta { + vertical-align: middle; + white-space: nowrap; + color: var(--ln-text-secondary); +} + +.lbd-changelog-cell-field { + vertical-align: middle; +} + +.lbd-changelog-field-tag { + display: inline-block; + max-width: 100%; + padding: 2px 8px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--ln-primary) 18%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-primary) 6%, var(--ln-surface-card)); + color: var(--ln-ink); + font-size: 0.75rem; + font-weight: 500; + line-height: 1.4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; +} + +.lbd-changelog-cell-diff { + vertical-align: middle; +} + +.lbd-changelog-diff-text { + display: block; + max-width: 100%; + padding: 4px 8px; + border-radius: 6px; + font-size: 0.8125rem; + line-height: 1.45; + word-break: break-word; + overflow-wrap: anywhere; +} + +.lbd-changelog-diff-text--before { + color: #b42318; + background: color-mix(in srgb, #fecaca 28%, transparent); +} + +.lbd-changelog-diff-text--after { + color: #067647; + background: color-mix(in srgb, #bbf7d0 32%, transparent); +} + +.lbd-changelog-empty { + display: inline-block; + min-width: 1.25em; + color: var(--ln-muted); + text-align: center; +} + +.lbd-edit-hint { + margin: 0 0 12px; + font-size: 0.8125rem; + color: var(--ln-muted); + line-height: 1.5; +} + +.lbd-edit-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px 16px; +} + +@media (max-width: 960px) { + .lbd-edit-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 640px) { + .lbd-edit-grid { + grid-template-columns: 1fr; + } +} + +.lbd-edit-field { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.lbd-edit-label { + font-size: 0.8125rem; + color: var(--ln-text-secondary); +} + +.lbd-edit-form { + max-height: min(68vh, 640px); + overflow: auto; +} + diff --git a/src/prototypes/lease-business-detail/types.ts b/src/prototypes/lease-business-detail/types.ts index c6778e4..de20d4d 100644 --- a/src/prototypes/lease-business-detail/types.ts +++ b/src/prototypes/lease-business-detail/types.ts @@ -1,3 +1,5 @@ +export type DetailArchiveStatus = 'active' | 'archived'; + export interface LeaseBusinessDetailRow { id: string; year: number; @@ -47,6 +49,10 @@ export interface LeaseBusinessDetailRow { assetOwner: string; signingCompany: string; remark: string; + /** 未对账可编辑;已对账后仅业务管理部主管可改 */ + archiveStatus?: DetailArchiveStatus; + /** 完成对账日期 YYYY-MM-DD */ + reconciledAt?: string; } export interface LeaseDetailFilters { @@ -55,6 +61,8 @@ export interface LeaseDetailFilters { salesperson: string; /** 客户名称模糊匹配 */ customerName: string; + /** 收款状态:已结清 / 部分收款 / 未收款 */ + paymentStatus: string; plateNos: string[]; } @@ -116,5 +124,6 @@ export const EMPTY_LEASE_DETAIL_FILTERS: LeaseDetailFilters = { businessDept: '', salesperson: '', customerName: '', + paymentStatus: '', plateNos: [], }; diff --git a/src/prototypes/lease-business-detail/utils/batch-io.ts b/src/prototypes/lease-business-detail/utils/batch-io.ts index 0d981b3..9e2e888 100644 --- a/src/prototypes/lease-business-detail/utils/batch-io.ts +++ b/src/prototypes/lease-business-detail/utils/batch-io.ts @@ -1,20 +1,83 @@ import * as XLSX from 'xlsx'; import type { LeaseBusinessDetailRow } from '../types'; -import { - DETAIL_EXPORT_HEADERS, - DETAIL_IMPORT_TEMPLATE_HEADERS, - DETAIL_TABLE_COLUMNS, -} from '../components/tableColumns'; +import { DETAIL_EXPORT_HEADERS, DETAIL_EXPORT_SPECS } from '../components/tableColumns'; import { applyDetailCalculations } from './calc-detail'; +import { getContractRef, getVehicleRef, isPlateInVehicleSystem } from './system-ref'; + +/** 导入模板列(不含公式列;业务部门/业务员由系统合同反写) */ +export const DETAIL_IMPORT_TEMPLATE_HEADERS = [ + '年份', + '月份', + '付款日期', + '车牌号码', + '客户名称', + '增值服务', + '享免政策', + '提车日期', + '合同生效日期', + '合同到期日期', + '起始日期', + '退车日期', + '平均天数', + '押金', + '合同标的租金', + '维保包干收入', + '保险上浮费', + '运维费', + '其他收入', + '里程减免金额', + '其他减免金额', + '实收金额', + '开票日期', + '实际付款日期', + '付款方式', + '车辆标准成本', + '车辆实际成本', + '保险费', + '运维费', + '居间费', + '其他', + 'GPS里程', + '减免金额明细内容', + '资产归属', + '签约公司', + '备注', +] as const; + +export const DETAIL_IMPORT_REQUIRED_HEADERS = [ + '年份', + '月份', + '付款日期', + '车牌号码', + '客户名称', + '提车日期', + '合同生效日期', + '合同到期日期', + '平均天数', + '押金', + '合同标的租金', + '实收金额', + '资产归属', + '签约公司', +] as const; + +export interface DetailImportErrorRow { + reason: string; + rawCells: (string | number)[]; +} + +export interface DetailImportParseResult { + success: LeaseBusinessDetailRow[]; + errors: DetailImportErrorRow[]; + /** 上传文件表头(用于错误日志) */ + fileHeaders: string[]; +} const TEMPLATE_SAMPLE: Record = { 年份: 2026, 月份: 2, - 业务部门: '业务二部', - 业务员: '刘念忠', 付款日期: '2026-02-05', 车牌号码: '沪A52898F', - 车型: '18T', 客户名称: '上海虹钦物流有限公司', 增值服务: '', 享免政策: '', @@ -23,28 +86,26 @@ const TEMPLATE_SAMPLE: Record = { 合同到期日期: '2026-11-30', 起始日期: '2026-02-01', 退车日期: '', - 平均天数: 28, + 平均天数: 1, 押金: 20000, - 合同标的租金: 18500, - 应收租金: 18500, - 月度租金: 18500, + 合同标的租金: 4500, 维保包干收入: 0, 保险上浮费: 0, 运维费: 0, 其他收入: 0, 里程减免金额: 0, 其他减免金额: 0, - 实收金额: 18500, + 实收金额: 4500, 开票日期: '2026-02-06', 实际付款日期: '2026-02-05', - 付款方式: '月付指付', - 车辆标准成本: 12000, + 付款方式: '月度预付', + 车辆标准成本: 3900, + 车辆实际成本: 0, 保险费: 0, - 氢费: 3200, - 居间费: 800, + 居间费: 500, 其他: 0, - GPS里程: '3260', - 其他减免金额明细内容: '', + GPS里程: '', + 减免金额明细内容: '', 资产归属: '浙江羚牛氢能科技有限公司', 签约公司: '浙江羚牛氢能科技有限公司', 备注: '', @@ -81,11 +142,14 @@ const HEADER_TO_KEY: Record = { 实际付款日期: 'actualPaymentDate', 付款方式: 'paymentMethod', 车辆标准成本: 'vehicleStandardCost', + 车辆实际成本: 'vehicleActualCost', 保险费: 'insuranceFee', 氢费: 'hydrogenFee', 居间费: 'brokerageFee', 其他: 'otherCost', GPS里程: 'gpsMileage', + 'GPS 里程': 'gpsMileage', + 减免金额明细内容: 'otherDeductionDetail', 其他减免金额明细内容: 'otherDeductionDetail', 资产归属: 'assetOwner', 签约公司: 'signingCompany', @@ -97,7 +161,7 @@ const IMPORT_NUMERIC_KEYS = new Set([ 'contractRent', 'receivableRent', 'monthlyRent', 'maintenanceIncome', 'insuranceSurcharge', 'opsFeeIncome', 'otherIncome', 'mileageDeduction', 'otherDeduction', 'receivedAmount', - 'vehicleStandardCost', 'insuranceFee', 'hydrogenFee', + 'vehicleStandardCost', 'vehicleActualCost', 'insuranceFee', 'hydrogenFee', 'opsFeeCost', 'brokerageFee', 'otherCost', ]); @@ -108,6 +172,13 @@ const IMPORT_TEXT_KEYS = new Set([ 'gpsMileage', 'otherDeductionDetail', 'assetOwner', 'signingCompany', 'remark', ]); +const REQUIRED_NUMERIC_HEADERS = new Set(['年份', '月份', '平均天数', '合同标的租金', '实收金额']); + +function isCellEmpty(value: unknown): boolean { + if (value === null || value === undefined) return true; + return String(value).trim() === ''; +} + function parseNumber(value: unknown): number { if (value === null || value === undefined || value === '') return 0; const n = Number(String(value).replace(/,/g, '')); @@ -121,10 +192,94 @@ function parseDeposit(value: unknown): number | string { return parseNumber(raw); } +function normalizeHeader(header: string): string { + return header.replace(/(必填)/g, '').trim(); +} + +function resolveHeaderKey(header: string): keyof LeaseBusinessDetailRow | undefined { + return HEADER_TO_KEY[normalizeHeader(header)]; +} + +function validateRequiredFields( + headers: string[], + cells: (string | number)[], +): string[] { + const reasons: string[] = []; + const headerIndex = new Map(headers.map((h, i) => [normalizeHeader(h), i])); + + DETAIL_IMPORT_REQUIRED_HEADERS.forEach((required) => { + const idx = headerIndex.get(required); + const raw = idx === undefined ? undefined : cells[idx]; + if (required === '押金') { + if (isCellEmpty(raw)) reasons.push(`${required}不能为空`); + return; + } + if (REQUIRED_NUMERIC_HEADERS.has(required)) { + if (isCellEmpty(raw)) reasons.push(`${required}不能为空`); + return; + } + if (isCellEmpty(raw)) reasons.push(`${required}不能为空`); + }); + + return reasons; +} + +function enrichDraftFromSystem(draft: Partial): void { + if (!draft.plateNo) return; + const vehicle = getVehicleRef(draft.plateNo); + if (vehicle) { + draft.vehicleType = vehicle.brandModel || draft.vehicleType || ''; + } + if (draft.customerName) { + const contract = getContractRef(draft.plateNo, draft.customerName); + if (contract) { + draft.businessDept = contract.businessDept; + draft.salesperson = contract.salesperson; + } + } +} + +function parseRowDraft( + headers: string[], + cells: (string | number)[], + index: number, +): Partial { + const draft: Partial = { + id: `import-${Date.now()}-${index}`, + }; + let opsFeeColIndex = 0; + + headers.forEach((header, colIndex) => { + const normalized = normalizeHeader(header); + let key = resolveHeaderKey(header); + if (normalized === '运维费') { + opsFeeColIndex += 1; + key = opsFeeColIndex === 1 ? 'opsFeeIncome' : 'opsFeeCost'; + } + if (!key) return; + const raw = cells[colIndex]; + if (key === 'deposit') { + (draft as Record)[key] = parseDeposit(raw); + } else if (IMPORT_NUMERIC_KEYS.has(key)) { + const num = parseNumber(raw); + if (key === 'mileageDeduction' || key === 'otherDeduction') { + (draft as Record)[key] = num > 0 ? -Math.abs(num) : num; + } else { + (draft as Record)[key] = num; + } + } else if (IMPORT_TEXT_KEYS.has(key)) { + (draft as Record)[key] = String(raw ?? '').trim(); + } + }); + + enrichDraftFromSystem(draft); + return draft; +} + function rowToExportCells(row: LeaseBusinessDetailRow): string[] { let opsIncomeWritten = false; - return DETAIL_TABLE_COLUMNS.map((col) => { - const header = col.title; + return DETAIL_EXPORT_SPECS.map((spec) => { + const header = spec.title; if (header === '运维费') { if (!opsIncomeWritten) { opsIncomeWritten = true; @@ -132,7 +287,7 @@ function rowToExportCells(row: LeaseBusinessDetailRow): string[] { } return row.opsFeeCost === 0 ? '' : String(row.opsFeeCost); } - const key = col.key as keyof LeaseBusinessDetailRow; + const key = spec.key; const raw = row[key]; if (key === 'deposit') { if (raw === '/' || raw === '') return raw === '/' ? '/' : ''; @@ -151,12 +306,25 @@ export function downloadDetailImportTemplate(): void { }, []); if (opsIndexes[1] !== undefined) sampleRow[opsIndexes[1]] = 0; - const sheet = XLSX.utils.aoa_to_sheet([DETAIL_IMPORT_TEMPLATE_HEADERS, sampleRow]); + const sheet = XLSX.utils.aoa_to_sheet([Array.from(DETAIL_IMPORT_TEMPLATE_HEADERS), sampleRow]); const book = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(book, sheet, '租赁业务明细'); XLSX.writeFile(book, '租赁业务明细导入模板.xlsx'); } +export function downloadDetailImportErrorLog( + errors: DetailImportErrorRow[], + fileHeaders: string[], +): void { + if (!errors.length) return; + const headers = ['导入失败原因', ...(fileHeaders.length ? fileHeaders : Array.from(DETAIL_IMPORT_TEMPLATE_HEADERS))]; + const body = errors.map((err) => [err.reason, ...err.rawCells]); + const sheet = XLSX.utils.aoa_to_sheet([headers, ...body]); + const book = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(book, sheet, '导入失败记录'); + XLSX.writeFile(book, `租赁业务明细导入失败记录_${Date.now()}.xlsx`); +} + export function exportDetailRows(rows: LeaseBusinessDetailRow[]): void { const body = [DETAIL_EXPORT_HEADERS, ...rows.map(rowToExportCells)]; const sheet = XLSX.utils.aoa_to_sheet(body); @@ -165,43 +333,38 @@ export function exportDetailRows(rows: LeaseBusinessDetailRow[]): void { XLSX.writeFile(book, `租赁业务明细_${Date.now()}.xlsx`); } -export async function parseDetailImportFile(file: File): Promise { +export async function parseDetailImportFile(file: File): Promise { const buffer = await file.arrayBuffer(); const book = XLSX.read(buffer, { type: 'array' }); const sheet = book.Sheets[book.SheetNames[0]]; const matrix = XLSX.utils.sheet_to_json<(string | number)[]>(sheet, { header: 1, defval: '' }) as (string | number)[][]; - if (matrix.length < 2) return []; + if (matrix.length < 2) { + return { success: [], errors: [], fileHeaders: [] }; + } - const headers = matrix[0].map((cell) => String(cell).trim()); - const rows: LeaseBusinessDetailRow[] = []; + const fileHeaders = matrix[0].map((cell) => String(cell).trim()); + const success: LeaseBusinessDetailRow[] = []; + const errors: DetailImportErrorRow[] = []; matrix.slice(1).forEach((cells, index) => { if (!cells.some((cell) => String(cell).trim())) return; - const draft: Partial = { - id: `import-${Date.now()}-${index}`, - }; - let opsFeeColIndex = 0; - headers.forEach((header, colIndex) => { - let key = HEADER_TO_KEY[header]; - if (header === '运维费') { - opsFeeColIndex += 1; - key = opsFeeColIndex === 1 ? 'opsFeeIncome' : 'opsFeeCost'; - } - if (!key) return; - const raw = cells[colIndex]; - if (key === 'deposit') { - (draft as Record)[key] = parseDeposit(raw); - } else if (IMPORT_NUMERIC_KEYS.has(key)) { - (draft as Record)[key] = parseNumber(raw); - } else if (IMPORT_TEXT_KEYS.has(key)) { - (draft as Record)[key] = String(raw ?? '').trim(); - } - }); + const rawCells = fileHeaders.map((_, colIndex) => cells[colIndex] ?? ''); + const requiredReasons = validateRequiredFields(fileHeaders, cells); + if (requiredReasons.length > 0) { + errors.push({ reason: requiredReasons.join(';'), rawCells }); + return; + } - if (!draft.plateNo || !draft.year || !draft.month) return; - rows.push(applyDetailCalculations(draft)); + const draft = parseRowDraft(fileHeaders, cells, index); + const plateNo = String(draft.plateNo ?? '').trim(); + if (!isPlateInVehicleSystem(plateNo)) { + errors.push({ reason: '车牌号不存在', rawCells }); + return; + } + + success.push(applyDetailCalculations(draft)); }); - return rows; + return { success, errors, fileHeaders }; } diff --git a/src/prototypes/lease-business-detail/utils/calc-detail.ts b/src/prototypes/lease-business-detail/utils/calc-detail.ts index a2d113a..a255c9e 100644 --- a/src/prototypes/lease-business-detail/utils/calc-detail.ts +++ b/src/prototypes/lease-business-detail/utils/calc-detail.ts @@ -1,4 +1,6 @@ import type { LeaseBusinessDetailRow } from '../types'; +import { sumHydrogenFeeForDetailRow } from './hydrogen-fee'; +import { resolveSettlementMonths } from './system-ref'; /** 对齐 Excel「租赁车辆收入明细表」公式列 */ export const DETAIL_CALCULATED_KEYS = [ @@ -26,7 +28,55 @@ type IncomeAdjustments = Pick< | 'otherDeduction' >; -/** 应收合计 = 应收租金 + 维保包干收入 + 保险上浮费 + 运维费(收入) + 其他收入 + 里程减免 + 其他减免 */ +/** 账单月自然日天数 */ +export function daysInMonth(year: number, month: number): number { + if (!year || !month) return 30; + return new Date(year, month, 0).getDate(); +} + +/** 本月在租天数(原型:有起始/退车日期则按区间,否则整月) */ +export function calcLeaseDaysInMonth( + year: number, + month: number, + periodStartDate: string, + returnDate: string, +): number { + if (!year || !month) return 0; + const monthStart = new Date(year, month - 1, 1); + const monthEnd = new Date(year, month, 0); + let start = periodStartDate ? new Date(periodStartDate.slice(0, 10)) : monthStart; + let end = returnDate ? new Date(returnDate.slice(0, 10)) : monthEnd; + if (start < monthStart) start = monthStart; + if (end > monthEnd) end = monthEnd; + if (end < start) return 0; + const ms = end.getTime() - start.getTime(); + return Math.max(0, Math.floor(ms / 86400000) + 1); +} + +/** 平均天数 = 本月在租天数 / 当月天数 */ +export function calcDetailAvgDays( + year: number, + month: number, + periodStartDate: string, + returnDate: string, +): number { + const leaseDays = calcLeaseDaysInMonth(year, month, periodStartDate, returnDate); + const dim = daysInMonth(year, month); + if (!dim) return 0; + return roundMoney(leaseDays / dim); +} + +/** 月度租金 = 合同标的租金(月标准),付款周期大于 1 时仍按月标准展示 */ +export function calcDetailMonthlyRent(contractRent: number): number { + return roundMoney(contractRent); +} + +/** 应收租金 = 合同标的租金 × 结算周期月数 */ +export function calcDetailReceivableRent(contractRent: number, settlementMonths: number): number { + return roundMoney(contractRent * settlementMonths); +} + +/** 应收合计 = 应收租金 + 收入调整项 */ export function calcDetailReceivableTotal( receivableRent: number, adjustments: IncomeAdjustments, @@ -127,19 +177,45 @@ export function applyDetailCalculations( otherDeduction: row.otherDeduction ?? 0, }; - const receivableRent = row.receivableRent ?? 0; - const monthlyRent = row.monthlyRent ?? 0; + const year = row.year ?? 0; + const month = row.month ?? 0; + const contractRent = row.contractRent ?? 0; + const settlementMonths = resolveSettlementMonths({ + ...row, + year, + month, + contractRent, + } as LeaseBusinessDetailRow); + + const computedAvgDays = calcDetailAvgDays( + year, + month, + row.periodStartDate ?? '', + row.returnDate ?? '', + ); + const avgDays = row.avgDays && row.avgDays > 0 ? row.avgDays : computedAvgDays; + const monthlyRent = calcDetailMonthlyRent(contractRent); + const receivableRent = row.receivableRent && row.receivableRent > 0 + ? row.receivableRent + : calcDetailReceivableRent(contractRent, settlementMonths); const receivedAmount = row.receivedAmount ?? 0; const vehicleStandardCost = row.vehicleStandardCost ?? 0; - const avgDays = row.avgDays ?? 0; const vehicleActualCost = calcDetailVehicleActualCost(vehicleStandardCost, avgDays); const monthlyIncome = calcDetailMonthlyIncome(monthlyRent, adjustments); const receivableTotal = calcDetailReceivableTotal(receivableRent, adjustments); + const hydrogenFee = sumHydrogenFeeForDetailRow({ + plateNo: row.plateNo ?? '', + customerName: row.customerName ?? '', + year, + month, + pickupDate: row.pickupDate ?? '', + returnDate: row.returnDate ?? '', + }); const totalCost = calcDetailTotalCost({ vehicleActualCost, insuranceFee: row.insuranceFee ?? 0, - hydrogenFee: row.hydrogenFee ?? 0, + hydrogenFee, opsFeeCost: row.opsFeeCost ?? 0, brokerageFee: row.brokerageFee ?? 0, otherCost: row.otherCost ?? 0, @@ -154,7 +230,7 @@ export function applyDetailCalculations( ...EMPTY_TEXT_FIELDS, ...row, deposit: row.deposit ?? 0, - contractRent: row.contractRent ?? 0, + contractRent, receivableRent, monthlyRent, ...adjustments, @@ -162,7 +238,7 @@ export function applyDetailCalculations( vehicleStandardCost, avgDays, insuranceFee: row.insuranceFee ?? 0, - hydrogenFee: row.hydrogenFee ?? 0, + hydrogenFee, opsFeeCost: row.opsFeeCost ?? 0, brokerageFee: row.brokerageFee ?? 0, otherCost: row.otherCost ?? 0, @@ -172,5 +248,7 @@ export function applyDetailCalculations( totalCost, outstanding, profitLoss, + archiveStatus: row.archiveStatus ?? 'active', + reconciledAt: row.reconciledAt ?? '', }; } diff --git a/src/prototypes/lease-business-detail/utils/change-log.ts b/src/prototypes/lease-business-detail/utils/change-log.ts new file mode 100644 index 0000000..1af0596 --- /dev/null +++ b/src/prototypes/lease-business-detail/utils/change-log.ts @@ -0,0 +1,126 @@ +import type { LeaseBusinessDetailRow } from '../types'; +import { DETAIL_EXPORT_SPECS } from '../components/tableColumns'; +import { displayDeposit, displayMoney, displayText } from './detail'; +import type { DetailOperator } from './role'; + +export interface DetailChangeLogEntry { + id: string; + operatedAt: string; + operatorId: string; + operatorName: string; + field: string; + fieldLabel: string; + before: string; + after: string; +} + +export type DetailChangeLogsByRowId = Record; + +const EXTRA_FIELD_LABELS: Record = { + archiveStatus: '对账状态', + reconciledAt: '对账日期', + _delete: '删除记录', + _import: '批量导入', + _reconcile: '完成对账', +}; + +const FIELD_LABELS: Record = Object.fromEntries( + DETAIL_EXPORT_SPECS.map((spec) => [spec.key, spec.title]), +); + +export function resolveFieldLabel(field: string): string { + return EXTRA_FIELD_LABELS[field] ?? FIELD_LABELS[field] ?? field; +} + +const MONEY_FIELDS = new Set([ + 'deposit', 'contractRent', 'receivableTotal', 'receivableRent', 'monthlyIncome', 'monthlyRent', + 'maintenanceIncome', 'insuranceSurcharge', 'opsFeeIncome', 'otherIncome', 'mileageDeduction', + 'otherDeduction', 'receivedAmount', 'outstanding', 'vehicleStandardCost', 'vehicleActualCost', + 'insuranceFee', 'hydrogenFee', 'opsFeeCost', 'brokerageFee', 'otherCost', 'totalCost', 'profitLoss', +]); + +export function formatLogValue(field: string, value: unknown): string { + if (field === 'archiveStatus') { + if (value === 'archived') return '已对账'; + if (value === 'active') return '未对账'; + return displayText(value); + } + if (field === 'year' || field === 'month') { + return value === null || value === undefined || value === '' ? '—' : String(value); + } + if (field === 'deposit') return displayDeposit(value as number | string) || '—'; + if (MONEY_FIELDS.has(field) && typeof value === 'number') { + return displayMoney(value, false) || '0.00'; + } + if (typeof value === 'number') return String(value); + return displayText(value) || '—'; +} + +export function valuesEqualForLog(field: string, before: unknown, after: unknown): boolean { + return formatLogValue(field, before) === formatLogValue(field, after); +} + +const LOGGED_ROW_FIELDS: (keyof LeaseBusinessDetailRow)[] = [ + ...DETAIL_EXPORT_SPECS.map((spec) => spec.key), + 'archiveStatus', + 'reconciledAt', +]; + +export function createChangeLogEntry( + operator: DetailOperator, + field: string, + before: unknown, + after: unknown, + meta?: { fieldLabel?: string; beforeText?: string; afterText?: string; force?: boolean }, +): DetailChangeLogEntry | null { + const beforeText = meta?.beforeText ?? formatLogValue(field, before); + const afterText = meta?.afterText ?? formatLogValue(field, after); + if (!meta?.force && beforeText === afterText) return null; + + const now = new Date(); + const operatedAt = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; + + return { + id: `lbd-clog-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + operatedAt, + operatorId: operator.id, + operatorName: operator.name, + field, + fieldLabel: meta?.fieldLabel ?? resolveFieldLabel(field), + before: beforeText, + after: afterText, + }; +} + +export function appendChangeLogs( + logs: DetailChangeLogsByRowId, + rowId: string, + entries: DetailChangeLogEntry[], +): DetailChangeLogsByRowId { + if (!entries.length) return logs; + return { + ...logs, + [rowId]: [...entries, ...(logs[rowId] ?? [])], + }; +} + +export function collectRowChangeLogs( + operator: DetailOperator, + before: LeaseBusinessDetailRow, + after: LeaseBusinessDetailRow, +): DetailChangeLogEntry[] { + const entries: DetailChangeLogEntry[] = []; + LOGGED_ROW_FIELDS.forEach((field) => { + const entry = createChangeLogEntry(operator, field, before[field], after[field]); + if (entry) entries.push(entry); + }); + return entries; +} + +export function canEditDetailRow( + row: Pick, + isSupervisor: boolean, +): boolean { + if (row.archiveStatus === 'archived') return isSupervisor; + return true; +} diff --git a/src/prototypes/lease-business-detail/utils/detail.ts b/src/prototypes/lease-business-detail/utils/detail.ts index daf1e8d..1ee0cf4 100644 --- a/src/prototypes/lease-business-detail/utils/detail.ts +++ b/src/prototypes/lease-business-detail/utils/detail.ts @@ -4,6 +4,7 @@ import type { LeaseDetailKpiSummary, } from '../types'; import { EMPTY_LEASE_DETAIL_KPI } from '../types'; +import { resolveDetailPaymentStatus } from './payment-status'; export function detailRowYearMonth(row: LeaseBusinessDetailRow): string { return `${row.year}-${String(row.month).padStart(2, '0')}`; @@ -30,7 +31,7 @@ export function displayDeposit(value: number | string): string { } export function displayOutstanding(value: number): { text: string; warn: boolean } { - if (value === 0) return { text: '', warn: false }; + if (value === 0) return { text: '0.00', warn: false }; return { text: formatMoney(value), warn: value > 0 }; } @@ -44,6 +45,10 @@ export function applyDetailFilters( if (filters.businessDept && row.businessDept !== filters.businessDept) return false; if (filters.salesperson && row.salesperson !== filters.salesperson) return false; if (customerQ && !row.customerName.toLowerCase().includes(customerQ)) return false; + if (filters.paymentStatus) { + const status = resolveDetailPaymentStatus(row.receivableTotal, row.receivedAmount); + if (status !== filters.paymentStatus) return false; + } if (filters.plateNos.length > 0 && !filters.plateNos.includes(row.plateNo)) return false; return true; }); diff --git a/src/prototypes/lease-business-detail/utils/edit-fields.ts b/src/prototypes/lease-business-detail/utils/edit-fields.ts new file mode 100644 index 0000000..5db6514 --- /dev/null +++ b/src/prototypes/lease-business-detail/utils/edit-fields.ts @@ -0,0 +1,87 @@ +import type { LeaseBusinessDetailRow } from '../types'; + +export type DetailEditFieldType = 'text' | 'number' | 'date'; + +export interface DetailEditFieldSpec { + key: keyof LeaseBusinessDetailRow; + label: string; + type: DetailEditFieldType; +} + +/** 编辑弹窗可改字段(对齐导入模板手工列) */ +export const DETAIL_EDIT_FIELDS: DetailEditFieldSpec[] = [ + { key: 'year', label: '年份', type: 'number' }, + { key: 'month', label: '月份', type: 'number' }, + { key: 'paymentDate', label: '付款日期', type: 'date' }, + { key: 'plateNo', label: '车牌号码', type: 'text' }, + { key: 'customerName', label: '客户名称', type: 'text' }, + { key: 'valueAddedService', label: '增值服务', type: 'text' }, + { key: 'exemptionPolicy', label: '享免政策', type: 'text' }, + { key: 'pickupDate', label: '提车日期', type: 'date' }, + { key: 'contractStartDate', label: '合同生效日期', type: 'date' }, + { key: 'contractEndDate', label: '合同到期日期', type: 'date' }, + { key: 'periodStartDate', label: '起始日期', type: 'date' }, + { key: 'returnDate', label: '退车日期', type: 'date' }, + { key: 'avgDays', label: '平均天数', type: 'number' }, + { key: 'deposit', label: '押金', type: 'text' }, + { key: 'contractRent', label: '合同标的租金', type: 'number' }, + { key: 'receivableRent', label: '应收租金', type: 'number' }, + { key: 'maintenanceIncome', label: '维保包干收入', type: 'number' }, + { key: 'insuranceSurcharge', label: '保险上浮费', type: 'number' }, + { key: 'opsFeeIncome', label: '运维费(收入)', type: 'number' }, + { key: 'otherIncome', label: '其他收入', type: 'number' }, + { key: 'mileageDeduction', label: '里程减免金额', type: 'number' }, + { key: 'otherDeduction', label: '其他减免金额', type: 'number' }, + { key: 'receivedAmount', label: '实收金额', type: 'number' }, + { key: 'invoiceDate', label: '开票日期', type: 'date' }, + { key: 'actualPaymentDate', label: '实际付款日期', type: 'date' }, + { key: 'paymentMethod', label: '付款方式', type: 'text' }, + { key: 'vehicleStandardCost', label: '车辆标准成本', type: 'number' }, + { key: 'insuranceFee', label: '保险费', type: 'number' }, + { key: 'opsFeeCost', label: '运维费(成本)', type: 'number' }, + { key: 'brokerageFee', label: '居间费', type: 'number' }, + { key: 'otherCost', label: '其他', type: 'number' }, + { key: 'gpsMileage', label: 'GPS里程', type: 'text' }, + { key: 'otherDeductionDetail', label: '其他减免金额明细内容', type: 'text' }, + { key: 'assetOwner', label: '资产归属', type: 'text' }, + { key: 'signingCompany', label: '签约公司', type: 'text' }, + { key: 'remark', label: '备注', type: 'text' }, +]; + +export function draftFromRow(row: LeaseBusinessDetailRow): Record { + const draft: Record = {}; + DETAIL_EDIT_FIELDS.forEach((field) => { + const value = row[field.key]; + if (field.key === 'deposit') { + draft[field.key] = value === '/' ? '/' : String(value ?? ''); + return; + } + if (typeof value === 'number') { + draft[field.key] = value; + return; + } + draft[field.key] = String(value ?? ''); + }); + return draft; +} + +export function parseDraftToPatch( + draft: Record, +): Partial { + const patch: Partial = {}; + DETAIL_EDIT_FIELDS.forEach((field) => { + const raw = draft[field.key]; + if (field.key === 'deposit') { + const text = String(raw ?? '').trim(); + patch.deposit = text === '/' || text === '' ? '/' : Number(text); + return; + } + if (field.type === 'number') { + const num = Number(raw); + patch[field.key] = Number.isFinite(num) ? num : 0; + return; + } + patch[field.key] = String(raw ?? ''); + }); + return patch; +} diff --git a/src/prototypes/lease-business-detail/utils/field-checks.ts b/src/prototypes/lease-business-detail/utils/field-checks.ts new file mode 100644 index 0000000..c7345ca --- /dev/null +++ b/src/prototypes/lease-business-detail/utils/field-checks.ts @@ -0,0 +1,172 @@ +import type { LeaseBusinessDetailRow } from '../types'; +import { + getContractRef, + getVehicleRef, + isKnownCustomerName, + isPlateInVehicleSystem, + resolveExpectedCustomer, + resolveSettlementMonths, +} from './system-ref'; + +export type FieldCheckStatus = 'none' | 'ok' | 'warn'; + +export interface FieldCheckInfo { + status: FieldCheckStatus; + title?: string; + details?: string[]; + replaceAction?: Partial; +} + +function roundMoney(value: number): number { + return Math.round(value * 100) / 100; +} + +function moneyEqual(a: number, b: number): boolean { + return Math.abs(a - b) < 0.005; +} + +function ok(details: string[]): FieldCheckInfo { + return { status: 'ok', title: '与系统一致', details }; +} + +function warn(title: string, details: string[], replaceAction?: Partial): FieldCheckInfo { + return { status: 'warn', title, details, replaceAction }; +} + +function none(): FieldCheckInfo { + return { status: 'none' }; +} + +export function checkPlateExists(row: LeaseBusinessDetailRow): FieldCheckInfo { + if (!row.plateNo) return none(); + if (isPlateInVehicleSystem(row.plateNo)) return ok(['车牌已在车辆管理中登记']); + return warn('车牌不存在', ['该车牌号码未在系统「车辆管理」中登记,无法导入']); +} + +export function checkCustomerName(row: LeaseBusinessDetailRow): FieldCheckInfo { + if (!row.plateNo || !row.customerName) return none(); + const contract = getContractRef(row.plateNo, row.customerName); + const expected = resolveExpectedCustomer(row.plateNo, row.pickupDate, row.returnDate) || contract?.customerName || ''; + if (expected && row.customerName.trim() === expected.trim()) { + return ok(['客户名称与系统登记一致']); + } + if (contract && row.customerName.trim() === contract.customerName.trim()) { + return ok(['客户名称与租赁合同一致']); + } + if (expected && row.customerName.trim() !== expected.trim()) { + return warn( + '客户名称与系统不对应', + ['客户名称与系统「客户管理」不对应', `建议客户:${expected}`], + { customerName: expected }, + ); + } + if (isKnownCustomerName(row.customerName)) { + return ok(['客户名称已在客户管理中登记']); + } + return warn( + '客户名称与系统不对应', + ['客户名称与系统「客户管理」不对应', '请核对提车日期与退车日期'], + ); +} + +export function checkPickupDate(row: LeaseBusinessDetailRow): FieldCheckInfo { + const vehicle = getVehicleRef(row.plateNo); + if (!vehicle || !row.pickupDate) return none(); + const systemDate = vehicle.deliveryDate; + if (!systemDate) return none(); + if (row.pickupDate.slice(0, 10) === systemDate) return ok(['提车日期与交车管理一致']); + return warn('提车日期不一致', [`交车管理提车日期:${systemDate}`], { pickupDate: systemDate }); +} + +export function checkReturnDate(row: LeaseBusinessDetailRow): FieldCheckInfo { + const vehicle = getVehicleRef(row.plateNo); + if (!vehicle || !row.returnDate) return none(); + const systemDate = vehicle.returnDate; + if (!systemDate) return none(); + if (row.returnDate.slice(0, 10) === systemDate) return ok(['退车日期与还车记录一致']); + return warn('退车日期不一致', [`还车记录日期:${systemDate}`], { returnDate: systemDate }); +} + +export function checkDeposit(row: LeaseBusinessDetailRow): FieldCheckInfo { + const contract = getContractRef(row.plateNo, row.customerName); + if (!contract || typeof row.deposit !== 'number') return none(); + if (moneyEqual(row.deposit, contract.deposit)) return ok(['押金与合同保证金一致']); + return warn('押金不一致', [`合同保证金:${contract.deposit.toFixed(2)}`], { deposit: contract.deposit }); +} + +export function checkContractRent(row: LeaseBusinessDetailRow): FieldCheckInfo { + const contract = getContractRef(row.plateNo, row.customerName); + if (!contract || !row.contractRent) return none(); + if (moneyEqual(row.contractRent, contract.contractRent)) return ok(['租金与合同一致']); + return warn('租金不一致', [`合同租金:${contract.contractRent.toFixed(2)}`], { contractRent: contract.contractRent }); +} + +export function checkReceivableRent(row: LeaseBusinessDetailRow): FieldCheckInfo { + const months = resolveSettlementMonths(row); + const expected = roundMoney(row.contractRent * months); + if (!expected && !row.receivableRent) return none(); + if (moneyEqual(row.receivableRent, expected)) return ok([`应收租金 = 合同标的租金 × ${months} 个月`]); + return warn('应收租金不一致', [`正确金额:${expected.toFixed(2)}`], { receivableRent: expected }); +} + +export function checkMileageDeduction(row: LeaseBusinessDetailRow): FieldCheckInfo { + if (row.mileageDeduction === 0) return none(); + if (row.mileageDeduction < 0) return ok(['里程减免以负数显示']); + return warn('里程减免金额应为负数', ['请录入负值,例如 -500']); +} + +export function checkOtherDeduction(row: LeaseBusinessDetailRow): FieldCheckInfo { + if (row.otherDeduction === 0) return none(); + if (row.otherDeduction < 0) return ok(['其他减免以负数显示']); + return warn('其他减免金额应为负数', ['请录入负值']); +} + +export function checkAssetOwner(row: LeaseBusinessDetailRow): FieldCheckInfo { + const vehicle = getVehicleRef(row.plateNo); + if (!vehicle || !row.assetOwner) return none(); + const expected = vehicle.registeredOwnership; + if (!expected) return none(); + if (row.assetOwner.trim() === expected.trim()) return ok(['与车辆登记所有权一致']); + return warn('资产归属不一致', [`登记所有权:${expected}`], { assetOwner: expected }); +} + +export function getFieldCheck( + row: LeaseBusinessDetailRow, + field: + | 'plateNo' + | 'customerName' + | 'pickupDate' + | 'returnDate' + | 'deposit' + | 'contractRent' + | 'receivableRent' + | 'mileageDeduction' + | 'otherDeduction' + | 'assetOwner', +): FieldCheckInfo { + switch (field) { + case 'plateNo': return checkPlateExists(row); + case 'customerName': return checkCustomerName(row); + case 'pickupDate': return checkPickupDate(row); + case 'returnDate': return checkReturnDate(row); + case 'deposit': return checkDeposit(row); + case 'contractRent': return checkContractRent(row); + case 'receivableRent': return checkReceivableRent(row); + case 'mileageDeduction': return checkMileageDeduction(row); + case 'otherDeduction': return checkOtherDeduction(row); + case 'assetOwner': return checkAssetOwner(row); + default: return none(); + } +} + +export function displayDeductionAmount(value: number): string { + if (value === 0) return ''; + const abs = Math.abs(value); + const text = abs.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + return value > 0 ? `-${text}` : `-${text}`; +} + +export function normalizeDeductionForDisplay(value: number): number { + if (value === 0) return 0; + return value > 0 ? -Math.abs(value) : value; +} diff --git a/src/prototypes/lease-business-detail/utils/hydrogen-fee.ts b/src/prototypes/lease-business-detail/utils/hydrogen-fee.ts new file mode 100644 index 0000000..ba58746 --- /dev/null +++ b/src/prototypes/lease-business-detail/utils/hydrogen-fee.ts @@ -0,0 +1,100 @@ +import type { LeaseBusinessDetailRow } from '../types'; +import seedRecords from '../data/hydrogen-refuel-records.json'; + +export interface HydrogenRefuelRecord { + hydrogenTime: string; + stationName: string; + customerName: string; + plateNo: string; + hydrogenKg: number; + unitPrice: number; + totalPrice: number; + borneBy: '我司承担' | '客户承担'; +} + +const HYDROGEN_REFUEL_RECORDS = seedRecords as HydrogenRefuelRecord[]; + +function roundMoney(value: number): number { + return Math.round(value * 100) / 100; +} + +function parseDateOnly(value: string): Date | null { + if (!value) return null; + const datePart = value.trim().slice(0, 10); + const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datePart); + if (!match) return null; + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + return new Date(year, month - 1, day); +} + +function lastDayOfMonth(year: number, month: number): Date { + return new Date(year, month, 0); +} + +function firstDayOfMonth(year: number, month: number): Date { + return new Date(year, month - 1, 1); +} + +/** 提车~退车与账单月份的交集,作为加氢记录筛选区间 */ +function resolveHydrogenDateRange( + row: Pick, +): { start: Date; end: Date } | null { + const pickup = parseDateOnly(row.pickupDate ?? ''); + if (!pickup) return null; + + const monthStart = firstDayOfMonth(row.year, row.month); + const monthEnd = lastDayOfMonth(row.year, row.month); + const returnDate = parseDateOnly(row.returnDate ?? ''); + const leaseEnd = returnDate ?? monthEnd; + + const start = pickup > monthStart ? pickup : monthStart; + const end = leaseEnd < monthEnd ? leaseEnd : monthEnd; + if (start > end) return null; + return { start, end }; +} + +function recordDate(record: HydrogenRefuelRecord): Date | null { + return parseDateOnly(record.hydrogenTime); +} + +function normalizeCustomer(value: string): string { + return (value ?? '').trim(); +} + +export function getHydrogenRefuelRecords( + row: Pick< + LeaseBusinessDetailRow, + 'plateNo' | 'customerName' | 'year' | 'month' | 'pickupDate' | 'returnDate' + >, + records: HydrogenRefuelRecord[] = HYDROGEN_REFUEL_RECORDS, +): HydrogenRefuelRecord[] { + const range = resolveHydrogenDateRange(row); + if (!range) return []; + + const customer = normalizeCustomer(row.customerName); + return records.filter((item) => { + if (item.plateNo !== row.plateNo) return false; + if (normalizeCustomer(item.customerName) !== customer) return false; + if (item.borneBy !== '我司承担') return false; + const date = recordDate(item); + if (!date) return false; + return date >= range.start && date <= range.end; + }); +} + +export function sumHydrogenFeeForDetailRow( + row: Pick< + LeaseBusinessDetailRow, + 'plateNo' | 'customerName' | 'year' | 'month' | 'pickupDate' | 'returnDate' + >, + records?: HydrogenRefuelRecord[], +): number { + return roundMoney( + getHydrogenRefuelRecords(row, records) + .reduce((sum, item) => sum + item.totalPrice, 0), + ); +} + +export { HYDROGEN_REFUEL_RECORDS }; diff --git a/src/prototypes/lease-business-detail/utils/payment-status.ts b/src/prototypes/lease-business-detail/utils/payment-status.ts new file mode 100644 index 0000000..285240c --- /dev/null +++ b/src/prototypes/lease-business-detail/utils/payment-status.ts @@ -0,0 +1,37 @@ +export type DetailPaymentStatus = '未收款' | '部分收款' | '已结清'; + +export const DETAIL_PAYMENT_STATUS_OPTIONS: DetailPaymentStatus[] = [ + '未收款', + '部分收款', + '已结清', +]; + +const STATUS_SORT_ORDER: Record = { + 未收款: 0, + 部分收款: 1, + 已结清: 2, +}; + +export const DETAIL_PAYMENT_STATUS_CLASS: Record = { + 未收款: 'ldb-status--unpaid', + 部分收款: 'ldb-status--partial', + 已结清: 'ldb-status--paid', +}; + +/** 根据实收金额与应收合计计算收款状态 */ +export function resolveDetailPaymentStatus( + receivableTotal: number, + receivedAmount: number | null | undefined, +): DetailPaymentStatus { + const received = receivedAmount ?? 0; + if (received <= 0) return '未收款'; + if (received >= receivableTotal) return '已结清'; + return '部分收款'; +} + +export function paymentStatusSortValue( + receivableTotal: number, + receivedAmount: number | null | undefined, +): number { + return STATUS_SORT_ORDER[resolveDetailPaymentStatus(receivableTotal, receivedAmount)]; +} diff --git a/src/prototypes/lease-business-detail/utils/role.ts b/src/prototypes/lease-business-detail/utils/role.ts new file mode 100644 index 0000000..5e4fc50 --- /dev/null +++ b/src/prototypes/lease-business-detail/utils/role.ts @@ -0,0 +1,32 @@ +export type DetailViewerRole = 'staff' | 'supervisor'; + +export interface DetailOperator { + id: string; + name: string; + role: DetailViewerRole; +} + +const STAFF_OPERATOR: DetailOperator = { + id: 'staff-001', + name: '刘念念', + role: 'staff', +}; + +const SUPERVISOR_OPERATOR: DetailOperator = { + id: 'supervisor-001', + name: '王主管', + role: 'supervisor', +}; + +/** 原型默认业管人员;主管预览 URL 追加 ?role=supervisor */ +export function resolveDetailOperator(): DetailOperator { + if (typeof window === 'undefined') return STAFF_OPERATOR; + const params = new URLSearchParams(window.location.search); + const role = params.get('role'); + if (role === 'supervisor' || role === 'manager') return SUPERVISOR_OPERATOR; + return STAFF_OPERATOR; +} + +export function isDetailSupervisor(operator: DetailOperator): boolean { + return operator.role === 'supervisor'; +} diff --git a/src/prototypes/lease-business-detail/utils/system-ref.ts b/src/prototypes/lease-business-detail/utils/system-ref.ts new file mode 100644 index 0000000..2639b57 --- /dev/null +++ b/src/prototypes/lease-business-detail/utils/system-ref.ts @@ -0,0 +1,205 @@ +import vehicles from '../../vehicle-management/data/vehicles.json'; +import customers from '../../customer-management/data/customers.json'; +import seedRows from '../data/rows.json'; +import type { LeaseBusinessDetailRow } from '../types'; + +export interface VehicleRef { + plateNo: string; + brand: string; + model: string; + brandModel: string; + registeredOwnership: string; + deliveryDate: string; + returnDate: string; +} + +export interface ContractRef { + businessDept: string; + salesperson: string; + customerName: string; + deposit: number; + contractRent: number; + settlementMonths: number; + paymentTiming: 'pre' | 'post'; + signingCompany: string; + pickupDate: string; + returnDate: string; +} + +type VehicleSeed = { + plateNo?: string; + brand?: string; + model?: string; + ownership?: string; + lastDeliveryTime?: string; + lastReturnTime?: string; +}; + +const plateRegistry = new Map(); +const contractByPlateCustomer = new Map(); +const customerNameSet = new Set(); + +function parseBrandModel(vehicleType: string): { brand: string; model: string } { + const raw = String(vehicleType || '').trim(); + if (!raw) return { brand: '', model: '' }; + const slash = raw.indexOf('/'); + if (slash >= 0) { + return { brand: raw.slice(0, slash).trim(), model: raw.slice(slash + 1).trim() }; + } + const digit = raw.search(/\d/); + if (digit > 0) { + return { brand: raw.slice(0, digit).trim(), model: raw.slice(digit).trim() }; + } + return { brand: raw, model: '' }; +} + +function formatBrandModel(brand: string, model: string): string { + if (brand && model) return `${brand}·${model}`; + return brand || model || ''; +} + +function normalizeDate(value: unknown): string { + const raw = String(value ?? '').trim(); + if (!raw || raw === '-') return ''; + return raw.slice(0, 10); +} + +function parsePaymentTiming(method: string): 'pre' | 'post' { + return method.includes('后付') ? 'post' : 'pre'; +} + +function parseSettlementMonths(method: string): number { + if (method.includes('年度') || method.includes('一年')) return 12; + if (method.includes('半年')) return 6; + if (method.includes('季度')) return 3; + return 1; +} + +function contractKey(plateNo: string, customerName: string): string { + return `${plateNo}::${customerName}`; +} + +function registerVehicle(ref: VehicleRef): void { + plateRegistry.set(ref.plateNo, ref); +} + +function registerContract(plateNo: string, customerName: string, row: Partial): void { + if (!plateNo || !customerName) return; + const key = contractKey(plateNo, customerName); + if (contractByPlateCustomer.has(key)) return; + contractByPlateCustomer.set(key, { + businessDept: row.businessDept ?? '', + salesperson: row.salesperson ?? '', + customerName, + deposit: typeof row.deposit === 'number' ? row.deposit : 0, + contractRent: row.contractRent ?? 0, + settlementMonths: parseSettlementMonths(row.paymentMethod ?? ''), + paymentTiming: parsePaymentTiming(row.paymentMethod ?? ''), + signingCompany: row.signingCompany ?? '', + pickupDate: normalizeDate(row.pickupDate), + returnDate: normalizeDate(row.returnDate), + }); +} + +function bootstrap(): void { + (customers as { name?: string }[]).forEach((c) => { + if (c.name) customerNameSet.add(c.name.trim()); + }); + + (vehicles as VehicleSeed[]).forEach((v) => { + if (!v.plateNo) return; + registerVehicle({ + plateNo: v.plateNo, + brand: v.brand ?? '', + model: v.model ?? '', + brandModel: formatBrandModel(v.brand ?? '', v.model ?? ''), + registeredOwnership: v.ownership ?? '', + deliveryDate: normalizeDate(v.lastDeliveryTime), + returnDate: normalizeDate(v.lastReturnTime), + }); + }); + + (seedRows as LeaseBusinessDetailRow[]).forEach((row) => { + if (!row.plateNo) return; + const parsed = parseBrandModel(row.vehicleType); + const existing = plateRegistry.get(row.plateNo); + if (!existing) { + registerVehicle({ + plateNo: row.plateNo, + brand: parsed.brand, + model: parsed.model, + brandModel: formatBrandModel(parsed.brand, parsed.model) || row.vehicleType, + registeredOwnership: row.assetOwner ?? '', + deliveryDate: normalizeDate(row.pickupDate), + returnDate: normalizeDate(row.returnDate), + }); + } else if (!existing.brandModel && row.vehicleType) { + existing.brandModel = row.vehicleType; + } + if (row.customerName) customerNameSet.add(row.customerName.trim()); + registerContract(row.plateNo, row.customerName, row); + }); +} + +bootstrap(); + +export function isPlateInVehicleSystem(plateNo: string): boolean { + return Boolean(plateNo && plateRegistry.has(plateNo.trim())); +} + +export function getVehicleRef(plateNo: string): VehicleRef | undefined { + return plateRegistry.get(plateNo.trim()); +} + +export function getContractRef(plateNo: string, customerName: string): ContractRef | undefined { + return contractByPlateCustomer.get(contractKey(plateNo.trim(), customerName.trim())); +} + +export function resolveExpectedCustomer( + plateNo: string, + pickupDate: string, + returnDate: string, +): string { + const vehicle = getVehicleRef(plateNo); + const pickup = normalizeDate(pickupDate); + const ret = normalizeDate(returnDate); + let best = ''; + contractByPlateCustomer.forEach((contract, key) => { + if (!key.startsWith(`${plateNo.trim()}::`)) return; + if (pickup && contract.pickupDate && contract.pickupDate !== pickup) return; + if (ret && contract.returnDate && contract.returnDate !== ret) return; + best = contract.customerName; + }); + if (best) return best; + return vehicle ? '' : ''; +} + +export function isKnownCustomerName(name: string): boolean { + const trimmed = name.trim(); + if (!trimmed) return false; + if (customerNameSet.has(trimmed)) return true; + return Array.from(customerNameSet).some((c) => c.includes(trimmed) || trimmed.includes(c)); +} + +export function formatPaymentMethodLabel(months: number, timing: 'pre' | 'post'): string { + const period = + months === 12 ? '年度' + : months === 6 ? '半年' + : months === 3 ? '季度' + : months === 1 ? '月度' + : `${months}个月`; + return `${period}${timing === 'post' ? '后付' : '预付'}`; +} + +export function resolvePaymentMethodLabel(row: LeaseBusinessDetailRow): string { + const contract = getContractRef(row.plateNo, row.customerName); + if (contract) { + return formatPaymentMethodLabel(contract.settlementMonths, contract.paymentTiming); + } + return row.paymentMethod; +} + +export function resolveSettlementMonths(row: LeaseBusinessDetailRow): number { + const contract = getContractRef(row.plateNo, row.customerName); + return contract?.settlementMonths ?? parseSettlementMonths(row.paymentMethod); +} diff --git a/src/prototypes/lease-business-ledger/annotation-source.json b/src/prototypes/lease-business-ledger/annotation-source.json index 9d9b016..b7009bf 100644 --- a/src/prototypes/lease-business-ledger/annotation-source.json +++ b/src/prototypes/lease-business-ledger/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "lease-business-ledger", "pageId": "list", - "updatedAt": 1783589987166, + "updatedAt": 1783800844966, "nodes": [ { "id": "lbl-filter", @@ -1158,36 +1158,6 @@ "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "lbl-doc-root", diff --git a/src/prototypes/lease-business-ledger/components/AccidentLinkModal.tsx b/src/prototypes/lease-business-ledger/components/AccidentLinkModal.tsx index 01f42d2..1400c5a 100644 --- a/src/prototypes/lease-business-ledger/components/AccidentLinkModal.tsx +++ b/src/prototypes/lease-business-ledger/components/AccidentLinkModal.tsx @@ -109,8 +109,8 @@ export function AccidentLinkModal({

- - +
diff --git a/src/prototypes/lease-business-ledger/components/ConfirmMarkModal.tsx b/src/prototypes/lease-business-ledger/components/ConfirmMarkModal.tsx index 4daf5ba..bb51d1a 100644 --- a/src/prototypes/lease-business-ledger/components/ConfirmMarkModal.tsx +++ b/src/prototypes/lease-business-ledger/components/ConfirmMarkModal.tsx @@ -33,8 +33,8 @@ export function ConfirmMarkModal({ open, count, onCancel, onConfirm }: ConfirmMa

已选择 {count} 条草稿记录

- - + +
diff --git a/src/prototypes/lease-business-ledger/components/DeleteConfirmModal.tsx b/src/prototypes/lease-business-ledger/components/DeleteConfirmModal.tsx index 3bbd969..f72a398 100644 --- a/src/prototypes/lease-business-ledger/components/DeleteConfirmModal.tsx +++ b/src/prototypes/lease-business-ledger/components/DeleteConfirmModal.tsx @@ -34,8 +34,8 @@ export function DeleteConfirmModal({ open, plateNo = '', meta, onCancel, onConfi

{meta ?? (plateNo ? `车牌:${plateNo}` : '')}

- - + +
diff --git a/src/prototypes/lease-business-ledger/components/FilterPanel.tsx b/src/prototypes/lease-business-ledger/components/FilterPanel.tsx index a3f71a1..8e360b2 100644 --- a/src/prototypes/lease-business-ledger/components/FilterPanel.tsx +++ b/src/prototypes/lease-business-ledger/components/FilterPanel.tsx @@ -218,21 +218,19 @@ export function FilterPanel({ records, filters, onChange, onReset, onSearch }: F onClick={() => setExpanded(!expanded)} aria-expanded={expanded} aria-controls="ldb-filter-expand-panel" - > + + data-vm-icon={expanded ? 'chevron-up' : 'filter'}> {expanded ? '收起' : '更多筛选'} {extraActiveCount > 0 && !expanded ? ( {extraActiveCount} ) : null} - - - + - diff --git a/src/prototypes/lease-business-ledger/components/LeaseLedgerImportModal.tsx b/src/prototypes/lease-business-ledger/components/LeaseLedgerImportModal.tsx index 43f6d71..3eaa3eb 100644 --- a/src/prototypes/lease-business-ledger/components/LeaseLedgerImportModal.tsx +++ b/src/prototypes/lease-business-ledger/components/LeaseLedgerImportModal.tsx @@ -38,9 +38,7 @@ export function LeaseLedgerImportModal({ 1 下载导入模板 -
@@ -71,7 +69,7 @@ export function LeaseLedgerImportModal({
- +
diff --git a/src/prototypes/lease-business-ledger/components/LeaseLedgerTable.tsx b/src/prototypes/lease-business-ledger/components/LeaseLedgerTable.tsx index 633c954..2f68cb8 100644 --- a/src/prototypes/lease-business-ledger/components/LeaseLedgerTable.tsx +++ b/src/prototypes/lease-business-ledger/components/LeaseLedgerTable.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { ArrowDown, ArrowUp, ArrowUpDown, CircleHelp, History } from 'lucide-react'; +import { ArrowDown, ArrowUp, ArrowUpDown, CircleHelp } from 'lucide-react'; +import { OperationActions } from '../../../common/OperationActions'; import type { HydrogenFeeRecord, LeaseLedgerRow, MaintenanceLedgerRecord, ReceiptRecord, ReturnSettlementFeeRecord } from '../types'; import { calcMonthlyIncome, @@ -50,7 +51,7 @@ interface LeaseLedgerTableProps { type SortDir = 'asc' | 'desc'; const CHECK_COL_WIDTH = 48; -const ACTION_COL_WIDTH = 208; +const ACTION_COL_WIDTH = 168; function buildLeftStickyLayout(columnWidths: Record) { const keys: string[] = []; @@ -539,7 +540,7 @@ function renderCell( label={formatMoney(row.receivedAmount)} title="收款明细" footer={showReceiptLink ? ( - ) : undefined} @@ -907,26 +908,23 @@ export function LeaseLedgerTable({ ))} -
- - {canEdit && ( - - )} - {editingRowId === row.id && canEdit && ( - - )} -
+ onEdit(row) } : undefined} + more={[ + { + key: 'history', + label: '操作记录', + onClick: () => setHistoryRow(row), + }, + ...(editingRowId === row.id && canEdit + ? [{ + key: 'save', + label: '保存', + onClick: () => onSave(row), + }] + : []), + ]} + /> ); diff --git a/src/prototypes/lease-business-ledger/components/LedgerLineItemsCell.tsx b/src/prototypes/lease-business-ledger/components/LedgerLineItemsCell.tsx index 031b377..b65316c 100644 --- a/src/prototypes/lease-business-ledger/components/LedgerLineItemsCell.tsx +++ b/src/prototypes/lease-business-ledger/components/LedgerLineItemsCell.tsx @@ -118,9 +118,9 @@ function LedgerLineItemsEditor({ diff --git a/src/prototypes/lease-business-ledger/components/MaintenanceLinkModal.tsx b/src/prototypes/lease-business-ledger/components/MaintenanceLinkModal.tsx index 9af3094..c8f3c59 100644 --- a/src/prototypes/lease-business-ledger/components/MaintenanceLinkModal.tsx +++ b/src/prototypes/lease-business-ledger/components/MaintenanceLinkModal.tsx @@ -107,8 +107,8 @@ export function MaintenanceLinkModal({

- - +
diff --git a/src/prototypes/lease-business-ledger/components/OperationHistoryModal.tsx b/src/prototypes/lease-business-ledger/components/OperationHistoryModal.tsx index 080521b..fcda2fa 100644 --- a/src/prototypes/lease-business-ledger/components/OperationHistoryModal.tsx +++ b/src/prototypes/lease-business-ledger/components/OperationHistoryModal.tsx @@ -59,7 +59,7 @@ export function OperationHistoryModal({ open, plateNo, entries, onClose }: Opera )}
- +
diff --git a/src/prototypes/lease-business-ledger/components/ReceiptLinkModal.tsx b/src/prototypes/lease-business-ledger/components/ReceiptLinkModal.tsx index e396958..2a29152 100644 --- a/src/prototypes/lease-business-ledger/components/ReceiptLinkModal.tsx +++ b/src/prototypes/lease-business-ledger/components/ReceiptLinkModal.tsx @@ -164,8 +164,8 @@ export function ReceiptLinkModal({
- - +
diff --git a/src/prototypes/lease-business-ledger/index.tsx b/src/prototypes/lease-business-ledger/index.tsx index 4f73eb9..7cbe3e5 100644 --- a/src/prototypes/lease-business-ledger/index.tsx +++ b/src/prototypes/lease-business-ledger/index.tsx @@ -7,11 +7,11 @@ import './styles/index.css'; import React, { useCallback, useMemo, useState } from 'react'; import { Archive, Download, FileUp, Save } from 'lucide-react'; import { - AnnotationViewer, type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import seedRows from './data/rows.json'; import seedReceipts from './data/receipts.json'; import seedReturnSettlementFees from './data/return-settlement-fees.json'; @@ -365,12 +365,10 @@ export default function LeaseBusinessLedgerApp() {
- -
@@ -491,7 +487,7 @@ export default function LeaseBusinessLedgerApp() { onDownloadTemplate={downloadLedgerBatchImportTemplate} onImport={handleBatchImport} /> - + ); } diff --git a/src/prototypes/lease-business-line-overview/annotation-source.json b/src/prototypes/lease-business-line-overview/annotation-source.json index 5f47ee4..e077013 100644 --- a/src/prototypes/lease-business-line-overview/annotation-source.json +++ b/src/prototypes/lease-business-line-overview/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "lease-business-line-overview", "pageId": "overview", - "updatedAt": 1783589987166, + "updatedAt": 1783800844966, "nodes": [ { "id": "lblo-system-intro", @@ -173,36 +173,6 @@ "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "lblo-doc-root", diff --git a/src/prototypes/lease-business-line-overview/lines.ts b/src/prototypes/lease-business-line-overview/lines.ts index aefff56..7984b45 100644 --- a/src/prototypes/lease-business-line-overview/lines.ts +++ b/src/prototypes/lease-business-line-overview/lines.ts @@ -176,8 +176,8 @@ export const LEASE_MODULES: LineModule[] = [ ], closure: '应收完成后,按合同约定生成交车任务;运维部对应区域操作员在交车模块查看并执行,车辆正式交付给承租方。', - prototypeHref: '/prototypes/payment-records', - prototypeLabel: '打开收款记录', + prototypeHref: '/prototypes/vehicle-pickup-receivable', + prototypeLabel: '打开提车应收款', }, { id: 'lease-ledger', diff --git a/src/prototypes/lease-contract-management/.spec/requirements-annotation-context.md b/src/prototypes/lease-contract-management/.spec/requirements-annotation-context.md new file mode 100644 index 0000000..eb6faf5 --- /dev/null +++ b/src/prototypes/lease-contract-management/.spec/requirements-annotation-context.md @@ -0,0 +1,103 @@ +# 租赁合同管理 · 标注与原型上下文 + +> 由 `extract-annotation-source` 从运行态 `window.__AXHUB_ANNOTATION_SOURCE__` 提取,并与仓库源码对齐。 +> 原型地址:`/prototypes/lease-contract-management` + +--- + +## 1. 提取摘要 + +| 项 | 值 | +|---|---| +| 标注节点数 | 22 | +| 列表页节点 | 8(`pageId: list`) | +| 新增页节点 | 14(`pageId: create`) | +| 图片附件 | 无 | +| 开发态 `source.root` | 未发布(以仓库路径为准) | + +### 1.1 目录结构 + +```text +租赁合同管理说明 +├── 模块总览 +├── 合同模板管理(链接) +├── PRD 全文 +│ ├── PRD 完整文档 +│ ├── 列表页 PRD +│ ├── 新增页 PRD +│ └── 验收标准 +├── 列表页模块(route + 各标注说明) +├── 新增页模块(route + 各标注说明) +├── 操作流程(正向 / 逆向流程图) +└── 标注查看提示 +``` + +### 1.2 标注节点索引 + +**列表页** + +| id | 标题 | +|---|---| +| `lc-list-filter` | 筛选条件 | +| `lc-list-kpi` | KPI 统计卡片 | +| `lc-fleet-summary` | 在租车辆概览 | +| `lc-list-toolbar` | 列表工具栏 | +| `lc-list-table` | 合同台账列表 | +| `lc-list-action-more` | 更多操作 | +| `lc-action-convert-tripartite` | 转三方合同 | +| `lc-action-trial-to-formal` | 试用转正式 | + +**新增页** + +| id | 标题 | +|---|---| +| `lc-create-main-contract` | 主体合同信息 | +| `lc-create-signing-method` | 合同签署方式 | +| `lc-card-signing` | 签约信息 | +| `lc-signing-customer-principal` | 乙方负责人 | +| `lc-create-lease-order` | 附件1:租赁订单 | +| `lc-lease-order-brand-model` | 品牌车型与在库 | +| `lc-lease-order-rent` | 车辆租金与最低租金 | +| `lc-lease-order-service-content` | 服务项配置 | +| `lc-lease-order-lease-period` | 租赁期限与到期计算 | +| `lc-create-poa` | 授权委托书 | +| `lc-create-contract-remark` | 合同备注 | +| `lc-create-seal` | 用章类型 | +| `lc-create-preview` | 实时预览 | +| `lc-create-footer` | 底栏操作 | + +定位器统一为:`[data-annotation-id=""]` + +--- + +## 2. 履约与交还车规则(列表子表) + +| 规则 | 说明 | +|---|---| +| 审批未通过 | 合同下车辆**不视为已交车**;子表「交车」显示「未交车」,无租赁账单/还车应结款 | +| 审批通过 | 方进入履约态;有实际交车时间的车辆计入「已交车辆数」 | +| 子表还车 | **审批通过 + 已交车 + 未还车** 时,操作列显示「还车」 | +| 已还车 | 操作列为「—」;「还车应结款」按状态展示待提交/审批中/已完成 | + +子表列:车辆信息、提车应收款、交车、租赁账单、还车、还车应结款、交车安排、里程要求、里程完成、操作。 + +--- + +## 3. 源码入口 + +| 能力 | 文件 | +|---|---| +| 标注/路由入口 | `index.tsx` | +| 列表主逻辑 | `LeaseContractManagement.jsx` | +| 列表数据与履约规则 | `lease-contract-list-data.js` | +| 新增页 | `LeaseContractCreate.jsx` | +| 主体表单 | `LeaseContractEditorForm.jsx` | +| 标注数据构建 | `scripts/build-annotation-source.mjs` | +| 标注数据 | `annotation-source.json` | +| PRD 全文 | `src/resources/lease-contract-management/PRD.md` | + +同步命令: + +```bash +node src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs +``` diff --git a/src/prototypes/lease-contract-management/.spec/requirements-flow-operations.md b/src/prototypes/lease-contract-management/.spec/requirements-flow-operations.md new file mode 100644 index 0000000..98db3a5 --- /dev/null +++ b/src/prototypes/lease-contract-management/.spec/requirements-flow-operations.md @@ -0,0 +1,371 @@ +# 租赁合同管理 · 操作流程图 + +> 以「新增合同」为起点,覆盖主流程正向操作与可逆/异常逆向操作。 +> 与列表操作列、子表交还车、签署闭环对齐原型实现。 + +--- + +## 1. 总览:从新增到履约结束 + +```mermaid +flowchart TB + subgraph create [草拟与审批] + A[列表 · 新增] --> B[新增/编辑页填写] + B --> C{底栏操作} + C -->|取消| Z1[返回列表 · 不保存] + C -->|保存草稿| D[草稿] + C -->|提交审核| E[待审批] + E --> F[审批中] + F --> G[审批通过] + F --> H[审批驳回] + F --> I[审批终止] + E --> J[撤回] + F --> J + J --> K[撤回 / 可再编辑] + H --> L[编辑后重提] + L --> E + D --> M[删除草稿] + end + + subgraph signing [签署闭环] + G --> N{签署方式} + N -->|线上电子签章| O[E签宝发送乙方] + O --> P[签署完成归档] + N -->|线下人工上传| Q[盖章合同补传] + Q --> P + end + + subgraph fulfill [履约] + G --> R[合同进行中] + R --> S[提车应收款] + S --> T[交车] + T --> U[在租 · 租赁账单] + U --> V[子表 · 还车] + V --> W[还车应结款] + W --> X[全部车辆已还车] + X --> Y[已终止 / 已结束] + end + + subgraph change [进行中变更] + R --> AA[续签 / 增车 / 转三方 / 附加费用 / 添加授权书 / 试用转正式] + AA --> AB[变更审批] + AB --> G + R --> AC[主动终止] + AC --> AD[终止审批] + AD --> Y + end +``` + +--- + +## 2. 新增合同 · 正向流程 + +### 2.1 保存草稿 + +```mermaid +flowchart LR + A[点击新增] --> B[填写主体/订单/授权书/预览] + B --> C[系统判定标准/非标准审批类型] + C --> D[保存草稿] + D --> E[合同状态: 草稿] + E --> F[审批状态: 未提交] + F --> G[列表可编辑/删除] +``` + +### 2.2 提交审核 → 审批通过 + +```mermaid +flowchart TD + A[提交审核] --> B{必填与证照校验} + B -->|不通过| C[Toast 提示 · 停留当前页] + B -->|通过| D[审批状态: 待审批] + D --> E[合同状态: 已提交审批] + E --> F[进入审批流节点] + F --> G[审批中] + G --> H{终审结果} + H -->|通过| I[审批通过] + I --> J[合同状态: 合同进行中] + H -->|驳回| K[审批驳回 → 合同回草稿可编辑] + H -->|终止| L[审批终止] +``` + +### 2.3 签署闭环(审批通过后) + +```mermaid +flowchart TD + A[审批通过] --> B{合同签署方式} + B -->|线上电子签章| C[向乙方负责人手机号发送 E签宝] + C --> D[乙方签署] + D --> E[电子合同自动归档] + B -->|线下人工上传| F[列表 · 盖章合同补传] + F --> G[法务/业务上传 PDF/图片] + G --> H[标记已补传 · 查看可预览下载] + E --> I[履约开始] + H --> I +``` + +--- + +## 3. 新增合同 · 逆向流程 + +### 3.1 取消草拟(不保存) + +```mermaid +flowchart LR + A[新增页] --> B[点击取消/返回列表] + B --> C[丢弃未保存修改] + C --> D[回到列表 · 无新记录] +``` + +### 3.2 删除草稿 + +```mermaid +flowchart LR + A[草稿合同] --> B[更多 · 删除合同] + B --> C[二次确认] + C --> D[从台账移除] +``` + +### 3.3 撤回已提交审批 + +```mermaid +flowchart TD + A[待审批 / 审批中] --> B[更多 · 撤回合同] + B --> C[二次确认] + C --> D[审批状态: 撤回] + D --> E[可重新编辑后再次提交] +``` + +### 3.4 审批驳回后重提 + +```mermaid +flowchart LR + A[审批驳回] --> B[主按钮 · 编辑] + B --> C[修改表单/预览] + C --> D[保存草稿或提交审核] + D --> E[重新进入审批流] +``` + +--- + +## 4. 履约正向:交车 → 还车 → 结清 + +> **前置条件**:仅 `审批通过` 的合同才产生交车/还车履约数据。 + +```mermaid +flowchart TD + A[合同进行中] --> B[办理提车应收款] + B --> C[实际交车] + C --> D[子表 · 交车列展示里程/交车人/时间] + D --> E[租赁账单: 正常/欠费] + E --> F{是否还车} + F -->|未还车| G[子表操作列 · 还车] + G --> H[还车管理] + H --> I[子表 · 还车列展示还车信息] + I --> J[还车应结款] + J --> K{结款状态} + K -->|待提交| L[去处理] + K -->|审批中| M[展示审批人 · 同主表样式] + K -->|已完成| N[查看结款] + F -->|已还车| N + N --> O{全部车辆已还?} + O -->|是| P[KPI · 已终止口径] +``` + +### 4.1 子表还车操作条件 + +```mermaid +flowchart TD + A[展开已交车子表] --> B{审批通过?} + B -->|否| C[交车显示未交车 · 无还车按钮] + B -->|是| D{已交车且未还车?} + D -->|是| E[操作列: 还车] + D -->|否| F[操作列: —] +``` + +--- + +## 5. 履约逆向与异常 + +### 5.1 非审批通过合同不出现交车 + +```mermaid +flowchart LR + A[待审批/审批中/驳回/撤回] --> B[即使有 planned 交车计划] + B --> C[不计入已交车辆数] + C --> D[子表交车列: 未交车] +``` + +### 5.2 主动终止合同 + +```mermaid +flowchart TD + A[合同进行中] --> B[更多 · 主动终止合同] + B --> C[填写终止信息] + C --> D[提交终止审批] + D --> E{审批结果} + E -->|通过| F[合同已终止/已结束] + E -->|驳回| G[维持进行中] +``` + +### 5.3 续签 / 转三方导致旧合同终止 + +```mermaid +flowchart LR + A[进行中旧合同] --> B[续签或转三方] + B --> C[生成新合同并走审批] + C --> D[旧合同自动标记已终止] + D --> E[在租统计转移至新合同] +``` + +--- + +## 6. 进行中变更 · 正向流程 + +### 6.1 新增车辆 + +```mermaid +flowchart LR + A[合同进行中] --> B[更多 · 新增车辆] + B --> C[打开增车编辑页] + C --> D[仅新增车辆走审批] + D --> E[审批通过] + E --> F[新车生成提车应收款] +``` + +### 6.2 续签合同 + +```mermaid +flowchart TD + A[进行中/到期合同] --> B[更多 · 续签合同] + B --> C[复制原合同] + C --> D[锁定已交未还车辆] + D --> E[可新增车辆卡片] + E --> F[提交续签审批] + F --> G[新合同审批通过] + G --> H[旧合同自动终止] +``` + +### 6.3 试用转正式 + +```mermaid +flowchart TD + A[试用合同] --> B[更多 · 试用转正式] + B --> C[自动拉取客户/授权人/车辆订单] + C --> D[车辆订单可编辑] + D --> E{同时转三方?} + E -->|是| F[同一步维护三方协议与附件] + E -->|否| G[仅转正式] + F --> H[提交审批] + G --> H + H --> I[生成正式合同] +``` + +### 6.4 转三方合同 + +```mermaid +flowchart LR + A[合同进行中] --> B[更多 · 转三方合同] + B --> C[新增多条三方协议] + C --> D[上传协议附件与对方公函] + D --> E[提交变更审批] + E --> F[审批通过后沿用原签署方式] +``` + +### 6.5 附加费用 + +```mermaid +flowchart LR + A[合同进行中] --> B[更多 · 附加费用] + B --> C[按车辆录入服务项与费用] + C --> D[已有费用只读] + D --> E[提交审批] + E --> F[通过后按计费规则入账] +``` + +### 6.6 添加授权委托书 + +```mermaid +flowchart LR + A[合同进行中] --> B[更多 · 添加授权委托书] + B --> C[维护受托人列表] + C --> D[仅授权书走审批/签署] + D --> E[主合同无需重签] +``` + +--- + +## 7. 进行中变更 · 逆向流程 + +### 7.1 变更审批撤回 + +```mermaid +flowchart TD + A[增车/续签/转三方/附加费用/授权书/终止] --> B[已提交变更审批] + B --> C[撤回合同] + C --> D[变更单撤回] + D --> E[原进行中合同保持不变] +``` + +### 7.2 变更审批驳回 + +```mermaid +flowchart LR + A[变更审批驳回] --> B[合同状态回变更/草稿口径] + B --> C[业务修改后重新提交] +``` + +--- + +## 8. 交车安排编辑(列表与子表) + +### 8.1 正向:修改交车安排 + +```mermaid +flowchart TD + A[列表 · 交车安排列] --> B{可编辑?} + B -->|未锁定| C[编辑区域/日期] + C --> D[同步所有未交车车辆] + D --> E[回写提车应收款 · 刷新待办] + B -->|已锁定| F[提示在提车应收款中调整] +``` + +### 8.2 子表:单车交车时间 + +```mermaid +flowchart LR + A[未交车车辆行] --> B[编辑本车交车时间] + B --> C[仅影响当前车辆] + C --> D[不影响已交车车辆] +``` + +--- + +## 9. 操作与状态对照表 + +| 操作 | 显示条件 | 正向结果 | 逆向/回退 | +|---|---|---|---| +| 新增 | 工具栏 | 进入新增页 | 取消不保存 | +| 保存草稿 | 新增/编辑页 | 草稿 | 删除草稿 | +| 提交审核 | 新增/编辑页 | 进入审批 | 撤回 | +| 编辑 | 草稿/驳回 | 修改后重提 | — | +| 删除 | 草稿 | 记录删除 | — | +| 撤回 | 待审批/审批中 | 审批撤回 | 可再提交 | +| 盖章补传 | 审批通过+线下 | 附件归档 | — | +| 交车 | 审批通过后履约 | 在租 | — | +| 还车 | 子表·已交未还 | 还车应结款 | — | +| 续签 | 进行中/到期 | 新合同+旧合同终止 | 撤回变更审批 | +| 增车 | 进行中 | 变更审批 | 驳回/撤回 | +| 转三方 | 进行中 | 变更审批 | 驳回/撤回 | +| 试用转正式 | 试用合同 | 新正式合同 | 驳回/撤回 | +| 附加费用 | 进行中 | 费用入账审批 | 驳回/撤回 | +| 添加授权书 | 进行中 | 授权书审批 | 驳回/撤回 | +| 主动终止 | 进行中 | 终止审批 | 驳回 | + +--- + +## 10. 修订记录 + +| 版本 | 日期 | 说明 | +|---|---|---| +| v1.0 | 2026-07-12 | 从新增合同起梳理正向/逆向全流程;对齐审批通过后交还与子表还车规则 | diff --git a/src/prototypes/lease-contract-management/.spec/requirements-prd-list.md b/src/prototypes/lease-contract-management/.spec/requirements-prd-list.md index f7986be..80740f4 100644 --- a/src/prototypes/lease-contract-management/.spec/requirements-prd-list.md +++ b/src/prototypes/lease-contract-management/.spec/requirements-prd-list.md @@ -15,7 +15,7 @@ ## 4. 筛选区 -默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 12 项;支持「重置」「查询」。 +默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 **13 项**;支持「重置」「查询」。 | 字段 | 控件 | 说明 | |---|---|---| @@ -23,36 +23,45 @@ | 项目名称 | 可搜索下拉 | 模糊匹配项目名称 | | 客户名称 | 可搜索下拉 | 模糊匹配客户名称 | | 签约公司 | 下拉 | 租赁合同签约公司枚举 | -| 审批状态 | 多选下拉 | 全部 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交 | -| 合同状态 | 多选下拉 | 全部 / 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 | +| 审批状态 | 多选下拉 | 全部 / 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 审批终止 / 撤回 | +| 合同状态 | 多选下拉 | 全部 / 草稿 / 已提交审批 / 合同进行中 / 已终止 | | 业务部门 | 多选下拉 | 业务相关部门 | | 业务负责人 | 多选下拉 | 业务组人员 | -| 合同类型 | 多选下拉 | 全部 / 正式合同 / 试用合同 | +| 合同模板 | 单选下拉 | 选项来自合同模板管理已发布且启用的模板「合同名称」维度(如正式合同、试用合同、现代18吨正式合同);可清空 | +| 标准合同名称 | 单选下拉 | **联动合同模板**:仅在选择合同模板后启用;选项为该模板类别下已发布文档名(如「2026年标准商用车租赁合同」);支持搜索;可清空 | | 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 | | 创建人 | 多选下拉 | 业务相关人员 | | 合同结束日期 | 日期范围 | 单输入框双日历,中文界面,开始—结束精确至日 | +**合同模板联动规则**: + +1. 未选合同模板时,「标准合同名称」禁用并提示「请先选择合同模板」。 +2. 切换合同模板时,自动清空已选标准合同名称。 +3. 查询时按合同记录的 `contractTemplateId`(或由合同类型推断的模板 ID)匹配;合同模板与标准合同名称可单独或组合使用。 + **交互**: - **查询**:将当前筛选条件写入「已应用筛选」,列表与 KPI 统计联动。 -- **重置**:清空为默认(多选类默认「全部」),并刷新列表。 +- **重置**:清空为默认(多选类默认「全部」,单选类清空),并刷新列表。 --- ## 5. KPI 统计卡片 -共 5 张卡片,点击切换列表筛选(与筛选区叠加生效)。 +共 5 张卡片,点击切换列表筛选(与筛选区条件 **叠加** 生效)。 -| 卡片 | 口径 | +| 卡片 | 口径(按合同状态) | |---|---| | 全部合同 | 不做 KPI 维度过滤 | -| 进行中 | 至少有一辆租赁车辆已交车且尚未还车 | -| 审批中 | 审批状态为「待审批」或「审批中」 | -| 临期合同 | 存在在租车辆,且合同签订有效期距今日 ≤ 30 天(以最后一辆在租车辆为准) | -| 已终止 | 全部车辆已还车;或续签/转三方产生新合同后旧合同自动标记为已终止 | +| 草稿 | 合同状态 = 草稿 | +| 进行中 | 合同状态 = 合同进行中 | +| 审批中 | 合同状态 = 已提交审批 | +| 已终止 | 合同状态 = 已终止 | 卡片右上角 `?` 悬停展示口径说明。 +> **说明**:KPI 按合同状态快速分桶;交还车履约口径(已交未还、临期等)见 §7.2 子表与 §6.6 全局规则,不在 KPI 卡片中重复统计。 + --- ## 6. 列表工具栏 @@ -81,24 +90,43 @@ | 列 | 说明 | |---|---| -| 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 | -| 已交车 | 已有实际交车日期的车辆数;点击弹出已交车列表 | +| 租赁订单 · 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 | +| 租赁订单 · 已交车 | **审批通过**且已有实际交车日期的车辆数;点击展开已交车子表 | -车辆明细字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。 +**履约规则**:非审批通过的合同不视为已交车;「已交车辆数」为 0 时按钮禁用。 + +### 7.2.1 已交车子表 + +展开「已交车辆数」后展示车辆子表,列包括: + +| 列 | 说明 | +|---|---| +| 车辆信息 | 车牌、VIN、品牌型号 | +| 提车应收款 | 支付状态,可跳转详情 | +| 交车 | 已交车展示里程/交车人/时间;未交车显示「未交车」 | +| 租赁账单 | 正常 / 欠费(仅已交车) | +| 还车 | 已还车展示还车信息;未还车显示「未还车」 | +| 还车应结款 | 还车后展示结款状态;**审批中**时展示当前审批人(UI 与主表「审批状态」列一致,含节点悬停) | +| 交车安排 | 区域与计划/实际交车日期 | +| 里程要求 / 里程完成 | 有最低里程要求时展示 | +| 操作 | **审批通过 + 已交车 + 未还车** 显示「还车」链接 | + +车辆明细 Popover 字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。 ### 7.3 状态与业务字段 | 列 | 说明 | |---|---| -| 审批类型 | 标准合同 / 非标准合同;位于审批状态左侧 | -| 审批状态 | 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交;「审批中」悬停展示审批流节点 | -| 合同状态 | 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 | +| 合同签署方式 | 线上电子签章 / 线下人工上传;可展开查看或变更 | +| 审批状态 | 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回等;「审批中」悬停展示审批流节点与当前处理人 | | 签约公司 | 创建时所选甲方签约主体简称 | -| 业务部门 / 业务负责人 | 创建时选取 | -| 合同类型 | 正式合同 / 试用合同 | -| 客户联系人 / 联系电话 | 来自客户档案 | -| 被授权人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 | -| 创建人 / 创建时间 | 精确至分钟 | +| 业务部门 | 创建时选取 | +| 费用信息 | 付款方式、周期、保证金等摘要 | +| 交车安排 | 合同级交车区域与计划日期 | +| 整体里程完成情况 | 按在租车辆里程汇总展示完成进度 | +| 客户联系人 | 来自客户档案 | +| 受托人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 | +| 创建信息 | 创建人 / 创建时间,精确至分钟 | | 最后更新 | 更新人 / 最后更新时间,无则显示 `-` | **分页**:每页 10 / 20 / 50 条可选。 @@ -120,7 +148,7 @@ | 添加被授权人 | 合同进行中 | 弹窗编辑被授权人列表 | | 附加费用 | 合同进行中 | 弹窗按车辆录入附加费用 | | 转三方合同 | 合同进行中 | 弹窗新增三方协议,上传协议附件与对方公函 | -| 试用转正式 | 试用合同 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 | +| 试用转正式 | 合同模板为「试用合同」 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 | | 终止合同 | 合同进行中 | 二次确认,提交终止审批 | | 上传盖章合同 | 审批通过且法务未上传 | 仅法务部可见;多文件上传后关闭入口 | @@ -133,6 +161,8 @@ | 审批中 | 已有节点通过,未完成终审 | | 审批通过 | 终审通过 | | 审批驳回 | 任一节点的驳回;可编辑重提 | +| 审批终止 | 流程被终止 | +| 撤回 | 提交人主动撤回 | ### 8.3 合同状态口径 @@ -140,10 +170,8 @@ |---|---| | 草稿 | 未提交审批 | | 已提交审批 | 初次提交,终审未完成 | -| 变更 | 已通过基础上变更并重新审批中 | | 合同进行中 | 终审通过且在有效期内 | -| 到期合同 | 已过结束日期 | -| 已结束 | 终止或自然结束 | +| 已终止 | 终止、自然结束,或续签/转三方后旧合同自动终止 | ### 8.4 弹窗摘要 @@ -155,3 +183,7 @@ - **转三方合同**:支持新增多条三方协议;每条可填协议名称、上传协议附件与对方公函 - **试用转正式**:自动带出客户信息、授权人与车辆订单;车辆订单字段可编辑;可勾选「同时转三方合同」并在同一步维护三方协议 - **上传盖章合同**:拖拽多文件;确认后标记已上传 + +### 8.5 操作流程文档 + +从「新增合同」起的正向与逆向全流程图见:`.spec/requirements-flow-operations.md` diff --git a/src/prototypes/lease-contract-management/LeaseContractCreate.jsx b/src/prototypes/lease-contract-management/LeaseContractCreate.jsx index 0e95e71..208ddc5 100644 --- a/src/prototypes/lease-contract-management/LeaseContractCreate.jsx +++ b/src/prototypes/lease-contract-management/LeaseContractCreate.jsx @@ -15,12 +15,24 @@ import { import { getPublishedContractTemplateOptions, subscribePublishedContractTemplateOptions, - getContractTemplatePreviewBadge, getContractTypeLabel, } from '../contract-template-management/contract-template-catalog.js'; import { buildLeaseContractEditFormState } from './lease-contract-edit-bridge.js'; -import { createDefaultLeaseOrderState, createDefaultPowerOfAttorneyState, hasLeaseOrderRentBelowMinimum } from './lease-order-vars.js'; -import { buildLeaseContractPreviewHtml } from './lease-contract-preview-build.js'; +import { + FLOW_MODE_CREATE, + FLOW_MODE_EDIT, + FLOW_MODE_ADD_VEHICLE, + FLOW_MODE_ADD_POA, + FLOW_MODE_TRIPARTITE, + getFlowPageTitle, + countNewPickupVehicles, +} from './lease-contract-flow-bridge.js'; +import { createDefaultLeaseOrderState, createDefaultPowerOfAttorneyState, normalizeLeaseOrderState } from './lease-order-vars.js'; +import { + buildLeaseContractPreviewHtml, + extractLeaseOrderAttachment1PreviewHtml, + extractPowerOfAttorneyPreviewHtml, +} from './lease-contract-preview-build.js'; import { resolveLeaseContractApprovalType, STANDARD_CONTRACT_APPROVAL, @@ -34,13 +46,27 @@ import { isPowerOfAttorneyOptionalEmpty, isSealTypeSelected, isLeaseContractFormComplete, + isTripartitePartyComplete, } from './lease-contract-form-validation.js'; -import LeaseContractPreviewPanel, { PreviewEditableHint } from './LeaseContractPreviewPanel.jsx'; +import LeaseContractPreviewPanel, { + PreviewEditableHint, + PreviewApprovalHint, + PreviewAttachment1ReadonlyHint, + PreviewPowerOfAttorneyReadonlyHint, +} from './LeaseContractPreviewPanel.jsx'; import LeaseContractEditorForm, { LeaseContractLeaseOrderSection, LeaseContractPowerOfAttorneySection, LeaseContractRemarkSection, } from './LeaseContractEditorForm.jsx'; +import { + CONTRACT_SIGNING_METHOD_ONLINE, + CONTRACT_SIGNING_METHOD_OPTIONS, +} from './lease-contract-signing.js'; +import { + createPickupReceivableFromLeaseSubmit, + formatLeaseContractCode, +} from '../vehicle-pickup-receivable/pickup-receivable-bridge.js'; var SEAL_TYPE_OPTIONS = [ { value: 'contract', label: '合同章' }, @@ -56,7 +82,49 @@ function normalizeSealTypes(types) { return next.length ? next : ['contract']; } -export default function LeaseContractCreate({ onBack, initialScrollSection, editRecord }) { +function renderCreateStepNumberIcon(step, options) { + var opts = options || {}; + var size = opts.size || 22; + var className = 'lc-create-step-icon' + (opts.className ? ' ' + opts.className : ''); + var hideLabel = opts.hideLabel === true; + return React.createElement('svg', { + className: className, + width: size, + height: size, + viewBox: '0 0 24 24', + focusable: 'false', + 'aria-hidden': hideLabel ? true : undefined, + role: hideLabel ? undefined : 'img', + 'aria-label': hideLabel ? undefined : ('步骤 ' + step), + }, + React.createElement('circle', { + cx: 12, + cy: 12, + r: 9.25, + className: 'lc-create-step-icon__ring', + }), + React.createElement('text', { + x: 12, + y: 12, + className: 'lc-create-step-icon__digit', + }, String(step)), + ); +} + +function renderCreateStepTitle(step, title) { + return React.createElement('div', { className: 'ct-panel-head__title lc-create-step-title' }, + renderCreateStepNumberIcon(step, { className: 'lc-create-step-title__badge', hideLabel: true }), + React.createElement('span', { className: 'lc-create-step-title__text' }, title), + ); +} + +export default function LeaseContractCreate({ onBack, initialScrollSection, editRecord, flowMode, flowInitialFormState }) { + var effectiveFlowMode = flowMode || (editRecord && editRecord.id ? FLOW_MODE_EDIT : FLOW_MODE_CREATE); + var isAddVehicleFlow = effectiveFlowMode === FLOW_MODE_ADD_VEHICLE; + var isAddPoaFlow = effectiveFlowMode === FLOW_MODE_ADD_POA; + var isTripartiteFlow = effectiveFlowMode === FLOW_MODE_TRIPARTITE; + var isScopedPreviewFlow = isAddVehicleFlow || isAddPoaFlow; + var pageTitle = getFlowPageTitle(effectiveFlowMode); var contractTypeState = useState(''); var contractType = contractTypeState[0]; var setContractType = contractTypeState[1]; @@ -72,11 +140,9 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit 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, + label: option.contractTypeLabel || option.title || option.kind || option.id, contractType: option.contractType, }; }); @@ -111,22 +177,11 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit var mileageState = useState(Object.assign({}, DEFAULT_MILEAGE_STANDARD)); var feeInfoState = useState(Object.assign({}, DEFAULT_FEE_INFO)); var contractCodeState = useState(function () { return generateAutoContractCodeSuffix(); }); + var projectNameState = useState(''); var businessDeptState = useState(''); var businessOwnerState = useState(''); var leaseOrderState = useState(function () { - var initial = createDefaultLeaseOrderState(); - return { - insuredVehicleCount: initial.insuredVehicleCount, - thirdPartyLiabilityMillion: initial.thirdPartyLiabilityMillion, - deliveryRegion: initial.deliveryRegion.slice(), - deliveryDate: initial.deliveryDate, - rows: initial.rows.map(function (row) { - return Object.assign({}, row, { - brandModels: row.brandModels ? row.brandModels.slice() : [], - plateNos: row.plateNos ? row.plateNos.slice() : undefined, - }); - }), - }; + return normalizeLeaseOrderState(createDefaultLeaseOrderState()); }); var lessorId = lessorIdState[0]; var setLessorId = lessorIdState[1]; @@ -138,6 +193,8 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit var setFeeInfo = feeInfoState[1]; var contractCode = contractCodeState[0]; var setContractCode = contractCodeState[1]; + var projectName = projectNameState[0]; + var setProjectName = projectNameState[1]; var businessDept = businessDeptState[0]; var setBusinessDept = businessDeptState[1]; var businessOwner = businessOwnerState[0]; @@ -163,16 +220,31 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit var customerPrincipalPhoneState = useState(''); var customerPrincipalPhone = customerPrincipalPhoneState[0]; var setCustomerPrincipalPhone = customerPrincipalPhoneState[1]; + var thirdPartyCustomerIdState = useState(''); + var thirdPartyCustomerId = thirdPartyCustomerIdState[0]; + var setThirdPartyCustomerId = thirdPartyCustomerIdState[1]; + var thirdPartyPrincipalNameState = useState(''); + var thirdPartyPrincipalName = thirdPartyPrincipalNameState[0]; + var setThirdPartyPrincipalName = thirdPartyPrincipalNameState[1]; + var thirdPartyPrincipalPhoneState = useState(''); + var thirdPartyPrincipalPhone = thirdPartyPrincipalPhoneState[0]; + var setThirdPartyPrincipalPhone = thirdPartyPrincipalPhoneState[1]; + var contractSigningMethodState = useState(CONTRACT_SIGNING_METHOD_ONLINE); + var contractSigningMethod = contractSigningMethodState[0]; + var setContractSigningMethod = contractSigningMethodState[1]; var sealTypesState = useState(['contract']); var sealTypes = sealTypesState[0]; var setSealTypes = sealTypesState[1]; var contractApprovalTypeState = useState(STANDARD_CONTRACT_APPROVAL); var contractApprovalType = contractApprovalTypeState[0]; var setContractApprovalType = contractApprovalTypeState[1]; + var previewMergedHtmlState = useState(''); + var previewMergedHtml = previewMergedHtmlState[0]; + var setPreviewMergedHtml = previewMergedHtmlState[1]; var previewHtml = useMemo(function () { if (!contractTemplateId) return ''; - return buildLeaseContractPreviewHtml({ + var fullHtml = buildLeaseContractPreviewHtml({ contractTemplateId: contractTemplateId, lessorId: lessorId, customerId: customerId, @@ -182,28 +254,40 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit leaseOrder: leaseOrder, powerOfAttorney: powerOfAttorney, }); - }, [contractTemplateId, lessorId, customerId, mileage, feeInfo, contractCode, leaseOrder, powerOfAttorney]); + if (isAddVehicleFlow) return extractLeaseOrderAttachment1PreviewHtml(fullHtml); + if (isAddPoaFlow) return extractPowerOfAttorneyPreviewHtml(fullHtml); + return fullHtml; + }, [contractTemplateId, lessorId, customerId, mileage, feeInfo, contractCode, leaseOrder, powerOfAttorney, isAddVehicleFlow, isAddPoaFlow]); + + var isPreviewEditable = !isScopedPreviewFlow; var handlePreviewContentChange = useCallback(function (mergedHtml) { + if (!isPreviewEditable) return; + setPreviewMergedHtml(mergedHtml || ''); setContractApprovalType(resolveLeaseContractApprovalType(previewHtml, mergedHtml, { leaseOrder: leaseOrder, + feeInfo: feeInfo, + sealTypes: sealTypes, })); - }, [previewHtml, leaseOrder]); + }, [previewHtml, leaseOrder, feeInfo, sealTypes, isPreviewEditable]); useEffect(function () { - if (hasLeaseOrderRentBelowMinimum(leaseOrder)) { - setContractApprovalType(NONSTANDARD_CONTRACT_APPROVAL); - return; - } - setContractApprovalType(STANDARD_CONTRACT_APPROVAL); - }, [previewHtml, leaseOrder]); + setContractApprovalType(resolveLeaseContractApprovalType( + previewHtml, + previewMergedHtml || previewHtml, + { + leaseOrder: leaseOrder, + feeInfo: feeInfo, + sealTypes: sealTypes, + }, + )); + }, [previewHtml, previewMergedHtml, leaseOrder, feeInfo, sealTypes]); var isNonStandardApproval = contractApprovalType === NONSTANDARD_CONTRACT_APPROVAL; var isTemplateSelected = Boolean(contractTemplateId); - var previewBadge = getContractTemplatePreviewBadge(contractTemplateId); var isMainContractComplete = useMemo(function () { - return isMainContractFormComplete({ + var baseComplete = isMainContractFormComplete({ lessorId: lessorId, customerId: customerId, contractCode: contractCode, @@ -212,7 +296,15 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit mileage: mileage, feeInfo: feeInfo, }); - }, [lessorId, customerId, contractCode, businessDept, businessOwner, mileage, feeInfo]); + if (!baseComplete) return false; + if (isTripartiteFlow) { + return isTripartitePartyComplete({ + customerId: customerId, + thirdPartyCustomerId: thirdPartyCustomerId, + }); + } + return true; + }, [lessorId, customerId, contractCode, businessDept, businessOwner, mileage, feeInfo, isTripartiteFlow, thirdPartyCustomerId]); var isLeaseOrderComplete = useMemo(function () { return isLeaseOrderFormComplete({ leaseOrder: leaseOrder }); @@ -258,11 +350,6 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit }); } - 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), @@ -284,38 +371,46 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit useEffect(function () { if (!editRecord || !editRecord.id) return; var formState = buildLeaseContractEditFormState(editRecord); + if (!formState) return; + applyBridgeFormState(formState); + }, [editRecord && editRecord.id]); + + useEffect(function () { + if (!flowInitialFormState) return; + applyBridgeFormState(flowInitialFormState); + }, [flowInitialFormState && flowInitialFormState.contractCode]); + + function applyBridgeFormState(formState) { if (!formState) return; setContractTemplateId(formState.contractTemplateId || ''); setLessorId(formState.lessorId || ''); setCustomerId(formState.customerId || ''); setContractCode(formState.contractCode || ''); + setProjectName(formState.projectName || ''); setBusinessDept(formState.businessDept || ''); setBusinessOwner(formState.businessOwner || ''); setMileage(Object.assign({}, formState.mileage)); setFeeInfo(Object.assign({}, formState.feeInfo)); - setLeaseOrder({ - insuredVehicleCount: formState.leaseOrder.insuredVehicleCount, - thirdPartyLiabilityMillion: formState.leaseOrder.thirdPartyLiabilityMillion, - deliveryRegion: (formState.leaseOrder.deliveryRegion || []).slice(), - deliveryDate: formState.leaseOrder.deliveryDate, - rows: (formState.leaseOrder.rows || []).map(function (row) { - return Object.assign({}, row, { - brandModels: row.brandModels ? row.brandModels.map(function (pair) { return pair.slice(); }) : [], - plateNos: row.plateNos ? row.plateNos.slice() : undefined, - extraServices: row.extraServices ? row.extraServices.slice() : [], - }); - }), - }); + setLeaseOrder(normalizeLeaseOrderState(formState.leaseOrder)); setPowerOfAttorney({ - delegates: (formState.powerOfAttorney.delegates || []).map(function (row) { + delegates: ((formState.powerOfAttorney && formState.powerOfAttorney.delegates) || []).map(function (row) { return Object.assign({}, row); }), }); setContractRemark(formState.contractRemark || ''); setCustomerPrincipalName(formState.customerPrincipalName || ''); setCustomerPrincipalPhone(formState.customerPrincipalPhone || ''); + setThirdPartyCustomerId(formState.thirdPartyCustomerId || ''); + setThirdPartyPrincipalName(formState.thirdPartyPrincipalName || ''); + setThirdPartyPrincipalPhone(formState.thirdPartyPrincipalPhone || ''); setSealTypes(normalizeSealTypes(formState.sealTypes)); - }, [editRecord && editRecord.id]); + if (formState.contractType) { + setContractType(formState.contractType); + } else if (formState.contractTemplateId) { + var tplMatch = templateOptions.find(function (item) { return item.id === formState.contractTemplateId; }); + if (tplMatch) setContractType(tplMatch.contractType || ''); + } + } var createSteps = useMemo(function () { return [ @@ -372,20 +467,16 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit contractRemark, ]); - 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' }); + var container = columnRef.current; + if (!target || !container) return; + var containerTop = container.getBoundingClientRect().top; + var targetTop = target.getBoundingClientRect().top; + container.scrollTo({ + top: container.scrollTop + (targetTop - containerTop), + behavior: 'smooth', + }); } function handleStepNavClick(step) { @@ -404,15 +495,72 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit role: 'region', 'aria-label': '需先选择合同模板', }, - React.createElement('p', { className: 'lc-create-section__lock-text' }, '请先完成第 1 步选择合同模板,再填写本节内容。'), + React.createElement('p', { className: 'lc-create-section__lock-text' }, + '请先完成', + renderCreateStepNumberIcon(1, { className: 'lc-create-section__lock-step-icon', size: 18, hideLabel: true }), + '选择合同模板,再填写本节内容。', + ), React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-secondary lc-create-section__lock-btn', + 'data-vm-icon': 'eye', onClick: function () { scrollToCreateSection('template'); }, }, '前往选择模板'), ); } + function isSectionFlowFormLocked(stepKey) { + return isAddPoaFlow && stepKey !== 'poa'; + } + + function renderFlowFormLock(stepKey) { + if (!isSectionFlowFormLocked(stepKey)) return null; + return React.createElement('div', { + className: 'lc-create-section__lock lc-create-section__lock--flow', + role: 'region', + 'aria-label': '本流程仅可编辑授权委托书', + }, + React.createElement('p', { className: 'lc-create-section__lock-text' }, + '本流程仅可编辑受托人信息,其余内容沿用来源合同,无需修改。', + ), + ); + } + + function getCreateSectionDisabledClass(stepKey) { + if (!isTemplateSelected && stepKey !== 'template') return ' lc-create-section--disabled'; + if (isSectionFlowFormLocked(stepKey)) return ' lc-create-section--disabled'; + return ''; + } + + function getPreviewPanelTitle() { + if (isAddVehicleFlow) return '附件1预览'; + if (isAddPoaFlow) return '授权委托书预览'; + return '实时预览'; + } + + function getPreviewPanelAriaLabel() { + return getPreviewPanelTitle(); + } + + function renderPreviewReadonlyHint() { + if (isAddVehicleFlow) return React.createElement(PreviewAttachment1ReadonlyHint); + if (isAddPoaFlow) return React.createElement(PreviewPowerOfAttorneyReadonlyHint); + return React.createElement(PreviewEditableHint); + } + + function getPreviewHintId() { + if (isAddVehicleFlow) return 'lc-preview-attachment1-hint'; + if (isAddPoaFlow) return 'lc-preview-poa-hint'; + return 'lc-preview-editable-hint'; + } + + function getPreviewPanelCardClass() { + var cls = 'ct-editor-column-card ct-editor-column-card--preview'; + if (isAddVehicleFlow) cls += ' ct-editor-column-card--preview-attachment1'; + if (isAddPoaFlow) cls += ' ct-editor-column-card--preview-poa'; + return cls; + } + function renderStepNav() { return React.createElement('nav', { className: 'lc-create-step-nav', @@ -435,12 +583,12 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit 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), + : renderCreateStepNumberIcon(index + 1, { className: 'lc-create-step-title__badge', hideLabel: true }), ), React.createElement('span', { className: 'lc-create-step-nav__label' }, step.label), - step.optional - ? React.createElement('span', { className: 'lc-create-step-nav__tag' }, '选填') - : null, + React.createElement('span', { + className: 'lc-create-step-nav__tag' + (step.optional ? '' : ' lc-create-step-nav__tag--required'), + }, step.optional ? '选填' : '必填'), ), index < createSteps.length - 1 ? React.createElement('span', { className: 'lc-create-step-nav__sep', 'aria-hidden': true }) @@ -453,34 +601,45 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit return React.createElement('div', { className: 'vm-page lc-page lc-create-page ct-page' }, React.createElement('div', { className: 'lc-create-topbar' }, - React.createElement('button', { - type: 'button', - className: 'vm-btn vm-btn-back', - 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('button', { + type: 'button', + className: 'vm-btn vm-btn-back', + onClick: onBack, + }, '返回列表'), + effectiveFlowMode !== FLOW_MODE_CREATE + ? React.createElement('span', { className: 'lc-create-topbar__title' }, pageTitle) + : null, ), + flowInitialFormState && (flowInitialFormState._pickupReceivableHint || flowInitialFormState._signingHint || flowInitialFormState._sourceContractCode) + ? React.createElement('div', { + className: 'lc-flow-banner', + role: 'note', + 'data-annotation-id': 'lc-create-flow-hint', + }, + flowInitialFormState._sourceContractCode + ? React.createElement('p', { className: 'lc-flow-banner__line' }, + '来源合同:', + React.createElement('strong', null, flowInitialFormState._sourceContractCode), + ) + : null, + flowInitialFormState._pickupReceivableHint + ? React.createElement('p', { className: 'lc-flow-banner__line' }, flowInitialFormState._pickupReceivableHint) + : null, + flowInitialFormState._signingHint + ? React.createElement('p', { className: 'lc-flow-banner__line' }, flowInitialFormState._signingHint) + : null, + ) + : null, React.createElement('div', { className: 'ct-editor-page lc-create-editor-page' }, React.createElement('div', { className: 'ct-editor-workspace' }, React.createElement(Row, { gutter: [16, 16] }, React.createElement(Col, { xs: 24, lg: 12 }, - React.createElement('div', { className: 'lc-create-editor-column', ref: columnRef }, + React.createElement('div', { className: 'lc-create-editor-column-shell' }, renderStepNav(), + React.createElement('div', { className: 'lc-create-editor-column', ref: columnRef }, React.createElement('section', { - className: 'ct-editor-column-card lc-template-step-card lc-create-section', + className: 'ct-editor-column-card lc-template-step-card lc-create-section' + + getCreateSectionDisabledClass('template'), 'aria-label': '选择合同类型', 'data-annotation-id': 'lc-create-template', id: 'lc-create-section-template', @@ -491,7 +650,7 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit '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 步:选择合同类型'), + renderCreateStepTitle(1, '选择合同类型'), React.createElement('span', { className: 'lc-form-card__hint', role: 'note', @@ -503,7 +662,7 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit 'aria-hidden': true, }), React.createElement('span', { className: 'lc-form-card__hint-text' }, - '请选择模板管理中已发布且启用的合同模板,右侧将自动生成预览。', + '选择对应合同模板后,右侧自动生成该合同预览内容', ), ), ), @@ -514,33 +673,56 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit }, 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': '合同模板', - }), + React.createElement('div', { className: 'lc-template-step-fields', 'data-annotation-id': 'lc-create-template-fields' }, + React.createElement('div', { className: 'lc-form-field' }, + React.createElement('label', { className: 'lc-form-field__label' }, '合同模板'), + React.createElement('div', { className: 'lc-form-field__control' }, + React.createElement(Select, { + className: 'lc-form-select lc-template-select', + value: contractTemplateId || undefined, + placeholder: templateSelectOptions.length ? '请选择合同模板' : '暂无可用模板', + allowClear: !isAddPoaFlow, + showSearch: true, + optionFilterProp: 'label', + disabled: !templateSelectOptions.length || isAddPoaFlow, + onChange: handleTemplateSelect, + options: templateSelectOptions, + 'aria-label': '合同模板', + }), + ), + ), + React.createElement('div', { + className: 'lc-form-field', + 'data-annotation-id': 'lc-create-signing-method', + }, + React.createElement('label', { className: 'lc-form-field__label' }, '合同签署方式'), + React.createElement('div', { className: 'lc-form-field__control' }, + React.createElement(Select, { + className: 'lc-form-select lc-template-signing-row__select', + value: contractSigningMethod, + options: CONTRACT_SIGNING_METHOD_OPTIONS, + onChange: function (value) { setContractSigningMethod(value || CONTRACT_SIGNING_METHOD_ONLINE); }, + 'aria-label': '合同签署方式', + }), + ), + ), + ), !templateSelectOptions.length ? React.createElement('p', { className: 'lc-template-step-empty' }, '暂无已启用的合同模板,请先在合同模板管理中发布并启用。') : null, ), + renderFlowFormLock('template'), ), React.createElement('section', { className: 'ct-editor-column-card lc-main-contract-card lc-create-section' - + (isTemplateSelected ? '' : ' lc-create-section--disabled'), + + getCreateSectionDisabledClass('main'), '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__title' }, '第 2 步:主体合同信息'), + renderCreateStepTitle(2, '主体合同信息'), React.createElement('span', { className: 'lc-form-completion-badge ' + (isMainContractComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'), role: 'status', @@ -551,15 +733,25 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit React.createElement(LeaseContractEditorForm, { lessorId: lessorId, customerId: customerId, + thirdPartyCustomerId: thirdPartyCustomerId, + showThirdPartyParty: isTripartiteFlow, contractCode: contractCode, - contractCodeReadonly: !(editRecord && editRecord.id), + projectName: projectName, + contractCodeReadonly: effectiveFlowMode !== FLOW_MODE_EDIT, businessDept: businessDept, businessOwner: businessOwner, mileage: mileage, feeInfo: feeInfo, onLessorChange: setLessorId, - onCustomerChange: setCustomerId, + onCustomerChange: function (value) { + setCustomerId(value); + if (thirdPartyCustomerId && thirdPartyCustomerId === value) { + setThirdPartyCustomerId(''); + } + }, + onThirdPartyCustomerChange: setThirdPartyCustomerId, onContractCodeChange: setContractCode, + onProjectNameChange: setProjectName, onBusinessAssignmentChange: function (dept, owner) { setBusinessDept(dept); setBusinessOwner(owner); @@ -572,20 +764,27 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit setCustomerPrincipalName(name); setCustomerPrincipalPhone(phone); }, + thirdPartyPrincipalName: thirdPartyPrincipalName, + thirdPartyPrincipalPhone: thirdPartyPrincipalPhone, + onThirdPartyPrincipalChange: function (name, phone) { + setThirdPartyPrincipalName(name); + setThirdPartyPrincipalPhone(phone); + }, }), ), renderSectionLock('main'), + renderFlowFormLock('main'), ), React.createElement('section', { className: 'ct-editor-column-card lc-create-section' - + (isTemplateSelected ? '' : ' lc-create-section--disabled'), + + getCreateSectionDisabledClass('leaseOrder'), '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__title' }, '第 3 步:附件1 · 租赁订单'), + renderCreateStepTitle(3, '附件1 · 租赁订单'), React.createElement('span', { className: 'lc-form-completion-badge ' + (isLeaseOrderComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'), role: 'status', @@ -599,10 +798,11 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit }), ), renderSectionLock('leaseOrder'), + renderFlowFormLock('leaseOrder'), ), React.createElement('section', { className: 'ct-editor-column-card lc-create-section' - + (isTemplateSelected ? '' : ' lc-create-section--disabled'), + + getCreateSectionDisabledClass('poa'), 'aria-label': '授权委托书', 'data-annotation-id': 'lc-create-poa', id: 'lc-create-section-poa', @@ -613,7 +813,7 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit '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 步:授权委托书'), + renderCreateStepTitle(4, '授权委托书'), React.createElement('span', { className: 'lc-form-card__hint lc-create-poa-hint', role: 'note', @@ -626,7 +826,7 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit 'aria-hidden': true, }), React.createElement('span', { className: 'lc-form-card__hint-text' }, - '添加受托人须同时上传授权委托书', + '添加受托人须同时上传授权委托书,受托人主要用于交还车时E签宝签章', ), ), ), @@ -651,14 +851,14 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit ), React.createElement('section', { className: 'ct-editor-column-card lc-create-section' - + (isTemplateSelected ? '' : ' lc-create-section--disabled'), + + getCreateSectionDisabledClass('remark'), 'aria-label': '合同备注', 'data-annotation-id': 'lc-create-contract-remark', id: 'lc-create-section-remark', ref: sectionRefs.remark, }, React.createElement('div', { className: 'ct-panel-head' }, - React.createElement('div', { className: 'ct-panel-head__title' }, '第 5 步:合同备注'), + renderCreateStepTitle(5, '合同备注'), React.createElement('span', { className: 'lc-form-completion-badge ' + ((contractRemark || '').trim() @@ -675,10 +875,11 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit }), ), renderSectionLock('remark'), + renderFlowFormLock('remark'), ), React.createElement('section', { className: 'ct-editor-column-card lc-create-section' - + (isTemplateSelected ? '' : ' lc-create-section--disabled'), + + getCreateSectionDisabledClass('seal'), 'aria-label': '用章类型', 'data-annotation-id': 'lc-create-seal', id: 'lc-create-section-seal', @@ -686,22 +887,25 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit }, React.createElement('div', { className: 'ct-panel-head lc-create-section__head lc-create-section-seal-head', + 'aria-describedby': 'lc-create-seal-hint', }, React.createElement('div', { className: 'lc-template-step-card__title-row' }, - React.createElement('div', { className: 'ct-panel-head__title' }, '第 6 步:用章类型'), - 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, + renderCreateStepTitle(6, '用章类型'), + React.createElement('span', { + className: 'lc-form-card__hint lc-create-seal-hint', + role: 'note', + id: 'lc-create-seal-hint', + 'data-annotation-id': 'lc-create-seal-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 ' + (isSealTypeComplete ? 'lc-form-completion-badge--done' : 'lc-form-completion-badge--pending'), @@ -729,24 +933,29 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit ), ), renderSectionLock('seal'), + renderFlowFormLock('seal'), + ), ), ), ), React.createElement(Col, { xs: 24, lg: 12 }, React.createElement('section', { - className: 'ct-editor-column-card ct-editor-column-card--preview', - 'aria-label': '实时预览', + className: getPreviewPanelCardClass(), + 'aria-label': getPreviewPanelAriaLabel(), 'data-annotation-id': 'lc-create-preview', }, React.createElement('div', { className: 'ct-panel-head lc-create-preview-head', - 'aria-describedby': isTemplateSelected ? 'lc-preview-editable-hint' : undefined, + 'aria-describedby': isTemplateSelected ? getPreviewHintId() : 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__title' }, + getPreviewPanelTitle(), + ), + isTemplateSelected ? renderPreviewReadonlyHint() : null, ), React.createElement('div', { className: 'ct-panel-head__actions' }, + isScopedPreviewFlow ? null : React.createElement(PreviewApprovalHint), React.createElement('span', { className: 'lc-approval-type-badge' + (isNonStandardApproval ? ' lc-approval-type-badge--nonstd' : ' lc-approval-type-badge--standard'), @@ -761,8 +970,8 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit html: previewHtml, zoom: workspaceZoom, onZoomChange: setWorkspaceZoom, - editable: true, - onContentChange: handlePreviewContentChange, + editable: isPreviewEditable, + onContentChange: isPreviewEditable ? handlePreviewContentChange : undefined, }) : React.createElement('div', { className: 'lc-template-preview-empty' }, '请先在左侧选择合同模板,右侧将自动生成对应预览。'), ), @@ -775,11 +984,13 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-ghost', + 'data-vm-icon': 'x', onClick: onBack, }, '取消'), React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-secondary', + 'data-vm-icon': 'save', onClick: function () { if (!contractTemplateId) { message.warning('请先选择合同模板'); @@ -791,12 +1002,32 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-primary', + 'data-vm-icon': 'send', onClick: function () { if (!contractTemplateId) { message.warning('请先选择合同模板'); return; } - if (!isFormSubmitReady) { + if (isAddPoaFlow) { + if (!isPoaSubmitReady) { + message.warning('提交审核前须完成授权委托书填写'); + scrollToCreateSection('poa'); + return; + } + } else if (isTripartiteFlow) { + if (!isTripartitePartyComplete({ + customerId: customerId, + thirdPartyCustomerId: thirdPartyCustomerId, + })) { + message.warning('转三方合同须选择丙方客户,且不能与乙方相同'); + scrollToCreateSection('main'); + return; + } + if (!isFormSubmitReady) { + message.warning('请先完成左侧必填项'); + return; + } + } else if (!isFormSubmitReady) { if (!isPoaSubmitReady) { message.warning('提交审核前须完成授权委托书填写'); scrollToCreateSection('poa'); @@ -806,11 +1037,46 @@ export default function LeaseContractCreate({ onBack, initialScrollSection, edit return; } var submitCustomer = getLeaseCustomerById(customerId); - if (hasBlockingCustomerCredentials(submitCustomer)) { + if (!isAddPoaFlow && hasBlockingCustomerCredentials(submitCustomer)) { message.error(formatCredentialSubmitBlockMessage(summarizeCustomerCredentials(submitCustomer))); scrollToCreateSection('main'); return; } + var fullContractCode = formatLeaseContractCode(contractCode); + var newVehicleCount = countNewPickupVehicles(leaseOrder); + if (effectiveFlowMode === 'renew' && newVehicleCount > 0) { + message.info('续签:新增 ' + newVehicleCount + ' 台车辆将生成提车应收款,已交未还车辆不生成(原型)'); + } + if (effectiveFlowMode === 'trialToFormal') { + message.info('转正式:已交未还车辆完成后不再生成交车任务;金额变更将重新记录提车应收款(原型)'); + } + if (effectiveFlowMode === 'addVehicle') { + message.info('新增车辆:审批通过后按新增合同规则生成提车应收款(原型)'); + } + if (effectiveFlowMode === 'addPoa') { + message.info('添加授权委托书:审批通过后仅对授权委托书发起签署,主合同无需重签(原型)'); + message.success('提交审核成功(' + fullContractCode + ')'); + message.info('提交审核:' + getContractTypeLabel(contractType) + ' · ' + contractApprovalType + '(原型)'); + return; + } + createPickupReceivableFromLeaseSubmit({ + contractCode: fullContractCode, + contractType: getContractTypeLabel(contractType), + projectName: projectName + || ((submitCustomer && submitCustomer.companyName) + ? submitCustomer.companyName + '租赁项目' + : ''), + customerName: submitCustomer ? submitCustomer.companyName : '', + businessDept: businessDept, + businessPerson: businessOwner, + feeInfo: feeInfo, + leaseOrder: leaseOrder, + paymentMethod: feeInfo && feeInfo.paymentMethod === 'postpay' ? '后付' : '预付', + }); + if (effectiveFlowMode === 'tripartite') { + message.info('转三方:审批通过后沿用原合同签署方式,线上 E签宝 / 线下盖章补传(原型)'); + } + message.success('提交审核成功,已同步创建提车应收款(' + fullContractCode + ')'); message.info('提交审核:' + getContractTypeLabel(contractType) + ' · ' + contractApprovalType + '(原型)'); }, }, '提交审核'), diff --git a/src/prototypes/lease-contract-management/LeaseContractEditorForm.jsx b/src/prototypes/lease-contract-management/LeaseContractEditorForm.jsx index 0052c2d..e19c28d 100644 --- a/src/prototypes/lease-contract-management/LeaseContractEditorForm.jsx +++ b/src/prototypes/lease-contract-management/LeaseContractEditorForm.jsx @@ -1,7 +1,7 @@ import React from 'react'; -import { Select, InputNumber, Input, Image, Cascader, Button, DatePicker, Popover, Modal, message } from 'antd'; +import { Select, InputNumber, Input, Image, Button, DatePicker, Popover, Modal, message } from 'antd'; import dayjs from 'dayjs'; -import { ChevronDown, Plus, Trash2, Info, X, Search } from 'lucide-react'; +import { ChevronDown, Plus, Trash2, Info, X, Search, Check, MapPin, ChevronRight } from 'lucide-react'; import { getLessorCompanies, getLessorCompanyById, @@ -16,6 +16,7 @@ import { MILEAGE_PERIOD_OPTIONS, DEFAULT_FEE_INFO, PAYMENT_PERIOD_OPTIONS, + PAYMENT_METHOD_OPTIONS, HYDROGEN_PAYMENT_METHOD_OPTIONS, CONTRACT_CODE_PREFIX, LEASE_CUSTOMER_RISK_FILTER_OPTIONS, @@ -32,20 +33,32 @@ import { createEmptyDelegateRow, createDefaultPowerOfAttorneyState, PROVINCE_CITY_CASCADER_OPTIONS, + flattenProvinceCityOptions, + matchProvinceCityOption, + formatDeliveryRegionDisplay, LEASE_VEHICLE_BRAND_MODEL_CATALOG, LEASE_EXTRA_SERVICE_TREE, flattenExtraServiceOptions, matchExtraServiceOption, getExtraServiceCategoryByValue, + getExtraServiceByValue, + formatExtraServiceUnitPrice, + LEASE_SERVICE_FEE_TYPE, PLATE_ACTUAL_DELIVERY, + PLATE_MODE_ACTUAL, + PLATE_MODE_SPECIFIC, + PLATE_SPECIFIC_LABEL, POA_MAX_DELEGATES, normalizeBrandModels, normalizePlateNos, formatBrandModelsDisplay, formatBrandModelPair, parsePlateSearchText, + getPlateMode, + matchPlatesAgainstAssets, + validatePlatesForLeaseOrder, + syncRowPlateFields, calcInsuredVehicleCount, - isInsuredVehicleCountEditable, getRowVehicleCountForPricing, isDelegateRowStarted, isDelegateRowComplete, @@ -54,10 +67,19 @@ import { getReadyVehiclePlateOptions, calcLeaseOrderKpis, formatKpiAmount, - getExtraServiceNames, countNationalInStockByBrandModel, + getInStockBreakdownByBrandModel, getLeaseMinRentForBrandModel, isLeaseRentBelowMinimum, + sumFixedExtraServiceFees, + calcRowServiceFee, + formatServiceFeeAmount, + syncRowPricingFields, + normalizeLeaseOrderState, + DELIVERY_REGION_TBD_LABEL, + DELIVERY_REGION_TBD_DISPLAY, + LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL, + LEASE_DELIVERY_DATE_UNCONFIRMED_DISPLAY, } from './lease-order-vars.js'; import { LEASE_BUSINESS_ORG_TREE, @@ -67,7 +89,6 @@ import { getBusinessOwnersByDept, } from './lease-business-org-data.js'; import { getMatchedPrototypeClauses } from './lease-contract-vehicle-clause.js'; - function FieldHint(props) { if (!props.children) return null; return React.createElement('p', { @@ -133,6 +154,11 @@ function BrandModelsClauseHint(props) { function LeaseCustomerPickerField(props) { var value = props.value; var onChange = props.onChange; + var partyLabel = props.partyLabel || '乙方'; + var modalTitle = props.modalTitle || ('选择' + partyLabel + '客户'); + var triggerAriaLabel = props.triggerAriaLabel || (partyLabel + '客户'); + var clearAriaLabel = props.clearAriaLabel || ('清除' + partyLabel + '客户'); + var excludeCustomerId = props.excludeCustomerId || ''; var selectedCustomer = getLeaseCustomerById(value); var modalOpenState = React.useState(false); var pendingIdState = React.useState(''); @@ -164,6 +190,10 @@ function LeaseCustomerPickerField(props) { message.warning('请选择风控等级符合要求的客户'); return; } + if (excludeCustomerId && picked.id === excludeCustomerId) { + message.warning(partyLabel + '不能与乙方为同一客户'); + return; + } onChange(picked.id); setModalOpen(false); } @@ -182,6 +212,7 @@ function LeaseCustomerPickerField(props) { } var filteredCustomers = MOCK_LEASE_CUSTOMERS.filter(function (customer) { + if (excludeCustomerId && customer.id === excludeCustomerId) return false; if (riskFilter && customer.riskLevel !== riskFilter) return false; if (keyword && customer.name.indexOf(keyword.trim()) < 0) return false; return true; @@ -202,7 +233,7 @@ function LeaseCustomerPickerField(props) { openModal(); } }, - 'aria-label': '乙方客户', + 'aria-label': triggerAriaLabel, }, React.createElement('span', { className: selectedCustomer ? 'lc-customer-picker__value' : 'lc-customer-picker__placeholder', @@ -212,13 +243,13 @@ function LeaseCustomerPickerField(props) { type: 'button', className: 'lc-customer-picker__clear', onClick: handleClear, - 'aria-label': '清除乙方客户', + 'aria-label': clearAriaLabel, }, '×') : null, React.createElement(ChevronDown, { size: 16, 'aria-hidden': true }), ), ), React.createElement(Modal, { - title: '选择乙方客户', + title: modalTitle, open: modalOpen, onCancel: handleCancel, onOk: handleConfirm, @@ -300,9 +331,12 @@ function LeaseCustomerPickerField(props) { ); } -function LeaseOrderServiceContentField(props) { +export function LeaseOrderServiceContentField(props) { var selected = props.value || []; var onChange = props.onChange; + var lockedValues = props.lockedValues || []; + var variant = props.variant || 'form'; + var triggerLabel = props.triggerLabel || '选择服务项'; var openState = React.useState(false); var categoryState = React.useState(''); var queryState = React.useState(''); @@ -312,27 +346,36 @@ function LeaseOrderServiceContentField(props) { var setActiveCategory = categoryState[1]; var query = queryState[0]; var setQuery = queryState[1]; - var serviceNames = getExtraServiceNames(selected); var searchText = query.trim(); var isSearchMode = searchText.length > 0; var resolvedCategory = activeCategory || LEASE_EXTRA_SERVICE_TREE[0].category; + var activeRoot = LEASE_EXTRA_SERVICE_TREE.find(function (node) { + return node.category === resolvedCategory; + }) || LEASE_EXTRA_SERVICE_TREE[0]; var displayOptions = isSearchMode ? flattenExtraServiceOptions().filter(function (item) { return matchExtraServiceOption(item, searchText); }) - : (function () { - var group = LEASE_EXTRA_SERVICE_TREE.find(function (node) { - return node.category === resolvedCategory; - }); - if (!group) return []; - return group.items.map(function (item) { - return { - value: item.value, - name: item.name, - category: group.category, - }; - }); - })(); + : []; + var fixedSelectedTotal = sumFixedExtraServiceFees(selected); + var removableSelected = selected.filter(function (value) { + return lockedValues.indexOf(value) < 0; + }); + var lockedSelectedCount = selected.filter(function (value) { + return lockedValues.indexOf(value) >= 0; + }).length; + + function isLockedValue(value) { + return lockedValues.indexOf(value) >= 0; + } + + function mergeWithLockedValues(nextSelected) { + var merged = lockedValues.slice(); + (nextSelected || []).forEach(function (value) { + if (merged.indexOf(value) < 0) merged.push(value); + }); + return merged; + } function resolveInitialCategory() { if (selected.length > 0) { @@ -342,6 +385,15 @@ function LeaseOrderServiceContentField(props) { return LEASE_EXTRA_SERVICE_TREE[0].category; } + function resolveServiceOptionItem(rawItem) { + var catalogItem = getExtraServiceByValue(rawItem && rawItem.value); + if (catalogItem) return catalogItem; + return Object.assign({}, rawItem, { + feeType: activeRoot.feeType, + category: activeRoot.category, + }); + } + function handleOpenChange(nextOpen) { setOpen(nextOpen); if (nextOpen) { @@ -352,20 +404,14 @@ function LeaseOrderServiceContentField(props) { } function toggleValue(value) { + if (isLockedValue(value)) return; var category = getExtraServiceCategoryByValue(value); if (category) setActiveCategory(category); if (selected.indexOf(value) >= 0) { - onChange(selected.filter(function (item) { return item !== value; })); + onChange(mergeWithLockedValues(selected.filter(function (item) { return item !== value; }))); return; } - onChange(selected.concat([value])); - } - - function clearAll(event) { - event.preventDefault(); - event.stopPropagation(); - onChange([]); - setQuery(''); + onChange(mergeWithLockedValues(selected.concat([value]))); } function clearSearch(event) { @@ -374,21 +420,128 @@ function LeaseOrderServiceContentField(props) { setQuery(''); } + function clearAllSelected(event) { + event.preventDefault(); + event.stopPropagation(); + onChange(mergeWithLockedValues([])); + } + function handleCategoryChange(nextCategory) { setActiveCategory(nextCategory); setQuery(''); } + function renderServiceOption(rawItem) { + var item = resolveServiceOptionItem(rawItem); + var checked = selected.indexOf(item.value) >= 0; + var isLocked = isLockedValue(item.value); + var isFixed = item.feeType === LEASE_SERVICE_FEE_TYPE.FIXED; + return React.createElement('button', { + key: item.value, + type: 'button', + role: 'option', + 'aria-selected': checked, + 'aria-disabled': isLocked, + disabled: isLocked, + className: 'lc-lease-order-service-option' + + (checked ? ' is-selected' : '') + + (isLocked ? ' is-locked' : '') + + (isFixed ? ' is-fixed' : ' is-floating'), + onClick: function () { toggleValue(item.value); }, + }, + React.createElement('span', { + className: 'lc-lease-order-service-option__check', + 'aria-hidden': true, + }, isLocked + ? React.createElement('span', { className: 'lc-lease-order-service-option__lock' }, '已购') + : (checked ? React.createElement(Check, { size: 14, strokeWidth: 2.5 }) : null)), + React.createElement('span', { className: 'lc-lease-order-service-option__body' }, + React.createElement('span', { className: 'lc-lease-order-service-option__name' }, item.name), + isSearchMode && item.subCategory + ? React.createElement('span', { className: 'lc-lease-order-service-option__context' }, + (item.category || '') + (item.subCategory ? ' · ' + item.subCategory : ''), + ) + : null, + ), + isFixed + ? React.createElement('span', { className: 'lc-lease-order-service-option__price-pill' }, + formatExtraServiceUnitPrice(item), + ) + : React.createElement('span', { className: 'lc-lease-order-service-option__meta lc-lease-order-service-option__meta--rule' }, + item.billingRule || '按实际用量计入账单', + ), + ); + } + + function renderSelectedChip(value) { + var item = getExtraServiceByValue(value); + if (!item) return null; + var isFixed = item.feeType === LEASE_SERVICE_FEE_TYPE.FIXED; + var isLocked = isLockedValue(value); + return React.createElement('span', { + key: value, + className: 'lc-lease-order-service-popover__selected-tag' + + (isFixed ? ' is-fixed' : ' is-floating') + + (isLocked ? ' is-locked' : ''), + }, + React.createElement('span', { className: 'lc-lease-order-service-popover__selected-tag-name' }, item.name), + React.createElement('span', { + className: 'lc-lease-order-service-popover__selected-tag-meta', + }, isLocked + ? '已购' + : (isFixed ? formatExtraServiceUnitPrice(item) : (item.billingRule || '浮动计费'))), + isLocked + ? null + : React.createElement('button', { + type: 'button', + 'aria-label': '移除' + item.name, + onMouseDown: function (e) { e.preventDefault(); }, + onClick: function () { toggleValue(value); }, + }, React.createElement(X, { size: 12, 'aria-hidden': true })), + ); + } + + function renderSelectedSummaryItem(value) { + var item = getExtraServiceByValue(value); + if (!item) return null; + var isFixed = item.feeType === LEASE_SERVICE_FEE_TYPE.FIXED; + var isLocked = isLockedValue(value); + return React.createElement('div', { + key: value, + className: 'lc-lease-order-service-selected__item' + + (isFixed ? ' is-fixed' : ' is-floating') + + (isLocked ? ' is-locked' : ''), + }, + React.createElement('span', { className: 'lc-lease-order-service-selected__badge' }, + isLocked ? '已购' : (isFixed ? '固定' : '浮动'), + ), + React.createElement('span', { className: 'lc-lease-order-service-selected__name' }, item.name), + React.createElement('span', { className: 'lc-lease-order-service-selected__meta' }, + isFixed ? formatExtraServiceUnitPrice(item) : (item.billingRule || '浮动计费'), + ), + ); + } + var popoverContent = React.createElement('div', { - className: 'vm-operate-city-edit-body vm-operate-city-picker-panel lc-lease-order-service-popover__panel', + className: '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('div', { className: 'lc-lease-order-service-popover__head' }, + React.createElement('h4', { className: 'lc-lease-order-service-popover__title' }, '选择服务项'), + removableSelected.length > 0 + ? React.createElement('button', { + type: 'button', + className: 'lc-lease-order-service-popover__clear-all', + onClick: clearAllSelected, + }, lockedValues.length ? '清空新增' : '清空已选') + : null, + ), + React.createElement('label', { className: 'lc-lease-order-service-popover__search' }, + React.createElement(Search, { size: 16, 'aria-hidden': true, className: 'lc-lease-order-service-popover__search-icon' }), React.createElement('input', { type: 'text', - className: 'vm-operate-city-edit-search-input', + className: 'lc-lease-order-service-popover__search-input', value: query, - placeholder: '输入类目或服务名称快速筛选', + placeholder: '搜索服务名称、类目或计费规则', 'aria-label': '筛选服务内容', autoComplete: 'off', onChange: function (e) { setQuery(e.target.value); }, @@ -396,120 +549,151 @@ function LeaseOrderServiceContentField(props) { query ? React.createElement('button', { type: 'button', - className: 'vm-operate-city-edit-search-clear', + className: 'lc-lease-order-service-popover__search-clear', onClick: clearSearch, 'aria-label': '清空搜索', }, React.createElement(X, { size: 14, 'aria-hidden': true })) : null, ), selected.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: 'lc-lease-order-service-popover__selected', 'aria-live': 'polite' }, + React.createElement('span', { className: 'lc-lease-order-service-popover__selected-label' }, + '已选 ' + selected.length + ' 项', + ), React.createElement('div', { className: 'lc-lease-order-service-popover__selected-tags' }, - serviceNames.map(function (name, index) { - var value = selected[index]; - return React.createElement('span', { - key: value || name, - className: 'lc-lease-order-service-popover__selected-tag', - }, - name, - React.createElement('button', { - type: 'button', - 'aria-label': '移除' + name, - onMouseDown: function (e) { e.preventDefault(); }, - onClick: function () { toggleValue(value); }, - }, React.createElement(X, { size: 12, 'aria-hidden': true })), - ); - }), + selected.map(renderSelectedChip), ), ) : null, + !isSearchMode + ? React.createElement('div', { className: 'lc-lease-order-service-popover__tabs', role: 'tablist', 'aria-label': '服务费用类型' }, + LEASE_EXTRA_SERVICE_TREE.map(function (group) { + var active = resolvedCategory === group.category; + return React.createElement('button', { + key: group.category, + type: 'button', + role: 'tab', + 'aria-selected': active, + className: 'lc-lease-order-service-popover__tab' + (active ? ' is-active' : ''), + onClick: function () { handleCategoryChange(group.category); }, + }, group.category); + }), + ) + : null, + !isSearchMode + ? React.createElement('p', { className: 'lc-lease-order-service-popover__hint' }, + activeRoot.feeType === LEASE_SERVICE_FEE_TYPE.FIXED + ? '固定费用按单价 × 车辆数计入「租金及服务费含税总价」' + : '浮动费用仅约定计费规则,后续按实际用量计入租赁账单', + ) + : null, React.createElement('div', { - className: 'vm-operate-city-edit-main' + (isSearchMode ? ' is-search' : ''), + className: 'lc-lease-order-service-popover__scroll' + (isSearchMode ? ' is-search' : ''), }, - !isSearchMode + 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_EXTRA_SERVICE_TREE.map(function (group) { - var active = resolvedCategory === group.category; - return React.createElement('button', { - key: group.category, - type: 'button', - role: 'tab', - 'aria-selected': active, - className: 'vm-operate-city-chip' + (active ? ' active' : ''), - onClick: function () { handleCategoryChange(group.category); }, - }, group.category); - }), - ), - ) - : 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 ? ('搜索结果(' + displayOptions.length + ')') : '服务内容', - ), - React.createElement('div', { - className: 'vm-operate-city-edit-chips', + className: 'lc-lease-order-service-option-list', role: 'listbox', 'aria-label': '服务内容列表', 'aria-multiselectable': true, }, + React.createElement('p', { className: 'lc-lease-order-service-popover__section-label' }, + '搜索结果(' + displayOptions.length + ')', + ), displayOptions.length === 0 - ? React.createElement('p', { className: 'vm-operate-city-empty' }, - isSearchMode ? '未找到匹配的服务项,请换个关键词' : '暂无可选服务项', - ) - : displayOptions.map(function (item) { - var checked = selected.indexOf(item.value) >= 0; - return React.createElement('button', { - key: item.value, - type: 'button', - role: 'option', - 'aria-selected': checked, - className: 'vm-operate-city-chip' + (checked ? ' active' : ''), - onClick: function () { toggleValue(item.value); }, - }, item.name); - }), + ? React.createElement('p', { className: 'lc-lease-order-service-popover__empty' }, '未找到匹配的服务项,请换个关键词') + : displayOptions.map(renderServiceOption), + ) + : React.createElement('div', { className: 'lc-lease-order-service-subgroups' }, + (activeRoot.subCategories || []).map(function (sub) { + return React.createElement('section', { + key: sub.name, + className: 'lc-lease-order-service-subgroup', + }, + React.createElement('h4', { className: 'lc-lease-order-service-subgroup__title' }, sub.name), + React.createElement('div', { + className: 'lc-lease-order-service-option-list', + role: 'listbox', + 'aria-label': sub.name + '服务项', + 'aria-multiselectable': true, + }, + (sub.items || []).map(renderServiceOption), + ), + ); + }), ), + ), + React.createElement('div', { className: 'lc-lease-order-service-popover__footer' }, + React.createElement('span', { className: 'lc-lease-order-service-popover__footer-count' }, + lockedValues.length + ? ('已购 ' + lockedSelectedCount + ' 项 · 新增 ' + removableSelected.length + ' 项') + : ('已选 ' + selected.length + ' 项'), ), + fixedSelectedTotal > 0 + ? React.createElement('span', { className: 'lc-lease-order-service-popover__footer-total tabular-nums' }, + '固定费用合计 ' + fixedSelectedTotal.toLocaleString('zh-CN') + ' 元/车/月', + ) + : null, ), ); - 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, { - trigger: 'click', - placement: 'bottomLeft', - open: open, - onOpenChange: handleOpenChange, - overlayClassName: 'lc-lease-order-service-popover', - getPopupContainer: function () { return document.body; }, - content: popoverContent, + var pickerTrigger = React.createElement(Popover, { + trigger: 'click', + placement: 'bottomLeft', + open: open, + onOpenChange: handleOpenChange, + overlayClassName: 'lc-lease-order-service-popover', + getPopupContainer: function () { return document.body; }, + content: popoverContent, + }, + React.createElement('button', { + type: 'button', + className: 'lc-lease-order-service-picker__btn' + + (variant === 'inline' ? ' lc-lease-order-service-picker__btn--inline' : ' lc-lease-order-service-picker__btn--label'), + 'aria-label': triggerLabel, + 'aria-expanded': open, + 'data-annotation-id': variant === 'inline' ? 'lc-extra-fee-service-picker' : 'lc-lease-order-service-content', }, - React.createElement('button', { - type: 'button', - className: 'lc-lease-order-service-picker__btn', - 'aria-label': '服务内容', - 'aria-expanded': open, - 'data-annotation-id': 'lc-lease-order-service-content', - }, '选择服务项'), + React.createElement(Plus, { size: 14, 'aria-hidden': true }), + triggerLabel, ), ); + + if (variant === 'inline') { + return React.createElement('div', { className: 'lc-lease-order-service-picker lc-lease-order-service-picker--inline' }, + pickerTrigger, + removableSelected.length > 0 + ? React.createElement('div', { + className: 'lc-lease-order-service-field lc-lease-order-service-field--inline', + }, + React.createElement('div', { + className: 'lc-lease-order-service-selected', + 'aria-live': 'polite', + }, removableSelected.map(renderSelectedSummaryItem)), + ) + : null, + ); + } + + return React.createElement('label', { className: 'lc-form-field lc-lease-order-service-form-field' }, + React.createElement('span', { className: 'lc-form-field__label-row' }, + React.createElement('span', { className: 'lc-form-field__label' }, '服务内容'), + pickerTrigger, + ), + selected.length > 0 + ? React.createElement('div', { + className: 'lc-lease-order-service-field', + }, + React.createElement('div', { + className: 'lc-lease-order-service-selected', + 'aria-live': 'polite', + }, selected.map(renderSelectedSummaryItem)), + ) + : null, + ); } + function BusinessDeptOwnerPickerField(props) { var businessDept = props.businessDept || ''; var businessOwner = props.businessOwner || ''; @@ -703,25 +887,28 @@ function brandModelPairKey(pair) { } function LeaseOrderBrandModelField(props) { + var single = props.single === true; var selected = normalizeBrandModels({ brandModels: props.value }); + if (single && selected.length > 1) { + selected = [selected[0]]; + } var onChange = props.onChange; var openState = React.useState(false); var brandState = React.useState(''); var queryState = React.useState(''); + var stockPopoverKeyState = React.useState(null); var open = openState[0]; var setOpen = openState[1]; var activeBrand = brandState[0]; var setActiveBrand = brandState[1]; var query = queryState[0]; var setQuery = queryState[1]; + var stockPopoverKey = stockPopoverKeyState[0]; + var setStockPopoverKey = stockPopoverKeyState[1]; var labels = formatBrandModelsDisplay(selected); var summary = labels.length <= 2 ? labels.join('、') : (labels.length > 0 ? '已选 ' + labels.length + ' 个型号' : ''); - var stockHints = selected.map(function (pair) { - var count = countNationalInStockByBrandModel(pair[0], pair[1]); - return formatBrandModelPair(pair[0], pair[1]) + ':全国在库 ' + count + ' 台'; - }); var searchText = query.trim(); var isSearchMode = searchText.length > 0; var resolvedBrand = activeBrand || LEASE_VEHICLE_BRAND_MODEL_CATALOG[0].brand; @@ -751,11 +938,61 @@ function LeaseOrderBrandModelField(props) { setActiveBrand(selected.length > 0 ? selected[0][0] : LEASE_VEHICLE_BRAND_MODEL_CATALOG[0].brand); } else { setQuery(''); + setStockPopoverKey(null); } } + function renderStockBreakdownContent(brand, model) { + var breakdown = getInStockBreakdownByBrandModel(brand, model); + if (!breakdown.regions.length) { + return React.createElement('div', { className: 'lc-brand-model-stock-popover' }, + React.createElement('p', { className: 'lc-brand-model-stock-popover__empty' }, '暂无在库车辆'), + ); + } + return React.createElement('div', { className: 'lc-brand-model-stock-popover' }, + React.createElement('p', { className: 'lc-brand-model-stock-popover__title' }, + '停车场省市在库 · 共 ', + React.createElement('span', { className: 'tabular-nums' }, breakdown.total), + ' 台', + ), + React.createElement('div', { className: 'lc-brand-model-stock-popover__list', role: 'list' }, + breakdown.regions.map(function (region) { + return React.createElement('section', { + key: region.province, + className: 'lc-brand-model-stock-popover__region', + role: 'listitem', + }, + React.createElement('header', { className: 'lc-brand-model-stock-popover__region-head' }, + React.createElement('span', { className: 'lc-brand-model-stock-popover__region-name' }, region.province), + React.createElement('span', { className: 'lc-brand-model-stock-popover__region-total tabular-nums' }, region.total + ' 台'), + ), + React.createElement('ul', { className: 'lc-brand-model-stock-popover__cities' }, + region.cities.map(function (cityItem) { + return React.createElement('li', { + key: region.province + '::' + cityItem.city, + className: 'lc-brand-model-stock-popover__city', + }, + React.createElement('span', { className: 'lc-brand-model-stock-popover__city-name' }, cityItem.city), + React.createElement('span', { className: 'lc-brand-model-stock-popover__city-count tabular-nums' }, cityItem.count + ' 台'), + ); + }), + ), + ); + }), + ), + ); + } + function togglePair(brand, model) { var key = brandModelPairKey([brand, model]); + setStockPopoverKey(null); + if (single) { + var isSame = selected.length === 1 && brandModelPairKey(selected[0]) === key; + onChange(isSame ? [] : [[brand, model]]); + setOpen(false); + setQuery(''); + return; + } var exists = selected.some(function (pair) { return brandModelPairKey(pair) === key; }); @@ -776,6 +1013,60 @@ function LeaseOrderBrandModelField(props) { setQuery(''); } + function renderModelOptionChip(item) { + var pairKey = brandModelPairKey([item.brand, item.model]); + var checked = selected.some(function (pair) { + return pair[0] === item.brand && pair[1] === item.model; + }); + var stockCount = countNationalInStockByBrandModel(item.brand, item.model); + var modelLabel = isSearchMode + ? formatBrandModelPair(item.brand, item.model) + : item.model; + var stockOpen = stockPopoverKey === pairKey; + return React.createElement('div', { + key: pairKey, + className: 'lc-brand-model-option-row', + role: 'presentation', + }, + React.createElement('button', { + type: 'button', + role: 'option', + 'aria-selected': checked, + className: 'vm-operate-city-chip lc-brand-model-option-chip' + (checked ? ' active' : ''), + onClick: function () { togglePair(item.brand, item.model); }, + }, + React.createElement('span', { className: 'lc-brand-model-option-chip__model' }, modelLabel), + ), + stockCount > 0 + ? React.createElement('span', { className: 'lc-brand-model-option-stock-meta' }, + '全国在库 ', + React.createElement(Popover, { + trigger: 'click', + open: stockOpen, + onOpenChange: function (nextOpen) { + setStockPopoverKey(nextOpen ? pairKey : null); + }, + placement: 'rightTop', + overlayClassName: 'lc-brand-model-stock-popover-overlay', + getPopupContainer: function () { return document.body; }, + content: renderStockBreakdownContent(item.brand, item.model), + }, + React.createElement('button', { + type: 'button', + className: 'lc-brand-model-option-stock-count tabular-nums', + 'aria-label': '查看' + modelLabel + '各省市在库数量', + onClick: function (event) { + event.preventDefault(); + event.stopPropagation(); + }, + }, String(stockCount)), + ), + ' 台', + ) + : React.createElement('span', { className: 'lc-brand-model-option-stock-meta is-empty' }, '全国在库 0 台'), + ); + } + var popoverContent = React.createElement('div', { className: 'vm-operate-city-edit-body vm-operate-city-picker-panel lc-lease-order-service-popover__panel', }, @@ -838,26 +1129,15 @@ function LeaseOrderBrandModelField(props) { isSearchMode ? ('搜索结果(' + displayModels.length + ')') : '型号', ), React.createElement('div', { - className: 'vm-operate-city-edit-chips', + className: 'vm-operate-city-edit-chips lc-brand-model-option-chips', role: 'listbox', 'aria-label': '型号列表', - 'aria-multiselectable': true, + 'aria-multiselectable': single ? false : true, + 'data-annotation-id': 'lc-lease-order-brand-model-stock', }, displayModels.length === 0 ? React.createElement('p', { className: 'vm-operate-city-empty' }, '暂无匹配型号') - : displayModels.map(function (item) { - var checked = selected.some(function (pair) { - return pair[0] === item.brand && pair[1] === item.model; - }); - return React.createElement('button', { - key: brandModelPairKey([item.brand, item.model]), - type: 'button', - role: 'option', - 'aria-selected': checked, - className: 'vm-operate-city-chip' + (checked ? ' active' : ''), - onClick: function () { togglePair(item.brand, item.model); }, - }, item.model); - }), + : displayModels.map(renderModelOptionChip), ), ), ), @@ -903,203 +1183,789 @@ function LeaseOrderBrandModelField(props) { React.createElement(ChevronDown, { size: 14, className: 'vm-filter-picker-chevron', 'aria-hidden': true }), ), ), - stockHints.length > 0 ? React.createElement('p', { - className: 'lc-brand-model-stock-hint', - 'data-annotation-id': 'lc-lease-order-brand-model-stock', - role: 'status', - }, stockHints.join(';')) : null, ); } -function LeaseOrderPlateNoField(props) { - var selected = normalizePlateNos({ plateNos: props.value }); - var onChange = props.onChange; +function LeaseOrderPlateField(props) { + var row = props.row || {}; var brandModels = props.brandModels || []; + var onChange = props.onChange; + var plateMode = getPlateMode(row); + var matchedPlates = plateMode === PLATE_MODE_SPECIFIC + ? matchPlatesAgainstAssets(normalizePlateNos(row), brandModels) + : []; var openState = React.useState(false); - var queryState = React.useState(''); var bulkState = React.useState(''); + var validationState = React.useState(null); var open = openState[0]; var setOpen = openState[1]; - var query = queryState[0]; - var setQuery = queryState[1]; var bulkText = bulkState[0]; var setBulkText = bulkState[1]; - var plateOptions = getReadyVehiclePlateOptions(brandModels); - var normalizedQuery = query.trim().toUpperCase(); - var filteredOptions = plateOptions.filter(function (opt) { - if (!normalizedQuery) return true; - return opt.label.toUpperCase().indexOf(normalizedQuery) >= 0; - }); + var validationResults = validationState[0]; + var setValidationResults = validationState[1]; + var assetCount = getReadyVehiclePlateOptions(brandModels).length; + var hasBrandModel = brandModels.length > 0; + + React.useEffect(function () { + if (plateMode !== PLATE_MODE_SPECIFIC) { + setBulkText(''); + setValidationResults(null); + return; + } + setBulkText(matchedPlates.join('\n')); + }, [plateMode, matchedPlates.join('\0'), brandModels.map(function (pair) { return pair.join('\0'); }).join('|')]); + + function emitPlatePatch(patch) { + onChange(syncRowPlateFields(Object.assign({}, row, patch))); + } function handleOpenChange(nextOpen) { setOpen(nextOpen); - if (!nextOpen) { - setQuery(''); + if (!nextOpen && plateMode === PLATE_MODE_SPECIFIC) { + setBulkText(matchedPlates.join('\n')); + setValidationResults(null); + } + } + + function handleModeChange(mode) { + if (mode === PLATE_MODE_SPECIFIC) { + if (plateMode === PLATE_MODE_SPECIFIC) { + setOpen(true); + return; + } + emitPlatePatch({ + plateMode: PLATE_MODE_SPECIFIC, + plateNos: [], + }); setBulkText(''); - } - } - - function togglePlate(plate) { - if (plate === PLATE_ACTUAL_DELIVERY) { - onChange([PLATE_ACTUAL_DELIVERY]); + setValidationResults(null); + setOpen(true); return; } - var next = selected.filter(function (item) { return item !== PLATE_ACTUAL_DELIVERY; }); - if (next.indexOf(plate) >= 0) { - onChange(next.filter(function (item) { return item !== plate; })); - return; - } - onChange(next.concat([plate])); - } - - function applyBulkPlates() { - var parsed = parsePlateSearchText(bulkText); - if (!parsed.length) return; - var next = selected.filter(function (item) { return item !== PLATE_ACTUAL_DELIVERY; }); - parsed.forEach(function (plate) { - if (next.indexOf(plate) < 0) next.push(plate); + emitPlatePatch({ + plateMode: PLATE_MODE_ACTUAL, + plateNos: [PLATE_ACTUAL_DELIVERY], }); - onChange(next.length ? next : [PLATE_ACTUAL_DELIVERY]); setBulkText(''); + setValidationResults(null); + setOpen(false); } - function clearAll(event) { - event.preventDefault(); - event.stopPropagation(); - onChange([PLATE_ACTUAL_DELIVERY]); + function handleBulkDraft(text) { + setBulkText(text); + setValidationResults(null); } + function handleConfirmPlates() { + if (!hasBrandModel) { + message.warning('请先选择品牌 / 型号'); + return; + } + var parsed = parsePlateSearchText(bulkText); + if (!parsed.length) { + message.warning('请输入至少一条车牌号'); + return; + } + var validation = validatePlatesForLeaseOrder(parsed, brandModels); + setValidationResults(validation.results); + emitPlatePatch({ + plateMode: PLATE_MODE_SPECIFIC, + plateNos: validation.matched, + }); + setBulkText(validation.matched.join('\n')); + var passCount = validation.matched.length; + var failCount = validation.results.length - passCount; + if (passCount > 0 && failCount === 0) { + message.success('已确认 ' + passCount + ' 辆已备车'); + } else if (passCount > 0) { + message.warning('已确认 ' + passCount + ' 辆,' + failCount + ' 条未通过校验'); + } else { + message.error('未通过校验,请检查车牌、品牌 / 型号或车辆状态'); + } + } + + function removePlate(plate) { + var next = matchedPlates.filter(function (item) { return item !== plate; }); + setBulkText(next.join('\n')); + setValidationResults(null); + emitPlatePatch({ + plateMode: PLATE_MODE_SPECIFIC, + plateNos: next, + }); + } + + function clearSpecific() { + emitPlatePatch({ + plateMode: PLATE_MODE_SPECIFIC, + plateNos: [], + }); + setBulkText(''); + setValidationResults(null); + } + + var passedValidationResults = (validationResults || []).filter(function (item) { return item.ok; }); + var popoverContent = React.createElement('div', { className: 'vm-operate-city-edit-body lc-lease-order-plate-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); }, - }), + React.createElement('div', { className: 'lc-lease-order-plate-popover__head' }, + React.createElement('span', { className: 'lc-lease-order-plate-popover__title' }, PLATE_SPECIFIC_LABEL), + matchedPlates.length + ? React.createElement('button', { + type: 'button', + className: 'lc-lease-order-plate-popover__clear', + onClick: clearSpecific, + }, '清空') + : null, ), + React.createElement('p', { className: 'lc-lease-order-plate-popover__hint', role: 'note' }, + '请输入已备车的车牌号,每行一条', + ), + !hasBrandModel + ? React.createElement('p', { className: 'lc-lease-order-plate-popover__hint lc-lease-order-plate-popover__hint--warn', role: 'note' }, + '请先选择品牌 / 型号,再录入车牌', + ) + : null, React.createElement('div', { className: 'lc-lease-order-plate-popover__bulk' }, - React.createElement('span', { className: 'lc-lease-order-plate-popover__bulk-label' }, '批量输入(每行一个车牌)'), React.createElement('textarea', { - className: 'lc-lease-order-plate-popover__bulk-input', + className: 'lc-lease-order-plate-popover__bulk-input vm-focus-border', value: bulkText, - rows: 3, - placeholder: '粤A12345\n粤B67890', + rows: 4, + disabled: !hasBrandModel, + placeholder: '粤AGR8556\n粤BHY6688', 'aria-label': '批量输入车牌号', - onChange: function (e) { setBulkText(e.target.value); }, + onChange: function (e) { handleBulkDraft(e.target.value); }, }), - React.createElement('button', { - type: 'button', - className: 'vm-btn vm-btn-primary lc-lease-order-plate-popover__bulk-btn', - onClick: applyBulkPlates, - }, '确认添加'), ), - React.createElement('button', { - type: 'button', - className: 'vm-operate-city-chip lc-lease-order-plate-popover__actual' + (selected.length === 1 && selected[0] === PLATE_ACTUAL_DELIVERY ? ' active' : ''), - onClick: function () { onChange([PLATE_ACTUAL_DELIVERY]); }, - }, PLATE_ACTUAL_DELIVERY), - React.createElement('div', { className: 'vm-operate-city-edit-chips', role: 'listbox', 'aria-label': '车牌号列表', 'aria-multiselectable': true }, - filteredOptions.length === 0 - ? React.createElement('p', { className: 'vm-operate-city-empty' }, '暂无匹配车牌,可批量输入') - : filteredOptions.map(function (opt) { - var checked = selected.indexOf(opt.value) >= 0; - return React.createElement('button', { - key: opt.value, - type: 'button', - role: 'option', - 'aria-selected': checked, - className: 'vm-operate-city-chip' + (checked ? ' active' : ''), - onClick: function () { togglePlate(opt.value); }, - }, opt.label); - }), + hasBrandModel && assetCount === 0 + ? React.createElement('p', { className: 'lc-lease-order-plate-popover__hint lc-lease-order-plate-popover__hint--warn', role: 'note' }, + '当前品牌 / 型号暂无已备车资产', + ) + : null, + validationResults + ? React.createElement('div', { + className: 'lc-lease-order-plate-popover__results', + role: 'status', + 'aria-live': 'polite', + }, + React.createElement('div', { className: 'lc-lease-order-plate-popover__results-head' }, + React.createElement('span', { className: 'lc-lease-order-plate-popover__results-title' }, '校验结果'), + React.createElement('span', { + className: 'lc-lease-order-plate-popover__results-summary tabular-nums', + }, '通过 ' + passedValidationResults.length + ' / 共 ' + validationResults.length), + ), + React.createElement('ul', { className: 'lc-lease-order-plate-popover__result-list' }, + validationResults.map(function (item) { + return React.createElement('li', { + key: item.plate + '-' + item.reason, + className: 'lc-lease-order-plate-popover__result-item' + + (item.ok ? ' is-ok' : ' is-fail'), + }, + React.createElement('span', { className: 'lc-lease-order-plate-popover__result-plate' }, item.plate), + React.createElement('span', { className: 'lc-lease-order-plate-popover__result-message' }, item.message), + ); + }), + ), + ) + : null, + matchedPlates.length > 0 + ? React.createElement('div', { className: 'lc-lease-order-plate-popover__matched' }, + React.createElement('div', { className: 'lc-lease-order-plate-popover__matched-head' }, + React.createElement('span', { className: 'lc-lease-order-plate-popover__matched-title' }, '已选车辆'), + React.createElement('span', { + className: 'lc-lease-order-plate-popover__matched-count tabular-nums', + }, matchedPlates.length + ' 辆'), + ), + React.createElement('div', { + className: 'lc-lease-order-plate-popover__chip-list', + role: 'list', + 'aria-label': '已选车辆列表', + }, + matchedPlates.map(function (plate) { + return React.createElement('span', { + key: plate, + className: 'lc-lease-order-plate-popover__chip', + role: 'listitem', + }, + React.createElement('span', { className: 'lc-lease-order-plate-popover__chip-text' }, plate), + React.createElement('button', { + type: 'button', + className: 'lc-lease-order-plate-popover__chip-remove', + onClick: function () { removePlate(plate); }, + 'aria-label': '移除车牌 ' + plate, + }, React.createElement(X, { size: 12, 'aria-hidden': true })), + ); + }), + ), + ) + : null, + React.createElement('div', { className: 'lc-lease-order-plate-popover__actions' }, + React.createElement(Button, { + size: 'small', + onClick: function () { handleOpenChange(false); }, + }, '取消'), + React.createElement(Button, { + type: 'primary', + size: 'small', + disabled: !hasBrandModel || !bulkText.trim(), + onClick: handleConfirmPlates, + }, '确认'), ), ); - return React.createElement('div', { className: 'vm-operate-city-field lc-lease-order-plate-picker' }, - React.createElement(Popover, { - trigger: 'click', - placement: 'bottomLeft', - open: open, - onOpenChange: handleOpenChange, - overlayClassName: 'lc-lease-order-service-popover', - getPopupContainer: function () { return document.body; }, - content: popoverContent, - }, - React.createElement('div', { - className: 'vm-filter-picker-control lc-lease-order-plate-picker__trigger' + (open ? ' open' : ''), - role: 'combobox', - 'aria-label': '车牌号', - 'aria-expanded': open, - tabIndex: 0, - 'data-annotation-id': 'lc-lease-order-plate-no', - }, - React.createElement('div', { className: 'lc-lease-order-plate-picker__summary-list' }, - selected.map(function (plate) { - return React.createElement('span', { - key: plate, - className: 'lc-lease-order-plate-picker__summary-line', - }, plate); - }), - ), - !(selected.length === 1 && selected[0] === PLATE_ACTUAL_DELIVERY) ? React.createElement('button', { - type: 'button', - className: 'vm-filter-picker-clear', - onClick: clearAll, - 'aria-label': '重置车牌号', - tabIndex: -1, - }, React.createElement(X, { size: 14, 'aria-hidden': true })) : null, - React.createElement(ChevronDown, { size: 14, className: 'vm-filter-picker-chevron', 'aria-hidden': true }), - ), - ), + var specificOptionLabel = plateMode === PLATE_MODE_SPECIFIC && matchedPlates.length + ? ('已选 ' + matchedPlates.length + ' 辆') + : PLATE_SPECIFIC_LABEL; + + var toggleSurface = React.createElement('div', { className: 'lc-lease-order-plate-control__surface' }, + React.createElement(LeaseOrderModeToggle, { + layout: 'segmented', + 'aria-label': '车牌号模式', + value: plateMode, + onChange: handleModeChange, + options: [ + { value: PLATE_MODE_ACTUAL, label: PLATE_ACTUAL_DELIVERY }, + { value: PLATE_MODE_SPECIFIC, label: specificOptionLabel }, + ], + }), + ); + + return React.createElement('div', { + className: 'lc-lease-order-plate-control', + 'data-annotation-id': 'lc-lease-order-plate-no', + }, + plateMode === PLATE_MODE_SPECIFIC + ? React.createElement(Popover, { + trigger: [], + placement: 'bottom', + open: open, + onOpenChange: handleOpenChange, + overlayClassName: 'lc-lease-order-plate-popover', + getPopupContainer: function () { return document.body; }, + content: popoverContent, + }, toggleSurface) + : toggleSurface, ); } function LeaseOrderAmountField(props) { return React.createElement('div', { className: 'lc-form-inline-sentence lc-lease-order-table__amount' }, React.createElement('div', { className: 'lc-lease-order-table__amount-field' }, props.children), - React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '元'), + props.suffix !== false + ? React.createElement('span', { className: 'lc-form-inline-sentence__text' }, props.suffixText || '元') + : null, ); } -function LeaseOrderRowActions(props) { - return React.createElement('div', { className: 'lc-lease-order-editor__actions' }, - React.createElement('button', { - type: 'button', - className: 'lc-lease-order-editor__action-btn lc-lease-order-editor__remove-btn', - onClick: function () { props.onRemove(props.rowId); }, - 'aria-label': '删除该行', - }, React.createElement(Trash2, { size: 14, 'aria-hidden': true })), +var LeaseOrderRangePicker = DatePicker.RangePicker; + +function LeaseOrderModeToggle(props) { + var layout = props.layout || 'default'; + var optionCount = (props.options || []).length; + return React.createElement('div', { + className: 'lc-lease-order-mode-toggle' + + (layout === 'segmented' ? ' lc-lease-order-mode-toggle--segmented' : ''), + role: 'radiogroup', + 'aria-label': props['aria-label'] || '模式切换', + style: layout === 'segmented' ? { '--mode-count': optionCount } : undefined, + }, + (props.options || []).map(function (option) { + var active = props.value === option.value; + return React.createElement('button', { + key: option.value, + type: 'button', + role: 'radio', + 'aria-checked': active, + className: 'lc-lease-order-mode-toggle__btn' + (active ? ' active' : ''), + onClick: function () { props.onChange(option.value); }, + }, option.label); + }), + ); +} + +function LeaseOrderDeliveryRegionField(props) { + var value = props.value || []; + var onChange = props.onChange; + var openState = React.useState(false); + var provinceState = React.useState(''); + var queryState = React.useState(''); + var open = openState[0]; + var setOpen = openState[1]; + var activeProvince = provinceState[0]; + var setActiveProvince = provinceState[1]; + var query = queryState[0]; + var setQuery = queryState[1]; + var searchText = query.trim(); + var isSearchMode = searchText.length > 0; + var resolvedProvince = activeProvince || value[0] || PROVINCE_CITY_CASCADER_OPTIONS[0].value; + var activeProvinceNode = PROVINCE_CITY_CASCADER_OPTIONS.find(function (node) { + return node.value === resolvedProvince; + }) || PROVINCE_CITY_CASCADER_OPTIONS[0]; + var displayOptions = isSearchMode + ? flattenProvinceCityOptions().filter(function (item) { + return matchProvinceCityOption(item, searchText); + }) + : []; + var summary = formatDeliveryRegionDisplay(value); + + function handleOpenChange(nextOpen) { + setOpen(nextOpen); + if (nextOpen) { + setActiveProvince(value[0] || PROVINCE_CITY_CASCADER_OPTIONS[0].value); + } else { + setQuery(''); + } + } + + function selectRegion(province, city) { + onChange([province, city]); + setActiveProvince(province); + setOpen(false); + setQuery(''); + } + + function clearSelection(event) { + event.preventDefault(); + event.stopPropagation(); + onChange([]); + setQuery(''); + } + + function clearSearch(event) { + event.preventDefault(); + event.stopPropagation(); + setQuery(''); + } + + var popoverContent = React.createElement('div', { + className: 'vm-operate-city-edit-body vm-operate-city-picker-panel lc-lease-order-delivery-region-popover__panel', + }, + React.createElement('label', { className: 'vm-operate-city-edit-search lc-lease-order-delivery-region-popover__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, + ), + value.length >= 2 + ? React.createElement('div', { className: 'vm-operate-city-edit-selected lc-lease-order-delivery-region-popover__selected', 'aria-live': 'polite' }, + React.createElement('span', { className: 'vm-operate-city-edit-selected-label' }, '当前'), + React.createElement('span', { className: 'lc-lease-order-delivery-region-popover__selected-value' }, summary), + ) + : null, + React.createElement('div', { + className: 'vm-operate-city-edit-main lc-lease-order-delivery-region-popover__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': '省份列表', + }, + PROVINCE_CITY_CASCADER_OPTIONS.map(function (prov) { + var active = resolvedProvince === prov.value; + var selectedProvince = value[0] === prov.value; + return React.createElement('button', { + key: prov.value, + type: 'button', + role: 'tab', + 'aria-selected': active, + className: 'vm-operate-city-chip lc-lease-order-delivery-region-popover__province-chip' + + (active ? ' active' : '') + + (selectedProvince ? ' is-current' : ''), + onClick: function () { + setActiveProvince(prov.value); + setQuery(''); + }, + }, + React.createElement('span', { className: 'lc-lease-order-delivery-region-popover__chip-label' }, prov.label), + active ? React.createElement(ChevronRight, { size: 14, 'aria-hidden': true, className: 'lc-lease-order-delivery-region-popover__chip-arrow' }) : null, + ); + }), + ), + ) + : 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 ? ('搜索结果(' + displayOptions.length + ')') : '城市', + ), + React.createElement('div', { + className: 'vm-operate-city-edit-chips lc-lease-order-delivery-region-popover__city-chips', + role: 'listbox', + 'aria-label': '城市列表', + }, + isSearchMode + ? (displayOptions.length === 0 + ? React.createElement('p', { className: 'vm-operate-city-empty' }, '未找到匹配的省市,请换个关键词') + : displayOptions.map(function (item) { + var checked = value[0] === item.province && value[1] === item.city; + return React.createElement('button', { + key: item.province + '::' + item.city, + type: 'button', + role: 'option', + 'aria-selected': checked, + className: 'vm-operate-city-chip lc-lease-order-delivery-region-popover__city-chip' + (checked ? ' active' : ''), + onClick: function () { selectRegion(item.province, item.city); }, + }, + React.createElement('span', { className: 'lc-lease-order-delivery-region-popover__city-name' }, item.city), + React.createElement('span', { className: 'lc-lease-order-delivery-region-popover__city-province' }, item.province), + ); + })) + : ((activeProvinceNode.children || []).map(function (city) { + var checked = value[0] === activeProvinceNode.value && value[1] === city.value; + return React.createElement('button', { + key: city.value, + type: 'button', + role: 'option', + 'aria-selected': checked, + className: 'vm-operate-city-chip lc-lease-order-delivery-region-popover__city-chip' + (checked ? ' active' : ''), + onClick: function () { selectRegion(activeProvinceNode.value, city.value); }, + }, + React.createElement('span', { className: 'lc-lease-order-delivery-region-popover__city-name' }, city.label), + checked ? React.createElement(Check, { size: 14, 'aria-hidden': true, className: 'lc-lease-order-delivery-region-popover__city-check' }) : null, + ); + })), + ), + ), + ), + ); + + return React.createElement('div', { className: 'vm-operate-city-field lc-lease-order-delivery-region-picker' }, + React.createElement(Popover, { + trigger: 'click', + placement: 'bottomLeft', + open: open, + onOpenChange: handleOpenChange, + overlayClassName: 'lc-lease-order-delivery-region-popover', + getPopupContainer: function () { return document.body; }, + content: popoverContent, + }, + React.createElement('div', { + className: 'vm-filter-picker-control lc-lease-order-delivery-region-picker__trigger' + (open ? ' open' : '') + (summary ? ' has-value' : ''), + role: 'combobox', + 'aria-label': '车辆交付交还地点', + 'aria-expanded': open, + tabIndex: 0, + 'data-annotation-id': 'lc-lease-order-delivery-region', + }, + React.createElement(MapPin, { size: 16, 'aria-hidden': true, className: 'lc-lease-order-delivery-region-picker__icon' }), + summary + ? React.createElement('span', { className: 'vm-filter-picker-input lc-lease-order-delivery-region-picker__summary' }, summary) + : React.createElement('span', { + className: 'vm-filter-picker-input lc-lease-order-delivery-region-picker__summary is-placeholder', + }, '请选择省 / 市'), + summary + ? React.createElement('button', { + type: 'button', + className: 'vm-filter-picker-clear', + onClick: clearSelection, + 'aria-label': '清空交付地点', + tabIndex: -1, + }, React.createElement(X, { size: 14, 'aria-hidden': true })) + : null, + React.createElement(ChevronDown, { size: 16, className: 'vm-filter-picker-chevron', 'aria-hidden': true }), + ), + ), + ); +} + +function LeaseOrderLeasePeriodField(props) { + var row = props.row; + var mode = row.leasePeriodMode || 'months'; + var periodMonths = row.leasePeriodMonths != null && row.leasePeriodMonths !== '' + ? row.leasePeriodMonths + : 1; + var periodRangeValue = row.leasePeriodStart && row.leasePeriodEnd + ? [dayjs(row.leasePeriodStart), dayjs(row.leasePeriodEnd)] + : null; + + return React.createElement('div', { className: 'lc-lease-order-mode-field lc-lease-order-period-field lc-lease-order-period-field--inline' }, + React.createElement(LeaseOrderModeToggle, { + layout: 'segmented', + 'aria-label': '租赁期限模式', + value: mode, + onChange: function (nextMode) { + if (nextMode === 'months') { + props.onChange({ + leasePeriodMode: 'months', + leasePeriodStart: null, + leasePeriodEnd: null, + leasePeriodMonths: row.leasePeriodMonths != null && row.leasePeriodMonths !== '' + ? row.leasePeriodMonths + : 1, + }); + return; + } + props.onChange({ + leasePeriodMode: 'fixed', + leasePeriodMonths: null, + }); + }, + options: [ + { value: 'months', label: '提车起算' }, + { value: 'fixed', label: '固定期限' }, + ], + }), + React.createElement('div', { className: 'lc-lease-order-period-field__input' }, + mode === 'fixed' + ? React.createElement(LeaseOrderRangePicker, { + className: 'lc-form-datepicker vm-focus-border', + style: { width: '100%' }, + placeholder: ['开始日期', '结束日期'], + value: periodRangeValue, + onChange: function (_dates, dateStrings) { + props.onChange({ + leasePeriodMode: 'fixed', + leasePeriodStart: dateStrings && dateStrings[0] ? dateStrings[0] : null, + leasePeriodEnd: dateStrings && dateStrings[1] ? dateStrings[1] : null, + leasePeriodMonths: null, + }); + }, + 'aria-label': '租赁期限固定日期', + }) + : React.createElement('div', { className: 'lc-lease-order-period-field__months' }, + React.createElement(InputNumber, { + className: 'lc-form-number lc-lease-order-period-input vm-focus-border', + controls: false, + min: 1, + step: 1, + precision: 0, + value: periodMonths, + onChange: function (v) { + props.onChange({ + leasePeriodMode: 'months', + leasePeriodMonths: v != null ? v : 1, + leasePeriodStart: null, + leasePeriodEnd: null, + }); + }, + addonAfter: '个月', + placeholder: '月数', + 'aria-label': '提车起算月数', + }), + React.createElement('span', { className: 'lc-lease-order-period-field__hint', role: 'note' }, + '从实际提车之日起「', + React.createElement('span', { + className: 'lc-lease-order-period-field__hint-value tabular-nums', + }, String(periodMonths)), + '个月」后到期', + ), + ), + ), + ); +} + +function LeaseOrderVehicleCard(props) { + var row = props.row; + var index = props.index; + var canDelete = props.canDelete; + var isFlowLocked = Boolean(row._flowLocked); + var lockedFields = row._flowLockedFields || []; + var lockBrand = isFlowLocked && lockedFields.indexOf('brandModels') >= 0; + var lockPlate = isFlowLocked && lockedFields.indexOf('plateNos') >= 0; + var lockQty = isFlowLocked && lockedFields.indexOf('vehicleQty') >= 0; + var extraServices = normalizeExtraServices(row); + var brandModels = normalizeBrandModels(row); + var plateMode = getPlateMode(row); + var vehicleCount = getRowVehicleCountForPricing(row); + var serviceFee = calcRowServiceFee(row); + var rentBelowMin = brandModels.some(function (pair) { + return isLeaseRentBelowMinimum(row.rent, pair[0], pair[1]); + }); + var minRentHint = brandModels.map(function (pair) { + var minRent = getLeaseMinRentForBrandModel(pair[0], pair[1]); + if (minRent == null) return null; + return formatBrandModelPair(pair[0], pair[1]) + ' 最低 ' + minRent.toLocaleString('zh-CN') + ' 元/月'; + }).filter(Boolean).join(';'); + var qtyEditable = plateMode === PLATE_MODE_ACTUAL; + var cardTitle = brandModels.length + ? formatBrandModelsDisplay(brandModels)[0] + : ('车型 ' + (index + 1)); + + return React.createElement('article', { + className: 'lc-lease-order-card', + 'data-annotation-id': index === 0 ? 'lc-lease-order-vehicle-card' : undefined, + }, + React.createElement('header', { className: 'lc-lease-order-card__head' }, + React.createElement('h3', { className: 'lc-lease-order-card__title' }, cardTitle), + isFlowLocked + ? React.createElement('span', { className: 'lc-lease-order-card__lock-tag', role: 'status' }, '已交车锁定') + : null, + canDelete + ? React.createElement('button', { + type: 'button', + className: 'lc-lease-order-card__remove', + onClick: function () { props.onRemove(row.id); }, + 'aria-label': '删除' + cardTitle, + }, React.createElement(Trash2, { size: 14, 'aria-hidden': true }), '删除') + : null, + ), + React.createElement('div', { className: 'lc-lease-order-card__row lc-lease-order-card__row--primary' }, + React.createElement(FormField, { label: '品牌 / 型号' }, + lockBrand + ? React.createElement(ReadonlyValue, { + value: formatBrandModelsDisplay(brandModels).join('、') || '-', + label: '品牌型号', + }) + : React.createElement(LeaseOrderBrandModelField, { + single: true, + value: brandModels, + onChange: function (values) { + props.onChange(row.id, { brandModels: values || [] }); + }, + }), + ), + React.createElement(FormField, { label: '数量' }, + lockQty + ? React.createElement(ReadonlyValue, { + value: vehicleCount > 0 ? String(vehicleCount) : '', + label: '车辆数量', + }) + : (qtyEditable + ? React.createElement(LeaseOrderAmountField, { suffixText: '辆' }, + React.createElement(InputNumber, { + className: 'lc-form-number vm-focus-border', + controls: false, + min: 1, + step: 1, + precision: 0, + value: row.vehicleQty != null ? row.vehicleQty : 1, + onChange: function (v) { props.onChange(row.id, { vehicleQty: v }); }, + style: { width: '100%' }, + 'aria-label': '车辆数量', + 'data-annotation-id': index === 0 ? 'lc-lease-order-row-qty' : undefined, + }), + ) + : React.createElement(ReadonlyValue, { + value: vehicleCount > 0 ? String(vehicleCount) : '', + label: '车辆数量', + computed: plateMode === PLATE_MODE_SPECIFIC, + })), + ), + React.createElement(FormField, { label: '车牌号' }, + lockPlate + ? React.createElement(ReadonlyValue, { + value: (row.plateNos || []).join('、') || '-', + label: '车牌号', + }) + : React.createElement(LeaseOrderPlateField, { + row: row, + brandModels: brandModels, + onChange: function (patch) { + props.onChange(row.id, patch); + }, + }), + ), + ), + React.createElement('div', { className: 'lc-lease-order-card__row lc-lease-order-card__row--pricing' }, + React.createElement(FormField, { label: '车辆租金(元/月/辆,含税)' }, + React.createElement(LeaseOrderAmountField, null, + React.createElement(InputNumber, { + className: 'lc-form-number vm-focus-border' + (rentBelowMin ? ' lc-form-number--warn' : ''), + controls: false, + min: 0, + step: 0.01, + precision: 2, + value: row.rent, + onChange: function (v) { props.onChange(row.id, { rent: v }); }, + placeholder: '车辆租金', + style: { width: '100%' }, + 'aria-label': '车辆租金', + 'data-annotation-id': index === 0 ? 'lc-lease-order-rent' : undefined, + }), + ), + rentBelowMin ? React.createElement('p', { + className: 'lc-rent-min-warn', + 'data-annotation-id': index === 0 ? 'lc-lease-order-rent-min-warn' : undefined, + role: 'alert', + }, '低于系统最低租金,将触发非标准合同审批' + (minRentHint ? '(' + minRentHint + ')' : '')) : null, + ), + React.createElement(FormField, { label: '保证金(元/辆)' }, + React.createElement(LeaseOrderAmountField, null, + React.createElement(InputNumber, { + className: 'lc-form-number vm-focus-border', + controls: false, + min: 0, + step: 0.01, + precision: 2, + value: row.deposit, + onChange: function (v) { props.onChange(row.id, { deposit: v }); }, + style: { width: '100%' }, + 'aria-label': '保证金', + }), + ), + ), + React.createElement(FormField, { + label: '服务费(自动计算)', + labelHint: React.createElement('span', { + className: 'lc-lease-order-service-fee-label-hint', + role: 'note', + }, '仅固定费用计入总价'), + }, + React.createElement(LeaseOrderAmountField, null, + React.createElement(ReadonlyValue, { + value: formatServiceFeeAmount(serviceFee), + label: '服务费', + numeric: true, + computed: true, + }), + ), + ), + ), + React.createElement('div', { className: 'lc-lease-order-card__row lc-lease-order-card__row--service-content' }, + React.createElement(LeaseOrderServiceContentField, { + value: extraServices, + onChange: function (values) { + props.onChange(row.id, { extraServices: values || [] }); + }, + }), + ), + React.createElement('div', { className: 'lc-lease-order-card__row lc-lease-order-card__row--lease-period' }, + React.createElement(FormField, { label: '租赁期限' }, + React.createElement('div', { + 'data-annotation-id': index === 0 ? 'lc-lease-order-lease-period' : undefined, + }, + React.createElement(LeaseOrderLeasePeriodField, { + row: row, + onChange: function (patch) { props.onChange(row.id, patch); }, + }), + ), + ), + ), ); } function LeaseOrderEditor(props) { - var order = props.order || createDefaultLeaseOrderState(); var onChange = props.onChange; + var order = normalizeLeaseOrderState(props.order || createDefaultLeaseOrderState()); var rows = order.rows || []; + var deliveryRegionMode = order.deliveryRegionMode || 'tbd'; + var deliveryDateMode = order.deliveryDateMode || 'unconfirmed'; + var deliveryDateRangeValue = order.deliveryDateStart && order.deliveryDateEnd + ? [dayjs(order.deliveryDateStart), dayjs(order.deliveryDateEnd)] + : null; + + function emitOrder(next) { + onChange(normalizeLeaseOrderState(next)); + } function patchOrder(patch) { - var next = Object.assign({}, order, patch); - if (!isInsuredVehicleCountEditable(next)) { - next.insuredVehicleCount = calcInsuredVehicleCount(next); - } - if ('insuredVehicleCount' in patch || 'rows' in patch) { - next.rows = (next.rows || rows).map(function (row) { - var subtotal = calcRentServiceSubtotal( - row.rent, - row.serviceFee, - getRowVehicleCountForPricing(row, next), - ); - return Object.assign({}, row, { rentServiceSubtotal: subtotal }); - }); - } - onChange(next); + emitOrder(Object.assign({}, order, patch)); } function updateRows(nextRows) { @@ -1108,34 +1974,35 @@ function LeaseOrderEditor(props) { function updateRow(id, patch) { updateRows(rows.map(function (row) { - if (row.id !== id) return row; - var next = Object.assign({}, row, patch); - if ('rent' in patch || 'serviceFee' in patch || 'plateNos' in patch || 'brandModels' in patch) { - next.rentServiceSubtotal = calcRentServiceSubtotal( - next.rent, - next.serviceFee, - getRowVehicleCountForPricing(next, Object.assign({}, order, { rows: rows })), - ); - } - return next; + if (row.id !== id) return syncRowPricingFields(syncRowPlateFields(row)); + return syncRowPricingFields(syncRowPlateFields(Object.assign({}, row, patch))); })); } + function addRow() { + updateRows(rows.concat([createEmptyLeaseOrderRow()])); + } + function removeRow(id) { - if (rows.length <= 1) { - updateRows([createEmptyLeaseOrderRow()]); - return; - } + if (rows.length <= 1) return; updateRows(rows.filter(function (row) { return row.id !== id; })); } - var deliveryDateValue = order.deliveryDate ? dayjs(order.deliveryDate) : null; var insuredVehicleCount = calcInsuredVehicleCount(order); - var insuredCountEditable = isInsuredVehicleCountEditable(order); var orderKpis = calcLeaseOrderKpis(rows, order); return React.createElement('div', { className: 'lc-lease-order-editor' }, React.createElement('div', { className: 'lc-lease-order-kpi-row vm-kpi-row', role: 'group', 'aria-label': '订单费用汇总' }, + React.createElement('div', { className: 'vm-kpi-card lc-lease-order-kpi-card lc-lease-order-kpi-card--count' }, + React.createElement('span', { className: 'lc-lease-order-kpi-card__icon', 'aria-hidden': true }, '辆'), + React.createElement('span', { className: 'vm-kpi-main' }, + React.createElement('span', { className: 'vm-kpi-eyebrow' }, '租赁车辆数'), + React.createElement('span', { + className: 'vm-kpi-val tabular-nums', + 'data-annotation-id': 'lc-lease-order-insured-count', + }, insuredVehicleCount + ' 辆'), + ), + ), React.createElement('div', { className: 'vm-kpi-card lc-lease-order-kpi-card lc-lease-order-kpi-card--rent' }, React.createElement('span', { className: 'lc-lease-order-kpi-card__icon', 'aria-hidden': true }, '¥'), React.createElement('span', { className: 'vm-kpi-main' }, @@ -1158,53 +2025,28 @@ function LeaseOrderEditor(props) { ), ), ), - React.createElement('div', { className: 'lc-lease-order-editor__summary lc-form-grid lc-form-grid--pair' }, - React.createElement(FormField, { label: '租赁车辆数' }, - React.createElement('div', { className: 'lc-form-inline-sentence' }, - React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '租赁车辆含保险总计'), - insuredCountEditable - ? React.createElement(InputNumber, { - className: 'lc-form-number lc-form-inline-sentence__input vm-focus-border', - controls: false, - min: 1, - step: 1, - precision: 0, - value: order.insuredVehicleCount != null ? order.insuredVehicleCount : insuredVehicleCount, - onChange: function (v) { patchOrder({ insuredVehicleCount: v }); }, - 'aria-label': '租赁车辆含保险总计', - 'data-annotation-id': 'lc-lease-order-insured-count', - }) - : React.createElement('div', { className: 'lc-form-inline-sentence__readonly' }, - React.createElement(ReadonlyValue, { - value: insuredVehicleCount > 0 ? String(insuredVehicleCount) : '', - label: '租赁车辆含保险总计', - }), - ), - React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '辆'), - ), - ), + React.createElement('div', { className: 'lc-lease-order-editor__summary lc-form-grid' }, React.createElement(FormField, { - label: '保险金额/辆', + label: '保险金额', labelHint: React.createElement(FormFieldLabelHint, { annotationId: 'lc-lease-order-third-party-hint', - text: '仅记录保额并同步至右侧预览,不计入本卡片及其他费用计算。', + text: '此处仅记录保额,不计入费用计算', }), }, - React.createElement('div', { className: 'lc-form-inline-sentence' }, - React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '三者责任险'), - React.createElement(InputNumber, { - className: 'lc-form-number lc-form-inline-sentence__input vm-focus-border', - controls: false, - min: 0, - step: 0.01, - precision: 2, - value: order.thirdPartyLiabilityMillion, - onChange: function (v) { patchOrder({ thirdPartyLiabilityMillion: v }); }, - 'aria-label': '三者责任险金额', - 'data-annotation-id': 'lc-lease-order-third-party', - }), - React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '万元、车损'), - ), + React.createElement(InputNumber, { + className: 'lc-form-number vm-focus-border', + controls: false, + min: 0, + step: 0.01, + precision: 2, + value: order.thirdPartyLiabilityMillion, + onChange: function (v) { patchOrder({ thirdPartyLiabilityMillion: v }); }, + placeholder: '请输入保险金额,单位为「万元」', + addonAfter: '万元', + style: { width: '100%' }, + 'aria-label': '保险金额', + 'data-annotation-id': 'lc-lease-order-third-party', + }), ), ), React.createElement('div', { @@ -1214,199 +2056,142 @@ function LeaseOrderEditor(props) { }, React.createElement(BrandModelsClauseHint, { brandModels: aggregateOrderBrandModels(rows) }), ), - 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('thead', null, - React.createElement('tr', { className: 'lc-lease-order-editor__head-row' }, - React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__th-brand' }, '品牌 / 车型'), - React.createElement('th', { rowSpan: 2, scope: 'col' }, '车牌号'), - 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', { rowSpan: 2, scope: 'col' }, '保证金(元/辆)'), - React.createElement('th', { - rowSpan: 2, - scope: 'col', - className: 'lc-lease-order-editor__th-period', - 'data-annotation-id': 'lc-lease-order-lease-period', - }, '租赁期限(月)'), - React.createElement('th', { rowSpan: 2, scope: 'col', className: 'lc-lease-order-editor__actions-col' }, '操作'), + React.createElement('div', { + className: 'lc-lease-order-editor__cards', + role: 'region', + 'aria-label': '租赁订单车型卡片', + }, + rows.map(function (row, index) { + return React.createElement(LeaseOrderVehicleCard, { + key: row.id, + row: row, + index: index, + canDelete: rows.length > 1 && !row._flowLocked, + onChange: updateRow, + onRemove: removeRow, + }); + }), + ), + React.createElement('div', { className: 'lc-lease-order-editor__toolbar' }, + React.createElement(Button, { + type: 'dashed', + className: 'lc-lease-order-editor__add-btn', + onClick: addRow, + icon: React.createElement(Plus, { size: 14, 'aria-hidden': true }), + 'data-annotation-id': 'lc-lease-order-add-row', + }, '新增车型'), + ), + React.createElement('div', { className: 'lc-lease-order-editor__delivery' }, + React.createElement('div', { className: 'lc-lease-order-delivery-grid' }, + React.createElement('section', { className: 'lc-lease-order-delivery-panel' }, + React.createElement('div', { className: 'lc-lease-order-delivery-panel__header' }, + React.createElement('span', { className: 'lc-lease-order-delivery-panel__title' }, '车辆交付(交还)地点'), + React.createElement(FormFieldLabelHint, { + annotationId: 'lc-lease-order-delivery-region-hint', + text: '由对应区域运维负责交/还车', + }), ), - React.createElement('tr', { className: 'lc-lease-order-editor__head-row lc-lease-order-editor__head-row--sub' }, - React.createElement('th', { scope: 'col' }, '车辆租金(元/月/辆,含税)'), - React.createElement('th', { scope: 'col' }, '服务费(含税)'), - React.createElement('th', { scope: 'col' }, '服务内容'), - ), - ), - React.createElement('tbody', null, - rows.map(function (row) { - var extraServices = normalizeExtraServices(row); - var brandModels = normalizeBrandModels(row); - var plateNos = normalizePlateNos(row); - var vehicleCount = getRowVehicleCountForPricing(row, order); - var subtotal = calcRentServiceSubtotal(row.rent, row.serviceFee, vehicleCount); - var rentBelowMin = brandModels.some(function (pair) { - return isLeaseRentBelowMinimum(row.rent, pair[0], pair[1]); - }); - var minRentHint = brandModels.map(function (pair) { - var minRent = getLeaseMinRentForBrandModel(pair[0], pair[1]); - if (minRent == null) return null; - return formatBrandModelPair(pair[0], pair[1]) + ' 最低 ' + minRent.toLocaleString('zh-CN') + ' 元/月'; - }).filter(Boolean).join(';'); - return React.createElement('tr', { key: row.id }, - React.createElement('td', { className: 'lc-lease-order-editor__brand-cell' }, - React.createElement(LeaseOrderBrandModelField, { - value: brandModels, - onChange: function (values) { - updateRow(row.id, { brandModels: values || [] }); - }, - }), - ), - React.createElement('td', null, - React.createElement(LeaseOrderPlateNoField, { - value: plateNos, - brandModels: brandModels, - onChange: function (values) { - updateRow(row.id, { plateNos: values && values.length ? values : [PLATE_ACTUAL_DELIVERY] }); - }, - }), - ), - React.createElement('td', null, - React.createElement(LeaseOrderAmountField, null, - React.createElement(ReadonlyValue, { - value: subtotal != null ? String(subtotal) : '', - label: '租金及服务费小计', - }), - ), - ), - React.createElement('td', null, - React.createElement(LeaseOrderAmountField, null, - React.createElement(InputNumber, { - className: 'lc-form-number vm-focus-border' + (rentBelowMin ? ' lc-form-number--warn' : ''), - controls: false, - min: 0, - step: 0.01, - precision: 2, - value: row.rent, - onChange: function (v) { updateRow(row.id, { rent: v }); }, - placeholder: '车辆租金', - style: { width: '100%' }, - 'aria-label': '车辆租金', - 'data-annotation-id': 'lc-lease-order-rent', - }), - rentBelowMin ? React.createElement('p', { - className: 'lc-rent-min-warn', - 'data-annotation-id': 'lc-lease-order-rent-min-warn', - role: 'alert', - }, '低于系统最低租金,将触发非标准合同审批' + (minRentHint ? '(' + minRentHint + ')' : '')) : null, - ), - ), - React.createElement('td', null, - React.createElement(LeaseOrderAmountField, null, - React.createElement(InputNumber, { - className: 'lc-form-number vm-focus-border', - controls: false, - min: 0, - max: 99, - step: 1, - precision: 0, - value: row.serviceFee, - onChange: function (v) { updateRow(row.id, { serviceFee: v }); }, - placeholder: '服务费', - style: { width: '100%' }, - 'aria-label': '服务费', - }), - ), - ), - React.createElement('td', null, - React.createElement(LeaseOrderServiceContentField, { - value: extraServices, - onChange: function (values) { - updateRow(row.id, { extraServices: values || [] }); - }, - }), - ), - React.createElement('td', null, - React.createElement(LeaseOrderAmountField, null, - React.createElement(InputNumber, { - className: 'lc-form-number vm-focus-border', - controls: false, - min: 0, - step: 0.01, - precision: 2, - value: row.deposit, - onChange: function (v) { updateRow(row.id, { deposit: v }); }, - style: { width: '100%' }, - 'aria-label': '保证金', - }), - ), - ), - React.createElement('td', { className: 'lc-lease-order-editor__period-cell' }, - React.createElement(InputNumber, { - className: 'lc-form-number lc-lease-order-period-input vm-focus-border', - controls: false, - min: 1, - step: 1, - precision: 0, - value: row.leasePeriodMonths, - onChange: function (v) { - updateRow(row.id, { - leasePeriodMonths: v, - leasePeriodStart: null, - leasePeriodEnd: null, + React.createElement('div', { className: 'lc-lease-order-delivery-field' }, + React.createElement(LeaseOrderModeToggle, { + layout: 'segmented', + 'aria-label': '交付地点模式', + value: deliveryRegionMode, + onChange: function (mode) { + if (mode === 'tbd') { + patchOrder({ + deliveryRegionMode: 'tbd', + deliveryRegionTbd: true, + deliveryRegion: [], + }); + return; + } + patchOrder({ + deliveryRegionMode: 'region', + deliveryRegionTbd: false, + }); + }, + options: [ + { value: 'region', label: '选择省市' }, + { value: 'tbd', label: DELIVERY_REGION_TBD_LABEL }, + ], + }), + React.createElement('div', { className: 'lc-lease-order-delivery-field__control' }, + deliveryRegionMode === 'region' + ? React.createElement(LeaseOrderDeliveryRegionField, { + value: order.deliveryRegion && order.deliveryRegion.length ? order.deliveryRegion : [], + onChange: function (value) { + patchOrder({ + deliveryRegionMode: 'region', + deliveryRegionTbd: false, + deliveryRegion: value || [], }); }, - placeholder: '月数', - 'aria-label': '车辆租赁期限', + }) + : React.createElement(ReadonlyValue, { + value: DELIVERY_REGION_TBD_DISPLAY, + label: '车辆交付交还地点', + computed: true, }), - ), - React.createElement('td', { className: 'lc-lease-order-editor__actions-col' }, - React.createElement(LeaseOrderRowActions, { - rowId: row.id, - onRemove: removeRow, - }), - ), - ); - }), + ), + ), + ), + React.createElement('section', { className: 'lc-lease-order-delivery-panel' }, + React.createElement('div', { className: 'lc-lease-order-delivery-panel__header' }, + React.createElement('span', { className: 'lc-lease-order-delivery-panel__title' }, '车辆交付时间'), + ), + React.createElement('div', { className: 'lc-lease-order-delivery-field' }, + React.createElement(LeaseOrderModeToggle, { + layout: 'segmented', + 'aria-label': '交付时间模式', + value: deliveryDateMode, + onChange: function (mode) { + if (mode === 'unconfirmed') { + patchOrder({ + deliveryDateMode: 'unconfirmed', + deliveryDateTbd: true, + deliveryDate: null, + deliveryDateStart: null, + deliveryDateEnd: null, + }); + return; + } + patchOrder({ + deliveryDateMode: 'range', + deliveryDateTbd: false, + }); + }, + options: [ + { value: 'range', label: '选择日期' }, + { value: 'unconfirmed', label: LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL }, + ], + }), + React.createElement('div', { className: 'lc-lease-order-delivery-field__control' }, + deliveryDateMode === 'range' + ? React.createElement(LeaseOrderRangePicker, { + className: 'lc-form-datepicker vm-focus-border', + style: { width: '100%' }, + placeholder: ['开始日期', '结束日期'], + value: deliveryDateRangeValue, + onChange: function (_dates, dateStrings) { + patchOrder({ + deliveryDateMode: 'range', + deliveryDateTbd: false, + deliveryDateStart: dateStrings && dateStrings[0] ? dateStrings[0] : null, + deliveryDateEnd: dateStrings && dateStrings[1] ? dateStrings[1] : null, + deliveryDate: dateStrings && dateStrings[0] ? dateStrings[0] : null, + }); + }, + 'aria-label': '车辆交付时间范围', + 'data-annotation-id': 'lc-lease-order-delivery-date', + }) + : React.createElement(ReadonlyValue, { + value: LEASE_DELIVERY_DATE_UNCONFIRMED_DISPLAY, + label: '车辆交付时间', + computed: true, + }), + ), + ), ), - ), - ), - React.createElement('div', { className: 'lc-lease-order-editor__delivery lc-form-grid lc-form-grid--pair' }, - React.createElement(FormField, { - label: '车辆交付(交还)地点', - labelHint: React.createElement(FormFieldLabelHint, { - annotationId: 'lc-lease-order-delivery-region-hint', - text: '交还地点决定由哪个区域运维负责交车与还车。', - }), - }, - React.createElement(Cascader, { - className: 'lc-form-cascader vm-focus-border', - options: PROVINCE_CITY_CASCADER_OPTIONS, - value: order.deliveryRegion && order.deliveryRegion.length ? order.deliveryRegion : undefined, - onChange: function (value) { patchOrder({ deliveryRegion: value || [] }); }, - placeholder: '请选择省 / 市', - showSearch: true, - 'aria-label': '车辆交付交还地点', - 'data-annotation-id': 'lc-lease-order-delivery-region', - }), - ), - React.createElement(FormField, { - label: '车辆交付时间', - labelHint: React.createElement(FormFieldLabelHint, { - annotationId: 'lc-lease-order-delivery-date-hint', - text: '用于生成交车任务时,提示运维何时交车。', - }), - }, - React.createElement(DatePicker, { - className: 'lc-form-datepicker vm-focus-border', - style: { width: '100%' }, - placeholder: '请选择日期', - value: deliveryDateValue, - onChange: function (_date, dateString) { - patchOrder({ deliveryDate: dateString || null }); - }, - 'aria-label': '车辆交付时间', - 'data-annotation-id': 'lc-lease-order-delivery-date', - }), ), ), ); @@ -1595,6 +2380,16 @@ function FormFieldLabelHint(props) { ); } +function FormFieldHint(props) { + if (!props.text) return null; + return React.createElement('span', { + className: 'lc-form-field__hint', + role: 'note', + id: props.id, + 'data-annotation-id': props.annotationId || undefined, + }, props.text); +} + function FormField(props) { var labelContent = [ props.required ? React.createElement('span', { className: 'lc-form-field__required', 'aria-hidden': 'true' }, '*') : null, @@ -1610,13 +2405,39 @@ function FormField(props) { return React.createElement('label', { className: 'lc-form-field' }, labelEl, props.children, + props.hint ? React.createElement(FormFieldHint, { + text: props.hint, + id: props.hintId, + annotationId: props.hintAnnotationId, + }) : null, ); } +function getPaymentMethodFieldHint(paymentMethod) { + if (paymentMethod === 'advance') return '当月25日生成下个周期租赁账单'; + if (paymentMethod === 'postpay') return '次月1日生成上个周期租赁账单'; + return ''; +} + +function getPaymentPeriodFieldHint(period) { + var months = period != null ? period : 1; + return '账单周期为' + months + '个月'; +} + +function getHydrogenPaymentFieldHint(method) { + if (method === 'self') return '车辆氢费明细承担方式:客户自行结算'; + if (method === 'prepay') return '车辆氢费明细承担方式:客户承担;无能源账户时自动创建并从账户扣除'; + if (method === 'month') return '车辆氢费明细承担方式:客户承担;每月5日生成上月氢费对账任务(工作台待办)'; + return ''; +} + function ReadonlyValue(props) { var hasValue = props.value != null && String(props.value).trim() !== ''; + var className = 'lc-form-readonly lc-form-readonly--synced' + + (props.numeric ? ' tabular-nums' : '') + + (props.computed ? ' lc-form-readonly--computed' : ''); return React.createElement('div', { - className: 'lc-form-readonly lc-form-readonly--synced', + className: className, 'aria-readonly': 'true', 'aria-label': props.label, }, @@ -1703,33 +2524,152 @@ function EditorCard(props) { export default function LeaseContractEditorForm(props) { var lessorId = props.lessorId; var customerId = props.customerId; + var thirdPartyCustomerId = props.thirdPartyCustomerId || ''; + var showThirdPartyParty = props.showThirdPartyParty === true; var contractCode = props.contractCode || ''; + var projectName = props.projectName || ''; var businessDept = props.businessDept || ''; var businessOwner = props.businessOwner || ''; var mileage = props.mileage || DEFAULT_MILEAGE_STANDARD; var feeInfo = props.feeInfo || DEFAULT_FEE_INFO; var onLessorChange = props.onLessorChange; var onCustomerChange = props.onCustomerChange; + var onThirdPartyCustomerChange = props.onThirdPartyCustomerChange; var onContractCodeChange = props.onContractCodeChange; + var onProjectNameChange = props.onProjectNameChange; var onBusinessAssignmentChange = props.onBusinessAssignmentChange; var onMileageChange = props.onMileageChange; var onFeeInfoChange = props.onFeeInfoChange; var customerPrincipalName = props.customerPrincipalName || ''; var customerPrincipalPhone = props.customerPrincipalPhone || ''; var onCustomerPrincipalChange = props.onCustomerPrincipalChange; + var thirdPartyPrincipalName = props.thirdPartyPrincipalName || ''; + var thirdPartyPrincipalPhone = props.thirdPartyPrincipalPhone || ''; + var onThirdPartyPrincipalChange = props.onThirdPartyPrincipalChange; var contractCodeReadonly = props.contractCodeReadonly !== false; var lessorCompanies = getLessorCompanies(); var selectedLessor = getLessorCompanyById(lessorId); var selectedCustomer = getLeaseCustomerById(customerId); + var selectedThirdPartyCustomer = getLeaseCustomerById(thirdPartyCustomerId); var invoice = getCustomerInvoicePreview(selectedCustomer); + var thirdPartyInvoice = getCustomerInvoicePreview(selectedThirdPartyCustomer); var lessorAccount = getLessorAccountPreview(selectedLessor); var lessorContact = getLessorContactPreview(selectedLessor); var customerContact = getCustomerContactPreview(selectedCustomer); + var thirdPartyContact = getCustomerContactPreview(selectedThirdPartyCustomer); var lessorEmptyHint = lessorId ? undefined : '请先选择甲方签约主体'; var customerEmptyHint = customerId ? undefined : '请先选择乙方客户'; + var thirdPartyEmptyHint = thirdPartyCustomerId ? undefined : '请先选择丙方客户'; var showPrepayAmount = feeInfo.hydrogenPaymentMethod === 'prepay'; + function renderCustomerPrincipalFields(partyPrefix, principalName, principalPhone, onPrincipalChange, annotationId) { + return React.createElement('div', { + className: 'lc-form-grid lc-form-grid--pair lc-signing-customer-principal', + 'data-annotation-id': annotationId, + }, + React.createElement(FormField, { label: partyPrefix + '负责人姓名' }, + React.createElement(Input, { + className: 'lc-form-input vm-focus-border', + value: principalName, + maxLength: 32, + placeholder: '请输入' + partyPrefix + '负责人姓名', + onChange: function (e) { + if (onPrincipalChange) onPrincipalChange(e.target.value, principalPhone); + }, + 'aria-label': partyPrefix + '负责人姓名', + }), + ), + React.createElement(FormField, { + label: partyPrefix + '负责人手机号', + labelHint: React.createElement(FormFieldLabelHint, { + annotationId: annotationId + '-phone-hint', + text: 'E签宝电子合同签章时' + partyPrefix + '经办人', + }), + hint: '提交审核且审批通过后:选择「线上电子签章」时,系统将向该手机号发送 E签宝电子签章文件;选择「线下人工上传」时不发送电子签,需在列表通过「盖章合同补传」上传 PDF/图片附件,查看时可预览与下载。', + }, + React.createElement(Input, { + className: 'lc-form-input vm-focus-border tabular-nums', + value: principalPhone, + maxLength: 11, + placeholder: '请输入手机号', + onChange: function (e) { + if (onPrincipalChange) onPrincipalChange(principalName, e.target.value); + }, + 'aria-label': partyPrefix + '负责人手机号', + }), + ), + ); + } + + function renderCustomerReadonlyPanel(customer, partyLabel, emptyHint, titleId, panelId, annotationId) { + if (!customer) return null; + var partyInvoice = getCustomerInvoicePreview(customer); + var partyContact = getCustomerContactPreview(customer); + return React.createElement(ReadonlyPanel, { + titleId: titleId, + title: partyLabel + '信息', + badge: '只读 · 客户档案', + }, + React.createElement('div', { + id: panelId, + className: 'lc-form-grid lc-form-grid--cols-2 lc-form-grid--readonly lc-invoice-readonly-panel', + 'aria-label': partyLabel + '信息,来自客户表只读', + 'data-annotation-id': annotationId, + }, + React.createElement(FormField, { label: '企业名称' }, React.createElement(ReadonlyValue, { value: partyInvoice.companyName, emptyHint: emptyHint })), + React.createElement(FormField, { label: '开户银行' }, React.createElement(ReadonlyValue, { value: partyInvoice.bank, emptyHint: emptyHint })), + React.createElement(FormField, { label: '银行账号' }, React.createElement(ReadonlyValue, { value: partyInvoice.bankAccount, emptyHint: emptyHint, numeric: true })), + React.createElement(FormField, { label: '纳税人识别号' }, React.createElement(ReadonlyValue, { value: partyInvoice.taxId, emptyHint: emptyHint })), + React.createElement(FormField, { label: '企业地址' }, React.createElement(ReadonlyValue, { value: partyInvoice.mailingAddress, emptyHint: emptyHint })), + React.createElement(FormField, { label: '企业电话' }, React.createElement(ReadonlyValue, { value: partyInvoice.companyPhone, emptyHint: emptyHint, numeric: true })), + React.createElement(FormField, { label: '通讯地址' }, React.createElement(ReadonlyValue, { value: partyContact.mailAddress, emptyHint: emptyHint })), + React.createElement(FormField, { label: '联系人姓名及电话' }, + React.createElement('div', { className: 'lc-form-contact-pair' }, + React.createElement(ReadonlyValue, { value: partyContact.contactName, emptyHint: emptyHint }), + React.createElement(ReadonlyValue, { value: partyContact.contactPhone, emptyHint: emptyHint, numeric: true }), + ), + ), + React.createElement(FormField, { label: '邮箱' }, React.createElement(ReadonlyValue, { value: partyContact.email, emptyHint: emptyHint })), + ), + ); + } + + function renderCustomerCredentialsBlock(customer, annotationSuffix) { + if (!customer) return null; + var credentialSummary = summarizeCustomerCredentials(customer); + return React.createElement('div', { + className: 'lc-credentials-block', + 'data-annotation-id': 'lc-credentials-ocr' + (annotationSuffix || ''), + }, + React.createElement('div', { className: 'lc-credentials-block__head' }, + React.createElement('h4', { className: 'lc-credentials-block__title' }, '客户资质证照'), + React.createElement('span', { className: 'lc-credentials-block__hint' }, 'OCR 识别有效期 · 点击缩略图放大'), + ), + React.createElement('p', { className: 'lc-credentials-block__policy' }, + '营业执照、法人身份证正反面及道路运输许可证上传后自动 OCR 识别并校验有效期;' + + '距到期 ' + + CREDENTIAL_EXPIRY_WARN_MONTHS + + ' 个月内将在工作台生成待办推送给业管;存在已过期证照时禁止提交新合同。', + ), + React.createElement(CustomerCredentialsAlerts, { summary: credentialSummary }), + React.createElement('div', { + className: 'lc-attach-gallery', + 'aria-label': '客户资质证照预览', + }, + credentialSummary.items.map(function (item) { + var previewUrl = getCustomerAttachmentPreviewUrl(customer, item.key, item.label); + return React.createElement(CustomerCredentialThumb, { + key: item.key, + label: item.label, + previewUrl: previewUrl, + meta: item, + }); + }), + ), + ); + } + function patchMileage(patch) { onMileageChange(Object.assign({}, mileage, patch)); } @@ -1752,7 +2692,7 @@ export default function LeaseContractEditorForm(props) { React.createElement(EditorCard, { titleId: 'lc-card-signing', title: '签约信息', - desc: '系统自动生成合同编号并选择甲乙双方;下方将自动带出档案信息。', + desc: '请选择合同签订各方信息', collapsible: true, expanded: signingExpanded, onToggle: function () { setSigningExpanded(!signingExpanded); }, @@ -1760,104 +2700,163 @@ export default function LeaseContractEditorForm(props) { annotationId: 'lc-card-signing', }, React.createElement('div', { className: 'lc-signing-core' }, - React.createElement('div', { className: 'lc-form-grid' }, - React.createElement(FormField, { label: '合同编号', required: true }, - React.createElement('div', { - className: 'lc-contract-code-auto', - 'data-annotation-id': 'lc-contract-code-auto', - }, - React.createElement('span', { className: 'lc-contract-code-auto__prefix' }, CONTRACT_CODE_PREFIX), - React.createElement(ReadonlyValue, { - value: contractCode || '—', - label: '合同编号', - emptyHint: '系统生成中…', - }), - contractCodeReadonly - ? React.createElement('span', { className: 'lc-contract-code-auto__tag' }, '系统自动生成') + React.createElement('div', { className: 'lc-signing-core-fields' }, + React.createElement('div', { className: 'lc-signing-meta-row' }, + React.createElement(FormField, { + label: '合同编码', + required: true, + labelHint: contractCodeReadonly + ? React.createElement('span', { + className: 'lc-contract-code-auto__tag', + 'data-annotation-id': 'lc-contract-code-auto-tag', + }, '系统自动生成') : null, - ), - ), - 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', { + 'data-annotation-id': 'lc-contract-code-auto', }, - }), - ), - React.createElement('div', { className: 'lc-form-grid lc-form-grid--pair' }, - React.createElement(FormField, { - label: '甲方', - required: true, - labelHint: lessorId - ? React.createElement(FormFieldLabelHint, { - annotationId: 'lc-lessor-select-hint', - text: '将使用该公司用章进行电子签章', - }) - : null, - }, - React.createElement(Select, { - className: 'lc-form-select vm-focus-border', - placeholder: '请选择甲方签约主体', - value: lessorId || undefined, - allowClear: true, - onChange: function (value) { onLessorChange(value || ''); }, - options: lessorCompanies.map(function (c) { - return { value: c.id, label: c.legalName }; + React.createElement(ReadonlyValue, { + value: CONTRACT_CODE_PREFIX + (contractCode || ''), + numeric: true, + computed: true, + label: '合同编码', + emptyHint: '系统生成中…', }), - 'aria-label': '甲方签约主体', - }), + ), ), React.createElement(FormField, { - label: '乙方', + label: '业务部门 / 业务人员', required: true, }, - React.createElement(LeaseCustomerPickerField, { - value: customerId, - onChange: function (value) { onCustomerChange(value || ''); }, + React.createElement(BusinessDeptOwnerPickerField, { + businessDept: businessDept, + businessOwner: businessOwner, + onChange: function (dept, owner) { + if (onBusinessAssignmentChange) onBusinessAssignmentChange(dept, owner); + }, }), ), ), + React.createElement('div', { className: 'lc-signing-project-row' }, + React.createElement(FormField, { + label: '项目名称', + required: true, + }, + React.createElement('div', { className: 'lc-form-field__control' }, + React.createElement(Input, { + className: 'lc-form-input vm-focus-border', + placeholder: '请输入项目名称', + value: projectName, + allowClear: true, + maxLength: 64, + onChange: function (e) { + if (onProjectNameChange) onProjectNameChange(e.target.value); + }, + 'aria-label': '项目名称', + }), + ), + ), + ), + showThirdPartyParty + ? React.createElement('div', { + className: 'lc-form-grid lc-form-grid--cols-3 lc-form-grid--tripartite-parties', + 'data-annotation-id': 'lc-tripartite-parties', + }, + React.createElement(FormField, { + label: '甲方', + required: true, + labelHint: lessorId + ? React.createElement(FormFieldLabelHint, { + annotationId: 'lc-lessor-select-hint', + text: '将使用该公司用章进行电子签章', + }) + : null, + }, + React.createElement(Select, { + className: 'lc-form-select vm-focus-border', + placeholder: '请选择甲方签约主体', + value: lessorId || undefined, + allowClear: true, + onChange: function (value) { onLessorChange(value || ''); }, + options: lessorCompanies.map(function (c) { + return { value: c.id, label: c.legalName }; + }), + 'aria-label': '甲方签约主体', + }), + ), + React.createElement(FormField, { + label: '乙方', + required: true, + }, + React.createElement(LeaseCustomerPickerField, { + value: customerId, + onChange: function (value) { onCustomerChange(value || ''); }, + partyLabel: '乙方', + }), + ), + React.createElement(FormField, { + label: '丙方', + required: true, + }, + React.createElement('div', { 'data-annotation-id': 'lc-tripartite-third-party-picker' }, + React.createElement(LeaseCustomerPickerField, { + value: thirdPartyCustomerId, + onChange: function (value) { + if (onThirdPartyCustomerChange) onThirdPartyCustomerChange(value || ''); + }, + partyLabel: '丙方', + excludeCustomerId: customerId, + }), + ), + ), + ) + : React.createElement('div', { className: 'lc-form-grid lc-form-grid--pair' }, + React.createElement(FormField, { + label: '甲方', + required: true, + labelHint: lessorId + ? React.createElement(FormFieldLabelHint, { + annotationId: 'lc-lessor-select-hint', + text: '将使用该公司用章进行电子签章', + }) + : null, + }, + React.createElement(Select, { + className: 'lc-form-select vm-focus-border', + placeholder: '请选择甲方签约主体', + value: lessorId || undefined, + allowClear: true, + onChange: function (value) { onLessorChange(value || ''); }, + options: lessorCompanies.map(function (c) { + return { value: c.id, label: c.legalName }; + }), + 'aria-label': '甲方签约主体', + }), + ), + React.createElement(FormField, { + label: '乙方', + required: true, + }, + React.createElement(LeaseCustomerPickerField, { + value: customerId, + onChange: function (value) { onCustomerChange(value || ''); }, + partyLabel: '乙方', + }), + ), + ), ), ), - customerId ? React.createElement('div', { - className: 'lc-form-grid lc-form-grid--pair lc-signing-customer-principal', - 'data-annotation-id': 'lc-signing-customer-principal', - }, - React.createElement(FormField, { label: '乙方负责人姓名' }, - React.createElement(Input, { - className: 'lc-form-input vm-focus-border', - value: customerPrincipalName, - maxLength: 32, - placeholder: '请输入乙方负责人姓名', - onChange: function (e) { - if (onCustomerPrincipalChange) { - onCustomerPrincipalChange(e.target.value, customerPrincipalPhone); - } - }, - 'aria-label': '乙方负责人姓名', - }), - ), - React.createElement(FormField, { label: '乙方负责人手机号' }, - React.createElement(Input, { - className: 'lc-form-input vm-focus-border', - value: customerPrincipalPhone, - maxLength: 11, - placeholder: '请输入手机号', - onChange: function (e) { - if (onCustomerPrincipalChange) { - onCustomerPrincipalChange(customerPrincipalName, e.target.value); - } - }, - 'aria-label': '乙方负责人手机号', - }), - ), - ) : null, - lessorId || customerId + customerId ? renderCustomerPrincipalFields('乙方', customerPrincipalName, customerPrincipalPhone, onCustomerPrincipalChange, 'lc-signing-customer-principal') : null, + showThirdPartyParty && thirdPartyCustomerId + ? renderCustomerPrincipalFields( + '丙方', + thirdPartyPrincipalName, + thirdPartyPrincipalPhone, + onThirdPartyPrincipalChange, + 'lc-signing-third-party-principal', + ) + : null, + lessorId || customerId || thirdPartyCustomerId ? React.createElement('div', { className: 'lc-party-profiles' }, lessorId ? React.createElement(ReadonlyPanel, { titleId: 'lc-lessor-account-title', @@ -1871,88 +2870,49 @@ export default function LeaseContractEditorForm(props) { }, React.createElement(FormField, { label: '户名' }, React.createElement(ReadonlyValue, { value: lessorAccount.accountName, emptyHint: lessorEmptyHint })), React.createElement(FormField, { label: '开户行' }, React.createElement(ReadonlyValue, { value: lessorAccount.bankName, emptyHint: lessorEmptyHint })), - React.createElement(FormField, { label: '账号' }, React.createElement(ReadonlyValue, { value: lessorAccount.bankAccount, emptyHint: lessorEmptyHint })), + React.createElement(FormField, { label: '账号' }, React.createElement(ReadonlyValue, { value: lessorAccount.bankAccount, emptyHint: lessorEmptyHint, numeric: true })), React.createElement(FormField, { label: '通讯地址' }, React.createElement(ReadonlyValue, { value: lessorContact.mailAddress, emptyHint: lessorEmptyHint })), React.createElement(FormField, { label: '联系人姓名及电话' }, React.createElement('div', { className: 'lc-form-contact-pair' }, React.createElement(ReadonlyValue, { value: lessorContact.contactName, emptyHint: lessorEmptyHint }), - React.createElement(ReadonlyValue, { value: lessorContact.contactPhone, emptyHint: lessorEmptyHint }), + React.createElement(ReadonlyValue, { value: lessorContact.contactPhone, emptyHint: lessorEmptyHint, numeric: true }), ), ), React.createElement(FormField, { label: '邮箱' }, React.createElement(ReadonlyValue, { value: lessorContact.email, emptyHint: lessorEmptyHint })), ), ) : null, - customerId ? React.createElement(ReadonlyPanel, { - titleId: 'lc-invoice-info-title', - title: '乙方信息', - badge: '只读 · 客户档案', - }, - React.createElement('div', { - id: 'lc-invoice-info-panel', - className: 'lc-form-grid lc-form-grid--cols-2 lc-form-grid--readonly lc-invoice-readonly-panel', - 'aria-label': '乙方信息,来自客户表只读', - }, - React.createElement(FormField, { label: '企业名称' }, React.createElement(ReadonlyValue, { value: invoice.companyName, emptyHint: customerEmptyHint })), - React.createElement(FormField, { label: '开户银行' }, React.createElement(ReadonlyValue, { value: invoice.bank, emptyHint: customerEmptyHint })), - React.createElement(FormField, { label: '银行账号' }, React.createElement(ReadonlyValue, { value: invoice.bankAccount, emptyHint: customerEmptyHint })), - React.createElement(FormField, { label: '纳税人识别号' }, React.createElement(ReadonlyValue, { value: invoice.taxId, emptyHint: customerEmptyHint })), - React.createElement(FormField, { label: '企业地址' }, React.createElement(ReadonlyValue, { value: invoice.mailingAddress, emptyHint: customerEmptyHint })), - React.createElement(FormField, { label: '企业电话' }, React.createElement(ReadonlyValue, { value: invoice.companyPhone, emptyHint: customerEmptyHint })), - React.createElement(FormField, { label: '通讯地址' }, React.createElement(ReadonlyValue, { value: customerContact.mailAddress, emptyHint: customerEmptyHint })), - React.createElement(FormField, { label: '联系人姓名及电话' }, - React.createElement('div', { className: 'lc-form-contact-pair' }, - React.createElement(ReadonlyValue, { value: customerContact.contactName, emptyHint: customerEmptyHint }), - React.createElement(ReadonlyValue, { value: customerContact.contactPhone, emptyHint: customerEmptyHint }), - ), - ), - React.createElement(FormField, { label: '邮箱' }, React.createElement(ReadonlyValue, { value: customerContact.email, emptyHint: customerEmptyHint })), - ), + customerId ? renderCustomerReadonlyPanel( + selectedCustomer, + '乙方', + customerEmptyHint, + 'lc-invoice-info-title', + 'lc-invoice-info-panel', + 'lc-invoice-info', ) : null, - customerId ? (function () { - var credentialSummary = summarizeCustomerCredentials(selectedCustomer); - return React.createElement('div', { - className: 'lc-credentials-block', - 'data-annotation-id': 'lc-credentials-ocr', - }, - React.createElement('div', { className: 'lc-credentials-block__head' }, - React.createElement('h4', { className: 'lc-credentials-block__title' }, '客户资质证照'), - React.createElement('span', { className: 'lc-credentials-block__hint' }, 'OCR 识别有效期 · 点击缩略图放大'), - ), - React.createElement('p', { className: 'lc-credentials-block__policy' }, - '营业执照、法人身份证正反面及道路运输许可证上传后自动 OCR 识别并校验有效期;' - + '距到期 ' - + CREDENTIAL_EXPIRY_WARN_MONTHS - + ' 个月内将在工作台生成待办推送给业管;存在已过期证照时禁止提交新合同。', - ), - React.createElement(CustomerCredentialsAlerts, { summary: credentialSummary }), - React.createElement('div', { - className: 'lc-attach-gallery', - 'aria-label': '客户资质证照预览', - }, - credentialSummary.items.map(function (item) { - var previewUrl = getCustomerAttachmentPreviewUrl(selectedCustomer, item.key, item.label); - return React.createElement(CustomerCredentialThumb, { - key: item.key, - label: item.label, - previewUrl: previewUrl, - meta: item, - }); - }), - ), - ); - }()) : null, + thirdPartyCustomerId ? renderCustomerReadonlyPanel( + selectedThirdPartyCustomer, + '丙方', + thirdPartyEmptyHint, + 'lc-third-party-info-title', + 'lc-third-party-info-panel', + 'lc-third-party-info', + ) : null, + customerId ? renderCustomerCredentialsBlock(selectedCustomer, '') : null, + thirdPartyCustomerId ? renderCustomerCredentialsBlock(selectedThirdPartyCustomer, '-third-party') : null, ) : React.createElement('div', { className: 'lc-party-profiles-empty', role: 'status' }, - React.createElement('p', { className: 'lc-party-profiles-empty__title' }, '选择签约双方后展示档案'), + React.createElement('p', { className: 'lc-party-profiles-empty__title' }, '选择签约各方后展示档案'), React.createElement('p', { className: 'lc-party-profiles-empty__desc' }, - '甲方、乙方的账户、联系人与资质证照将从档案自动同步,无需重复填写。', + showThirdPartyParty + ? '甲方、乙方、丙方的账户、联系人与资质证照将从档案自动同步,无需重复填写。' + : '甲方、乙方的账户、联系人与资质证照将从档案自动同步,无需重复填写。', ), ), ), React.createElement(EditorCard, { titleId: 'lc-card-mileage', title: '里程标准', - desc: '对应合同正文中的最低里程与租金减免条款。', + desc: '设置租赁合同里程要求规则和金额', collapsible: true, expanded: mileageExpanded, onToggle: function () { setMileageExpanded(!mileageExpanded); }, @@ -1974,11 +2934,15 @@ export default function LeaseContractEditorForm(props) { }), ), mileage.hasRequirement ? React.createElement('div', { className: 'lc-mileage-types-wrap' }, - React.createElement('span', { className: 'lc-mileage-types-wrap__label' }, '里程要求类型'), + React.createElement('span', { className: 'lc-mileage-types-wrap__label' }, + React.createElement('span', { className: 'lc-form-field__required', 'aria-hidden': 'true' }, '*'), + '里程要求类型', + ), React.createElement('div', { className: 'lc-mileage-types', role: 'radiogroup', 'aria-label': '里程要求类型', + 'aria-required': 'true', }, MILEAGE_PERIOD_OPTIONS.map(function (option) { var checked = mileage.period === option.value; @@ -2015,7 +2979,7 @@ export default function LeaseContractEditorForm(props) { className: 'lc-mileage-reduction', 'data-annotation-id': 'lc-card-mileage-reduction', }, - React.createElement(FormField, { label: '租金减免金额', required: true }, + React.createElement(FormField, { label: '次月租金减免金额', required: true }, React.createElement(InputNumber, { className: 'lc-form-number vm-focus-border', controls: false, @@ -2028,10 +2992,10 @@ export default function LeaseContractEditorForm(props) { }, addonAfter: '元/辆', style: { width: '100%' }, - 'aria-label': '租金减免金额', + 'aria-label': '次月租金减免金额', }), ), - React.createElement(FormField, { label: '减免有效期至' }, + React.createElement(FormField, { label: '减免有效期至', required: true }, React.createElement(DatePicker, { className: 'lc-form-datepicker vm-focus-border', style: { width: '100%' }, @@ -2042,9 +3006,6 @@ export default function LeaseContractEditorForm(props) { 'aria-label': '租金减免有效期', }), ), - React.createElement(FieldHint, { annotationId: 'lc-mileage-reduction-hint' }, - '与合同正文 2.1.5 租金减免条款联动;附件章节删减后,预览区将自动重排剩余章节序号。', - ), ), ) : null, ), @@ -2059,7 +3020,37 @@ export default function LeaseContractEditorForm(props) { bodyId: 'lc-card-fee-body', }, React.createElement('div', { className: 'lc-form-grid lc-form-grid--pair' }, - React.createElement(FormField, { label: '付款周期', required: true }, + React.createElement(FormField, { + label: '付款方式', + required: true, + labelHint: React.createElement(FormFieldLabelHint, { + annotationId: 'lc-payment-method-hint', + text: '决定租赁账单什么时候生成', + }), + hint: getPaymentMethodFieldHint(feeInfo.paymentMethod), + hintId: 'lc-payment-method-detail-hint', + hintAnnotationId: 'lc-payment-method-detail-hint', + }, + React.createElement(Select, { + className: 'lc-form-select vm-focus-border', + value: feeInfo.paymentMethod, + onChange: function (value) { + patchFeeInfo({ + paymentMethod: value || DEFAULT_FEE_INFO.paymentMethod, + }); + }, + options: PAYMENT_METHOD_OPTIONS, + 'aria-label': '付款方式', + 'data-annotation-id': 'lc-payment-method', + }), + ), + React.createElement(FormField, { + label: '付款周期', + required: true, + hint: getPaymentPeriodFieldHint(feeInfo.paymentPeriod), + hintId: 'lc-payment-period-hint', + hintAnnotationId: 'lc-payment-period-hint', + }, React.createElement(Select, { className: 'lc-form-select vm-focus-border', value: feeInfo.paymentPeriod, @@ -2068,9 +3059,16 @@ export default function LeaseContractEditorForm(props) { }, options: PAYMENT_PERIOD_OPTIONS, 'aria-label': '付款周期', + 'data-annotation-id': 'lc-payment-period', }), ), - React.createElement(FormField, { label: '氢费支付方式', required: true }, + React.createElement(FormField, { + label: '氢费支付方式', + required: true, + hint: getHydrogenPaymentFieldHint(feeInfo.hydrogenPaymentMethod), + hintId: 'lc-hydrogen-payment-hint', + hintAnnotationId: 'lc-hydrogen-payment-hint', + }, React.createElement(Select, { className: 'lc-form-select vm-focus-border', value: feeInfo.hydrogenPaymentMethod, @@ -2079,6 +3077,7 @@ export default function LeaseContractEditorForm(props) { patchFeeInfo({ hydrogenPaymentMethod: next, prepayAmount: next === 'prepay' ? feeInfo.prepayAmount : null, + payAheadWorkdays: next === 'prepay' ? null : feeInfo.payAheadWorkdays, }); }, options: HYDROGEN_PAYMENT_METHOD_OPTIONS, @@ -2100,26 +3099,6 @@ export default function LeaseContractEditorForm(props) { 'aria-label': '预付款金额', }), ) : null, - showPrepayAmount ? React.createElement(FormField, { label: '支付时间', required: true }, - React.createElement('div', { className: 'lc-form-inline-sentence' }, - React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '提前'), - React.createElement(InputNumber, { - className: 'lc-form-number lc-form-inline-sentence__input vm-focus-border', - controls: true, - min: 1, - step: 1, - precision: 0, - value: feeInfo.payAheadWorkdays, - onChange: function (v) { - patchFeeInfo({ - payAheadWorkdays: v != null ? v : DEFAULT_FEE_INFO.payAheadWorkdays, - }); - }, - 'aria-label': '提前支付工作日天数', - }), - React.createElement('span', { className: 'lc-form-inline-sentence__text' }, '个工作日支付'), - ), - ) : null, React.createElement(FormField, { label: '还车氢量差单价', required: true }, React.createElement(InputNumber, { className: 'lc-form-number vm-focus-border', @@ -2129,6 +3108,7 @@ export default function LeaseContractEditorForm(props) { precision: 2, value: feeInfo.returnHydrogenDiffUnitPrice, onChange: function (v) { patchFeeInfo({ returnHydrogenDiffUnitPrice: v }); }, + placeholder: '请输入金额,从而计算还车应结款「氢量差补缴金额」', addonAfter: '元', style: { width: '100%' }, 'aria-label': '还车氢量差单价', @@ -2170,18 +3150,25 @@ export function LeaseContractRemarkSection(props) { return React.createElement('div', { className: 'lc-create-editor-scroll lc-create-editor-scroll--section lc-contract-remark-section', }, - React.createElement(FormField, { label: '合同备注' }, + React.createElement(FormField, { + label: '合同备注', + labelHint: React.createElement(FormFieldLabelHint, { + annotationId: 'lc-contract-remark-hint', + text: '用于记录本合同相关说明,仅内部留档,不写入合同正文', + }), + }, React.createElement(Input.TextArea, { className: 'lc-contract-remark__textarea vm-focus-border', - rows: 4, + rows: 10, maxLength: 500, showCount: true, value: contractRemark || '', onChange: function (e) { if (onContractRemarkChange) onContractRemarkChange(e.target.value); }, - placeholder: '请输入合同备注(选填)', + placeholder: '请输入合同备注,用于内部记录(选填)', 'aria-label': '合同备注', + 'data-annotation-id': 'lc-contract-remark-input', }), ), ); diff --git a/src/prototypes/lease-contract-management/LeaseContractFleetSummary.jsx b/src/prototypes/lease-contract-management/LeaseContractFleetSummary.jsx index a615256..7cf996a 100644 --- a/src/prototypes/lease-contract-management/LeaseContractFleetSummary.jsx +++ b/src/prototypes/lease-contract-management/LeaseContractFleetSummary.jsx @@ -1,4 +1,5 @@ // 在租车辆概览 — 按品牌型号统计(KPI 卡片 + 下方车型面板 / 隐藏后弹层) +import React from 'react'; import { buildOnLeaseFleetByBrandModel } from './lease-contract-list-data.js'; function FleetDetailModal(props) { @@ -32,11 +33,11 @@ function FleetDetailModal(props) { title: titleNode, open: open, onCancel: onClose, - footer: React.createElement('button', { + footer: React.createElement('button', { 'data-vm-icon': 'x', type: 'button', className: 'vm-btn vm-btn-ghost lc-fleet-detail-modal__close', onClick: onClose, - }, '关闭'), + }, '关闭'), width: 960, centered: true, destroyOnClose: true, @@ -141,12 +142,13 @@ function FleetQuickViewModal(props) { open: open, onCancel: onClose, footer: React.createElement('div', { className: 'lc-fleet-quick-modal__footer' }, - React.createElement('button', { + React.createElement('button', { 'data-vm-icon': 'x', type: 'button', className: 'vm-btn vm-btn-ghost', onClick: onClose, - }, '关闭'), + }, '关闭'), React.createElement('button', { + 'data-vm-icon': 'plus', type: 'button', className: 'vm-btn vm-btn-primary', onClick: function () { diff --git a/src/prototypes/lease-contract-management/LeaseContractManagement.jsx b/src/prototypes/lease-contract-management/LeaseContractManagement.jsx index c76cbb4..83f18db 100644 --- a/src/prototypes/lease-contract-management/LeaseContractManagement.jsx +++ b/src/prototypes/lease-contract-management/LeaseContractManagement.jsx @@ -2,17 +2,41 @@ // 设计规范:oneos-app.css · 样式前缀 lc-* import LeaseContractCreate from './LeaseContractCreate.jsx'; -import LeaseContractFleetSummary from './LeaseContractFleetSummary.jsx'; +import LeaseContractView from './LeaseContractView.jsx'; +import { LeaseOrderServiceContentField } from './LeaseContractEditorForm.jsx'; +import { + getExtraFeeLockedServiceValues, + getExtraFeeNewServiceValues, + formatExtraFeeExistingServiceLabel, +} from './lease-order-vars.js'; +import { + FLOW_MODE_CREATE, + FLOW_MODE_EDIT, + FLOW_MODE_RENEW, + FLOW_MODE_TRIAL_TO_FORMAL, + FLOW_MODE_ADD_VEHICLE, + FLOW_MODE_ADD_POA, + FLOW_MODE_TRIPARTITE, + buildRenewContractFormState, + buildTrialToFormalFormState, + buildAddVehicleFormState, + buildAddPowerOfAttorneyFormState, + buildTripartiteContractFormState, +} from './lease-contract-flow-bridge.js'; +import { syncPickupReceivableContractStatus, syncPickupReceivableDeliveryPlan } from '../vehicle-pickup-receivable/pickup-receivable-bridge.js'; import { StatusTag } from '../vehicle-management/components/StatusTag'; import { TablePagination } from '../../common/TablePagination'; +import { OperationActions } from '../../common/OperationActions'; import { LEASE_CONTRACT_LIST_RECORDS, enrichLeaseContractListRecords, - buildOnLeaseFleetByBrandModel, formatDeliveryRegion, + formatContractDeliveryRegionLabel, formatPaymentPeriodLabel, + formatPaymentMethodLabel, formatHydrogenPaymentLabel, formatContractApprovalTypeLabel, + getContractApprovalFlowKindLabel, getAuthorizedDelegates, needsContractDelegateSupplement, formatContractHandoverMileage, @@ -33,10 +57,11 @@ import { computeContractOverallMileageProgress, computeContractMileageForecastStatus, getContractMileageForecastStatusTone, - resolveContractTemplateTypeLabel, resolveReturnSettlementDisplayLabel, resolveReturnSettlementCellMode, getReturnSettlementDisplayTone, + resolveLeaseBillStatus, + getLeaseBillStatusTone, getCurrentApproverLabel, shouldHideCurrentApprover, DELIVERY_DATE_TBD_LABEL, @@ -45,10 +70,36 @@ import { formatVehicleDeliveryPlanDateLabel, resolveVehicleDeliveryDateTbd, resolveVehiclePlannedDeliveryDate, - isContractExpiringForKpi, - isContractTerminatedForKpi, - resolveContractStatusByVehicleLease, + canEditContractDeliveryArrangement, + isContractDeliveryRegionTbd, + isContractDeliveryDateUnconfirmed, + DELIVERY_REGION_TBD_LABEL, + LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL, + resolveContractDisplayStatus, + resolveProjectInfoStatusTags, + projectInfoStatusTone, + inferContractTemplateId, + resolveRecordContractTemplateCategory, } from './lease-contract-list-data.js'; +import { + getPublishedContractTemplateOptions, + getPublishedContractTypeChoices, +} from '../contract-template-management/contract-template-catalog.js'; +import { + isOfflineContractSigning, + formatContractSigningMethodLabel, + resolveContractSigningMethod, + getContractSigningSubLabel, + getOnlineEsignContractFiles, + hasOnlineEsignCompleted, + openOnlineEsignPreviewInNewTab, + openContractAttachmentPreviewInNewTab, + downloadContractAttachment, + resolvePartyBEmailForStamp, + isPartyBEmailValid, + downloadPartyASignedContract, +} from './lease-contract-signing.js'; +import { PROVINCE_CITY_CASCADER_OPTIONS } from './lease-order-vars.js'; import { getLessorCompanies } from '../contract-template-management/contract-template-vars.js'; const Component = function(props) { @@ -72,6 +123,7 @@ const Component = function(props) { var Space = antd.Space; var DatePicker = antd.DatePicker; var Popover = antd.Popover; + var Cascader = antd.Cascader; var Tag = antd.Tag; var Avatar = antd.Avatar; var Dropdown = antd.Dropdown; @@ -87,14 +139,14 @@ const Component = function(props) { // 筛选展开(默认收起,只显示第一行 4 列,与车辆管理一致) var _filterExpanded = useState(false); var _kpiTab = useState('all'); - var _fleetExpanded = useState(false); - var _fleetPanelHidden = useState(false); - var _fleetQuickOpen = useState(false); var _page = useState(1); var _pageSize = useState(20); var _view = useState('list'); var _createInitialSection = useState(null); var _editContractRecord = useState(null); + var _viewContractRecord = useState(null); + var _flowMode = useState(FLOW_MODE_CREATE); + var _flowInitialFormState = useState(null); var _contractCode = useState(undefined); var _projectName = useState(undefined); var _customerName = useState(undefined); @@ -103,7 +155,8 @@ const Component = function(props) { var _contractStatus = useState(['全部']); var _businessDept = useState([]); var _businessOwner = useState([]); - var _contractType = useState(['全部']); + var _contractTemplateCategory = useState(undefined); + var _contractTemplateId = useState(undefined); var _contractApprovalType = useState(['全部']); var _creator = useState([]); var _endDateRange = useState(null); @@ -117,15 +170,20 @@ const Component = function(props) { contractStatus: ['全部'], businessDept: [], businessOwner: [], - contractType: ['全部'], + contractTemplateCategory: undefined, + contractTemplateId: undefined, contractApprovalType: ['全部'], creator: [], endDateRange: null }); var _vehiclePopoverRecord = useState(null); - var _deliveredPopoverRecord = useState(null); + var _expandedRowVehicleFilter = useState({}); var _deliveryChangePopoverRecord = useState(null); + var _deliveryChangeDraftRegionMode = useState('region'); + var _deliveryChangeDraftRegion = useState([]); + var _deliveryChangeDraftDateMode = useState('range'); + var _deliveryChangeDraftDateRange = useState(null); var _deliveryChangeDraftDate = useState(null); var _deliveryChangeDraftTbd = useState(false); var _vehicleDeliveryChangeTarget = useState(null); @@ -137,16 +195,25 @@ const Component = function(props) { var _authorizedList = useState([{ name: '', phone: '', idCard: '' }]); var _extraFeeModalVisible = useState(false); var _extraFeeModalRecord = useState(null); - var _extraFeeList = useState([]); + var _extraFeeExistingList = useState([]); + var _extraFeeNewList = useState([]); var _deleteModalVisible = useState(false); var _deleteModalRecord = useState(null); var _withdrawModalVisible = useState(false); var _withdrawModalRecord = useState(null); var _terminateModalVisible = useState(false); var _terminateModalRecord = useState(null); + var _terminateForm = useState({ terminateAt: null, reason: undefined, remark: '' }); var _stampModalVisible = useState(false); var _stampModalRecord = useState(null); + var _stampModalMode = useState('legal'); + var _stampPartyBEmail = useState(''); var _stampFileList = useState([]); + var _stampEmailSending = useState(false); + var _stampEmailSent = useState(false); + var _stampedFilesOverride = useState({}); + var _contractViewModalVisible = useState(false); + var _contractViewRecord = useState(null); var _tripartiteModalVisible = useState(false); var _tripartiteModalRecord = useState(null); var _tripartiteAgreements = useState([]); @@ -158,21 +225,24 @@ const Component = function(props) { var _trialToFormalAgreements = useState([]); // 上传盖章合同完成后,按合同 id 记录已上传(原型:与列表 legalStampedContractUploaded 合并判断) var _stampedUploadedOverride = useState({}); + var _stampedCompletedAtOverride = useState({}); // 撤回等列表行状态覆盖(原型) var _recordOverrides = useState({}); + var _signingMethodPopoverRecord = useState(null); var _expandedRowKeys = useState([]); useEffect(function() { if (props && props.onPageViewChange) { - props.onPageViewChange(_view[0] === 'create' ? 'create' : 'list'); + var viewKey = _view[0] === 'create' ? 'create' : (_view[0] === 'view' ? 'view' : 'list'); + props.onPageViewChange(viewKey); } }, [_view[0]]); useEffect(function() { if (props && props.navigationRef) { props.navigationRef.current = { - openCreate: function() { _view[1]('create'); }, - backToList: function() { _view[1]('list'); }, + openCreate: function() { openContractFlow(FLOW_MODE_CREATE, null); }, + backToList: function() { backToContractList(); }, }; } }, [props]); @@ -214,23 +284,32 @@ const Component = function(props) { { value: '审批中', label: '审批中' }, { value: '审批通过', label: '审批通过' }, { value: '审批驳回', label: '审批驳回' }, + { value: '审批终止', label: '审批终止' }, { value: '撤回', label: '撤回' }, ]; - // 合同状态:草稿、变更、合同进行中、到期合同、已提交审批、已结束 + // 合同状态:草稿、已提交审批、合同进行中、已终止 var contractStatusOptions = [ { value: '全部', label: '全部' }, { value: '草稿', label: '草稿' }, - { value: '变更', label: '变更' }, - { value: '合同进行中', label: '合同进行中' }, - { value: '到期合同', label: '到期合同' }, { value: '已提交审批', label: '已提交审批' }, - { value: '已结束', label: '已结束' } - ]; - var contractTypeOptions = [ - { value: '全部', label: '全部' }, - { value: '正式合同', label: '正式合同' }, - { value: '试用合同', label: '试用合同' } + { value: '合同进行中', label: '合同进行中' }, + { value: '已终止', label: '已终止' } ]; + var contractTemplateCategoryOptions = useMemo(function() { + return getPublishedContractTypeChoices().map(function(o) { + return { value: o.value, label: o.label }; + }); + }, []); + var contractTemplateStandardOptions = useMemo(function() { + var category = _contractTemplateCategory[0]; + var all = getPublishedContractTemplateOptions(); + var list = category + ? all.filter(function(o) { return o.contractType === category; }) + : all; + return list.map(function(o) { + return { value: o.id, label: o.title || o.fileName || o.id }; + }); + }, [_contractTemplateCategory[0]]); var contractApprovalTypeOptions = [ { value: '全部', label: '全部' }, { value: '标准合同', label: '标准合同' }, @@ -254,37 +333,25 @@ const Component = function(props) { var KPI_CARDS = [ { key: 'all', title: '全部合同', desc: '显示全部合同' }, - { key: 'inProgress', title: '进行中', desc: '显示至少有一辆车辆已交车但尚未还车的合同' }, - { key: 'inApproval', title: '审批中', desc: '显示审批状态为「待审批」或「审批中」的合同' }, - { key: 'expiring', title: '临期合同', desc: '显示最后一辆在租车辆对应合同,且距离合同签订有效期不足 30 天的合同' }, - { key: 'terminated', title: '已终止', desc: '显示所有车辆已还车,或因续签/转三方产生新合同而自动终止的合同' } + { key: 'draft', title: '草稿', desc: '显示所有状态为草稿的合同' }, + { key: 'inProgress', title: '进行中', desc: '显示审批已通过、合同进行中的合同' }, + { key: 'inApproval', title: '审批中', desc: '显示已提交审批的合同' }, + { key: 'terminated', title: '已终止', desc: '显示已终止的合同' } ]; - function contractHasDeliveredNotReturnedVehicle(record) { - var vehicles = record && record.vehicles; - if (!vehicles || !vehicles.length) return false; - for (var vi = 0; vi < vehicles.length; vi++) { - if (canContractVehicleReturn(vehicles[vi])) return true; - } - return false; - } - - function isInApprovalStatus(status) { - return status === '待审批' || status === '审批中'; - } - function matchContractKpi(record, key) { + var status = record.contractStatus; switch (key) { case 'all': return true; + case 'draft': + return status === '草稿'; case 'inProgress': - return contractHasDeliveredNotReturnedVehicle(record); + return status === '合同进行中'; case 'inApproval': - return isInApprovalStatus(record.approvalStatus); - case 'expiring': - return isContractExpiringForKpi(record); + return status === '已提交审批'; case 'terminated': - return isContractTerminatedForKpi(record); + return status === '已终止'; default: return true; } @@ -298,6 +365,13 @@ const Component = function(props) { React.createElement('path', { d: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z' }), React.createElement('path', { d: 'M14 2v4a2 2 0 0 0 2 2h4' }) ), + draft: React.createElement('svg', { + xmlns: 'http://www.w3.org/2000/svg', width: 18, height: 18, viewBox: '0 0 24 24', + fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true + }, + React.createElement('path', { d: 'M12 20h9' }), + React.createElement('path', { d: 'M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z' }) + ), inProgress: React.createElement('svg', { xmlns: 'http://www.w3.org/2000/svg', width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true @@ -313,13 +387,6 @@ const Component = function(props) { React.createElement('path', { d: 'M14 2v4a2 2 0 0 0 2 2h4' }), React.createElement('path', { d: 'm9 15 2 2 4-4' }) ), - expiring: React.createElement('svg', { - xmlns: 'http://www.w3.org/2000/svg', width: 18, height: 18, viewBox: '0 0 24 24', - fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true - }, - React.createElement('circle', { cx: 12, cy: 12, r: 10 }), - React.createElement('polyline', { points: '12 6 12 12 16 14' }) - ), terminated: React.createElement('svg', { xmlns: 'http://www.w3.org/2000/svg', width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true @@ -348,58 +415,14 @@ const Component = function(props) { return counts; }, [rawList]); - var fleetSourceList = useMemo(function () { - var overrides = _recordOverrides[0] || {}; - return rawList.map(function (r) { - var merged = overrides[r.id] ? Object.assign({}, r, overrides[r.id]) : r; - return Object.assign({}, merged, { - contractStatus: resolveContractStatusByVehicleLease(merged), - }); - }); - }, [rawList, _recordOverrides[0]]); - - var fleetOnLeaseSummary = useMemo(function () { - return buildOnLeaseFleetByBrandModel(fleetSourceList); - }, [fleetSourceList]); - - var fleetTotalOnLease = useMemo(function () { - return fleetOnLeaseSummary.reduce(function (sum, item) { return sum + item.count; }, 0); - }, [fleetOnLeaseSummary]); - - var fleetKpiActive = _fleetPanelHidden[0] ? _fleetQuickOpen[0] : _fleetExpanded[0]; - - function handleFleetKpiClick() { - if (_fleetPanelHidden[0]) { - _fleetQuickOpen[1](!_fleetQuickOpen[0]); - return; - } - _fleetExpanded[1](!_fleetExpanded[0]); - } - - function collapseFleetKpiPanel() { - _fleetExpanded[1](false); - _fleetQuickOpen[1](false); - } - - var FLEET_KPI_ICON = React.createElement('svg', { - xmlns: 'http://www.w3.org/2000/svg', width: 18, height: 18, viewBox: '0 0 24 24', - fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true, - }, - React.createElement('path', { d: 'M14 16H9m10 0h3v-3.5a1.5 1.5 0 0 0-1.5-1.5h-15A1.5 1.5 0 0 0 3 16.5V20h3' }), - React.createElement('circle', { cx: 7.5, cy: 17.5, r: 2.5 }), - React.createElement('circle', { cx: 17.5, cy: 17.5, r: 2.5 }), - ); - - var fleetKpiDesc = '按品牌型号统计在租车辆;点击展开下方车型卡片。隐藏面板后,再次点击将弹出各车型数量卡片。'; - - var listChromeOffsetPx = (_fleetExpanded[0] && !_fleetPanelHidden[0]) ? 558 : 434; + var listChromeOffsetPx = 434; var filteredList = useMemo(function() { var overrides = _recordOverrides[0] || {}; var list = rawList.map(function (r) { var merged = overrides[r.id] ? Object.assign({}, r, overrides[r.id]) : r; return Object.assign({}, merged, { - contractStatus: resolveContractStatusByVehicleLease(merged), + contractStatus: resolveContractDisplayStatus(merged), }); }); var f = appliedFilter; @@ -431,9 +454,16 @@ const Component = function(props) { if (f.businessOwner && f.businessOwner.length > 0) { list = list.filter(function(r) { return f.businessOwner.indexOf(r.businessOwner) !== -1; }); } - var type = f.contractType; - if (type && type.length > 0 && type.indexOf('全部') === -1) { - list = list.filter(function(r) { return type.indexOf(r.contractType) !== -1; }); + if (f.contractTemplateCategory) { + list = list.filter(function(r) { + return resolveRecordContractTemplateCategory(r) === f.contractTemplateCategory; + }); + } + if (f.contractTemplateId) { + list = list.filter(function(r) { + var tid = r.contractTemplateId || inferContractTemplateId(r); + return tid === f.contractTemplateId; + }); } var capType = f.contractApprovalType; if (capType && capType.length > 0 && capType.indexOf('全部') === -1) { @@ -486,7 +516,8 @@ const Component = function(props) { contractStatus: _contractStatus[0] ? _contractStatus[0].slice() : ['全部'], businessDept: _businessDept[0] ? _businessDept[0].slice() : [], businessOwner: _businessOwner[0] ? _businessOwner[0].slice() : [], - contractType: _contractType[0] ? _contractType[0].slice() : ['全部'], + contractTemplateCategory: _contractTemplateCategory[0], + contractTemplateId: _contractTemplateId[0], contractApprovalType: _contractApprovalType[0] ? _contractApprovalType[0].slice() : ['全部'], creator: _creator[0] ? _creator[0].slice() : [], endDateRange: _endDateRange[0] @@ -503,7 +534,8 @@ const Component = function(props) { _contractStatus[1](['全部']); _businessDept[1]([]); _businessOwner[1]([]); - _contractType[1](['全部']); + _contractTemplateCategory[1](undefined); + _contractTemplateId[1](undefined); _contractApprovalType[1](['全部']); _creator[1]([]); _endDateRange[1](null); @@ -516,7 +548,8 @@ const Component = function(props) { contractStatus: ['全部'], businessDept: [], businessOwner: [], - contractType: ['全部'], + contractTemplateCategory: undefined, + contractTemplateId: undefined, contractApprovalType: ['全部'], creator: [], endDateRange: null @@ -524,7 +557,7 @@ const Component = function(props) { _page[1](1); }, []); - // 审批状态/合同状态/合同类型:「全部」与其它选项互斥,不能同时多选 + // 审批状态/合同状态/审批类型:「全部」与其它选项互斥,不能同时多选 var handleApprovalStatusChange = useCallback(function(v) { if (!v || v.length === 0) { _approvalStatus[1](['全部']); return; } if (v.indexOf('全部') !== -1 && v.length > 1) { @@ -557,21 +590,12 @@ const Component = function(props) { } _contractStatus[1](v); }, []); - var handleContractTypeChange = useCallback(function(v) { - if (!v || v.length === 0) { _contractType[1](['全部']); return; } - if (v.indexOf('全部') !== -1 && v.length > 1) { - var prev = _contractType[0] || []; - var hadAllOnly = prev.length === 1 && prev.indexOf('全部') !== -1; - if (hadAllOnly) { - var next = []; - for (var k = 0; k < v.length; k++) { if (v[k] !== '全部') next.push(v[k]); } - _contractType[1](next); - } else { - _contractType[1](['全部']); - } - return; - } - _contractType[1](v); + var handleContractTemplateCategoryChange = useCallback(function(v) { + _contractTemplateCategory[1](v); + _contractTemplateId[1](undefined); + }, []); + var handleContractTemplateIdChange = useCallback(function(v) { + _contractTemplateId[1](v); }, []); var handleContractApprovalTypeChange = useCallback(function(v) { if (!v || v.length === 0) { _contractApprovalType[1](['全部']); return; } @@ -612,12 +636,9 @@ const Component = function(props) { }); }, []); - // 附加费用:服务项目枚举(与需求文档一致) - var extraFeeServiceOptions = [ - { value: '代处理费用', label: '代处理费用' }, { value: '罚款', label: '罚款' }, { value: '违章处理违约金', label: '违章处理违约金' }, { value: '未参加安全培训', label: '未参加安全培训' }, { value: '车辆出险', label: '车辆出险' }, { value: '年检年审违约', label: '年检年审违约' }, { value: '停车费', label: '停车费' }, { value: '设备损坏金(包含易损件)', label: '设备损坏金(包含易损件)' }, { value: '清洗费', label: '清洗费' }, { value: '上门收车人工费', label: '上门收车人工费' }, { value: '上门收车送车行驶费', label: '上门收车送车行驶费' }, { value: '上门收车基础服务费', label: '上门收车基础服务费' }, { value: '保险上浮', label: '保险上浮' }, { value: '保养费用', label: '保养费用' }, { value: '补办驾驶证', label: '补办驾驶证' }, { value: '补办牌照', label: '补办牌照' }, { value: '补办营运证', label: '补办营运证' }, { value: '补办加氢证', label: '补办加氢证' }, { value: '借用备用钥匙', label: '借用备用钥匙' }, { value: '补配钥匙', label: '补配钥匙' }, { value: '租金', label: '租金' }, { value: '氢气费-客', label: '氢气费-客' }, { value: '退还车氢量差', label: '退还车氢量差' }, { value: '能源费补缴', label: '能源费补缴' }, { value: '能源费退款', label: '能源费退款' }, { value: '送车上门人工费', label: '送车上门人工费' }, { value: '送车上门送车行驶费', label: '送车上门送车行驶费' }, { value: '送车上门基础服务费', label: '送车上门基础服务费' }, { value: '保证金', label: '保证金' }, { value: '氢气预付费', label: '氢气预付费' }, { value: '维修费用', label: '维修费用' }, { value: 'ETC-客', label: 'ETC-客' }, { value: 'ETC卡缺损费', label: 'ETC卡缺损费' }, { value: 'ETC设备缺损费', label: 'ETC设备缺损费' }, { value: '电费-客', label: '电费-客' }, { value: '未结算保养费', label: '未结算保养费' }, { value: '未结算维修费', label: '未结算维修费' }, { value: '车损费', label: '车损费' }, { value: '工具损坏或丢失费', label: '工具损坏或丢失费' }, { value: '证件费', label: '证件费' }, { value: '广告损坏费', label: '广告损坏费' }, { value: '送车服务费', label: '送车服务费' }, { value: '接车服务费', label: '接车服务费' }, { value: '补办行驶证', label: '补办行驶证' }, { value: '超赔险', label: '超赔险' }, { value: '轮胎磨损费', label: '轮胎磨损费' }, { value: '无忧包', label: '无忧包' }, { value: '轮胎保', label: '轮胎保' }, { value: '养护保', label: '养护保' }, { value: '尾板', label: '尾板' } - ]; - var updateExtraFeeRow = useCallback(function(index, field, value) { - _extraFeeList[1](function(prev) { + // 附加费用:新增行服务项选择(与租赁订单「选择服务项」共用弹层) + var updateExtraFeeNewRow = useCallback(function(index, field, value) { + _extraFeeNewList[1](function(prev) { var list = prev.slice(); var row = list[index] || {}; var next = {}; @@ -627,6 +648,20 @@ const Component = function(props) { }); }, []); + var addExtraFeeNewRow = useCallback(function() { + _extraFeeNewList[1](function(prev) { + var lockedValues = getExtraFeeLockedServiceValues(_extraFeeExistingList[0]); + return prev.concat([{ + key: 'new-' + Date.now(), + readonly: false, + serviceValues: lockedValues.slice(), + fee: '', + effectiveDate: null, + billingMode: '先付后用', + }]); + }); + }, []); + function mockUploadRequest(opts) { setTimeout(function() { if (opts.onSuccess) opts.onSuccess('ok'); @@ -671,18 +706,11 @@ const Component = function(props) { } function openTripartiteModal(record) { - _tripartiteModalRecord[1](record); - _tripartiteAgreements[1]([createEmptyTripartiteAgreement()]); - _tripartiteModalVisible[1](true); + openContractFlow(FLOW_MODE_TRIPARTITE, record); } function openTrialToFormalModal(record) { - _trialToFormalRecord[1](record); - _trialToFormalDelegates[1](mapDelegatesForConversionModal(record)); - _trialToFormalVehicles[1](mapVehiclesForFormalModal(record)); - _trialToFormalTripartite[1](false); - _trialToFormalAgreements[1]([createEmptyTripartiteAgreement()]); - _trialToFormalModalVisible[1](true); + openContractFlow(FLOW_MODE_TRIAL_TO_FORMAL, record); } function updateTripartiteAgreementList(setter, index, field, value) { @@ -837,6 +865,66 @@ const Component = function(props) { return formatVehiclePlateLabel(plateNo) || VEHICLE_PLATE_UNSELECTED_LABEL; } + function groupLeaseVehiclesByBrandModel(vehicles) { + var groups = []; + var indexByKey = {}; + (vehicles || []).forEach(function (vehicle) { + var brand = vehicle.brand && String(vehicle.brand).trim() ? vehicle.brand : '-'; + var model = vehicle.model && String(vehicle.model).trim() ? vehicle.model : '-'; + var key = brand + '::' + model; + if (indexByKey[key] != null) { + groups[indexByKey[key]].count += 1; + } else { + indexByKey[key] = groups.length; + groups.push({ brand: brand, model: model, count: 1 }); + } + }); + return groups; + } + + function renderRentalSummaryPopoverPanel(config) { + var vehicles = config.vehicles || []; + var groups = groupLeaseVehiclesByBrandModel(vehicles); + var totalCount = vehicles.length; + return React.createElement('div', { className: 'lc-vehicle-popover__panel', role: 'region', 'aria-label': config.title }, + React.createElement('div', { className: 'lc-vehicle-popover__header' }, + React.createElement('div', { + className: 'lc-vehicle-popover__header-icon', + 'aria-hidden': true, + }, renderVehiclePopoverIcon('rental')), + React.createElement('div', { className: 'lc-vehicle-popover__header-text' }, + React.createElement('div', { className: 'lc-vehicle-popover__header-title' }, config.title), + React.createElement('div', { className: 'lc-vehicle-popover__header-sub' }, + config.subtitle, + React.createElement('span', { className: 'lc-vehicle-popover__count tabular-nums' }, totalCount + ' 辆'), + ), + ), + ), + groups.length === 0 + ? React.createElement('div', { className: 'lc-vehicle-popover__empty' }, config.emptyText || '暂无车辆') + : React.createElement('div', { className: 'lc-vehicle-popover__summary-wrap' }, + React.createElement('table', { className: 'lc-vehicle-popover__summary-table' }, + React.createElement('thead', null, + React.createElement('tr', null, + React.createElement('th', { scope: 'col' }, '品牌'), + React.createElement('th', { scope: 'col' }, '型号'), + React.createElement('th', { scope: 'col', className: 'is-count' }, '车辆数'), + ), + ), + React.createElement('tbody', null, + groups.map(function (group, index) { + return React.createElement('tr', { key: 'rental-group-' + index }, + React.createElement('td', { title: group.brand }, group.brand), + React.createElement('td', { title: group.model }, group.model), + React.createElement('td', { className: 'is-count tabular-nums' }, group.count), + ); + }), + ), + ), + ), + ); + } + function renderVehiclePopoverCard(vehicle, index, variant) { var plate = formatVehiclePlateLabel(vehicle.plateNo); var brandModel = [vehicle.brand, vehicle.model].filter(function (part) { @@ -921,6 +1009,44 @@ const Component = function(props) { return s.length <= 2 ? s : s.slice(-2); } + function parseApproverNames(label) { + if (!label) return []; + return String(label).split(/[、,,]/).map(function (part) { + return part.trim(); + }).filter(Boolean); + } + + function renderApproversInline(approverLabel) { + var names = parseApproverNames(approverLabel); + if (!names.length) return null; + + var firstName = names[0]; + var content = React.createElement('span', { + className: 'lc-approval-status-cell__inline-approver' + + (names.length > 1 ? ' lc-approval-status-cell__inline-approver--multi' : ''), + }, + React.createElement('span', { + className: 'lc-approval-status-cell__inline-name', + title: names.length === 1 ? firstName : undefined, + }, firstName), + names.length > 1 + ? React.createElement('span', { + className: 'lc-approval-status-cell__count-badge', + 'aria-label': '共 ' + names.length + ' 位审批人', + }, String(names.length)) + : null, + ); + + if (names.length > 1) { + return React.createElement(Tooltip, { + title: names.join('、'), + placement: 'top', + mouseEnterDelay: 0.12, + }, content); + } + return content; + } + function canShowApprovalFlowPopover(status, nodes) { if (!nodes || !nodes.length) return false; return status === '待审批' @@ -929,14 +1055,23 @@ const Component = function(props) { || status === '审批驳回'; } - function renderApprovalFlowContent(nodes) { + function renderApprovalFlowContent(nodes, record) { var list = nodes && nodes.length ? nodes : []; if (!list.length) { return React.createElement('div', { className: 'lc-approval-flow__empty' }, '暂无审批节点明细'); } + var flowKind = getContractApprovalFlowKindLabel(record); + var isNonStandardFlow = flowKind === '非标准合同流程'; return React.createElement( 'div', { className: 'lc-approval-flow' }, + React.createElement('div', { className: 'lc-approval-flow__head' }, + React.createElement('span', { className: 'lc-approval-flow__head-label' }, '审批流程'), + React.createElement('span', { + className: 'lc-approval-flow__kind' + + (isNonStandardFlow ? ' lc-approval-flow__kind--nonstandard' : ' lc-approval-flow__kind--standard'), + }, flowKind), + ), list.map(function(node, index) { var isLast = index === list.length - 1; var isPending = node.result === 'pending'; @@ -1058,71 +1193,378 @@ const Component = function(props) { ); } + function getEditAction(record) { + var status = record.contractStatus; + var approval = record.approvalStatus; + if (status === '草稿' || approval === '审批驳回') { + return { + onClick: function () { openContractFlow(FLOW_MODE_EDIT, record); }, + }; + } + return null; + } + function getMoreMenuItems(record) { var status = record.contractStatus; var type = record.contractType; var approval = record.approvalStatus; var items = []; + var editAction = getEditAction(record); + if (editAction) { + items.push({ key: 'edit', label: '编辑', onClick: editAction.onClick }); + } if (status === '草稿') { - items.push({ key: 'edit', label: '编辑', onClick: function() { message.info('编辑(原型)'); } }); items.push({ key: 'del', label: '删除合同', danger: true, onClick: function() { _deleteModalRecord[1](record); _deleteModalVisible[1](true); } }); } if (status === '合同进行中') { - items.push({ key: 'addVehicle', label: '新增车辆', onClick: function() { message.info('新增车辆(原型)'); } }); - items.push({ key: 'renew', label: '续签合同', onClick: function() { message.info('续签合同(原型)'); } }); - items.push({ key: 'authorized', label: '添加授权委托书', onClick: function() { _authorizedModalRecord[1](record); _authorizedList[1]([{ name: '', phone: '', idCard: '' }]); _authorizedModalVisible[1](true); } }); + items.push({ key: 'addVehicle', label: '新增车辆', onClick: function() { openContractFlow(FLOW_MODE_ADD_VEHICLE, record); } }); + items.push({ key: 'renew', label: '续签合同', onClick: function() { openContractFlow(FLOW_MODE_RENEW, record); } }); + items.push({ key: 'authorized', label: '添加授权委托书', onClick: function() { openContractFlow(FLOW_MODE_ADD_POA, record); } }); items.push({ key: 'extraFee', label: '附加费用', onClick: function() { _extraFeeModalRecord[1](record); var vehicles = record.vehicles || []; - _extraFeeList[1](vehicles.map(function(v) { + var existingRows = vehicles.map(function(v, index) { + var serviceValues = Array.isArray(v.extraServiceValues) && v.extraServiceValues.length + ? v.extraServiceValues.slice() + : (v.extraServiceValue ? [v.extraServiceValue] : []); return { + key: 'existing-' + index, + readonly: true, vehicleType: v.vehicleType || '-', brand: v.brand || '-', model: v.model || '-', plateNo: v.plateNo || '-', - serviceItem: undefined, - fee: '', - effectiveDate: null + serviceValues: serviceValues, + serviceItem: formatExtraFeeExistingServiceLabel(serviceValues), + fee: v.extraFee != null ? v.extraFee : '—', + effectiveDate: v.extraFeeEffectiveDate || '—', + billingMode: v.extraFeeBillingMode || '先付后用', }; - })); + }); + var lockedValues = getExtraFeeLockedServiceValues(existingRows); + _extraFeeExistingList[1](existingRows); + _extraFeeNewList[1]([{ + key: 'new-0', + readonly: false, + serviceValues: lockedValues.slice(), + fee: '', + effectiveDate: null, + billingMode: '先付后用', + }]); _extraFeeModalVisible[1](true); } }); items.push({ key: 'toTripartite', label: '转三方合同', onClick: function() { openTripartiteModal(record); } }); - items.push({ key: 'terminate', label: '终止合同', danger: true, onClick: function() { _terminateModalRecord[1](record); _terminateModalVisible[1](true); } }); - } - if (status === '到期合同') { - items.push({ key: 'renew2', label: '续签合同', onClick: function() { message.info('续签合同(原型)'); } }); + items.push({ key: 'terminate', label: '主动终止合同', danger: true, onClick: function() { + _terminateModalRecord[1](record); + _terminateForm[1]({ terminateAt: null, reason: undefined, remark: '' }); + _terminateModalVisible[1](true); + } }); } if (approval === '待审批' || approval === '审批中') { items.push({ key: 'withdraw', label: '撤回合同', onClick: function() { _withdrawModalRecord[1](record); _withdrawModalVisible[1](true); } }); } - if (approval === '审批驳回') { - items.push({ key: 'editReject', label: '编辑', onClick: function() { message.info('编辑(原型)'); } }); - } if (type === '试用合同') { items.push({ key: 'toFormal', label: '试用转正式', onClick: function() { openTrialToFormalModal(record); } }); } + if (approval === '审批通过' && isOfflineContractSigning(record.contractSigningMethod)) { + var needStampSupplement = (record.legalStampedContractUploaded !== true) + && !(_stampedUploadedOverride[0][record.id] === true); + if (needStampSupplement) { + items.push({ + key: 'stampSupplement', + label: '盖章合同补传', + onClick: function() { openStampUploadModal(record, 'supplement'); }, + }); + } + } // 审批通过且法务审核环节尚未上传盖章合同附件时,法务部员工可在「更多」中上传(上传完成后入口关闭) - if (approval === '审批通过' && isLegalDeptUser) { + if (approval === '审批通过' && isLegalDeptUser && !isOfflineContractSigning(record.contractSigningMethod)) { var needStampUpload = (record.legalStampedContractUploaded === false) && !(_stampedUploadedOverride[0][record.id] === true); if (needStampUpload) { items.push({ key: 'uploadStamped', label: '上传盖章合同', - onClick: function() { - _stampModalRecord[1](record); - _stampFileList[1]([]); - _stampModalVisible[1](true); - } + onClick: function() { openStampUploadModal(record, 'legal'); }, }); } } return items; } - function openContractView(record) { - var code = record && record.contractCode ? record.contractCode : ''; - message.info('查看(跳转车辆租赁合同-查看)' + (code ? ':' + code : '')); + function openStampUploadModal(record, mode) { + _stampModalMode[1](mode || 'legal'); + _stampModalRecord[1](record); + _stampFileList[1]([]); + _stampPartyBEmail[1](mode === 'supplement' ? resolvePartyBEmailForStamp(record) : ''); + _stampModalVisible[1](true); + } + + function closeStampUploadModal() { + _stampModalVisible[1](false); + _stampModalRecord[1](null); + _stampFileList[1]([]); + _stampPartyBEmail[1](''); + _stampEmailSending[1](false); + _stampEmailSent[1](false); + _stampModalMode[1]('legal'); + } + + function renderStampModalIcon(type) { + var paths = { + download: 'M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z', + mail: 'M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z', + upload: 'M9 16h6v-6h4l-7-7-7 7h4v6zm-4 2h14v2H5v-2z', + check: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z', + file: 'M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z', + }; + return React.createElement('svg', { + className: 'lc-stamp-modal__icon', + viewBox: '0 0 24 24', + width: 18, + height: 18, + fill: 'currentColor', + 'aria-hidden': true, + }, React.createElement('path', { d: paths[type] || paths.file })); + } + + function handleSendPartyASignedContractEmail() { + var rec = _stampModalRecord[0]; + var email = (_stampPartyBEmail[0] || '').trim(); + if (!isPartyBEmailValid(email)) { + message.warning('请输入有效的乙方邮箱地址'); + return; + } + _stampEmailSending[1](true); + setTimeout(function () { + _stampEmailSending[1](false); + _stampEmailSent[1](true); + message.success('已将甲方已签章合同发送至 ' + email + '(原型)'); + }, 600); + } + + function confirmStampUpload() { + var rec = _stampModalRecord[0]; + var list = _stampFileList[0] || []; + if (!list.length) { + message.warning('请先选择要上传的文件'); + return; + } + var pending = list.some(function (f) { return f.status === 'uploading'; }); + if (pending) { + message.warning('请等待文件上传完成'); + return; + } + _stampedUploadedOverride[1](function (prev) { + var n = Object.assign({}, prev); + if (rec) n[rec.id] = true; + return n; + }); + _stampedFilesOverride[1](function (prev) { + var n = Object.assign({}, prev); + if (rec) { + n[rec.id] = list.map(function (file) { + return { + uid: file.uid, + name: file.name, + type: file.type || '', + }; + }); + } + return n; + }); + _stampedCompletedAtOverride[1](function (prev) { + var n = Object.assign({}, prev); + if (rec) n[rec.id] = formatStampUploadCompletedMinute(); + return n; + }); + message.success(_stampModalMode[0] === 'supplement' + ? '盖章合同已补传(原型),查看时可预览与下载全部附件' + : '盖章合同已上传(原型)'); + closeStampUploadModal(); + } + + function renderStampUploadModal() { + var rec = _stampModalRecord[0]; + var mode = _stampModalMode[0]; + var isSupplement = mode === 'supplement'; + var fileList = _stampFileList[0] || []; + var fileCount = fileList.length; + var email = (_stampPartyBEmail[0] || '').trim(); + var emailReady = isPartyBEmailValid(email); + + return React.createElement(Modal, { + title: isSupplement ? '盖章合同补传' : '上传盖章合同', + open: _stampModalVisible[0], + onCancel: closeStampUploadModal, + width: isSupplement ? 680 : 560, + className: 'lc-stamp-upload-modal', + destroyOnClose: true, + footer: [ + React.createElement(Button, { key: 'cancel', onClick: closeStampUploadModal }, '取消'), + React.createElement(Button, { + key: 'ok', + type: 'primary', + disabled: !fileCount, + onClick: confirmStampUpload, + }, fileCount ? ('确认上传(' + fileCount + ')') : '确认上传'), + ], + }, + React.createElement('div', { + className: 'lc-stamp-modal' + (isSupplement ? ' lc-stamp-modal--supplement' : ''), + 'data-annotation-id': 'lc-stamp-supplement-modal', + }, + isSupplement + ? React.createElement('div', { className: 'lc-stamp-modal__hero' }, + React.createElement('div', { className: 'lc-stamp-modal__hero-main' }, + React.createElement('span', { className: 'lc-stamp-modal__hero-label' }, '合同编码'), + React.createElement('span', { className: 'lc-stamp-modal__hero-code' }, rec ? rec.contractCode : '-'), + ), + rec && (rec.customerName || rec.projectName) + ? React.createElement('p', { className: 'lc-stamp-modal__hero-sub' }, + [rec.customerName, rec.projectName].filter(Boolean).join(' · '), + ) + : null, + ) + : React.createElement('div', { className: 'lc-stamp-modal__meta' }, + '合同编码:', + React.createElement('span', { className: 'lc-stamp-modal__meta-value' }, rec ? rec.contractCode : '-'), + ), + isSupplement + ? React.createElement('div', { className: 'lc-stamp-modal__steps', role: 'list', 'aria-label': '补传流程' }, + React.createElement('div', { className: 'lc-stamp-modal__step is-active', role: 'listitem' }, + React.createElement('span', { className: 'lc-stamp-modal__step-num' }, '1'), + React.createElement('span', { className: 'lc-stamp-modal__step-text' }, '发送甲方合同给乙方'), + ), + React.createElement('span', { className: 'lc-stamp-modal__step-line', 'aria-hidden': true }), + React.createElement('div', { className: 'lc-stamp-modal__step' + (fileCount ? ' is-active' : ''), role: 'listitem' }, + React.createElement('span', { className: 'lc-stamp-modal__step-num' }, '2'), + React.createElement('span', { className: 'lc-stamp-modal__step-text' }, '上传乙方盖章合同'), + ), + ) + : null, + isSupplement + ? React.createElement('section', { + className: 'lc-stamp-modal__section', + 'data-annotation-id': 'lc-stamp-send-to-party-b', + }, + React.createElement('header', { className: 'lc-stamp-modal__section-head' }, + React.createElement('h4', { className: 'lc-stamp-modal__section-title' }, '发送甲方已签章合同给乙方'), + React.createElement('p', { className: 'lc-stamp-modal__section-desc' }, '任选一种方式将甲方已签章合同交付乙方,便于乙方盖章后回传。'), + ), + React.createElement('div', { className: 'lc-stamp-modal__method-grid' }, + React.createElement('article', { className: 'lc-stamp-modal__method-card' }, + React.createElement('div', { className: 'lc-stamp-modal__method-head' }, + React.createElement('span', { className: 'lc-stamp-modal__method-badge' }, '方式 1'), + React.createElement('span', { className: 'lc-stamp-modal__method-icon', 'aria-hidden': true }, + renderStampModalIcon('download'), + ), + ), + React.createElement('p', { className: 'lc-stamp-modal__method-title' }, '手动下载并发送'), + React.createElement('p', { className: 'lc-stamp-modal__method-desc' }, + '下载甲方已签章合同 PDF,通过线下渠道发送给乙方负责人。', + ), + React.createElement(Button, { + type: 'default', + className: 'lc-stamp-modal__method-btn', + onClick: function () { + if (!rec) return; + downloadPartyASignedContract(rec); + message.success('已开始下载甲方已签章合同(原型)'); + }, + }, + renderStampModalIcon('download'), + '下载合同(甲方已签章)', + ), + ), + React.createElement('article', { className: 'lc-stamp-modal__method-card' }, + React.createElement('div', { className: 'lc-stamp-modal__method-head' }, + React.createElement('span', { className: 'lc-stamp-modal__method-badge' }, '方式 2'), + React.createElement('span', { className: 'lc-stamp-modal__method-icon', 'aria-hidden': true }, + renderStampModalIcon('mail'), + ), + ), + React.createElement('p', { className: 'lc-stamp-modal__method-title' }, '发送至乙方邮箱'), + React.createElement('p', { className: 'lc-stamp-modal__method-desc' }, + '系统将甲方已签章合同以邮件附件形式发送至乙方邮箱。', + ), + React.createElement('div', { className: 'lc-stamp-modal__email-field' }, + React.createElement('label', { className: 'lc-stamp-modal__email-label', htmlFor: 'lc-stamp-party-b-email' }, '乙方邮箱'), + React.createElement('div', { className: 'lc-stamp-modal__email-row' }, + React.createElement(Input, { + id: 'lc-stamp-party-b-email', + className: 'lc-stamp-modal__email-input vm-focus-border', + placeholder: '请输入乙方邮箱地址', + value: _stampPartyBEmail[0], + allowClear: true, + status: email && !emailReady ? 'error' : undefined, + onChange: function (e) { + _stampPartyBEmail[1](e.target.value); + _stampEmailSent[1](false); + }, + onPressEnter: handleSendPartyASignedContractEmail, + }), + React.createElement(Button, { + type: 'primary', + className: 'lc-stamp-modal__email-send-btn', + loading: _stampEmailSending[0], + disabled: !emailReady || _stampEmailSending[0], + onClick: handleSendPartyASignedContractEmail, + }, '发送'), + ), + email && !emailReady + ? React.createElement('p', { className: 'lc-stamp-modal__email-error' }, '请输入有效的邮箱地址') + : null, + ), + ), + ), + _stampEmailSent[0] + ? React.createElement('div', { className: 'lc-stamp-modal__sent-banner', role: 'status' }, + renderStampModalIcon('check'), + React.createElement('span', null, '已发送至 ', React.createElement('strong', null, email)), + ) + : null, + ) + : null, + React.createElement('section', { className: 'lc-stamp-modal__section lc-stamp-modal__section--upload' }, + isSupplement + ? React.createElement('header', { className: 'lc-stamp-modal__section-head' }, + React.createElement('h4', { className: 'lc-stamp-modal__section-title' }, '上传乙方盖章合同'), + React.createElement('p', { className: 'lc-stamp-modal__section-desc' }, + '支持 PDF、PNG、JPG 等格式;可一次选择多个附件,补传后可在「查看」中预览与下载。', + ), + ) + : React.createElement('p', { className: 'lc-stamp-modal__hint' }, '支持上传多个 PDF 或图片文件。'), + React.createElement('div', { className: 'lc-stamp-modal__upload-panel' }, + React.createElement(Upload.Dragger, { + multiple: true, + accept: isSupplement ? '.pdf,.png,.jpg,.jpeg,.webp' : '.pdf,.doc,.docx,image/*', + fileList: fileList, + onChange: function (info) { _stampFileList[1](info.fileList); }, + customRequest: function (opts) { + setTimeout(function () { + if (opts.onSuccess) opts.onSuccess('ok'); + }, 200); + }, + showUploadList: { showRemoveIcon: true, showPreviewIcon: false }, + }, + React.createElement('div', { className: 'lc-stamp-modal__dropzone' }, + React.createElement('span', { className: 'lc-stamp-modal__dropzone-icon', 'aria-hidden': true }, + renderStampModalIcon('upload'), + ), + React.createElement('p', { className: 'lc-stamp-modal__dropzone-title' }, '点击或拖拽文件到此处上传'), + React.createElement('p', { className: 'lc-stamp-modal__dropzone-hint' }, '支持多文件、可分批添加'), + ), + ), + ), + fileCount + ? React.createElement('div', { className: 'lc-stamp-modal__file-summary', role: 'status' }, + renderStampModalIcon('file'), + React.createElement('span', null, '已选择 ', React.createElement('strong', null, fileCount), ' 个附件,确认后将归档至合同档案'), + ) + : null, + ), + ), + ); } function renderMileageProgressBarRow(percent, ariaLabel) { @@ -1183,14 +1625,6 @@ const Component = function(props) { ); } - function renderContractTypeCell(_, record) { - var label = resolveContractTemplateTypeLabel(record); - return React.createElement('span', { - className: 'lc-contract-type-label', - title: label, - }, label); - } - function renderPickupReceivableCell(vehicle) { if (!isContractVehicleDelivered(vehicle)) { return React.createElement('div', { className: 'lc-contract-pickup-payment lc-contract-pickup-payment--empty' }, '—'); @@ -1210,12 +1644,30 @@ const Component = function(props) { className: 'lc-contract-status-tag-btn', title: '点击查看提车应收款详情:' + plate, onClick: function () { - message.info('即将跳转至提车应收款详情(原型演示):' + plate); + if (typeof window !== 'undefined') { + window.location.href = '/prototypes/vehicle-pickup-receivable'; + } else { + message.info('即将跳转至提车应收款详情(原型演示):' + plate); + } }, }, tag), ); } + function renderLeaseBillCell(vehicle) { + var status = resolveLeaseBillStatus(vehicle); + if (!status) { + return React.createElement('div', { className: 'lc-contract-lease-bill lc-contract-lease-bill--empty' }); + } + return React.createElement('div', { className: 'lc-contract-lease-bill' }, + React.createElement(StatusTag, { + label: status, + tone: getLeaseBillStatusTone(status), + title: status === '欠费' ? '存在未结清租赁账单' : '租赁账单状态正常', + }), + ); + } + function renderReturnSettlementCell(vehicle) { if (!isContractVehicleReturned(vehicle)) { return React.createElement('div', { className: 'lc-contract-return-settlement lc-contract-return-settlement--empty' }); @@ -1246,16 +1698,15 @@ const Component = function(props) { }, }, tag) : tag; - var approverNode = displayLabel === '审批中' - ? React.createElement('div', { - className: 'lc-contract-return-settlement__approver', - title: '当前审批节点操作人', - }, vehicle.returnSettlementApprover || '—') - : null; - return React.createElement('div', { className: 'lc-contract-return-settlement lc-contract-return-settlement--stacked' }, - tagNode, - approverNode, - ); + var approver = vehicle.returnSettlementApprover; + var showApprover = displayLabel === '审批中' && approver; + if (showApprover) { + return React.createElement('div', { className: 'lc-approval-status-cell lc-approval-status-cell--inline' }, + React.createElement('span', { className: 'lc-approval-status-cell__tag-wrap' }, tagNode), + renderApproversInline(approver), + ); + } + return React.createElement('div', { className: 'lc-contract-return-settlement' }, tagNode); } function renderMileageRequirementCell(contractRecord, vehicle) { @@ -1400,8 +1851,8 @@ const Component = function(props) { ); } - function renderContractVehicleActionCell(vehicle) { - if (!canContractVehicleReturn(vehicle)) { + function renderContractVehicleActionCell(vehicle, record) { + if (!canContractVehicleReturn(vehicle, record)) { return React.createElement('span', { className: 'lc-contract-vehicle-action-empty' }, '—'); } var plate = getVehiclePlateDisplayLabel(vehicle.plateNo); @@ -1504,7 +1955,7 @@ const Component = function(props) { } function renderVehicleDeliveryPlanCell(record, vehicle, vehicleIndex) { - var regionLabel = formatDeliveryRegion(record.deliveryRegion); + var regionLabel = formatContractDeliveryRegionLabel(record); var dateLabel = formatVehicleDeliveryPlanDateLabel(vehicle, record); var delivered = isContractVehicleDelivered(vehicle); var isTbd = !delivered && resolveVehicleDeliveryDateTbd(vehicle, record); @@ -1542,7 +1993,6 @@ const Component = function(props) { } closeDeliveryChangePopover(); _vehiclePopoverRecord[1](null); - _deliveredPopoverRecord[1](null); closeDelegatePopover(); var vehicleTbd = resolveVehicleDeliveryDateTbd(vehicle, record); _vehicleDeliveryChangeTarget[1]({ @@ -1571,6 +2021,7 @@ const Component = function(props) { React.createElement('th', { scope: 'col', className: 'lc-contract-vehicle-native-table__info' }, '车辆信息'), React.createElement('th', { scope: 'col', className: 'lc-contract-vehicle-native-table__pickup' }, '提车应收款'), React.createElement('th', { scope: 'col', className: 'lc-contract-vehicle-native-table__handover', title: '交车情况' }, '交车'), + React.createElement('th', { scope: 'col', className: 'lc-contract-vehicle-native-table__lease-bill', title: '租赁账单状态' }, '租赁账单'), React.createElement('th', { scope: 'col', className: 'lc-contract-vehicle-native-table__handover', title: '还车情况' }, '还车'), React.createElement('th', { scope: 'col', className: 'lc-contract-vehicle-native-table__return-settlement' }, '还车应结款'), React.createElement('th', { scope: 'col', className: 'lc-contract-vehicle-native-table__delivery-plan', title: '交车安排' }, '交车安排'), @@ -1586,19 +2037,33 @@ const Component = function(props) { React.createElement('td', { className: 'lc-contract-vehicle-native-table__info' }, renderVehicleInfoCell(vehicle)), React.createElement('td', { className: 'lc-contract-vehicle-native-table__pickup' }, renderPickupReceivableCell(vehicle)), React.createElement('td', { className: 'lc-contract-vehicle-native-table__handover' }, renderHandoverSituationCell(vehicle, 'delivery', { compact: true })), + React.createElement('td', { className: 'lc-contract-vehicle-native-table__lease-bill' }, renderLeaseBillCell(vehicle)), React.createElement('td', { className: 'lc-contract-vehicle-native-table__handover' }, renderHandoverSituationCell(vehicle, 'return', { compact: true })), React.createElement('td', { className: 'lc-contract-vehicle-native-table__return-settlement' }, renderReturnSettlementCell(vehicle)), React.createElement('td', { className: 'lc-contract-vehicle-native-table__delivery-plan' }, renderVehicleDeliveryPlanCell(record, vehicle, index)), React.createElement('td', { className: 'lc-contract-vehicle-native-table__mileage-req' }, renderMileageRequirementCell(record, vehicle)), React.createElement('td', { className: 'lc-contract-vehicle-native-table__mileage-completion' }, renderMileageCompletionCell(vehicle, record)), - React.createElement('td', { className: 'lc-contract-vehicle-native-table__action' }, renderContractVehicleActionCell(vehicle)), + React.createElement('td', { className: 'lc-contract-vehicle-native-table__action' }, renderContractVehicleActionCell(vehicle, record)), ); } - function renderExpandedVehicleTable(record) { + function getContractVehiclesForExpandedRow(record) { var vehicles = record.vehicles || []; + if (_expandedRowVehicleFilter[0][record.id] === 'delivered') { + return vehicles.filter(function (vehicle) { + return isContractVehicleDelivered(vehicle); + }); + } + return vehicles; + } + + function renderExpandedVehicleTable(record) { + var vehicles = getContractVehiclesForExpandedRow(record); + var isDeliveredOnly = _expandedRowVehicleFilter[0][record.id] === 'delivered'; if (!vehicles.length) { - return React.createElement('div', { className: 'lc-contract-vehicle-expand__empty' }, '暂无车辆明细'); + return React.createElement('div', { className: 'lc-contract-vehicle-expand__empty' }, + isDeliveredOnly ? '暂无已交车记录' : '暂无车辆明细', + ); } return React.createElement('div', { className: 'lc-contract-vehicle-table-wrap' }, React.createElement('table', { className: 'lc-contract-vehicle-native-table' }, @@ -1618,6 +2083,7 @@ const Component = function(props) { case '审批中': return 'amber'; case '待审批': return 'blue'; case '审批驳回': return 'red'; + case '审批终止': return 'red'; case '撤回': return 'gray'; case '未提交': return 'gray'; default: return 'gray'; @@ -1628,11 +2094,8 @@ const Component = function(props) { switch (status) { case '合同进行中': return 'green'; case '已提交审批': return 'blue'; - case '变更': return 'amber'; - case '到期合同': return 'amber'; - case '草稿': return 'gray'; - case '已结束': return 'gray'; case '已终止': return 'red'; + case '草稿': return 'gray'; default: return 'gray'; } } @@ -1652,7 +2115,7 @@ const Component = function(props) { var statusContent = hasFlowPopover ? React.createElement(Popover, { - content: renderApprovalFlowContent(nodes), + content: renderApprovalFlowContent(nodes, record), trigger: 'hover', placement: 'rightTop', overlayClassName: 'lc-approval-flow-popover', @@ -1665,14 +2128,12 @@ const Component = function(props) { ) : statusTag; - return React.createElement('div', { className: 'vm-stack lc-approval-status-cell' }, + return React.createElement('div', { + className: 'lc-approval-status-cell' + + (showApprover ? ' lc-approval-status-cell--inline' : ''), + }, statusContent, - showApprover - ? React.createElement('span', { - className: 'sub lc-approval-status-cell__approver', - title: approver, - }, approver) - : null, + showApprover ? renderApproversInline(approver) : null, ); } @@ -1684,13 +2145,30 @@ const Component = function(props) { }, fullName); } - function toggleContractRowExpand(recordId) { + function setContractRowExpanded(recordId, expanded, filterMode) { _expandedRowKeys[1](function (prev) { - if (prev.indexOf(recordId) >= 0) { - return prev.filter(function (key) { return key !== recordId; }); - } - return prev.concat([recordId]); + var has = prev.indexOf(recordId) >= 0; + if (expanded && !has) return prev.concat([recordId]); + if (!expanded && has) return prev.filter(function (key) { return key !== recordId; }); + return prev; }); + _expandedRowVehicleFilter[1](function (prev) { + var next = Object.assign({}, prev); + if (expanded && filterMode) next[recordId] = filterMode; + else delete next[recordId]; + return next; + }); + } + + function toggleContractRowExpand(recordId, filterMode) { + var expanded = _expandedRowKeys[0].indexOf(recordId) >= 0; + var currentFilter = _expandedRowVehicleFilter[0][recordId] || 'all'; + var nextFilter = filterMode || 'all'; + if (expanded && currentFilter === nextFilter) { + setContractRowExpanded(recordId, false); + return; + } + setContractRowExpanded(recordId, true, nextFilter); } function renderContractRowExpandIcon(record) { @@ -1711,20 +2189,22 @@ const Component = function(props) { 'aria-expanded': expanded, onClick: function (e) { e.stopPropagation(); - toggleContractRowExpand(record.id); + _vehiclePopoverRecord[1](null); + toggleContractRowExpand(record.id, 'all'); }, onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); - toggleContractRowExpand(record.id); + _vehiclePopoverRecord[1](null); + toggleContractRowExpand(record.id, 'all'); } }, }); } function renderProjectInfoCell(_, record) { - var status = record.contractStatus || '-'; + var statusTags = resolveProjectInfoStatusTags(record); return React.createElement('div', { className: 'lc-project-info-merged-cell' }, renderContractRowExpandIcon(record), React.createElement('div', { className: 'lc-project-info-cell' }, @@ -1734,21 +2214,24 @@ const Component = function(props) { type: 'button', className: 'lc-project-info-btn', onClick: function() { openContractView(record); }, - title: '查看合同详情' + title: '合同详情' }, React.createElement( 'div', { className: 'vm-stack' }, - React.createElement('span', { className: 'primary' }, record.projectName || '-'), - React.createElement('span', { className: 'code' }, record.contractCode || '-'), - React.createElement('span', { className: 'sub' }, record.customerName || '-') + React.createElement('span', { className: 'primary', title: record.projectName || undefined }, record.projectName || '-'), + React.createElement('span', { className: 'code', title: record.contractCode || undefined }, record.contractCode || '-'), + React.createElement('span', { className: 'sub', title: record.customerName || undefined }, record.customerName || '-') ) ), React.createElement('div', { className: 'lc-project-info-cell__status' }, - React.createElement(StatusTag, { - label: status, - tone: contractStatusTone(status), - title: status, + statusTags.map(function (label) { + return React.createElement(StatusTag, { + key: label, + label: label, + tone: projectInfoStatusTone(label), + title: label, + }); }), ), ), @@ -1774,10 +2257,21 @@ const Component = function(props) { } function renderFeeInfoCell(_, record) { + var paymentLabel = formatPaymentMethodLabel(record.paymentMethod); var periodLabel = formatPaymentPeriodLabel(record.paymentPeriod); var hydrogenLabel = formatHydrogenPaymentLabel(record.hydrogenPaymentMethod); var isPrepay = record.hydrogenPaymentMethod === 'prepay'; return React.createElement('div', { className: 'lc-fee-info-cell' }, + React.createElement('div', { + className: 'lc-fee-info-cell__side lc-fee-info-cell__side--payment' + (record.paymentMethod === 'postpay' ? ' is-postpay' : ''), + }, + React.createElement('span', { className: 'lc-fee-info-cell__caption' }, '付款方式'), + React.createElement('span', { + className: 'lc-fee-info-cell__value', + title: paymentLabel, + }, paymentLabel), + ), + React.createElement('span', { className: 'lc-fee-info-cell__sep', 'aria-hidden': true }), React.createElement('div', { className: 'lc-fee-info-cell__side lc-fee-info-cell__side--period' }, React.createElement('span', { className: 'lc-fee-info-cell__caption' }, '付款周期'), React.createElement('span', { @@ -1813,8 +2307,49 @@ const Component = function(props) { function closeDeliveryChangePopover() { _deliveryChangePopoverRecord[1](null); + _deliveryChangeDraftRegionMode[1]('region'); + _deliveryChangeDraftRegion[1]([]); + _deliveryChangeDraftDateMode[1]('range'); + _deliveryChangeDraftDateRange[1](null); _deliveryChangeDraftDate[1](null); _deliveryChangeDraftTbd[1](false); + closeSigningMethodPopover(); + } + + function initDeliveryChangeDraft(record) { + var regionTbd = isContractDeliveryRegionTbd(record); + var dateUnconfirmed = isContractDeliveryDateUnconfirmed(record); + _deliveryChangeDraftRegionMode[1](regionTbd ? 'tbd' : 'region'); + _deliveryChangeDraftRegion[1](regionTbd ? [] : ((record.deliveryRegion || []).slice())); + _deliveryChangeDraftDateMode[1](dateUnconfirmed ? 'unconfirmed' : 'range'); + if (record.deliveryDateStart && record.deliveryDateEnd) { + _deliveryChangeDraftDateRange[1]([record.deliveryDateStart, record.deliveryDateEnd]); + _deliveryChangeDraftDate[1](record.deliveryDateStart); + } else { + _deliveryChangeDraftDateRange[1](null); + _deliveryChangeDraftDate[1](dateUnconfirmed ? null : (record.deliveryDate || null)); + } + _deliveryChangeDraftTbd[1](dateUnconfirmed); + } + + function renderDeliveryModeToggle(props) { + return React.createElement('div', { + className: 'lc-delivery-change__mode-toggle', + role: 'radiogroup', + 'aria-label': props['aria-label'], + }, + (props.options || []).map(function (option) { + var active = props.value === option.value; + return React.createElement('button', { + key: option.value, + type: 'button', + role: 'radio', + 'aria-checked': active, + className: 'lc-delivery-change__mode-btn' + (active ? ' is-active' : ''), + onClick: function () { props.onChange(option.value); }, + }, option.label); + }), + ); } function closeVehicleDeliveryChangePopover() { @@ -1823,22 +2358,40 @@ const Component = function(props) { _vehicleDeliveryChangeDraftTbd[1](false); } - function confirmDeliveryDateChange(record) { - var draftTbd = _deliveryChangeDraftTbd[0]; - var newDate = draftTbd ? DELIVERY_DATE_TBD_LABEL : _deliveryChangeDraftDate[0]; - var oldTbd = record.deliveryDateTbd || isDeliveryDateTbd(record.deliveryDate); - var oldDate = oldTbd ? DELIVERY_DATE_TBD_LABEL : (record.deliveryDate || null); - if (!draftTbd && !newDate) { - message.warning('请选择交车日期,或标记为暂未确定'); + function confirmDeliveryArrangementChange(record) { + var regionMode = _deliveryChangeDraftRegionMode[0]; + var regionTbd = regionMode === 'tbd'; + var region = regionTbd ? [] : (_deliveryChangeDraftRegion[0] || []).slice(); + var dateMode = _deliveryChangeDraftDateMode[0]; + var dateUnconfirmed = dateMode === 'unconfirmed'; + var dateRange = _deliveryChangeDraftDateRange[0] || []; + var dateStart = dateUnconfirmed ? null : (dateRange[0] || _deliveryChangeDraftDate[0] || null); + var dateEnd = dateUnconfirmed ? null : (dateRange[1] || null); + if (!regionTbd && (!region || region.length < 2)) { + message.warning('请选择车辆交付(交还)地点,或选择交还车时约定'); return; } - if (draftTbd === oldTbd && (draftTbd || newDate === oldDate)) { + if (!dateUnconfirmed && (!dateStart || !dateEnd)) { + message.warning('请选择车辆交付时间范围,或选择暂未确认'); + return; + } + + var oldRegionLabel = formatContractDeliveryRegionLabel(record); + var oldDateLabel = formatContractDeliveryDateLabel(record); + var nextRegionLabel = regionTbd ? DELIVERY_REGION_TBD_LABEL : formatDeliveryRegion(region); + var nextDateLabel = dateUnconfirmed + ? LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL + : (dateStart + ' ~ ' + dateEnd); + if (oldRegionLabel === nextRegionLabel && oldDateLabel === nextDateLabel) { closeDeliveryChangePopover(); return; } + var logEntry = { - beforeDate: oldDate || '-', - afterDate: newDate, + beforeDate: oldDateLabel, + afterDate: nextDateLabel, + beforeRegion: oldRegionLabel, + afterRegion: nextRegionLabel, operatorName: record.businessOwner || record.creator || '当前用户', operateTime: formatPrototypeOperateTime(), }; @@ -1848,9 +2401,9 @@ const Component = function(props) { var updatedVehicles = baseVehicles.map(function (vehicle) { if (isContractVehicleDelivered(vehicle)) return vehicle; return Object.assign({}, vehicle, { - plannedDeliveryDate: draftTbd ? null : newDate, - deliveryDateTbd: draftTbd, - deliveryTaskSuspended: draftTbd, + plannedDeliveryDate: dateUnconfirmed ? null : dateStart, + deliveryDateTbd: dateUnconfirmed, + deliveryTaskSuspended: dateUnconfirmed, }); }); var baseLogs = prevRec.deliveryDateChangeLogs != null @@ -1858,15 +2411,39 @@ const Component = function(props) { : (record.deliveryDateChangeLogs || []); return Object.assign({}, prev, { [record.id]: Object.assign({}, prevRec, { - deliveryDate: draftTbd ? DELIVERY_DATE_TBD_LABEL : newDate, - deliveryDateTbd: draftTbd, - deliveryTaskSuspended: draftTbd, + deliveryRegion: region, + deliveryRegionMode: regionMode, + deliveryRegionTbd: regionTbd, + deliveryDate: dateUnconfirmed ? null : dateStart, + deliveryDateStart: dateStart, + deliveryDateEnd: dateEnd, + deliveryDateMode: dateMode, + deliveryDateTbd: dateUnconfirmed, + deliveryTaskSuspended: dateUnconfirmed, vehicles: updatedVehicles, deliveryDateChangeLogs: [logEntry].concat(baseLogs), }), }); }); - message.success(draftTbd ? '交车时间已标记为暂未确定(原型)' : '交车时间已更新,已同步至未交车车辆(原型)'); + + var syncedPickup = syncPickupReceivableDeliveryPlan(record.contractCode, { + deliveryRegion: region, + deliveryRegionMode: regionMode, + deliveryRegionTbd: regionTbd, + deliveryDateStart: dateStart, + deliveryDateEnd: dateEnd, + deliveryDateMode: dateMode, + deliveryDateTbd: dateUnconfirmed, + deliveryPlanLabel: nextRegionLabel + ' · ' + nextDateLabel, + }); + if (syncedPickup) { + message.success('交车安排已更新,并同步至提车应收款(原型)'); + } else { + message.success('交车安排已更新,已同步至未交车车辆(原型)'); + } + if (!dateUnconfirmed || regionTbd) { + message.info('工作台将根据最新交车安排重新评估「提车应收款」待办(原型)'); + } closeDeliveryChangePopover(); } @@ -1915,49 +2492,95 @@ const Component = function(props) { function renderDeliveryChangePanel(record) { var dayjs = window.dayjs; var logs = record.deliveryDateChangeLogs || []; - var draftDate = _deliveryChangeDraftDate[0]; - var draftTbd = _deliveryChangeDraftTbd[0]; - var draftValue = draftDate && dayjs && !draftTbd ? dayjs(draftDate) : null; + var regionMode = _deliveryChangeDraftRegionMode[0]; + var regionValue = _deliveryChangeDraftRegion[0] || []; + var dateMode = _deliveryChangeDraftDateMode[0]; + var dateRange = _deliveryChangeDraftDateRange[0] || []; + var rangeValue = dateRange.length === 2 && dayjs + ? [dayjs(dateRange[0]), dayjs(dateRange[1])] + : null; + var canEdit = canEditContractDeliveryArrangement(record); return React.createElement('div', { className: 'lc-delivery-change__panel', role: 'dialog', - 'aria-label': '修改交车时间', + 'aria-label': '修改交车安排', }, React.createElement('div', { className: 'lc-delivery-change__header' }, - React.createElement('div', { className: 'lc-delivery-change__title' }, '修改交车时间'), + React.createElement('div', { className: 'lc-delivery-change__title' }, '修改交车安排'), ), React.createElement('div', { className: 'lc-delivery-change__form' }, React.createElement('label', { className: 'lc-delivery-change__field' }, - React.createElement('span', { className: 'lc-delivery-change__label' }, '计划交车日期'), - React.createElement(DatePicker, { - style: { width: '100%' }, - value: draftValue, - disabled: draftTbd, - placeholder: draftTbd ? '暂未确定' : '请选择交车日期', - 'aria-label': '计划交车日期', - onChange: function (_date, dateString) { - _deliveryChangeDraftDate[1](dateString || null); - }, - }), - ), - React.createElement('label', { className: 'lc-delivery-change__tbd' }, - React.createElement(Checkbox, { - checked: draftTbd, - onChange: function (event) { - var checked = event.target.checked; - _deliveryChangeDraftTbd[1](checked); - if (checked) { - _deliveryChangeDraftDate[1](null); - } else { - _deliveryChangeDraftDate[1](record.deliveryDate && !isDeliveryDateTbd(record.deliveryDate) - ? record.deliveryDate - : null); + React.createElement('span', { className: 'lc-delivery-change__label' }, '车辆交付(交还)地点'), + renderDeliveryModeToggle({ + 'aria-label': '交付地点模式', + value: regionMode, + onChange: function (mode) { + _deliveryChangeDraftRegionMode[1](mode); + if (mode === 'tbd') { + _deliveryChangeDraftRegion[1]([]); } }, - }, '暂未确定'), + options: [ + { value: 'region', label: '选择省市' }, + { value: 'tbd', label: DELIVERY_REGION_TBD_LABEL }, + ], + }), + regionMode === 'region' + ? React.createElement(Cascader, { + style: { width: '100%', marginTop: 8 }, + options: PROVINCE_CITY_CASCADER_OPTIONS, + value: regionValue, + placeholder: '请选择省市', + allowClear: true, + onChange: function (value) { + _deliveryChangeDraftRegion[1](value || []); + }, + }) + : React.createElement('p', { className: 'lc-delivery-change__readonly' }, + '后续可通过提车应收款或还车流程反写具体交还地点', + ), + ), + React.createElement('label', { className: 'lc-delivery-change__field' }, + React.createElement('span', { className: 'lc-delivery-change__label' }, '车辆交付时间'), + renderDeliveryModeToggle({ + 'aria-label': '交付时间模式', + value: dateMode, + onChange: function (mode) { + _deliveryChangeDraftDateMode[1](mode); + if (mode === 'unconfirmed') { + _deliveryChangeDraftDateRange[1](null); + _deliveryChangeDraftDate[1](null); + _deliveryChangeDraftTbd[1](true); + } else { + _deliveryChangeDraftTbd[1](false); + } + }, + options: [ + { value: 'range', label: '选择日期' }, + { value: 'unconfirmed', label: LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL }, + ], + }), + dateMode === 'range' + ? React.createElement(RangePicker, { + style: { width: '100%', marginTop: 8 }, + value: rangeValue, + placeholder: ['开始日期', '结束日期'], + 'aria-label': '车辆交付时间范围', + onChange: function (_dates, dateStrings) { + _deliveryChangeDraftDateRange[1](dateStrings && dateStrings[0] && dateStrings[1] + ? [dateStrings[0], dateStrings[1]] + : null); + _deliveryChangeDraftDate[1](dateStrings && dateStrings[0] ? dateStrings[0] : null); + }, + }) + : React.createElement('p', { className: 'lc-delivery-change__readonly' }, + '提车前通过提车应收款功能生成交车任务', + ), ), React.createElement('p', { className: 'lc-delivery-change__hint' }, - '修改后将同步至本合同所有未交车车辆;标记暂未确定后,交车任务暂时挂起。', + canEdit + ? '修改后将同步至本合同所有未交车车辆,并回写提车应收款;工作台将据此重新评估提车应收款待办。' + : '该合同交车安排已由提车应收款流程锁定,请在提车应收款中调整。', ), React.createElement('div', { className: 'lc-delivery-change__actions' }, React.createElement(Button, { @@ -1967,7 +2590,8 @@ const Component = function(props) { React.createElement(Button, { size: 'small', type: 'primary', - onClick: function () { confirmDeliveryDateChange(record); }, + disabled: !canEdit, + onClick: function () { confirmDeliveryArrangementChange(record); }, }, '确定'), ), ), @@ -1981,8 +2605,27 @@ const Component = function(props) { key: (log.operateTime || '') + '-' + index, className: 'lc-delivery-change__history-item', }, + log.beforeRegion || log.afterRegion + ? React.createElement('div', { className: 'lc-delivery-change__history-row' }, + React.createElement('span', { className: 'lc-delivery-change__history-field-label' }, '交还地点'), + React.createElement('div', { className: 'lc-delivery-change__date-compare' }, + React.createElement('span', { + className: 'lc-delivery-change__date-before', + title: '修改前', + }, log.beforeRegion || '-'), + React.createElement('span', { + className: 'lc-delivery-change__date-arrow', + 'aria-hidden': true, + }, '→'), + React.createElement('span', { + className: 'lc-delivery-change__date-after', + title: '修改后', + }, log.afterRegion || '-'), + ), + ) + : null, React.createElement('div', { className: 'lc-delivery-change__history-row' }, - React.createElement('span', { className: 'lc-delivery-change__history-field-label' }, '交车时间'), + React.createElement('span', { className: 'lc-delivery-change__history-field-label' }, '交付时间'), React.createElement('div', { className: 'lc-delivery-change__date-compare' }, React.createElement('span', { className: 'lc-delivery-change__date-before tabular-nums', @@ -2099,20 +2742,52 @@ const Component = function(props) { function closeDelegatePopover() { _delegatePopoverRecord[1](null); + closeSigningMethodPopover(); + } + + function openContractFlow(mode, record, options) { + var opts = options || {}; + _flowMode[1](mode || FLOW_MODE_CREATE); + _flowInitialFormState[1](null); + _editContractRecord[1](null); + _createInitialSection[1](opts.section || null); + if (mode === FLOW_MODE_EDIT && record) { + _editContractRecord[1](record); + } else if (mode === FLOW_MODE_RENEW && record) { + _flowInitialFormState[1](buildRenewContractFormState(record)); + } else if (mode === FLOW_MODE_TRIAL_TO_FORMAL && record) { + _flowInitialFormState[1](buildTrialToFormalFormState(record)); + } else if (mode === FLOW_MODE_ADD_VEHICLE && record) { + _flowInitialFormState[1](buildAddVehicleFormState(record)); + _createInitialSection[1]('leaseOrder'); + } else if (mode === FLOW_MODE_ADD_POA && record) { + _flowInitialFormState[1](buildAddPowerOfAttorneyFormState(record)); + _createInitialSection[1]('poa'); + } else if (mode === FLOW_MODE_TRIPARTITE && record) { + _flowInitialFormState[1](buildTripartiteContractFormState(record)); + _createInitialSection[1]('main'); + } + _view[1]('create'); } function openContractEditForPoa(record) { - _createInitialSection[1]('poa'); - _editContractRecord[1](record || null); - _view[1]('create'); + openContractFlow(FLOW_MODE_ADD_POA, record); } function backToContractList() { _createInitialSection[1](null); _editContractRecord[1](null); + _viewContractRecord[1](null); + _flowMode[1](FLOW_MODE_CREATE); + _flowInitialFormState[1](null); _view[1]('list'); } + function openContractView(record) { + _viewContractRecord[1](record || null); + _view[1]('view'); + } + function renderDelegatePopoverPanel(record) { var delegates = getAuthorizedDelegates(record); return React.createElement('div', { @@ -2178,7 +2853,6 @@ const Component = function(props) { return; } _vehiclePopoverRecord[1](null); - _deliveredPopoverRecord[1](null); closeDeliveryChangePopover(); _delegatePopoverRecord[1](record); }, @@ -2191,81 +2865,250 @@ const Component = function(props) { ); } - function renderApprovalTypeCell(_, record) { - var label = formatContractApprovalTypeLabel(record.contractApprovalType); - var isNonStandard = label === '非标准合同'; - return React.createElement('span', { - className: 'lc-approval-type-label' + (isNonStandard ? ' lc-approval-type-label--nonstandard' : ''), - }, label); + function hasContractStampedAttachments(record) { + if (!record) return false; + return getContractStampedFiles(record).length > 0 + && (record.legalStampedContractUploaded === true || _stampedUploadedOverride[0][record.id] === true); + } + + function getContractStampedFiles(record) { + if (!record) return []; + var override = _stampedFilesOverride[0][record.id]; + if (override && override.length) return override; + if (record.stampedContractFiles && record.stampedContractFiles.length) return record.stampedContractFiles; + return []; + } + + function getContractSigningFiles(record) { + if (!record) return []; + var method = resolveContractSigningMethod(record.contractSigningMethod); + if (isOfflineContractSigning(method)) return getContractStampedFiles(record); + return getOnlineEsignContractFiles(record); + } + + function canExpandSigningMethodCard(record) { + if (!record) return false; + var method = resolveContractSigningMethod(record.contractSigningMethod); + if (isOfflineContractSigning(method)) return hasContractStampedAttachments(record); + return hasOnlineEsignCompleted(record, _stampedCompletedAtOverride[0]); + } + + function closeSigningMethodPopover() { + _signingMethodPopoverRecord[1](null); + } + + function formatStampUploadCompletedMinute() { + var now = new Date(); + var pad = function (n) { return n < 10 ? '0' + n : String(n); }; + return now.getFullYear() + '-' + + pad(now.getMonth() + 1) + '-' + + pad(now.getDate()) + ' ' + + pad(now.getHours()) + ':' + + pad(now.getMinutes()); + } + + function previewSigningMethodFile(file, record) { + var method = resolveContractSigningMethod(record.contractSigningMethod); + var opened = isOfflineContractSigning(method) + ? openContractAttachmentPreviewInNewTab(file, record) + : openOnlineEsignPreviewInNewTab(record); + if (!opened) message.warning('请允许浏览器弹出新窗口后重试'); + } + + function renderSigningMethodCellContent(record, classSuffix) { + var method = resolveContractSigningMethod(record.contractSigningMethod); + var label = formatContractSigningMethodLabel(method); + var hasUploaded = hasContractStampedAttachments(record); + var subLabel = getContractSigningSubLabel(record, { + hasUploaded: hasUploaded, + completedAtOverride: _stampedCompletedAtOverride[0], + }); + var isPending = isOfflineContractSigning(method) && !hasUploaded; + return React.createElement(React.Fragment, null, + React.createElement('span', { className: 'lc-signing-method-cell__label' }, label), + React.createElement('span', { + className: 'lc-signing-method-cell__sub tabular-nums' + + (isPending ? ' is-pending' : ''), + title: subLabel, + }, subLabel), + ); + } + + function renderSigningMethodAttachmentsPanel(record) { + var files = getContractSigningFiles(record); + var method = resolveContractSigningMethod(record.contractSigningMethod); + var isOffline = isOfflineContractSigning(method); + return React.createElement('div', { className: 'lc-signing-method-popover', role: 'region', 'aria-label': '合同签署附件' }, + React.createElement('div', { className: 'lc-signing-method-popover__title' }, isOffline ? '盖章附件' : '电子签章合同'), + React.createElement('div', { className: 'lc-signing-method-popover__sub' }, record.contractCode || '-'), + files.length + ? React.createElement('ul', { className: 'lc-signing-method-popover__list' }, + files.map(function (file, index) { + var fileName = file.name || ('附件 ' + (index + 1)); + return React.createElement('li', { key: file.uid || file.name || index, className: 'lc-signing-method-popover__row' }, + React.createElement('span', { + className: 'lc-signing-method-popover__name', + title: fileName, + }, fileName), + React.createElement('div', { className: 'lc-signing-method-popover__actions' }, + React.createElement('button', { + type: 'button', + className: 'lc-signing-method-popover__action', + onClick: function () { + previewSigningMethodFile(file, record); + }, + }, '预览'), + React.createElement('button', { + type: 'button', + className: 'lc-signing-method-popover__action', + onClick: function () { + downloadContractAttachment(file, record); + message.success('已开始下载(原型)'); + }, + }, '下载'), + ), + ); + }), + ) + : React.createElement('div', { className: 'lc-signing-method-popover__empty' }, '暂无附件'), + ); + } + + function renderSigningMethodPopoverCell(record, classSuffix, ariaLabel) { + var popoverOpen = _signingMethodPopoverRecord[0] && _signingMethodPopoverRecord[0].id === record.id; + return React.createElement(Popover, { + overlayClassName: 'lc-signing-method-popover-wrap', + placement: 'bottom', + trigger: 'click', + arrow: { pointAtCenter: true }, + destroyTooltipOnHide: true, + open: popoverOpen, + title: null, + content: renderSigningMethodAttachmentsPanel(record), + onOpenChange: function (visible) { + if (!visible) closeSigningMethodPopover(); + else { + closeDeliveryChangePopover(); + closeDelegatePopover(); + _vehiclePopoverRecord[1](null); + _signingMethodPopoverRecord[1](record); + } + }, + }, + React.createElement('button', { + type: 'button', + className: 'lc-signing-method-cell__btn ' + classSuffix, + 'aria-label': ariaLabel, + 'aria-expanded': popoverOpen, + }, renderSigningMethodCellContent(record, classSuffix)), + ); + } + + function renderContractSigningMethodCell(_, record) { + var method = resolveContractSigningMethod(record.contractSigningMethod); + var label = formatContractSigningMethodLabel(method); + var isOffline = isOfflineContractSigning(method); + var hasUploaded = hasContractStampedAttachments(record); + var subLabel = getContractSigningSubLabel(record, { + hasUploaded: hasUploaded, + completedAtOverride: _stampedCompletedAtOverride[0], + }); + + if (isOffline && !hasUploaded) { + return React.createElement('button', { + type: 'button', + className: 'lc-signing-method-cell__btn is-offline is-pending', + 'aria-label': label + ',' + subLabel + ',点击上传盖章附件', + onClick: function (e) { + e.stopPropagation(); + closeSigningMethodPopover(); + if (record.approvalStatus !== '审批通过') { + message.info('审批通过后可补传盖章合同(原型)'); + return; + } + openStampUploadModal(record, 'supplement'); + }, + }, renderSigningMethodCellContent(record, 'is-offline is-pending')); + } + + if (canExpandSigningMethodCard(record)) { + var files = getContractSigningFiles(record); + return renderSigningMethodPopoverCell( + record, + isOffline ? 'is-offline is-uploaded' : 'is-online is-completed', + label + ',' + subLabel + ',已归档 ' + files.length + ' 个附件', + ); + } + + return React.createElement('div', { + className: 'lc-signing-method-cell__static' + (isOffline ? ' is-offline' : ' is-online'), + title: label + ':' + subLabel, + }, renderSigningMethodCellContent(record, 'is-static')); } function renderDeliveryCell(_, record) { - var regionLabel = formatDeliveryRegion(record.deliveryRegion); + var regionLabel = formatContractDeliveryRegionLabel(record); var dateLabel = formatContractDeliveryDateLabel(record); - var isTbd = record.deliveryDateTbd || isDeliveryDateTbd(record.deliveryDate); + var isTbd = isContractDeliveryDateUnconfirmed(record); + var canEdit = canEditContractDeliveryArrangement(record); var popoverOpen = _deliveryChangePopoverRecord[0] && _deliveryChangePopoverRecord[0].id === record.id; return React.createElement('div', { className: 'lc-delivery-cell' }, - React.createElement('span', { className: 'lc-delivery-cell__region' }, regionLabel), + React.createElement('span', { + className: 'lc-delivery-cell__region' + (isContractDeliveryRegionTbd(record) ? ' lc-delivery-cell__region--tbd' : ''), + title: regionLabel, + }, regionLabel), React.createElement('div', { className: 'lc-delivery-cell__date-row' }, React.createElement('span', { className: 'lc-delivery-cell__date tabular-nums' + (isTbd ? ' lc-delivery-cell__date--tbd' : ''), + title: dateLabel, }, dateLabel), - React.createElement(Popover, { - overlayClassName: 'lc-delivery-change-popover', - placement: 'bottom', - trigger: 'click', - arrow: { pointAtCenter: true }, - destroyTooltipOnHide: true, - open: popoverOpen, - title: null, - content: renderDeliveryChangePanel(record), - onOpenChange: function (visible) { - if (!visible) { - closeDeliveryChangePopover(); - return; - } - closeVehicleDeliveryChangePopover(); - _vehiclePopoverRecord[1](null); - _deliveredPopoverRecord[1](null); - closeDelegatePopover(); - var recordTbd = record.deliveryDateTbd || isDeliveryDateTbd(record.deliveryDate); - _deliveryChangePopoverRecord[1](record); - _deliveryChangeDraftTbd[1](!!recordTbd); - _deliveryChangeDraftDate[1](recordTbd ? null : (record.deliveryDate || null)); + canEdit + ? React.createElement(Popover, { + overlayClassName: 'lc-delivery-change-popover', + placement: 'bottom', + trigger: 'click', + arrow: { pointAtCenter: true }, + destroyTooltipOnHide: true, + open: popoverOpen, + title: null, + content: renderDeliveryChangePanel(record), + onOpenChange: function (visible) { + if (!visible) { + closeDeliveryChangePopover(); + return; + } + closeVehicleDeliveryChangePopover(); + _vehiclePopoverRecord[1](null); + closeDelegatePopover(); + _deliveryChangePopoverRecord[1](record); + initDeliveryChangeDraft(record); + }, }, - }, - React.createElement('button', { - type: 'button', - className: 'lc-delivery-cell__edit-btn', - 'aria-label': '编辑交车时间', - }, renderDeliveryEditIcon()), - ), + React.createElement('button', { + type: 'button', + className: 'lc-delivery-cell__edit-btn', + 'aria-label': '编辑交车安排', + }, renderDeliveryEditIcon()), + ) + : null, ), ); } function renderLeaseOrderCountsCell(_, record) { var deliveredVehicles = (record.vehicles || []).filter(function (v) { - var d = v.actualDelivery; - return d && String(d).trim() && d !== '-'; + return isContractVehicleDelivered(v); }); var deliveredCount = deliveredVehicles.length; var rentalOpen = _vehiclePopoverRecord[0] && _vehiclePopoverRecord[0].id === record.id; - var deliveredOpen = _deliveredPopoverRecord[0] && _deliveredPopoverRecord[0].id === record.id; - var rentalContent = renderVehiclePopoverPanel({ - variant: 'rental', + var deliveredExpanded = _expandedRowKeys[0].indexOf(record.id) >= 0 + && _expandedRowVehicleFilter[0][record.id] === 'delivered'; + var rentalContent = renderRentalSummaryPopoverPanel({ title: '租赁车辆明细', subtitle: record.contractCode || '', vehicles: record.vehicles || [], emptyText: '该合同暂无租赁车辆', }); - var deliveredContent = renderVehiclePopoverPanel({ - variant: 'delivered', - title: '已交车列表', - subtitle: record.contractCode || '', - vehicles: deliveredVehicles, - emptyText: '暂无已交车记录', - }); var popoverCommonProps = { overlayClassName: 'lc-vehicle-popover', placement: 'bottom', @@ -2282,9 +3125,9 @@ const Component = function(props) { onOpenChange: function (visible) { if (!visible) _vehiclePopoverRecord[1](null); else { - _deliveredPopoverRecord[1](null); closeDeliveryChangePopover(); closeDelegatePopover(); + closeSigningMethodPopover(); _vehiclePopoverRecord[1](record); } }, @@ -2299,34 +3142,31 @@ const Component = function(props) { ), ), React.createElement('span', { className: 'lc-lease-order-counts__sep', 'aria-hidden': true }), - React.createElement(Popover, Object.assign({}, popoverCommonProps, { - content: deliveredContent, - title: null, - open: deliveredOpen, - onOpenChange: function (visible) { - if (!visible) _deliveredPopoverRecord[1](null); - else { - _vehiclePopoverRecord[1](null); - closeDeliveryChangePopover(); - closeDelegatePopover(); - _deliveredPopoverRecord[1](record); - } + React.createElement('button', { + type: 'button', + className: 'lc-lease-order-counts__item' + + (deliveredExpanded ? ' lc-lease-order-counts__item--active' : '') + + (deliveredCount > 0 ? '' : ' is-disabled'), + disabled: deliveredCount === 0, + 'aria-label': '已交车辆数 ' + deliveredCount, + 'aria-expanded': deliveredExpanded, + onClick: function (e) { + e.stopPropagation(); + if (deliveredCount === 0) return; + _vehiclePopoverRecord[1](null); + closeDeliveryChangePopover(); + closeDelegatePopover(); + toggleContractRowExpand(record.id, 'delivered'); }, - }), - React.createElement('button', { - type: 'button', - className: 'lc-lease-order-counts__item', - 'aria-label': '已交车辆数 ' + deliveredCount, - }, - React.createElement('span', { className: 'lc-lease-order-counts__value tabular-nums' }, deliveredCount), - React.createElement('span', { className: 'lc-lease-order-counts__label' }, '已交'), - ), + }, + React.createElement('span', { className: 'lc-lease-order-counts__value tabular-nums' }, deliveredCount), + React.createElement('span', { className: 'lc-lease-order-counts__label' }, '已交'), ), ); } function renderContactCell(_, record) { - return renderStackCell(record.contactName, record.contactPhone); + return renderStackCell(record.contactName, record.contactPhone, 'sub tabular-nums'); } function renderCreateAuditCell(_, record) { @@ -2337,57 +3177,20 @@ const Component = function(props) { return renderStackCell(record.updater, record.updateTime, 'code'); } - function renderOperationMoreIcon() { - return React.createElement('svg', { - viewBox: '0 0 16 16', - width: 16, - height: 16, - fill: 'currentColor', - 'aria-hidden': true, - }, - React.createElement('circle', { cx: 8, cy: 3, r: 1.5 }), - React.createElement('circle', { cx: 8, cy: 8, r: 1.5 }), - React.createElement('circle', { cx: 8, cy: 13, r: 1.5 }), - ); - } - function getOperationButtons(record) { - var menuItems = getMoreMenuItems(record); - var moreDropdown = menuItems.length > 0 - ? React.createElement( - Dropdown, - { - menu: { items: menuItems }, - trigger: ['click'], - placement: 'bottomRight', - }, - React.createElement(Tooltip, { title: '更多' }, - React.createElement('span', { - className: 'lc-action-more-btn', - role: 'button', - tabIndex: 0, - 'aria-label': '更多操作', - 'data-annotation-id': 'lc-list-action-more', - onClick: function (e) { e.stopPropagation(); }, - }, renderOperationMoreIcon()), - ), - ) - : null; - return React.createElement( - 'div', - { className: 'lc-row-actions' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function() { openContractView(record); } }, '查看'), - moreDropdown, - ); + return React.createElement(OperationActions, { + view: { onClick: function () { openContractView(record); } }, + more: getMoreMenuItems(record), + annotationId: 'lc-list-action-more', + }); } var columns = [ { title: '项目信息', key: 'projectInfo', - width: 248, + width: 320, fixed: 'left', - ellipsis: true, className: 'lc-project-info-col', onHeaderCell: function () { return { @@ -2407,17 +3210,17 @@ const Component = function(props) { render: renderLeaseOrderCountsCell, }, { - title: '审批类型', - key: 'contractApprovalType', - width: 108, - className: 'lc-approval-type-col', - onCell: function () { return { className: 'lc-approval-type-col' }; }, - render: renderApprovalTypeCell, + title: '合同签署方式', + key: 'contractSigningMethod', + width: 156, + className: 'lc-signing-method-col', + onCell: function () { return { className: 'lc-signing-method-col' }; }, + render: renderContractSigningMethodCell, }, { title: '审批状态', key: 'approvalStatus', - width: 120, + width: 156, className: 'lc-approval-status-col', onCell: function () { return { className: 'lc-approval-status-col' }; }, render: renderApprovalStatusCell, @@ -2439,15 +3242,18 @@ const Component = function(props) { { title: '费用信息', key: 'feeInfo', - width: 192, + width: 248, className: 'lc-fee-info-col', + onHeaderCell: function () { + return { className: 'lc-fee-info-col' }; + }, onCell: function () { return { className: 'lc-fee-info-col' }; }, render: renderFeeInfoCell, }, { title: '交车安排', key: 'deliveryPlan', - width: 148, + width: 176, className: 'lc-delivery-col', onCell: function () { return { className: 'lc-delivery-col' }; }, render: renderDeliveryCell, @@ -2466,13 +3272,6 @@ const Component = function(props) { onCell: function () { return { className: 'lc-overall-mileage-col' }; }, render: renderOverallMileageProgressCell, }, - { - title: '合同类型', - key: 'contractType', - width: 148, - ellipsis: true, - render: renderContractTypeCell, - }, { title: '客户联系人', key: 'contact', @@ -2505,7 +3304,7 @@ const Component = function(props) { width: 148, render: renderUpdateAuditCell }, - { title: '操作', key: 'action', width: 140, fixed: 'right', render: function(_, record) { return getOperationButtons(record); } } + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function(_, record) { return getOperationButtons(record); } } ]; var listTableScrollX = columns.reduce(function (sum, col) { @@ -2516,6 +3315,7 @@ const Component = function(props) { React.createElement('label', { key: 'contractCode', className: 'vm-filter-field' }, React.createElement('span', null, '合同编码'), React.createElement(Select, { + className: 'lc-filter-select', placeholder: '请选择或输入合同编码', style: { width: '100%' }, value: _contractCode[0], @@ -2530,6 +3330,7 @@ const Component = function(props) { React.createElement('label', { key: 'projectName', className: 'vm-filter-field' }, React.createElement('span', null, '项目名称'), React.createElement(Select, { + className: 'lc-filter-select', placeholder: '请选择或输入项目名称', style: { width: '100%' }, value: _projectName[0], @@ -2544,6 +3345,7 @@ const Component = function(props) { React.createElement('label', { key: 'customerName', className: 'vm-filter-field' }, React.createElement('span', null, '客户名称'), React.createElement(Select, { + className: 'lc-filter-select', placeholder: '请选择或输入客户名称', style: { width: '100%' }, value: _customerName[0], @@ -2558,6 +3360,7 @@ const Component = function(props) { React.createElement('label', { key: 'signingCompany', className: 'vm-filter-field' }, React.createElement('span', null, '签约公司'), React.createElement(Select, { + className: 'lc-filter-select', placeholder: '请选择或输入签约公司名称', style: { width: '100%' }, value: _signingCompany[0], @@ -2568,6 +3371,7 @@ const Component = function(props) { React.createElement('label', { key: 'approvalStatus', className: 'vm-filter-field' }, React.createElement('span', null, '审批状态'), React.createElement(Select, { + className: 'lc-filter-select', mode: 'multiple', placeholder: '请选择', style: { width: '100%' }, @@ -2578,6 +3382,7 @@ const Component = function(props) { React.createElement('label', { key: 'contractStatus', className: 'vm-filter-field' }, React.createElement('span', null, '合同状态'), React.createElement(Select, { + className: 'lc-filter-select', mode: 'multiple', placeholder: '请选择', style: { width: '100%' }, @@ -2588,6 +3393,7 @@ const Component = function(props) { React.createElement('label', { key: 'businessDept', className: 'vm-filter-field' }, React.createElement('span', null, '业务部门'), React.createElement(Select, { + className: 'lc-filter-select', mode: 'multiple', placeholder: '请选择或输入业务部门名称', style: { width: '100%' }, @@ -2598,6 +3404,7 @@ const Component = function(props) { React.createElement('label', { key: 'businessOwner', className: 'vm-filter-field' }, React.createElement('span', null, '业务负责人'), React.createElement(Select, { + className: 'lc-filter-select', mode: 'multiple', placeholder: '请选择或输入业务负责人姓名', style: { width: '100%' }, @@ -2605,19 +3412,35 @@ const Component = function(props) { onChange: function(v) { _businessOwner[1](v); }, options: userOptions })), - React.createElement('label', { key: 'contractType', className: 'vm-filter-field' }, - React.createElement('span', null, '合同类型'), + React.createElement('label', { key: 'contractTemplateCategory', className: 'vm-filter-field' }, + React.createElement('span', null, '合同模板'), React.createElement(Select, { - mode: 'multiple', - placeholder: '请选择', + className: 'lc-filter-select', + allowClear: true, + placeholder: '请选择合同模板', style: { width: '100%' }, - value: _contractType[0], - onChange: handleContractTypeChange, - options: contractTypeOptions + value: _contractTemplateCategory[0], + onChange: handleContractTemplateCategoryChange, + options: contractTemplateCategoryOptions + })), + React.createElement('label', { key: 'contractTemplateId', className: 'vm-filter-field' }, + React.createElement('span', null, '标准合同名称'), + React.createElement(Select, { + className: 'lc-filter-select', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + placeholder: _contractTemplateCategory[0] ? '请选择标准合同名称' : '请先选择合同模板', + disabled: !_contractTemplateCategory[0], + style: { width: '100%' }, + value: _contractTemplateId[0], + onChange: handleContractTemplateIdChange, + options: contractTemplateStandardOptions })), React.createElement('label', { key: 'contractApprovalType', className: 'vm-filter-field' }, React.createElement('span', null, '审批类型'), React.createElement(Select, { + className: 'lc-filter-select', mode: 'multiple', placeholder: '请选择', style: { width: '100%' }, @@ -2628,6 +3451,7 @@ const Component = function(props) { React.createElement('label', { key: 'creator', className: 'vm-filter-field' }, React.createElement('span', null, '创建人'), React.createElement(Select, { + className: 'lc-filter-select', mode: 'multiple', placeholder: '请选择或输入创建人姓名', style: { width: '100%' }, @@ -2659,11 +3483,23 @@ const Component = function(props) { filterNodes.push(filterItems[i]); } + if (_view[0] === 'view') { + var viewRecord = _viewContractRecord[0]; + var stampedFiles = viewRecord ? (_stampedFilesOverride[0][viewRecord.id] || []) : []; + return React.createElement(LeaseContractView, { + record: viewRecord, + onBack: backToContractList, + stampedFilesOverride: stampedFiles, + }); + } + if (_view[0] === 'create') { return React.createElement(LeaseContractCreate, { onBack: backToContractList, initialScrollSection: _createInitialSection[0], editRecord: _editContractRecord[0], + flowMode: _flowMode[0], + flowInitialFormState: _flowInitialFormState[0], }); } @@ -2685,32 +3521,26 @@ const Component = function(props) { ? React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-link ldb-filter-toggle', + 'data-vm-icon': _filterExpanded[0] ? 'chevron-up' : 'filter', onClick: function() { _filterExpanded[1](!_filterExpanded[0]); } }, _filterExpanded[0] ? '收起' : '更多筛选') : null, - React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: handleReset }, '重置'), - React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: handleQuery }, - React.createElement('svg', { - xmlns: 'http://www.w3.org/2000/svg', - width: 16, - height: 16, - viewBox: '0 0 24 24', - fill: 'none', - stroke: 'currentColor', - strokeWidth: 2, - strokeLinecap: 'round', - strokeLinejoin: 'round', - 'aria-hidden': true - }, - React.createElement('circle', { cx: 11, cy: 11, r: 8 }), - React.createElement('path', { d: 'm21 21-4.3-4.3' }) - ), - '查询' - ) + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', + 'data-vm-icon': 'rotate-ccw', + onClick: handleReset, + }, '重置'), + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-primary ldb-toolbar-btn', + 'data-vm-icon': 'search', + onClick: handleQuery, + }, '查询') ) ), React.createElement('div', { - className: 'vm-kpi-row' + (fleetTotalOnLease > 0 ? ' vm-kpi-row--with-fleet' : ''), + className: 'vm-kpi-row', role: 'group', 'aria-label': '合同统计', 'data-annotation-id': 'lc-list-kpi', @@ -2721,7 +3551,6 @@ const Component = function(props) { type: 'button', className: 'vm-kpi-card' + (_kpiTab[0] === card.key ? ' active' : ''), onClick: function() { - collapseFleetKpiPanel(); _kpiTab[1](card.key); _page[1](1); }, @@ -2744,80 +3573,22 @@ const Component = function(props) { React.createElement('span', { className: 'vm-kpi-val' }, kpiCounts[card.key] != null ? kpiCounts[card.key] : 0) ) ); - }), - fleetTotalOnLease > 0 - ? React.createElement('button', { - key: 'onLeaseFleet', - type: 'button', - className: 'vm-kpi-card lc-fleet-kpi-card' + (fleetKpiActive ? ' active' : ''), - onClick: handleFleetKpiClick, - 'aria-pressed': fleetKpiActive, - 'data-annotation-id': 'lc-fleet-summary', - }, - React.createElement('span', { - className: 'vm-kpi-tip', - tabIndex: 0, - role: 'note', - 'aria-label': '在租车辆说明', - onClick: function(e) { e.stopPropagation(); }, - onMouseDown: function(e) { e.stopPropagation(); } - }, - kpiHelpIcon, - React.createElement('span', { className: 'vm-kpi-tooltip', role: 'tooltip' }, fleetKpiDesc) - ), - React.createElement('span', { className: 'vm-kpi-icon', 'aria-hidden': true }, FLEET_KPI_ICON), - React.createElement('span', { className: 'vm-kpi-main' }, - React.createElement('span', { className: 'vm-kpi-eyebrow' }, '在租车辆'), - React.createElement('span', { className: 'vm-kpi-val tabular-nums' }, fleetTotalOnLease) - ) - ) - : null + }) ), - React.createElement(LeaseContractFleetSummary, { - records: fleetSourceList, - Modal: Modal, - renderVehicleNativeTableHeader: renderContractVehicleNativeTableHeader, - renderVehicleNativeTableRow: renderContractVehicleNativeTableRow, - expanded: _fleetExpanded[0], - onExpandedChange: _fleetExpanded[1], - panelHidden: _fleetPanelHidden[0], - onPanelHiddenChange: _fleetPanelHidden[1], - quickViewOpen: _fleetQuickOpen[0], - onQuickViewOpenChange: _fleetQuickOpen[1], - onViewContract: function (record) { - message.info('查看合同(原型):' + (record.contractCode || record.projectName || '')); - }, - }), React.createElement('section', { className: 'vm-table-section' }, React.createElement('div', { className: 'vm-table-toolbar', 'data-annotation-id': 'lc-list-toolbar' }, React.createElement('div', { className: 'vm-table-actions' }, React.createElement('button', { type: 'button', - className: 'vm-btn vm-btn-ghost', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', + 'data-vm-icon': 'download', onClick: function() { message.info('根据筛选条件,导出相应记录'); } - }, - React.createElement('svg', { - xmlns: 'http://www.w3.org/2000/svg', - width: 16, - height: 16, - viewBox: '0 0 24 24', - fill: 'none', - stroke: 'currentColor', - strokeWidth: 2, - strokeLinecap: 'round', - strokeLinejoin: 'round', - 'aria-hidden': true - }, - React.createElement('path', { d: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4' }), - React.createElement('polyline', { points: '7 10 12 15 17 10' }), - React.createElement('line', { x1: 12, x2: 12, y1: 15, y2: 3 }) - ), - '导出' - ), + }, '导出'), React.createElement('button', { type: 'button', - className: 'vm-btn vm-btn-primary', - onClick: function() { _view[1]('create'); } + className: 'vm-btn vm-btn-primary ldb-toolbar-btn', + 'data-vm-icon': 'plus', + onClick: function() { openContractFlow(FLOW_MODE_CREATE, null); } }, '新增') ) ), @@ -2836,7 +3607,16 @@ const Component = function(props) { showExpandColumn: false, expandIconColumnIndex: -1, expandedRowKeys: _expandedRowKeys[0], - onExpandedRowsChange: function (keys) { _expandedRowKeys[1](keys); }, + onExpandedRowsChange: function (keys) { + _expandedRowKeys[1](keys); + _expandedRowVehicleFilter[1](function (prev) { + var next = {}; + keys.forEach(function (key) { + if (prev[key]) next[key] = prev[key]; + }); + return next; + }); + }, expandedRowRender: renderExpandedVehicleTable, rowExpandable: function (record) { return (record.vehicles || []).length > 0; @@ -2895,17 +3675,100 @@ const Component = function(props) { cancelText: '取消' }), React.createElement(Modal, { - title: '是否确认终止合同', + title: '主动终止合同', open: _terminateModalVisible[0], - onCancel: function() { _terminateModalVisible[1](false); _terminateModalRecord[1](null); }, - onOk: function() { - message.success('已提交审核(原型),审核通过后合同状态变更为已结束'); + onCancel: function() { _terminateModalVisible[1](false); _terminateModalRecord[1](null); + _terminateForm[1]({ terminateAt: null, reason: undefined, remark: '' }); }, - okText: '提交审核', - cancelText: '取消' - }), + width: 520, + footer: [ + React.createElement(Button, { key: 'cancel', onClick: function() { + _terminateModalVisible[1](false); + _terminateModalRecord[1](null); + _terminateForm[1]({ terminateAt: null, reason: undefined, remark: '' }); + } }, '取消'), + React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() { + var rec = _terminateModalRecord[0]; + var form = _terminateForm[0] || {}; + if (!form.terminateAt) { + message.warning('请选择主动终止时间'); + return; + } + if (!form.reason) { + message.warning('请选择终止原因'); + return; + } + if (rec && rec.contractCode) { + syncPickupReceivableContractStatus(rec.contractCode, { + contractStatus: '已终止', + contractApprovalStatus: '审批终止', + }); + _recordOverrides[1](function (prev) { + var next = Object.assign({}, prev); + next[rec.id] = Object.assign({}, next[rec.id] || {}, { + approvalStatus: '审批终止', + terminatedBy: true, + remark: form.remark || '', + terminateReason: form.reason, + terminateAt: form.terminateAt, + }); + return next; + }); + } + message.success('已提交审核(原型);审批通过后合同标记为「已终止」,沿用原签署方式完成签章或盖章补传'); + _terminateModalVisible[1](false); + _terminateModalRecord[1](null); + _terminateForm[1]({ terminateAt: null, reason: undefined, remark: '' }); + } }, '提交审核'), + ], + }, React.createElement('div', { className: 'lc-flow-modal lc-flow-modal--terminate', 'data-annotation-id': 'lc-action-terminate' }, + renderModalContractCode(_terminateModalRecord[0]), + React.createElement('p', { className: 'lc-flow-modal__hint' }, '审批通过后沿用原合同签署方式:线上向乙方负责人发送 E签宝链接,线下需完成盖章合同补传。'), + React.createElement('div', { className: 'lc-flow-modal__field' }, + React.createElement('label', { className: 'lc-flow-modal__label' }, '主动终止时间'), + React.createElement(DatePicker, { + showTime: { format: 'HH:mm' }, + format: 'YYYY-MM-DD HH:mm', + style: { width: '100%' }, + placeholder: '请选择终止时间', + onChange: function (_, dateString) { + _terminateForm[1](function (prev) { + return Object.assign({}, prev, { terminateAt: dateString || null }); + }); + }, + }), + ), + React.createElement('div', { className: 'lc-flow-modal__field' }, + React.createElement('label', { className: 'lc-flow-modal__label' }, '终止原因'), + React.createElement(Select, { + style: { width: '100%' }, + placeholder: '请选择', + options: [ + { value: '客户原因', label: '客户原因' }, + { value: '我方原因', label: '我方原因' }, + ], + onChange: function (value) { + _terminateForm[1](function (prev) { + return Object.assign({}, prev, { reason: value }); + }); + }, + }), + ), + React.createElement('div', { className: 'lc-flow-modal__field' }, + React.createElement('label', { className: 'lc-flow-modal__label' }, '备注'), + React.createElement(Input.TextArea, { + rows: 3, + placeholder: '可填写补充说明', + onChange: function (e) { + _terminateForm[1](function (prev) { + return Object.assign({}, prev, { remark: e.target.value }); + }); + }, + }), + ), + )), React.createElement(Modal, { title: '添加授权委托书', open: _authorizedModalVisible[0], @@ -2913,9 +3776,17 @@ const Component = function(props) { width: 640, footer: [ React.createElement(Button, { key: 'cancel', onClick: function() { _authorizedModalVisible[1](false); _authorizedModalRecord[1](null); } }, '取消'), - React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() { message.success('已提交审核(原型)'); _authorizedModalVisible[1](false); _authorizedModalRecord[1](null); } }, '提交审核') + React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() { + var rec = _authorizedModalRecord[0]; + message.success('已提交审核(原型)'); + message.info('审批通过后沿用原签署方式:线上 E签宝 / 线下盖章补传,新受托人生效(' + (rec && rec.contractCode ? rec.contractCode : '') + ')'); + _authorizedModalVisible[1](false); + _authorizedModalRecord[1](null); + } }, '提交审核') ] - }, React.createElement('div', { style: { padding: '8px 0' } }, + }, React.createElement('div', { className: 'lc-flow-modal', style: { padding: '8px 0' }, 'data-annotation-id': 'lc-action-add-poa' }, + renderModalContractCode(_authorizedModalRecord[0]), + React.createElement('p', { className: 'lc-flow-modal__hint' }, '新增受托人信息,审批通过后沿用原合同签署方式完成闭环。'), React.createElement(Table, { rowKey: function(_, i) { return String(i); }, size: 'small', @@ -2933,85 +3804,129 @@ const Component = function(props) { React.createElement(Modal, { title: '附加费用', open: _extraFeeModalVisible[0], - onCancel: function() { _extraFeeModalVisible[1](false); _extraFeeModalRecord[1](null); _extraFeeList[1]([]); }, - width: 900, - footer: [ - React.createElement(Button, { key: 'cancel', onClick: function() { _extraFeeModalVisible[1](false); _extraFeeModalRecord[1](null); _extraFeeList[1]([]); } }, '取消'), - React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() { message.success('已提交审核(原型)'); _extraFeeModalVisible[1](false); _extraFeeModalRecord[1](null); _extraFeeList[1]([]); } }, '提交审核') - ] - }, React.createElement('div', { style: { padding: '8px 0' } }, - React.createElement(Table, { - rowKey: function(_, i) { return String(i); }, - size: 'small', - columns: [ - { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 160, render: function(v) { return v || '-'; } }, - { title: '品牌', dataIndex: 'brand', key: 'brand', width: 90, render: function(v) { return v || '-'; } }, - { title: '型号', dataIndex: 'model', key: 'model', width: 100, render: function(v) { return v || '-'; } }, - { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100, render: function(v) { return v || '-'; } }, - { title: '服务项目', key: 'serviceItem', width: 180, render: function(_, row, index) { return React.createElement(Select, { placeholder: '请选择', style: { width: '100%' }, value: row.serviceItem, onChange: function(v) { updateExtraFeeRow(index, 'serviceItem', v); }, options: extraFeeServiceOptions, allowClear: true }); } }, - { title: '费用(元)', key: 'fee', width: 140, render: function(_, row, index) { return React.createElement(Input, { value: row.fee, onChange: function(e) { updateExtraFeeRow(index, 'fee', e.target.value); }, placeholder: '请输入', addonAfter: '元' }); } }, - { title: '生效时间', key: 'effectiveDate', width: 140, render: function(_, row, index) { var dayjs = window.dayjs; var val = row.effectiveDate && dayjs ? dayjs(row.effectiveDate) : null; return React.createElement(DatePicker, { style: { width: '100%' }, placeholder: '请选择日期', value: val, onChange: function(date, dateString) { updateExtraFeeRow(index, 'effectiveDate', dateString || null); } }); } } - ], - dataSource: _extraFeeList[0], - pagination: false, - scroll: { x: 920 } - }) - )), - React.createElement(Modal, { - title: '上传盖章合同', - open: _stampModalVisible[0], onCancel: function() { - _stampModalVisible[1](false); - _stampModalRecord[1](null); - _stampFileList[1]([]); + _extraFeeModalVisible[1](false); + _extraFeeModalRecord[1](null); + _extraFeeExistingList[1]([]); + _extraFeeNewList[1]([]); }, - width: 560, + width: 960, footer: [ React.createElement(Button, { key: 'cancel', onClick: function() { - _stampModalVisible[1](false); - _stampModalRecord[1](null); - _stampFileList[1]([]); + _extraFeeModalVisible[1](false); + _extraFeeModalRecord[1](null); + _extraFeeExistingList[1]([]); + _extraFeeNewList[1]([]); } }, '取消'), React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() { - var rec = _stampModalRecord[0]; - var list = _stampFileList[0] || []; - if (!list.length) { - message.warning('请先选择要上传的文件'); - return; - } - var pending = list.some(function(f) { return f.status === 'uploading'; }); - if (pending) { - message.warning('请等待文件上传完成'); - return; - } - _stampedUploadedOverride[1](function(prev) { - var n = Object.assign({}, prev); - if (rec) n[rec.id] = true; - return n; + var lockedValues = getExtraFeeLockedServiceValues(_extraFeeExistingList[0]); + var hasNewService = (_extraFeeNewList[0] || []).some(function(row) { + return getExtraFeeNewServiceValues(row.serviceValues, lockedValues).length > 0; }); - message.success('盖章合同已上传(原型)'); - _stampModalVisible[1](false); - _stampModalRecord[1](null); - _stampFileList[1]([]); - } }, '确认上传') + if (!hasNewService) { + message.warning('请至少新增一项服务项'); + return; + } + message.success('已提交审核(原型)'); + message.info('审批通过后按计费规则入账:先付后用计入下期租赁账单,先用后付计入当月并于下月一并支付;需完成线上签章或线下盖章补传'); + _extraFeeModalVisible[1](false); + _extraFeeModalRecord[1](null); + _extraFeeExistingList[1]([]); + _extraFeeNewList[1]([]); + } }, '提交审核') ] - }, React.createElement('div', { style: { padding: '8px 0' } }, - React.createElement('div', { style: { marginBottom: 12, fontSize: 14, color: 'rgba(0,0,0,0.65)' } }, - '合同编码:', - React.createElement('span', { style: { color: 'rgba(0,0,0,0.85)', fontWeight: 500 } }, _stampModalRecord[0] ? _stampModalRecord[0].contractCode : '-') - ), - React.createElement(Upload.Dragger, { - multiple: true, - fileList: _stampFileList[0], - onChange: function(info) { _stampFileList[1](info.fileList); }, - customRequest: function(opts) { - setTimeout(function() { - if (opts.onSuccess) opts.onSuccess('ok'); - }, 200); - }, - accept: '.pdf,.doc,.docx,image/*' - }, React.createElement('p', { style: { margin: 0, padding: '20px 0' } }, '点击或拖拽文件到此区域上传;支持一次选择多个附件,可分批添加')) + }, React.createElement('div', { className: 'lc-flow-modal lc-flow-modal--extra-fee', style: { padding: '8px 0' }, 'data-annotation-id': 'lc-action-extra-fee' }, + renderModalContractCode(_extraFeeModalRecord[0]), + React.createElement('p', { className: 'lc-flow-modal__hint' }, '已有附加费用不可修改;可新增服务项。审批通过后形成附件1补充附件。'), + React.createElement('h4', { className: 'lc-flow-modal__section-title' }, '已有附加费用(不可修改)'), + React.createElement(Table, { + rowKey: 'key', + size: 'small', + columns: [ + { title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100 }, + { title: '服务项目', dataIndex: 'serviceItem', key: 'serviceItem', width: 160 }, + { title: '费用(元)', dataIndex: 'fee', key: 'fee', width: 100, className: 'tabular-nums' }, + { title: '计费方式', dataIndex: 'billingMode', key: 'billingMode', width: 100 }, + { title: '生效时间', dataIndex: 'effectiveDate', key: 'effectiveDate', width: 120 }, + ], + dataSource: _extraFeeExistingList[0], + pagination: false, + locale: { emptyText: '暂无历史附加费用' }, + }), + React.createElement('h4', { className: 'lc-flow-modal__section-title', style: { marginTop: 16 } }, '新增附加费用'), + React.createElement(Table, { + rowKey: 'key', + size: 'small', + columns: (function() { + var lockedValues = getExtraFeeLockedServiceValues(_extraFeeExistingList[0]); + return [ + { title: '服务项目', key: 'serviceItem', width: 280, render: function(_, row, index) { + return React.createElement(LeaseOrderServiceContentField, { + variant: 'inline', + triggerLabel: '选择服务项', + value: row.serviceValues || lockedValues.slice(), + lockedValues: lockedValues, + onChange: function(nextValues) { + updateExtraFeeNewRow(index, 'serviceValues', nextValues); + }, + }); + } }, + { title: '费用(元/天)', key: 'fee', width: 140, render: function(_, row, index) { return React.createElement(Input, { value: row.fee, onChange: function(e) { updateExtraFeeNewRow(index, 'fee', e.target.value); }, placeholder: '请输入', addonAfter: '元' }); } }, + { title: '计费方式', key: 'billingMode', width: 140, render: function(_, row, index) { return React.createElement(Select, { style: { width: '100%' }, value: row.billingMode, options: [{ value: '先付后用', label: '先付后用' }, { value: '先用后付', label: '先用后付' }], onChange: function(v) { updateExtraFeeNewRow(index, 'billingMode', v); } }); } }, + { title: '生效时间', key: 'effectiveDate', width: 160, render: function(_, row, index) { var dayjs = window.dayjs; var val = row.effectiveDate && dayjs ? dayjs(row.effectiveDate) : null; return React.createElement(DatePicker, { style: { width: '100%' }, placeholder: '请选择日期', value: val, onChange: function(date, dateString) { updateExtraFeeNewRow(index, 'effectiveDate', dateString || null); } }); } }, + ]; + })(), + dataSource: _extraFeeNewList[0], + pagination: false, + }), + React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addExtraFeeNewRow }, '添加服务项') )), + renderStampUploadModal(), + React.createElement(Modal, { + title: '合同查看 · 盖章附件', + open: _contractViewModalVisible[0], + onCancel: function() { + _contractViewModalVisible[1](false); + _contractViewRecord[1](null); + }, + width: 640, + footer: [ + React.createElement(Button, { key: 'close', type: 'primary', onClick: function() { + _contractViewModalVisible[1](false); + _contractViewRecord[1](null); + } }, '关闭'), + ], + }, function () { + var rec = _contractViewRecord[0]; + var files = rec ? (_stampedFilesOverride[0][rec.id] || []) : []; + return React.createElement('div', { className: 'lc-contract-view-attachments' }, + React.createElement('p', { className: 'lc-contract-view-attachments__meta' }, + '合同编码:', + React.createElement('strong', null, rec ? rec.contractCode : '-'), + ), + files.length + ? React.createElement('ul', { className: 'lc-contract-view-attachments__list' }, + files.map(function (file) { + return React.createElement('li', { key: file.uid || file.name, className: 'lc-contract-view-attachments__item' }, + React.createElement('span', { className: 'lc-contract-view-attachments__name', title: file.name }, file.name), + React.createElement('div', { className: 'lc-contract-view-attachments__actions' }, + React.createElement(Button, { + type: 'link', + size: 'small', + onClick: function () { message.info('预览:' + file.name + '(原型)'); }, + }, '预览'), + React.createElement(Button, { + type: 'link', + size: 'small', + onClick: function () { message.success('已开始下载:' + file.name + '(原型)'); }, + }, '下载'), + ), + ); + }), + ) + : React.createElement('p', { className: 'lc-contract-view-attachments__empty' }, '暂无盖章附件'), + ); + }()), React.createElement(Modal, { title: '转三方合同', open: _tripartiteModalVisible[0], @@ -3089,7 +4004,7 @@ const Component = function(props) { React.createElement('div', { className: 'lc-flow-modal__readonly-grid' }, React.createElement('div', null, React.createElement('span', { className: 'lc-flow-modal__readonly-label' }, '客户名称'), React.createElement('span', null, _trialToFormalRecord[0] ? _trialToFormalRecord[0].customerName : '-')), React.createElement('div', null, React.createElement('span', { className: 'lc-flow-modal__readonly-label' }, '联系人'), React.createElement('span', null, _trialToFormalRecord[0] ? _trialToFormalRecord[0].contactName : '-')), - React.createElement('div', null, React.createElement('span', { className: 'lc-flow-modal__readonly-label' }, '联系电话'), React.createElement('span', null, _trialToFormalRecord[0] ? _trialToFormalRecord[0].contactPhone : '-')), + React.createElement('div', null, React.createElement('span', { className: 'lc-flow-modal__readonly-label' }, '联系电话'), React.createElement('span', { className: 'tabular-nums' }, _trialToFormalRecord[0] ? _trialToFormalRecord[0].contactPhone : '-')), ), ), React.createElement('section', { className: 'lc-flow-modal__section' }, diff --git a/src/prototypes/lease-contract-management/LeaseContractPreviewPanel.jsx b/src/prototypes/lease-contract-management/LeaseContractPreviewPanel.jsx index 65d9fef..6f3b2ee 100644 --- a/src/prototypes/lease-contract-management/LeaseContractPreviewPanel.jsx +++ b/src/prototypes/lease-contract-management/LeaseContractPreviewPanel.jsx @@ -1,7 +1,8 @@ import React, { useEffect, useRef, useState } from 'react'; -import { Select } from 'antd'; -import { Info } from 'lucide-react'; +import { Select, Popover } from 'antd'; +import { Info, FileDiff } from 'lucide-react'; import { paginateWordHtml } from './contract-word-preview.js'; +import { collectClauseChangeRecords } from './lease-contract-risk-detect.js'; var PREVIEW_ZOOM_STEPS = [50, 75, 90, 100, 110, 125, 150, 175, 200]; @@ -48,7 +49,145 @@ export function PreviewEditableHint() { React.createElement('span', { className: 'lc-form-card__hint-text lc-preview-editable-hint__text' }, '正文可编辑;改动', React.createElement('span', { className: 'lc-preview-editable-hint__risk' }, '风控红线条款'), - '走非标准审批,未改则标准审批。', + '或新增条款走非标准审批,未改则标准审批。', + ), + ); +} + +export function PreviewAttachment1ReadonlyHint() { + return React.createElement('span', { + className: 'lc-form-card__hint lc-preview-editable-hint lc-preview-editable-hint--inline lc-preview-attachment1-hint', + role: 'note', + 'data-annotation-id': 'lc-preview-attachment1-hint', + id: 'lc-preview-attachment1-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' }, + '仅展示附件1条款,主合同及其他附件不可编辑、无需重签;请在左侧填写新增车辆订单。', + ), + ); +} + +export function PreviewPowerOfAttorneyReadonlyHint() { + return React.createElement('span', { + className: 'lc-form-card__hint lc-preview-editable-hint lc-preview-editable-hint--inline lc-preview-poa-hint', + role: 'note', + 'data-annotation-id': 'lc-preview-poa-hint', + id: 'lc-preview-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 lc-preview-editable-hint__text' }, + '仅展示授权委托书,主合同及其他附件不可编辑、无需重签;请在左侧填写受托人信息。', + ), + ); +} + +export function PreviewApprovalHint() { + return React.createElement('span', { + className: 'lc-form-card__hint lc-preview-approval-hint', + role: 'note', + 'data-annotation-id': 'lc-preview-approval-hint', + id: 'lc-preview-approval-hint', + title: '非标准合同触发条件:1.实时预览新增条款;2.改动风控红线条款;3.付款方式为先用后付;4.付款周期为季付/半年付/年付;5.车辆租金或保证金低于车型最低标准价;6.用章类型额外选择公章或法人章', + }, + React.createElement(Info, { + size: 13, + className: 'lc-form-card__hint-icon', + 'aria-hidden': true, + }), + React.createElement('span', { className: 'lc-form-card__hint-text' }, '非标准触发条件'), + ); +} + +function PreviewClauseChangeRecordPanel(props) { + var records = props.records || []; + + return React.createElement('div', { + className: 'lc-preview-clause-record-panel', + role: 'region', + 'aria-label': '条款修改记录', + 'data-annotation-id': 'lc-preview-clause-record', + }, + React.createElement('div', { className: 'lc-preview-clause-record-panel__head' }, + React.createElement('span', { className: 'lc-preview-clause-record-panel__title' }, '条款修改记录'), + records.length + ? React.createElement('span', { className: 'lc-preview-clause-record-panel__count tabular-nums' }, records.length + ' 处') + : null, + ), + records.length + ? React.createElement('ul', { className: 'lc-preview-clause-record-panel__list' }, + records.map(function (item) { + return React.createElement('li', { + key: item.id, + className: 'lc-preview-clause-record-panel__item lc-preview-clause-record-panel__item--' + item.kind, + }, + React.createElement('span', { className: 'lc-preview-clause-record-panel__kind' }, item.kindLabel), + React.createElement('div', { className: 'lc-preview-clause-record-panel__diff' }, + React.createElement('div', { className: 'lc-preview-clause-record-panel__col' }, + React.createElement('span', { className: 'lc-preview-clause-record-panel__label' }, '标准条款'), + React.createElement('p', { className: 'lc-preview-clause-record-panel__text' }, item.standardText), + ), + React.createElement('div', { className: 'lc-preview-clause-record-panel__col' }, + React.createElement('span', { className: 'lc-preview-clause-record-panel__label' }, '修改后'), + React.createElement('p', { className: 'lc-preview-clause-record-panel__text' }, item.modifiedText), + ), + ), + ); + }), + ) + : React.createElement('p', { className: 'lc-preview-clause-record-panel__empty', role: 'status' }, + '暂无条款修改,编辑预览正文后将在此对比标准条款与修改后内容。', + ), + ); +} + +function PreviewClauseChangeRecordTrigger(props) { + var records = props.records || []; + var openState = useState(false); + var open = openState[0]; + var setOpen = openState[1]; + var count = records.length; + + return React.createElement('div', { + className: 'ct-preview-pager__group ct-preview-pager__group--clause-record', + role: 'group', + 'aria-label': '条款修改记录', + }, + React.createElement(Popover, { + trigger: 'click', + placement: 'bottomRight', + open: open, + onOpenChange: setOpen, + overlayClassName: 'lc-preview-clause-record-popover', + getPopupContainer: function () { return document.body; }, + content: React.createElement(PreviewClauseChangeRecordPanel, { records: records }), + }, + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-secondary lc-preview-clause-record-btn' + + (open ? ' is-active' : '') + + (count ? ' has-changes' : ''), + 'aria-expanded': open, + 'aria-haspopup': 'dialog', + 'data-annotation-id': 'lc-preview-clause-record-btn', + }, + React.createElement(FileDiff, { size: 14, 'aria-hidden': true }), + React.createElement('span', { className: 'lc-preview-clause-record-btn__label' }, '条款修改记录'), + count + ? React.createElement('span', { + className: 'lc-preview-clause-record-btn__badge tabular-nums', + 'aria-label': count + ' 处修改', + }, count) + : null, + ), ), ); } @@ -89,6 +228,7 @@ export default function LeaseContractPreviewPanel({ var pageIdxState = useState(0); var viewModeState = useState('scroll'); var internalZoomState = useState(100); + var clauseRecordsState = useState([]); var baselinePages = baselinePagesState[0]; var setBaselinePages = baselinePagesState[1]; var draftPagesRef = useRef([]); @@ -96,6 +236,8 @@ export default function LeaseContractPreviewPanel({ var setPageIdx = pageIdxState[1]; var viewMode = viewModeState[0]; var setViewMode = viewModeState[1]; + var clauseRecords = clauseRecordsState[0]; + var setClauseRecords = clauseRecordsState[1]; var previewZoom = zoom != null ? zoom : internalZoomState[0]; var setPreviewZoom = onZoomChange || internalZoomState[1]; @@ -103,6 +245,7 @@ export default function LeaseContractPreviewPanel({ if (!html) { setBaselinePages([]); draftPagesRef.current = []; + setClauseRecords([]); return; } var timer = window.setTimeout(function () { @@ -110,14 +253,17 @@ export default function LeaseContractPreviewPanel({ setBaselinePages(nextPages); draftPagesRef.current = nextPages.slice(); pageIdxState[1](0); + setClauseRecords([]); notifyContentChange(nextPages); }, 0); return function () { window.clearTimeout(timer); }; }, [html]); function notifyContentChange(nextPages) { + var merged = (nextPages || []).join(''); + setClauseRecords(collectClauseChangeRecords(html, merged)); if (!onContentChange) return; - onContentChange((nextPages || []).join('')); + onContentChange(merged); } function handlePageInput(pageIndex, nextHtml) { @@ -205,6 +351,7 @@ export default function LeaseContractPreviewPanel({ React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-secondary ct-preview-pager__btn', + 'data-vm-icon': 'chevron-left', disabled: pageIdx <= 0, onClick: function () { setPageIdx(Math.max(0, pageIdx - 1)); }, }, '上一页'), @@ -216,6 +363,7 @@ export default function LeaseContractPreviewPanel({ React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-secondary ct-preview-pager__btn', + 'data-vm-icon': 'chevron-right', disabled: pageIdx >= baselinePages.length - 1, onClick: function () { setPageIdx(Math.min(baselinePages.length - 1, pageIdx + 1)); }, }, '下一页'), @@ -238,6 +386,9 @@ export default function LeaseContractPreviewPanel({ }, '单页预览'), ), ), + editable + ? React.createElement(PreviewClauseChangeRecordTrigger, { records: clauseRecords }) + : null, ), ); diff --git a/src/prototypes/lease-contract-management/LeaseContractView.jsx b/src/prototypes/lease-contract-management/LeaseContractView.jsx new file mode 100644 index 0000000..1e6426b --- /dev/null +++ b/src/prototypes/lease-contract-management/LeaseContractView.jsx @@ -0,0 +1,667 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Image } from 'antd'; +import { StatusTag } from '../vehicle-management/components/StatusTag'; +import { + getCustomerAttachmentPreviewUrl, +} from '../contract-template-management/contract-template-vars.js'; +import { + summarizeCustomerCredentials, +} from './lease-customer-credentials.js'; +import { + buildContractAttachments, + buildContractOperationLogs, + buildContractChangeRecords, + previewContractAttachment, + downloadContractAttachmentFile, + CHANGE_RECORD_TYPES, +} from './lease-contract-view-data.js'; +import { + buildContractViewSummary, + buildContractViewTemplateFields, + buildContractViewSigningFields, + buildContractViewLessorProfileFields, + buildContractViewCustomerProfileFields, + buildContractViewMileageFields, + buildContractViewFeeFields, + buildContractViewLeaseOrderMetaFields, + buildContractViewVehicleCards, + buildContractViewAuditFields, + buildContractViewSealTypes, + getViewSectionNavItems, + resolveViewFormContext, +} from './lease-contract-view-sections.js'; +import { getAuthorizedDelegates } from './lease-contract-list-data.js'; + +var SEAL_TYPE_OPTIONS = [ + { value: 'contract', label: '合同章' }, + { value: 'official', label: '公章' }, + { value: 'legal_person', label: '法人章' }, +]; + +function formatChangeTypeLabel(type) { + var found = CHANGE_RECORD_TYPES.find(function (item) { return item.key === type; }); + return found ? found.label : type || '-'; +} + +function contractStatusTone(status) { + if (status === '合同进行中') return 'green'; + if (status === '已提交审批') return 'blue'; + if (status === '已终止') return 'red'; + return 'gray'; +} + +function approvalStatusTone(status) { + if (status === '审批通过') return 'green'; + if (status === '审批中' || status === '待审批') return 'amber'; + if (status === '审批驳回' || status === '审批终止') return 'red'; + return 'gray'; +} + +var VIEW_TAB_SCROLL_OFFSET = 12; + +function formatViewDisplayValue(value) { + return value != null && value !== '' && value !== '-' ? value : null; +} + +function getParamSpanClass(props) { + if (props.wide || props.span === 3) return ' vm-model-param-item--wide'; + if (props.span === 2) return ' vm-model-param-item--span-2'; + return ''; +} + +function ViewParamField(props) { + var display = formatViewDisplayValue(props.value); + var empty = !display && props.children == null; + var valueNode = props.children != null + ? props.children + : (display || '—'); + return React.createElement('div', { + className: 'vm-model-param-item' + getParamSpanClass(props) + (props.className ? ' ' + props.className : ''), + }, + React.createElement('span', { className: 'vm-model-param-label' }, props.label), + React.createElement('div', { + className: 'vm-model-param-value' + + (empty ? ' is-empty' : '') + + (props.numeric ? ' tabular-nums' : ''), + }, valueNode), + ); +} + +function ViewParamSection(props) { + return React.createElement('section', { + className: 'vm-model-param-section', + 'data-annotation-id': props.annotationId, + }, + React.createElement('h3', { className: 'vm-model-param-section-title' }, props.title), + props.children, + ); +} + +function renderFieldsGrid(fields, cols) { + var gridClass = 'vm-model-param-grid'; + if (cols === 2) gridClass += ' vm-model-param-grid--2'; + if (cols === 1) gridClass += ' vm-model-param-grid--1'; + return React.createElement('div', { className: gridClass }, + (fields || []).map(function (field) { + return React.createElement(ViewParamField, { + key: field.label, + label: field.label, + value: field.value, + numeric: field.mono, + wide: field.wide, + span: field.span, + }); + }), + ); +} + +function ViewPartyFields(props) { + var fields = props.fields || []; + var contactName = (fields.find(function (f) { return f.label === '联系人姓名'; }) || {}).value; + var contactPhone = (fields.find(function (f) { return f.label === '联系人电话'; }) || {}).value; + function resolveSpan(field) { + if (field.span != null) return field.span; + if (field.label === '邮箱') return 1; + if (field.wide) return 3; + return 1; + } + var contactDisplay = [contactName, contactPhone] + .map(formatViewDisplayValue) + .filter(Boolean) + .join(' / ') || null; + return React.createElement('div', { className: 'vm-model-param-grid' }, + fields.map(function (field) { + if (field.label === '联系人姓名' || field.label === '联系人电话') return null; + return React.createElement(ViewParamField, { + key: props.prefix + '-' + field.label, + label: field.label, + value: field.value, + numeric: field.mono, + span: resolveSpan(field), + }); + }), + React.createElement(ViewParamField, { + key: props.prefix + '-contact-pair', + label: '联系人姓名及电话', + value: contactDisplay, + numeric: true, + span: 2, + }), + ); +} + +function ViewDetailStat(props) { + return React.createElement('div', { className: 'vm-detail-stat' }, + React.createElement('div', { className: 'vm-detail-stat-main' }, + React.createElement('span', { className: 'vm-detail-stat-label' }, props.label), + React.createElement('span', { + className: 'vm-detail-stat-value' + (props.numeric ? ' tabular-nums' : ''), + }, props.value), + ), + ); +} + +function ViewCredentialThumb(props) { + var label = props.label; + var previewUrl = props.previewUrl; + var meta = props.meta || {}; + var status = meta.status || 'unknown'; + var thumbClass = 'lc-attach-thumb' + + (status === 'expired' ? ' is-expired' : '') + + (status === 'expiring' ? ' is-expiring' : ''); + return React.createElement('div', { + className: thumbClass, + 'aria-label': label, + }, + React.createElement(Image, { + className: 'lc-attach-thumb__img', + src: previewUrl, + alt: label, + preview: { src: previewUrl }, + fallback: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(''), + }), + React.createElement('div', { className: 'lc-attach-thumb__meta' }, + React.createElement('span', { className: 'lc-attach-thumb__label' }, label), + meta.ocrVerified + ? React.createElement('span', { className: 'lc-attach-thumb__ocr', title: '已通过 OCR 识别证照信息' }, 'OCR 已识别') + : React.createElement('span', { className: 'lc-attach-thumb__ocr is-pending' }, '待 OCR'), + meta.expiryDate + ? React.createElement('span', { className: 'lc-attach-thumb__expiry' }, '有效期至 ' + meta.expiryDate) + : null, + React.createElement('span', { + className: 'lc-attach-thumb__status lc-attach-thumb__status--' + status, + role: 'status', + }, meta.statusLabel || '待识别'), + ), + ); +} + +function ViewCredentialsBlock(props) { + var customer = props.customer; + if (!customer) return null; + var credentialSummary = summarizeCustomerCredentials(customer); + var embedded = props.embedded; + return React.createElement('div', { + className: 'lc-credentials-block lc-view-credentials-block' + + (embedded ? ' lc-view-credentials-block--embedded' : ''), + 'data-annotation-id': 'lc-view-credentials-ocr', + }, + React.createElement('div', { className: 'lc-credentials-block__head' }, + React.createElement('h4', { className: 'lc-credentials-block__title' }, '客户资质证照'), + ), + React.createElement('div', { + className: 'lc-attach-gallery', + 'aria-label': '客户资质证照预览', + }, + credentialSummary.items.map(function (item) { + var previewUrl = getCustomerAttachmentPreviewUrl(customer, item.key, item.label); + return React.createElement(ViewCredentialThumb, { + key: item.key, + label: item.label, + previewUrl: previewUrl, + meta: item, + }); + }), + ), + ); +} + +function ViewSealChips(props) { + var selected = props.sealTypes || ['contract']; + var activeOptions = SEAL_TYPE_OPTIONS.filter(function (option) { + return selected.indexOf(option.value) >= 0; + }); + if (!activeOptions.length) { + return React.createElement('p', { className: 'lc-view-section__empty' }, '未选择用章类型'); + } + return React.createElement('div', { + className: 'lc-seal-type-chips lc-view-seal-type-chips', + role: 'group', + 'aria-label': '用章类型', + 'data-annotation-id': 'lc-view-seal-type', + }, + activeOptions.map(function (option) { + return React.createElement('span', { + key: option.value, + className: 'lc-seal-type-chip lc-view-seal-type-chip is-active', + }, option.label); + }), + ); +} + +function ViewAttachmentCapsules(props) { + var attachments = props.attachments || []; + var record = props.record; + var onPreview = props.onPreview; + var onDownload = props.onDownload; + var Button = props.Button; + if (!attachments.length) { + return React.createElement('p', { className: 'lc-view-section__empty' }, '暂无合同附件'); + } + return React.createElement('div', { + className: 'lc-view-attachment-capsules', + 'aria-label': '合同附件列表', + }, + attachments.map(function (file) { + var fileName = file.name || '未命名附件'; + return React.createElement('div', { + key: file.uid || fileName, + className: 'lc-view-attachment-capsule', + }, + React.createElement('span', { + className: 'lc-view-attachment-capsule__name', + title: fileName, + }, fileName), + React.createElement('div', { className: 'lc-view-attachment-capsule__actions' }, + React.createElement(Button, { + type: 'link', + size: 'small', + onClick: function () { onPreview(file, record); }, + }, '预览'), + React.createElement(Button, { + type: 'link', + size: 'small', + onClick: function () { onDownload(file, record); }, + }, '下载'), + ), + ); + }), + ); +} + +function ViewRecordTimeline(props) { + var items = props.items || []; + if (!items.length) { + return React.createElement('p', { className: 'lc-view-section__empty' }, props.emptyText || '暂无记录'); + } + return React.createElement('ol', { className: 'lc-view-timeline' }, + items.map(function (item) { + return React.createElement('li', { key: item.id, className: 'lc-view-timeline__item' }, + React.createElement('div', { className: 'lc-view-timeline__dot', 'aria-hidden': true }), + React.createElement('div', { className: 'lc-view-timeline__content' }, + React.createElement('div', { className: 'lc-view-timeline__head' }, + React.createElement('span', { className: 'lc-view-timeline__title' }, item.title), + item.badge + ? React.createElement('span', { className: 'lc-view-timeline__badge' }, item.badge) + : null, + ), + item.summary + ? React.createElement('p', { className: 'lc-view-timeline__summary' }, item.summary) + : null, + React.createElement('div', { className: 'lc-view-timeline__meta' }, + React.createElement('span', null, item.operatorName || '-'), + React.createElement('span', { className: 'tabular-nums' }, item.operateTime || '-'), + ), + ), + ); + }), + ); +} + +export default function LeaseContractView({ record, onBack, stampedFilesOverride }) { + var antd = window.antd; + var Table = antd.Table; + var Button = antd.Button; + var message = antd.message; + + var activeSectionState = useState('main'); + var activeSection = activeSectionState[0]; + var setActiveSection = activeSectionState[1]; + + var scrollRef = useRef(null); + var sectionRefs = useRef({}); + + var formContext = useMemo(function () { return resolveViewFormContext(record); }, [record]); + var attachments = useMemo(function () { + if (!record) return []; + if (stampedFilesOverride && stampedFilesOverride.length) { + return stampedFilesOverride.map(function (file) { + return { uid: file.uid, name: file.name, type: file.type || '' }; + }); + } + return buildContractAttachments(record); + }, [record, stampedFilesOverride]); + var operationLogs = useMemo(function () { return buildContractOperationLogs(record); }, [record]); + var changeRecords = useMemo(function () { return buildContractChangeRecords(record); }, [record]); + var summary = useMemo(function () { return buildContractViewSummary(record); }, [record]); + var navItems = useMemo(function () { return getViewSectionNavItems(); }, []); + var templateFields = useMemo(function () { return buildContractViewTemplateFields(record); }, [record]); + var signingFields = useMemo(function () { return buildContractViewSigningFields(record, formContext); }, [record, formContext]); + var lessorFields = useMemo(function () { return buildContractViewLessorProfileFields(formContext); }, [formContext]); + var customerFields = useMemo(function () { return buildContractViewCustomerProfileFields(formContext); }, [formContext]); + var mileageFields = useMemo(function () { return buildContractViewMileageFields(record, formContext); }, [record, formContext]); + var feeFields = useMemo(function () { return buildContractViewFeeFields(record, formContext); }, [record, formContext]); + var leaseOrderMetaFields = useMemo(function () { return buildContractViewLeaseOrderMetaFields(record); }, [record]); + var vehicleCards = useMemo(function () { return buildContractViewVehicleCards(record); }, [record]); + var auditFields = useMemo(function () { return buildContractViewAuditFields(record); }, [record]); + var sealTypes = useMemo(function () { return buildContractViewSealTypes(record, formContext); }, [record, formContext]); + var delegates = useMemo(function () { return getAuthorizedDelegates(record); }, [record]); + + var contractRemark = useMemo(function () { + if (!record) return ''; + return record.remark && record.remark !== '-' ? record.remark : ''; + }, [record]); + + var changeTimelineItems = useMemo(function () { + return changeRecords.map(function (item) { + return { + id: item.id, + title: item.typeLabel || formatChangeTypeLabel(item.type), + summary: item.summary, + operatorName: item.operatorName, + operateTime: item.operateTime, + badge: item.approvalStatus, + }; + }); + }, [changeRecords]); + + var scrollToSection = useCallback(function (sectionKey) { + setActiveSection(sectionKey); + var target = sectionRefs.current[sectionKey]; + var container = scrollRef.current; + if (!target || !container) return; + var containerTop = container.getBoundingClientRect().top; + var targetTop = target.getBoundingClientRect().top; + container.scrollTo({ + top: container.scrollTop + (targetTop - containerTop) - VIEW_TAB_SCROLL_OFFSET, + behavior: 'smooth', + }); + }, []); + + useEffect(function () { + var container = scrollRef.current; + if (!container) return undefined; + function onScroll() { + var keys = navItems.map(function (item) { return item.key; }); + var current = keys[0]; + keys.forEach(function (key) { + var el = sectionRefs.current[key]; + if (!el) return; + var top = el.getBoundingClientRect().top - container.getBoundingClientRect().top; + if (top <= VIEW_TAB_SCROLL_OFFSET + 4) current = key; + }); + setActiveSection(current); + } + container.addEventListener('scroll', onScroll, { passive: true }); + return function () { container.removeEventListener('scroll', onScroll); }; + }, [navItems]); + + function bindSectionRef(key) { + return function (node) { + sectionRefs.current[key] = node; + }; + } + + function renderAnchorSection(key, annotationId, children) { + return React.createElement('section', { + key: key, + id: 'lc-view-section-' + key, + className: 'lc-view-detail-anchor', + ref: bindSectionRef(key), + 'data-annotation-id': annotationId, + }, children); + } + + function renderScrollSections() { + return React.createElement(React.Fragment, null, + renderAnchorSection('template', 'lc-view-template', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '模板与签署', annotationId: 'lc-view-template-fields' }, + renderFieldsGrid(templateFields, 2), + ), + ), + ), + renderAnchorSection('main', 'lc-view-main-contract', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '签约信息', annotationId: 'lc-view-card-signing' }, + renderFieldsGrid(signingFields, 3), + ), + lessorFields.length + ? React.createElement(ViewParamSection, { title: '甲方信息', annotationId: 'lc-view-lessor-account' }, + React.createElement(ViewPartyFields, { fields: lessorFields, prefix: 'lessor' }), + ) + : null, + customerFields.length + ? React.createElement(ViewParamSection, { title: '乙方信息', annotationId: 'lc-view-invoice-info' }, + React.createElement(ViewPartyFields, { fields: customerFields, prefix: 'customer' }), + formContext && formContext.customer + ? React.createElement(ViewCredentialsBlock, { customer: formContext.customer, embedded: true }) + : null, + ) + : null, + React.createElement(ViewParamSection, { title: '里程标准', annotationId: 'lc-view-card-mileage' }, + renderFieldsGrid(mileageFields, 3), + ), + React.createElement(ViewParamSection, { title: '费用信息', annotationId: 'lc-view-card-fee' }, + renderFieldsGrid(feeFields, 3), + ), + ), + ), + renderAnchorSection('leaseOrder', 'lc-view-lease-order', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '订单概要', annotationId: 'lc-view-lease-order-summary' }, + renderFieldsGrid(leaseOrderMetaFields, 3), + ), + vehicleCards.length + ? vehicleCards.map(function (card, index) { + return React.createElement(ViewParamSection, { + key: card.id, + title: '车辆 ' + String(index + 1).padStart(2, '0') + ' · ' + card.title, + annotationId: 'lc-view-vehicle-' + card.id, + }, + React.createElement('div', { className: 'lc-view-vehicle-card__status' }, + React.createElement(StatusTag, { label: card.status.label, tone: card.status.tone }), + ), + renderFieldsGrid(card.fields, 3), + ); + }) + : React.createElement('p', { className: 'lc-view-section__empty' }, '暂无车辆订单'), + ), + ), + renderAnchorSection('poa', 'lc-view-poa', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '授权委托', annotationId: 'lc-view-poa-table' }, + delegates.length + ? React.createElement(Table, { + rowKey: function (_, index) { return 'poa-' + index; }, + size: 'small', + className: 'lc-view-table', + columns: [ + { title: '受托人', dataIndex: 'name', key: 'name', width: 100 }, + { title: '联系方式', dataIndex: 'contact', key: 'contact', width: 130, className: 'tabular-nums', render: function (v, row) { return v || row.phone || '-'; } }, + { title: '身份证号', key: 'idNumber', width: 180, className: 'tabular-nums', render: function (_, row) { return row.idNumber || row.idCard || '-'; } }, + ], + dataSource: delegates, + pagination: false, + }) + : React.createElement('p', { className: 'lc-view-section__empty' }, '未添加受托人'), + record.poaRemark + ? React.createElement('p', { className: 'lc-view-remark-text lc-view-poa-remark' }, record.poaRemark) + : null, + ), + ), + ), + renderAnchorSection('remark', 'lc-view-remark', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '合同备注', annotationId: 'lc-view-remark-body' }, + contractRemark + ? React.createElement('p', { className: 'lc-view-remark-text' }, contractRemark) + : React.createElement('p', { className: 'lc-view-section__empty' }, '未填写备注'), + ), + ), + ), + renderAnchorSection('seal', 'lc-view-seal', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '用章类型', annotationId: 'lc-view-seal-chips' }, + React.createElement(ViewSealChips, { sealTypes: sealTypes }), + ), + ), + ), + renderAnchorSection('attachments', 'lc-view-attachments', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '合同附件', annotationId: 'lc-view-attachment-list' }, + React.createElement(ViewAttachmentCapsules, { + attachments: attachments, + record: record, + Button: Button, + onPreview: function (file, contractRecord) { + var ok = previewContractAttachment(file, contractRecord); + if (!ok) message.info('预览:' + (file.name || '附件') + '(原型)'); + }, + onDownload: function (file, contractRecord) { + var ok = downloadContractAttachmentFile(file, contractRecord); + if (!ok) message.success('已开始下载:' + (file.name || '附件') + '(原型)'); + }, + }), + ), + ), + ), + renderAnchorSection('audit', 'lc-view-audit', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '建档信息', annotationId: 'lc-view-audit-fields' }, + renderFieldsGrid(auditFields, 3), + ), + ), + ), + renderAnchorSection('operations', 'lc-view-operation-logs', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '操作记录', annotationId: 'lc-view-operation-table' }, + React.createElement(Table, { + rowKey: 'id', + size: 'small', + className: 'lc-view-table lc-view-table--compact', + columns: [ + { title: '操作', dataIndex: 'action', key: 'action', width: 96 }, + { title: '操作人', dataIndex: 'operatorName', key: 'operatorName', width: 88 }, + { title: '操作时间', dataIndex: 'operateTime', key: 'operateTime', width: 132, className: 'tabular-nums' }, + { title: '修改人', dataIndex: 'modifierName', key: 'modifierName', width: 88 }, + { title: '修改时间', dataIndex: 'modifyTime', key: 'modifyTime', width: 132, className: 'tabular-nums' }, + ], + dataSource: operationLogs, + pagination: false, + locale: { emptyText: '暂无操作记录' }, + scroll: { x: 580 }, + }), + ), + ), + ), + renderAnchorSection('changes', 'lc-view-change-records', + React.createElement('div', { className: 'vm-model-param-tab vm-model-param-tab--readonly' }, + React.createElement(ViewParamSection, { title: '变更记录', annotationId: 'lc-view-change-body' }, + changeTimelineItems.length <= 6 + ? React.createElement(ViewRecordTimeline, { + items: changeTimelineItems, + emptyText: '暂无变更记录', + }) + : React.createElement(Table, { + rowKey: 'id', + size: 'small', + className: 'lc-view-table', + columns: [ + { title: '变更类型', key: 'typeLabel', width: 120, render: function (_, row) { return row.typeLabel || formatChangeTypeLabel(row.type); } }, + { title: '摘要', dataIndex: 'summary', key: 'summary', ellipsis: true }, + { title: '操作人', dataIndex: 'operatorName', key: 'operatorName', width: 88 }, + { title: '操作时间', dataIndex: 'operateTime', key: 'operateTime', width: 132, className: 'tabular-nums' }, + { title: '审批状态', dataIndex: 'approvalStatus', key: 'approvalStatus', width: 96 }, + ], + dataSource: changeRecords, + pagination: false, + scroll: { x: 640 }, + }), + ), + ), + ), + ); + } + + if (!record || !summary) { + return React.createElement('div', { className: 'vm-page lc-page lc-view-page vm-detail' }, + React.createElement('div', { className: 'vm-detail-topbar' }, + React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-back', onClick: onBack }, '返回列表'), + ), + React.createElement('p', { className: 'lc-view-page__empty' }, '未找到合同记录'), + ); + } + + return React.createElement('div', { className: 'vm-page lc-page lc-view-page vm-detail lc-view-detail-page' }, + React.createElement('div', { className: 'vm-detail-topbar lc-view-detail-topbar', 'data-annotation-id': 'lc-view-topbar' }, + React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-back', onClick: onBack }, '返回列表'), + ), + React.createElement('div', { className: 'lc-view-detail-shell' }, + React.createElement('section', { className: 'vm-detail-card lc-view-detail-header-card' }, + React.createElement('div', { className: 'vm-detail-hero', 'data-annotation-id': 'lc-view-hero' }, + React.createElement('div', null, + React.createElement('div', { className: 'vm-detail-plate lc-view-hero__title' }, summary.projectName), + React.createElement('p', { className: 'vm-detail-meta' }, summary.customerName), + React.createElement('p', { className: 'vm-detail-meta mono tabular-nums' }, summary.contractCode), + ), + React.createElement('div', { className: 'vm-detail-aside lc-view-hero__aside' }, + React.createElement('div', { className: 'lc-view-hero-tags' }, + React.createElement(StatusTag, { label: summary.displayStatus, tone: contractStatusTone(summary.displayStatus) }), + summary.showApprovalBadge + ? React.createElement(StatusTag, { label: summary.approvalStatus, tone: approvalStatusTone(summary.approvalStatus) }) + : null, + ), + React.createElement('p', { className: 'vm-detail-meta' }, summary.contractType + ' · ' + summary.contractApprovalType), + React.createElement('p', { className: 'vm-detail-gps-time' }, '签署状态:' + summary.signingSubLabel), + ), + ), + React.createElement('div', { className: 'vm-detail-stats', 'data-annotation-id': 'lc-view-stats' }, + React.createElement(ViewDetailStat, { label: '签署方式', value: summary.signingMethodLabel }), + React.createElement(ViewDetailStat, { label: '业务部门', value: record.businessDept || '—' }), + React.createElement(ViewDetailStat, { label: '业务负责人', value: summary.businessOwner }), + React.createElement(ViewDetailStat, { + label: '租赁车辆', + value: String(summary.vehicleCount || 0) + ' 辆', + numeric: true, + }), + ), + React.createElement('div', { + className: 'vm-tabs lc-view-detail-tabs', + role: 'tablist', + 'aria-label': '合同详情章节', + 'data-annotation-id': 'lc-view-section-nav', + }, + navItems.map(function (item) { + var isActive = activeSection === item.key; + return React.createElement('button', { + key: item.key, + type: 'button', + role: 'tab', + className: 'vm-tab' + (isActive ? ' active' : ''), + 'aria-selected': isActive ? 'true' : 'false', + onClick: function () { scrollToSection(item.key); }, + }, item.label); + }), + ), + ), + React.createElement('div', { + className: 'lc-view-detail-body', + ref: scrollRef, + 'aria-label': '合同详情内容', + }, + renderScrollSections(), + ), + ), + ); +} diff --git a/src/prototypes/lease-contract-management/annotation-source.json b/src/prototypes/lease-contract-management/annotation-source.json index dedf512..d712c64 100644 --- a/src/prototypes/lease-contract-management/annotation-source.json +++ b/src/prototypes/lease-contract-management/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "lease-contract-management", "pageId": "list", - "updatedAt": 1783589987167, + "updatedAt": 1783861999237, "nodes": [ { "id": "lc-list-filter", @@ -20,7 +20,7 @@ "path": [] }, "aiPrompt": "列表筛选区字段与查询逻辑。", - "annotationText": "默认展示 4 项,可展开至 12 项;查询与 KPI 联动。", + "annotationText": "默认展示 4 项,可展开至 13 项;合同模板联动标准合同名称;查询与 KPI 联动。", "hasMarkdown": true, "color": "#2563eb", "images": [], @@ -40,7 +40,7 @@ "path": [] }, "aiPrompt": "五张 KPI 卡片口径与点击筛选。", - "annotationText": "全部 / 进行中 / 审批中 / 临期 / 已终止。", + "annotationText": "全部 / 草稿 / 进行中 / 审批中 / 已终止。", "hasMarkdown": true, "color": "#64748b", "images": [], @@ -188,8 +188,28 @@ "updatedAt": 1740787200000 }, { - "id": "lc-card-signing", + "id": "lc-create-signing-method", "index": 10, + "title": "合同签署方式", + "pageId": "create", + "locator": { + "selectors": [ + "[data-annotation-id=\"lc-create-signing-method\"]" + ], + "fingerprint": "lc-create-signing-method", + "path": [] + }, + "aiPrompt": "线上电子签章或线下人工上传。", + "annotationText": "影响电子签发送或盖章合同补传闭环。", + "hasMarkdown": true, + "color": "#0369a1", + "images": [], + "createdAt": 1740787200000, + "updatedAt": 1740787200000 + }, + { + "id": "lc-card-signing", + "index": 11, "title": "签约信息", "pageId": "create", "locator": { @@ -209,7 +229,7 @@ }, { "id": "lc-credentials-ocr", - "index": 10.5, + "index": 12, "title": "客户资质证照 OCR", "pageId": "create", "locator": { @@ -229,7 +249,7 @@ }, { "id": "lc-signing-customer-principal", - "index": 11, + "index": 13, "title": "乙方负责人", "pageId": "create", "locator": { @@ -249,7 +269,7 @@ }, { "id": "lc-create-lease-order", - "index": 12, + "index": 14, "title": "附件1:租赁订单", "pageId": "create", "locator": { @@ -269,7 +289,7 @@ }, { "id": "lc-lease-order-brand-model", - "index": 13, + "index": 15, "title": "品牌车型与在库", "pageId": "create", "locator": { @@ -289,7 +309,7 @@ }, { "id": "lc-lease-order-rent", - "index": 14, + "index": 16, "title": "车辆租金与最低租金", "pageId": "create", "locator": { @@ -309,7 +329,7 @@ }, { "id": "lc-lease-order-service-content", - "index": 15, + "index": 17, "title": "服务项配置", "pageId": "create", "locator": { @@ -329,7 +349,7 @@ }, { "id": "lc-lease-order-lease-period", - "index": 16, + "index": 18, "title": "租赁期限与到期计算", "pageId": "create", "locator": { @@ -349,7 +369,7 @@ }, { "id": "lc-create-poa", - "index": 17, + "index": 19, "title": "授权委托书", "pageId": "create", "locator": { @@ -369,7 +389,7 @@ }, { "id": "lc-create-contract-remark", - "index": 18, + "index": 20, "title": "合同备注", "pageId": "create", "locator": { @@ -389,7 +409,7 @@ }, { "id": "lc-create-seal", - "index": 18.5, + "index": 21, "title": "用章类型", "pageId": "create", "locator": { @@ -409,7 +429,7 @@ }, { "id": "lc-create-preview", - "index": 19, + "index": 22, "title": "实时预览", "pageId": "create", "locator": { @@ -429,7 +449,7 @@ }, { "id": "lc-create-footer", - "index": 20, + "index": 23, "title": "底栏操作", "pageId": "create", "locator": { @@ -450,18 +470,19 @@ ] }, "markdownMap": { - "lc-list-filter": "## 4. 筛选区\n\n默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 12 项;支持「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同编码 | 可搜索下拉 | 模糊匹配合同编码 |\n| 项目名称 | 可搜索下拉 | 模糊匹配项目名称 |\n| 客户名称 | 可搜索下拉 | 模糊匹配客户名称 |\n| 签约公司 | 下拉 | 租赁合同签约公司枚举 |\n| 审批状态 | 多选下拉 | 全部 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交 |\n| 合同状态 | 多选下拉 | 全部 / 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |\n| 业务部门 | 多选下拉 | 业务相关部门 |\n| 业务负责人 | 多选下拉 | 业务组人员 |\n| 合同类型 | 多选下拉 | 全部 / 正式合同 / 试用合同 |\n| 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 |\n| 创建人 | 多选下拉 | 业务相关人员 |\n| 合同结束日期 | 日期范围 | 单输入框双日历,中文界面,开始—结束精确至日 |\n\n**交互**:\n\n- **查询**:将当前筛选条件写入「已应用筛选」,列表与 KPI 统计联动。\n- **重置**:清空为默认(多选类默认「全部」),并刷新列表。\n\n---", - "lc-list-kpi": "## 5. KPI 统计卡片\n\n共 5 张卡片,点击切换列表筛选(与筛选区叠加生效)。\n\n| 卡片 | 口径 |\n|---|---|\n| 全部合同 | 不做 KPI 维度过滤 |\n| 进行中 | 至少有一辆租赁车辆已交车且尚未还车 |\n| 审批中 | 审批状态为「待审批」或「审批中」 |\n| 临期合同 | 存在在租车辆,且合同签订有效期距今日 ≤ 30 天(以最后一辆在租车辆为准) |\n| 已终止 | 全部车辆已还车;或续签/转三方产生新合同后旧合同自动标记为已终止 |\n\n卡片右上角 `?` 悬停展示口径说明。\n\n---", + "lc-list-filter": "## 4. 筛选区\n\n默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 **13 项**;支持「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同编码 | 可搜索下拉 | 模糊匹配合同编码 |\n| 项目名称 | 可搜索下拉 | 模糊匹配项目名称 |\n| 客户名称 | 可搜索下拉 | 模糊匹配客户名称 |\n| 签约公司 | 下拉 | 租赁合同签约公司枚举 |\n| 审批状态 | 多选下拉 | 全部 / 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 审批终止 / 撤回 |\n| 合同状态 | 多选下拉 | 全部 / 草稿 / 已提交审批 / 合同进行中 / 已终止 |\n| 业务部门 | 多选下拉 | 业务相关部门 |\n| 业务负责人 | 多选下拉 | 业务组人员 |\n| 合同模板 | 单选下拉 | 选项来自合同模板管理已发布且启用的模板「合同名称」维度(如正式合同、试用合同、现代18吨正式合同);可清空 |\n| 标准合同名称 | 单选下拉 | **联动合同模板**:仅在选择合同模板后启用;选项为该模板类别下已发布文档名(如「2026年标准商用车租赁合同」);支持搜索;可清空 |\n| 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 |\n| 创建人 | 多选下拉 | 业务相关人员 |\n| 合同结束日期 | 日期范围 | 单输入框双日历,中文界面,开始—结束精确至日 |\n\n**合同模板联动规则**:\n\n1. 未选合同模板时,「标准合同名称」禁用并提示「请先选择合同模板」。\n2. 切换合同模板时,自动清空已选标准合同名称。\n3. 查询时按合同记录的 `contractTemplateId`(或由合同类型推断的模板 ID)匹配;合同模板与标准合同名称可单独或组合使用。\n\n**交互**:\n\n- **查询**:将当前筛选条件写入「已应用筛选」,列表与 KPI 统计联动。\n- **重置**:清空为默认(多选类默认「全部」,单选类清空),并刷新列表。\n\n---", + "lc-list-kpi": "## 5. KPI 统计卡片\n\n共 5 张卡片,点击切换列表筛选(与筛选区条件 **叠加** 生效)。\n\n| 卡片 | 口径(按合同状态) |\n|---|---|\n| 全部合同 | 不做 KPI 维度过滤 |\n| 草稿 | 合同状态 = 草稿 |\n| 进行中 | 合同状态 = 合同进行中 |\n| 审批中 | 合同状态 = 已提交审批 |\n| 已终止 | 合同状态 = 已终止 |\n\n卡片右上角 `?` 悬停展示口径说明。\n\n> **说明**:KPI 按合同状态快速分桶;交还车履约口径(已交未还、临期等)见 §7.2 子表与 §6.6 全局规则,不在 KPI 卡片中重复统计。\n\n---", "lc-fleet-summary": "# 在租车辆概览\n\n按品牌型号统计在租车辆,支持品牌标签筛选。\n\n## 说明\n\n顶部展示在租总数;品牌标签筛选车型卡片;点击卡片查看车辆明细。", "lc-list-toolbar": "## 6. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 租赁费用模板 | 跳转或打开费用模板配置(本期原型 Toast) |\n| 导出 | 按当前筛选结果导出(本期原型 Toast) |\n| 新增 | 进入「新增租赁合同」页 |\n\n---", - "lc-list-table": "## 7. 列表字段说明\n\n### 7.1 项目信息(合并列)\n\n一列展示三行:\n\n1. 项目名称(主文案)\n2. 合同编码(次级)\n3. 客户名称(次级)\n\n点击整格跳转「查看」页(原型 Toast)。\n\n### 7.2 车辆与交车\n\n| 列 | 说明 |\n|---|---|\n| 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 |\n| 已交车 | 已有实际交车日期的车辆数;点击弹出已交车列表 |\n\n车辆明细字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。\n\n### 7.3 状态与业务字段\n\n| 列 | 说明 |\n|---|---|\n| 审批类型 | 标准合同 / 非标准合同;位于审批状态左侧 |\n| 审批状态 | 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交;「审批中」悬停展示审批流节点 |\n| 合同状态 | 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |\n| 签约公司 | 创建时所选甲方签约主体简称 |\n| 业务部门 / 业务负责人 | 创建时选取 |\n| 合同类型 | 正式合同 / 试用合同 |\n| 客户联系人 / 联系电话 | 来自客户档案 |\n| 被授权人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 |\n| 创建人 / 创建时间 | 精确至分钟 |\n| 最后更新 | 更新人 / 最后更新时间,无则显示 `-` |\n\n**分页**:每页 10 / 20 / 50 条可选。\n\n---\n\n---\n\n## 8. 操作与弹窗\n\n### 8.1 操作列(主按钮 + 更多)\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 查看 | 始终 | 跳转合同查看页 |\n| 编辑 | 草稿;或审批驳回 | 进入编辑页 |\n| 删除合同 | 草稿 | 二次确认后删除 |\n| 撤回合同 | 已提交审批且非驳回 | 二次确认后撤回 |\n| 新增车辆 | 合同进行中 | 租赁订单内增车并重新审批 |\n| 续签合同 | 进行中 / 到期 | 跳转续签页 |\n| 添加被授权人 | 合同进行中 | 弹窗编辑被授权人列表 |\n| 附加费用 | 合同进行中 | 弹窗按车辆录入附加费用 |\n| 转三方合同 | 合同进行中 | 弹窗新增三方协议,上传协议附件与对方公函 |\n| 试用转正式 | 试用合同 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 |\n| 终止合同 | 合同进行中 | 二次确认,提交终止审批 |\n| 上传盖章合同 | 审批通过且法务未上传 | 仅法务部可见;多文件上传后关闭入口 |\n\n### 8.2 审批状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 未提交 | 仅保存草稿 |\n| 待审批 | 已提交,尚无节点通过 |\n| 审批中 | 已有节点通过,未完成终审 |\n| 审批通过 | 终审通过 |\n| 审批驳回 | 任一节点的驳回;可编辑重提 |\n\n### 8.3 合同状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 草稿 | 未提交审批 |\n| 已提交审批 | 初次提交,终审未完成 |\n| 变更 | 已通过基础上变更并重新审批中 |\n| 合同进行中 | 终审通过且在有效期内 |\n| 到期合同 | 已过结束日期 |\n| 已结束 | 终止或自然结束 |\n\n### 8.4 弹窗摘要\n\n- **删除草稿**:「是否确认删除该合同草稿」\n- **撤回**:「是否确认撤回该合同」\n- **终止**:「是否确认终止合同」→ 提交审核\n- **添加被授权人**:姓名、电话、身份证;支持增删行\n- **附加费用**:按车辆展示类型/品牌/型号/车牌,可填服务项目、费用(元,2 位小数)、生效日期\n- **转三方合同**:支持新增多条三方协议;每条可填协议名称、上传协议附件与对方公函\n- **试用转正式**:自动带出客户信息、授权人与车辆订单;车辆订单字段可编辑;可勾选「同时转三方合同」并在同一步维护三方协议\n- **上传盖章合同**:拖拽多文件;确认后标记已上传", + "lc-list-table": "## 7. 列表字段说明\n\n### 7.1 项目信息(合并列)\n\n一列展示三行:\n\n1. 项目名称(主文案)\n2. 合同编码(次级)\n3. 客户名称(次级)\n\n点击整格跳转「查看」页(原型 Toast)。\n\n### 7.2 车辆与交车\n\n| 列 | 说明 |\n|---|---|\n| 租赁订单 · 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 |\n| 租赁订单 · 已交车 | **审批通过**且已有实际交车日期的车辆数;点击展开已交车子表 |\n\n**履约规则**:非审批通过的合同不视为已交车;「已交车辆数」为 0 时按钮禁用。\n\n### 7.2.1 已交车子表\n\n展开「已交车辆数」后展示车辆子表,列包括:\n\n| 列 | 说明 |\n|---|---|\n| 车辆信息 | 车牌、VIN、品牌型号 |\n| 提车应收款 | 支付状态,可跳转详情 |\n| 交车 | 已交车展示里程/交车人/时间;未交车显示「未交车」 |\n| 租赁账单 | 正常 / 欠费(仅已交车) |\n| 还车 | 已还车展示还车信息;未还车显示「未还车」 |\n| 还车应结款 | 还车后展示结款状态;**审批中**时展示当前审批人(UI 与主表「审批状态」列一致,含节点悬停) |\n| 交车安排 | 区域与计划/实际交车日期 |\n| 里程要求 / 里程完成 | 有最低里程要求时展示 |\n| 操作 | **审批通过 + 已交车 + 未还车** 显示「还车」链接 |\n\n车辆明细 Popover 字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。\n\n### 7.3 状态与业务字段\n\n| 列 | 说明 |\n|---|---|\n| 合同签署方式 | 线上电子签章 / 线下人工上传;可展开查看或变更 |\n| 审批状态 | 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回等;「审批中」悬停展示审批流节点与当前处理人 |\n| 签约公司 | 创建时所选甲方签约主体简称 |\n| 业务部门 | 创建时选取 |\n| 费用信息 | 付款方式、周期、保证金等摘要 |\n| 交车安排 | 合同级交车区域与计划日期 |\n| 整体里程完成情况 | 按在租车辆里程汇总展示完成进度 |\n| 客户联系人 | 来自客户档案 |\n| 受托人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 |\n| 创建信息 | 创建人 / 创建时间,精确至分钟 |\n| 最后更新 | 更新人 / 最后更新时间,无则显示 `-` |\n\n**分页**:每页 10 / 20 / 50 条可选。\n\n---\n\n---\n\n## 8. 操作与弹窗\n\n### 8.1 操作列(主按钮 + 更多)\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 查看 | 始终 | 跳转合同查看页 |\n| 编辑 | 草稿;或审批驳回 | 进入编辑页 |\n| 删除合同 | 草稿 | 二次确认后删除 |\n| 撤回合同 | 已提交审批且非驳回 | 二次确认后撤回 |\n| 新增车辆 | 合同进行中 | 租赁订单内增车并重新审批 |\n| 续签合同 | 进行中 / 到期 | 跳转续签页 |\n| 添加被授权人 | 合同进行中 | 弹窗编辑被授权人列表 |\n| 附加费用 | 合同进行中 | 弹窗按车辆录入附加费用 |\n| 转三方合同 | 合同进行中 | 弹窗新增三方协议,上传协议附件与对方公函 |\n| 试用转正式 | 合同模板为「试用合同」 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 |\n| 终止合同 | 合同进行中 | 二次确认,提交终止审批 |\n| 上传盖章合同 | 审批通过且法务未上传 | 仅法务部可见;多文件上传后关闭入口 |\n\n### 8.2 审批状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 未提交 | 仅保存草稿 |\n| 待审批 | 已提交,尚无节点通过 |\n| 审批中 | 已有节点通过,未完成终审 |\n| 审批通过 | 终审通过 |\n| 审批驳回 | 任一节点的驳回;可编辑重提 |\n| 审批终止 | 流程被终止 |\n| 撤回 | 提交人主动撤回 |\n\n### 8.3 合同状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 草稿 | 未提交审批 |\n| 已提交审批 | 初次提交,终审未完成 |\n| 合同进行中 | 终审通过且在有效期内 |\n| 已终止 | 终止、自然结束,或续签/转三方后旧合同自动终止 |\n\n### 8.4 弹窗摘要\n\n- **删除草稿**:「是否确认删除该合同草稿」\n- **撤回**:「是否确认撤回该合同」\n- **终止**:「是否确认终止合同」→ 提交审核\n- **添加被授权人**:姓名、电话、身份证;支持增删行\n- **附加费用**:按车辆展示类型/品牌/型号/车牌,可填服务项目、费用(元,2 位小数)、生效日期\n- **转三方合同**:支持新增多条三方协议;每条可填协议名称、上传协议附件与对方公函\n- **试用转正式**:自动带出客户信息、授权人与车辆订单;车辆订单字段可编辑;可勾选「同时转三方合同」并在同一步维护三方协议\n- **上传盖章合同**:拖拽多文件;确认后标记已上传\n\n### 8.5 操作流程文档\n\n从「新增合同」起的正向与逆向全流程图见:`.spec/requirements-flow-operations.md`", "lc-list-action-more": "# 更多操作\n\n列表操作列「更多」入口,按合同状态与类型展示不同菜单项。\n\n- **合同进行中**:含转三方合同、续签、增车等\n- **试用合同**:含试用转正式", "lc-action-convert-tripartite": "# 转三方合同\n\n**显示条件**:合同状态为「合同进行中」。\n\n**能力**:\n\n1. 支持 **新增多条三方协议**(协议名称)\n2. 每条协议可上传 **三方协议附件**\n3. 每条协议可上传 **对方公函**\n\n提交后走变更审批(原型演示 Toast)。", "lc-action-trial-to-formal": "# 试用转正式\n\n**显示条件**:合同类型为「试用合同」。\n\n**单步完成**(转正式与转三方可合并,无需分两步):\n\n1. **自动拉取**当前合同的客户信息、授权人、车辆订单\n2. **车辆订单可编辑**(品牌、型号、车牌、租金、租期月数等)\n3. 可勾选 **同时转三方合同**,在同一步新增三方协议并上传附件/公函\n\n提交后生成正式合同并走审批(原型演示 Toast)。", - "lc-create-main-contract": "## 2. 主体合同信息\n\n卡片内三个可折叠区块,标题行右侧展示区块说明(展开时显示在标题后方)。\n\n### 2.1 签约信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 合同编号 | 是 | 前缀 `HT-ZL-` + 用户输入 |\n| 甲方 | 是 | 出租方签约主体,来自组织机构 |\n| 乙方 | 是 | 客户选择器;仅 A/B 级客户可选 |\n| 乙方负责人姓名 | 否 | 签约对接负责人,可与档案联系人不同 |\n| 乙方负责人手机号 | 否 | 11 位手机号 |\n\n选择甲方/乙方后,下方只读面板自动带出档案:\n\n**甲方**:户名、开户行、账号、通讯地址、联系人、电话、邮箱。\n\n**乙方**:企业名称、开户银行、银行账号、纳税人识别号、企业地址、企业电话、通讯地址、联系人、电话、邮箱;资质证照缩略图可点击放大,并展示 OCR 识别结果与有效期状态(有效 / 即将到期 / 已过期)。距到期 ≤ 3 个月在工作台生成业管待办;存在已过期证照时禁止提交新合同。\n\n未选双方时展示空状态引导。\n\n### 2.2 里程标准\n\n对应合同正文最低里程与租金减免条款。\n\n| 字段 | 说明 |\n|---|---|\n| 是否有最低里程要求 | 开关 |\n| 统计周期 | 有要求时必选:月 / 季 / 年 |\n| 目标里程 | 有要求时必填(公里) |\n| 里程类型 | 多选标签:实际里程 / 仪表里程 等 |\n\n### 2.3 费用信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 付款周期 | 是 | 如月付、季付 |\n| 氢费支付方式 | 是 | 自行解决 / 预付款 / 按月结算(选按月结算时预览切换月结算占位条款) |\n| 预付金额 | 条件 | 选择预付款时必填 |\n| 提前付款工作日 | 条件 | 选择预付款时必填 |\n| 还车氢量差单价 | 是 | 元 |\n\n**完成度**:上述必填项均已填则卡片显示「已完成」。\n\n---", - "lc-card-signing": "# 签约信息\n\n合同编号、甲乙双方与乙方负责人。\n\n选择乙方后,下方只读面板带出客户档案,并在「客户资质证照」区展示 OCR 识别结果与有效期状态。", - "lc-credentials-ocr": "# 客户资质证照(OCR 与有效期)\n\n对 **客户营业执照**、**法人身份证正反面**、**道路运输许可证** 做 OCR 识别,校验有效期。\n\n| 规则 | 说明 |\n|---|---|\n| OCR | 上传后自动识别证照信息与有效期 |\n| 即将到期 | 距到期 ≤ 3 个月时,工作台提前生成待办推送给业管 |\n| 已过期 | 业管未更新前,禁止提交新合同 |\n| 页面展示 | 缩略图旁显示 OCR 标识、有效期与状态徽章;临期/过期时顶部展示提示条 |", - "lc-signing-customer-principal": "# 乙方负责人\n\n选择乙方后,在签约信息中补充填写:\n\n- **乙方负责人姓名**:签约对接负责人,可与客户档案中的联系人不同\n- **乙方负责人手机号**:用于履约沟通与通知\n\n选填;用于合同履约联系,不覆盖客户档案只读信息。", + "lc-create-main-contract": "## 2. 主体合同信息\n\n卡片内三个可折叠区块,标题行右侧展示区块说明(展开时显示在标题后方)。\n\n### 2.1 签约信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 合同编号 | 是 | 前缀 `HT-ZL-` + 用户输入 |\n| 甲方 | 是 | 出租方签约主体,来自组织机构 |\n| 乙方 | 是 | 客户选择器;仅 A/B 级客户可选 |\n| 乙方负责人姓名 | 否 | 签约对接负责人,可与档案联系人不同 |\n| 乙方负责人手机号 | 否 | 11 位手机号 |\n\n选择甲方/乙方后,下方只读面板自动带出档案:\n\n**甲方**:户名、开户行、账号、通讯地址、联系人、电话、邮箱。\n\n**乙方**:企业名称、开户银行、银行账号、纳税人识别号、企业地址、企业电话、通讯地址、联系人、电话、邮箱;资质证照缩略图可点击放大。\n\n#### 客户资质证照(OCR 与有效期)\n\n对以下四类证照做 **OCR 识别**并校验有效期:\n\n| 证照 | 说明 |\n|---|---|\n| 客户营业执照 | 识别统一社会信用代码与营业期限 |\n| 法人身份证(正面) | 识别姓名、证件号与有效期 |\n| 法人身份证(反面) | 识别签发机关与有效期 |\n| 道路运输许可证 | 识别许可编号与有效期 |\n\n**业务规则:**\n\n1. 上传后自动 OCR 提取有效期,证照区展示「OCR 已识别」、有效期及状态(有效 / 即将到期 / 已过期)。\n2. 距到期 **≤ 3 个月** 时,系统在工作台提前生成待办任务,推送给业管人员更新客户档案。\n3. 若业管尚未更新,业务人员选择该客户后仍可查看证照,但 **禁止提交新合同**;提交时给出明确拦截提示。\n4. 原型演示:客户「杭州某某租赁有限公司(B)」含即将到期与已过期证照样例,可用于验证提示与拦截。\n\n未选双方时展示空状态引导。\n\n### 2.2 里程标准\n\n对应合同正文最低里程与租金减免条款。\n\n| 字段 | 说明 |\n|---|---|\n| 是否有最低里程要求 | 开关 |\n| 统计周期 | 有要求时必选:月 / 季 / 年 |\n| 目标里程 | 有要求时必填(公里) |\n| 里程类型 | 多选标签:实际里程 / 仪表里程 等 |\n\n### 2.3 费用信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 付款周期 | 是 | 如月付、季付 |\n| 氢费支付方式 | 是 | 自行解决 / 预付款 / 按月结算(选按月结算时预览切换月结算占位条款) |\n| 预付金额 | 条件 | 选择预付款时必填 |\n| 提前付款工作日 | 条件 | 选择预付款时必填 |\n| 还车氢量差单价 | 是 | 元 |\n\n**完成度**:上述必填项均已填则卡片显示「已完成」。\n\n---", + "lc-create-signing-method": "# 合同签署方式\n\n与「选择合同类型」同一步配置,可选:\n\n| 方式 | 说明 |\n|---|---|\n| 线上电子签章 | 审批通过后向乙方负责人手机号发送 E签宝文件并自动归档 |\n| 线下人工上传 | 不发送电子签,审批通过后通过列表「盖章合同补传」完成闭环 |", + "lc-card-signing": "# 签约信息\n\n合同编号、甲乙双方与乙方负责人。", + "lc-credentials-ocr": "# 客户资质证照(OCR 与有效期)\n\n对 **客户营业执照**、**法人身份证正反面**、**道路运输许可证** 做 OCR 识别,校验有效期。\n\n| 规则 | 说明 |\n|---|---|\n| OCR | 上传后自动识别证照信息与有效期 |\n| 即将到期 | 距到期 ≤ 3 个月时,工作台提前生成待办推送给业管 |\n| 已过期 | 业管未更新前,禁止提交新合同 |\n| 页面展示 | 缩略图旁显示 OCR 标识、有效期与状态徽章;临期/过期时顶部展示提示条", + "lc-signing-customer-principal": "# 乙方负责人\n\n选择乙方后,在签约信息中补充填写:\n\n- **乙方负责人姓名**:签约对接负责人,可与客户档案中的联系人不同\n- **乙方负责人手机号**:E签宝电子合同签章时乙方经办人\n\n**签署闭环:**\n\n- **线上电子签章**:审批通过后自动向该手机号发送 E签宝电子签章文件\n- **线下人工上传**:审批通过不发送电子签,需在列表「盖章合同补传」上传 PDF/图片,查看时可预览与下载全部附件", "lc-create-lease-order": "## 3. 附件1:租赁订单\n\n| 区域 | 说明 |\n|---|---|\n| 投保车辆数 / 三者责任险(百万) | 三者责任险必填 |\n| 交车区域 | 省市区级联 |\n| 计划交车日期 | 日期 |\n| 订单车辆表 | 品牌型号级联、车牌、租期、租金、氢费、增值服务等 |\n\n支持新增行、复制行、删除行;表头 KPI 展示合计。\n\n**完成度**:三者责任险已填即视为完成(原型简化口径)。\n\n---", "lc-lease-order-brand-model": "# 品牌车型与在库\n\n选择品牌 / 型号后,在选框下方展示该车型**全国在库**台数。\n\n- 统计口径:车辆管理中运营状态为「可运营」或「待运营」\n- 按品牌 + 型号精确匹配\n- 用于签约前评估库存是否满足交付需求", "lc-lease-order-rent": "# 车辆租金与最低租金\n\n- 车型最低租金可在后台按「品牌 · 型号」配置(原型见 `LEASE_VEHICLE_MIN_RENT_BY_MODEL`)\n- 录入租金 **低于** 系统最低值时,行内提示并自动判定为 **非标准合同** 审批\n- 与风控红线条款修改规则并列生效", @@ -469,43 +490,13 @@ "lc-lease-order-lease-period": "# 租赁期限与到期计算\n\n租赁到期时间支持两种计算模式:\n\n## 1. 固定日期模式\n\n手动指定合同的开始日期与结束日期;系统到达设定的结束日期时,自动标记合同为过期状态。\n\n## 2. 租期月数模式\n\n无需手动指定起止日期,仅输入租赁总月数。系统自动以 **车辆实际交付时间** 作为租期起始点,向后顺延对应月数,自动计算合同到期日期;到达该日期后自动标记合同为过期。\n\n### 多车合同\n\n当合同下关联多台车辆时,按固定月份规则取当前合同下 **第一辆到期** 的车辆到期时间,作为合同整体到期时间。\n\n> 原型表格列「租赁期限(月)」当前展示租期月数模式输入;固定日期模式在正式产品中于同行或弹层切换。", "lc-create-poa": "## 4. 授权委托书\n\n被委托人表格:姓名、联系方式、身份证号。\n\n支持新增行、删除行。\n\n**完成度**:至少一行且三项均非空。\n\n---", "lc-create-contract-remark": "## 5. 合同备注\n\n独立卡片,与授权委托书分离。\n\n提供文本域,支持自定义输入,非必填(最多 500 字);列表页不展示备注列。\n\n---", - "lc-create-seal": "# 用章类型(第 6 步)\n\n支持多选,可选范围仅包含:\n\n| 用章 | 说明 |\n|---|---|\n| 合同章 | 合同正文用章 |\n| 公章 | 公司公章 |\n| 法人章 | 法定代表人章 |\n\n**不含**财务章、发票章。\n\n默认选中合同章;至少选择一种用章后方可提交审核。", + "lc-create-seal": "# 用章类型(第 6 步)\n\n支持多选,可选范围仅包含:\n\n| 用章 | 说明 |\n|---|---|\n| 合同章 | 合同正文用章 |\n| 公章 | 公司公章 |\n| 法人章 | 法定代表人章 |\n\n**不含**财务章、发票章。\n\n默认选中合同章;至少选择一种用章后方可提交审核。\n\n**提示**:额外勾选公章、法人章会进入非标审批流程。", "lc-create-preview": "## 6. 实时预览\n\n- 基于标准合同模板 V26.6 拼装 HTML,A4 分页展示。\n- 支持缩放(50%—150%)、连续预览 / 单页预览切换。\n- **可编辑预览**:合同正文 `contentEditable`;用户可直接改字。\n- 顶栏展示当前 **审批类型** 徽章。\n\n### 5.1 占位符替换\n\n左侧表单变更时,预览内 `{{contractCode}}`、`{{customerName}}` 等变量实时替换。\n\n### 5.2 条件条款与附件章节\n\n按租赁订单车辆品牌型号,过滤模板中条件条款与附件章节绑定(与合同模板管理规则一致)。\n\n---\n\n---\n\n## 7. 审批类型判定\n\n| 类型 | 触发条件 |\n|---|---|\n| 标准合同 | 默认可编辑预览相对基线未改动风控红线条款 |\n| 非标准合同 | 预览中 `.ct-risk-redline[data-risk-redline=\"1\"]` 正文相对基线有增删改 |\n\n检测逻辑:`lease-contract-risk-detect.js` 对比基线 HTML 与当前合并页 HTML。\n\n保存草稿 / 提交审核时 Toast 展示将走的审批类型(原型)。\n\n---", "lc-create-footer": "## 8. 底栏操作\n\n| 按钮 | 行为 |\n|---|---|\n| 取消 / 返回列表 | 不保存,回列表 |\n| 保存草稿 | 保存为草稿,审批类型随检测结果记录 |\n| 提交审核 | 校验必填后发起审批流程 |\n\n---" }, "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "lc-doc-root", @@ -516,13 +507,13 @@ "type": "markdown", "id": "lc-doc-overview", "title": "模块总览", - "markdown": "## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁业务以合同为履约核心。业务人员需要基于法务维护的标准 Word 合同模板,快速草拟租赁合同、录入订单车辆与费用条款,并走标准/非标准审批。合同全生命周期包含草稿、审批、进行中变更、到期续签、终止等状态。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 台账可视 | 多维度筛选 + KPI 卡片快速定位进行中/临期/过期合同 |\n| 草拟高效 | 左侧结构化表单 + 右侧 Word 版式实时预览 |\n| 合规可控 | 修改模板风控红线条款自动走非标准合同审批 |\n| 档案复用 | 甲乙双方信息从主数据只读带出,减少重复录入 |\n| 流程闭环 | 列表操作覆盖续签、增车、附加费用、终止、法务盖章等场景 |\n\n### 1.3 非目标(本期不做)\n\n- 完整审批流配置界面(审批节点由流程引擎提供)\n- 合同 PDF 在线签署\n- 与财务系统的自动对账接口\n- 三方合同变更页的完整表单(列表入口预留,原型 Toast)\n\n---" + "markdown": "# 租赁合同管理 · 模块总览\n\n---\n\n商用车租赁合同全生命周期管理:台账筛选与 KPI 监控、结构化草拟与实时预览、标准/非标准审批判定、进行中变更与法务归档。\n\n---\n\n## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 租赁合同管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 面包屑 | 业务管理 → 租赁合同管理 |\n| 目标用户 | 业务人员(主)、法务(盖章上传)、客服/运营(查看) |\n| 核心任务 | 筛选与查看租赁合同台账,发起新增/变更/续签等流程 |\n| 交互原型 | `/prototypes/lease-contract-management` |\n\n---\n\n---\n\n## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁业务以合同为履约核心。业务人员需要基于法务维护的标准 Word 合同模板,快速草拟租赁合同、录入订单车辆与费用条款,并走标准/非标准审批。合同全生命周期包含草稿、审批、进行中变更、到期续签、终止等状态。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 台账可视 | 多维度筛选 + KPI 卡片快速定位进行中/临期/过期合同 |\n| 草拟高效 | 左侧结构化表单 + 右侧 Word 版式实时预览 |\n| 合规可控 | 修改模板风控红线条款自动走非标准合同审批 |\n| 档案复用 | 甲乙双方信息从主数据只读带出,减少重复录入 |\n| 流程闭环 | 列表操作覆盖续签、增车、附加费用、终止、法务盖章等场景 |\n\n### 1.3 非目标(本期不做)\n\n- 完整审批流配置界面(审批节点由流程引擎提供)\n- 合同 PDF 在线签署\n- 与财务系统的自动对账接口\n- 三方合同变更页的完整表单(列表入口预留,原型 Toast)\n\n---\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 业务人员 | 新增/编辑合同、提交审批、进行中变更 |\n| 法务人员 | 审批通过后上传盖章合同扫描件 |\n| 客服/运营 | 查看合同台账与车辆交车情况 |\n| 风控(间接) | 非标准合同审批识别风控条款变更 |\n\n### 2.2 核心用户故事\n\n1. **作为业务**,我希望按项目/客户/状态筛选合同,以便跟进我的在管合同。\n2. **作为业务**,我希望新增合同时选客户即带出开票与证照信息,以便快速核对乙方资料。\n3. **作为业务**,我希望右侧预览与左侧表单联动,以便确认合同正文与填写一致。\n4. **作为业务**,我若只改非红线内容,应走标准审批;改了风控红线则自动走非标准审批。\n5. **作为法务**,我希望审批通过后在列表上传盖章合同,以便归档。\n6. **作为业务**,我希望进行中的合同能续签、增车、录附加费用,以便业务持续运营。\n\n---\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| 标准合同审批 | 未改动模板风控红线条款正文时的审批路径 |\n| 非标准合同审批 | 预览中风控红线条款(`data-risk-redline=\"1\"`)相对模板基线被修改 |\n| 风控红线条款 | 合同模板中标记的合规关键条款 |\n| 合同状态 | 草稿、已提交审批、变更、合同进行中、到期合同、已结束等 |\n| 审批状态 | 未提交、待审批、审批中、审批通过、审批驳回 |\n| 标准模板 | 合同模板管理中 `isDefault=true` 的已发布租赁模板 |\n\n---\n\n---\n\n## 4. 信息架构\n\n```text\n租赁合同管理\n├── 列表页\n│ ├── 筛选区(13 项,默认展示 4 项)\n│ ├── KPI 卡片(5 张)\n│ ├── 工具栏(费用模板 / 导出 / 新增)\n│ └── 合同台账表\n└── 新增 / 编辑页\n ├── 主体合同信息\n │ ├── 签约信息\n │ ├── 里程标准\n │ └── 费用信息\n ├── 附件1:租赁订单\n ├── 授权委托书\n ├── 实时预览(可编辑 + 审批类型)\n └── 底栏(保存草稿 / 提交审核)\n```\n\n---\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 乙方客户可选规则\n\n仅 **A/B 级** 风控客户可选;C/D 级在客户选择器中标记「不可选」。\n\n### 6.2 合同编号\n\n展示前缀 `HT-ZL-`,用户输入后缀;存储时拼接完整编码。\n\n### 6.3 审批类型自动判定\n\n- 基线:由 `buildLeaseContractPreviewHtml` 生成的初始 HTML。\n- 当前:用户可编辑预览合并后的 HTML。\n- 若 `isRiskRedlineContentModified(baseline, current)` 为真 → **非标准合同审批**。\n\n### 6.4 列表 KPI 与筛选\n\nKPI 卡片筛选与筛选区条件 **叠加**(先 KPI 维度,再应用筛选表单)。\n\n### 6.5 法务盖章上传\n\n- 条件:`approvalStatus = 审批通过` 且 `legalStampedContractUploaded != true`。\n- 权限:当前用户部门为法务部。\n- 上传成功后隐藏「上传盖章合同」入口。\n\n### 6.6 交还与履约规则\n\n| 规则 | 说明 |\n|---|---|\n| 审批 gate | **仅审批通过**的合同才产生交车/还车履约数据;待审批、审批中、驳回、撤回等状态下,车辆不计入「已交车辆数」 |\n| 交车 | 审批通过后,经提车应收款办理实际交车;子表「交车」列展示里程、交车人、时间 |\n| 租赁账单 | 仅已交车车辆展示「正常 / 欠费」 |\n| 子表还车 | **审批通过 + 已交车 + 未还车** 时,子表操作列显示「还车」;已还车为「—」 |\n| 还车应结款 | 还车后按待提交 → 审批中 → 已完成流转;审批中审批人展示与主表「审批状态」列一致 |\n| 已终止 | 全部车辆已还车,或续签/转三方后旧合同自动终止 |\n\n> 操作流程图见 `.spec/requirements-flow-operations.md`;标注上下文见 `.spec/requirements-annotation-context.md`。\n\n---\n\n---\n\n## 9. 与其他模块的关系\n\n\n\n![流程图 1](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNzQwLjAzMXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA3NDAuMDMxMjUgMzgyIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xNjQsMzVMMTc3LjE2OSwzNUMxOTAuMzM5LDM1LDIxNi42NzcsMzUsMjUxLjAxMyw1Ni4wM0MyODUuMzUsNzcuMDYsMzI3LjY4NCwxMTkuMTIxLDM0OC44NTEsMTQwLjE1MUwzNzAuMDE4LDE2MS4xODEiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRZMExDSjVJam96Tlgwc2V5SjRJam95TkRNdU1ERTFOakkxTENKNUlqb3pOWDBzZXlKNElqb3pOekl1T0RVMU5EWTROelVzSW5raU9qRTJOSDFkIiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTQ4LDEzOUwxNjMuODM2LDEzOUMxNzkuNjcyLDEzOSwyMTEuMzQ0LDEzOSwyMzkuNzE2LDE0My4xNTJDMjY4LjA4OCwxNDcuMzA0LDI5My4xNjEsMTU1LjYwNywzMDUuNjk4LDE1OS43NTlMMzE4LjIzNCwxNjMuOTExIiBpZD0ibXktc3ZnLUxfQ19CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UUTRMQ0o1SWpveE16bDlMSHNpZUNJNk1qUXpMakF4TlRZeU5Td2llU0k2TVRNNWZTeDdJbmdpT2pNeU1pNHdNekV5TlN3aWVTSTZNVFkxTGpFMk9ERTNOVGt6Tnprd05ESTNmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTQ4LDI0M0wxNjMuODM2LDI0M0MxNzkuNjcyLDI0MywyMTEuMzQ0LDI0MywyMzkuNzE2LDIzOC44NDhDMjY4LjA4OCwyMzQuNjk2LDI5My4xNjEsMjI2LjM5MywzMDUuNjk4LDIyMi4yNDFMMzE4LjIzNCwyMTguMDg5IiBpZD0ibXktc3ZnLUxfRF9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UUTRMQ0o1SWpveU5ETjlMSHNpZUNJNk1qUXpMakF4TlRZeU5Td2llU0k2TWpRemZTeDdJbmdpT2pNeU1pNHdNekV5TlN3aWVTSTZNakUyTGpnek1UZ3lOREEyTWpBNU5UY3pmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTQ4LDM0N0wxNjMuODM2LDM0N0MxNzkuNjcyLDM0NywyMTEuMzQ0LDM0NywyNDguMzQ3LDMyNS45N0MyODUuMzUsMzA0Ljk0LDMyNy42ODQsMjYyLjg3OSwzNDguODUxLDI0MS44NDlMMzcwLjAxOCwyMjAuODE5IiBpZD0ibXktc3ZnLUxfRV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UUTRMQ0o1SWpvek5EZDlMSHNpZUNJNk1qUXpMakF4TlRZeU5Td2llU0k2TXpRM2ZTeDdJbmdpT2pNM01pNDROVFUwTmpnM05Td2llU0k2TWpFNGZWMD0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik00NzAuMTI3LDE2NEw0ODAuOTQ1LDE1OS44MzNDNDkxLjc2MiwxNTUuNjY3LDUxMy4zOTcsMTQ3LjMzMyw1MzMuMDQ3LDE0My4xNjdDNTUyLjY5OCwxMzksNTcwLjM2NSwxMzksNTc5LjE5OCwxMzlMNTg4LjAzMSwxMzkiIGlkPSJteS1zdmctTF9CX0ZfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0ZfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRjd0xqRXlOelF3TXpnME5qRTFNemdzSW5raU9qRTJOSDBzZXlKNElqbzFNelV1TURNeE1qVXNJbmtpT2pFek9YMHNleUo0SWpvMU9USXVNRE14TWpVc0lua2lPakV6T1gxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQ3MC4xMjcsMjE4TDQ4MC45NDUsMjIyLjE2N0M0OTEuNzYyLDIyNi4zMzMsNTEzLjM5NywyMzQuNjY3LDUzNC4zODEsMjM4LjgzM0M1NTUuMzY1LDI0Myw1NzUuNjk4LDI0Myw1ODUuODY1LDI0M0w1OTYuMDMxLDI0MyIgaWQ9Im15LXN2Zy1MX0JfR18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfR18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZORGN3TGpFeU56UXdNemcwTmpFMU16Z3NJbmtpT2pJeE9IMHNleUo0SWpvMU16VXVNRE14TWpVc0lua2lPakkwTTMwc2V5SjRJam8yTURBdU1ETXhNalVzSW5raU9qSTBNMzFkIiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0My4wMTU2MjUsIDM1KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTQuMDE1NjI1LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTA4LjAzMTI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7moIflh4bmqKHmnb8gSFRNTDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjQzLjAxNTYyNSwgMTM5KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5LmZ5pa55qGj5qGIPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNDMuMDE1NjI1LCAyNDMpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7nlLLmlrnmoaPmoYg8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0My4wMTU2MjUsIDM0NykiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9FX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTUyLjE5NTMxMjUsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMDQuMzkwNjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7lk4HniYzlnovlj7cv6L2m54mMPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1MzUuMDMxMjUsIDEzOSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuWuoeaJueexu+WeizwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTM1LjAzMTI1LCAyNDMpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9HXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7nm5bnq6DpmYTku7Y8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4NiwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzgiIHk9Ii0yNyIgd2lkdGg9IjE1NiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCI5ZCM5qih5p2/566h55CGPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDAwLjAzMTI1LCAxOTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzgiIHk9Ii0yNyIgd2lkdGg9IjE1NiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+56ef6LWB5ZCI5ZCM5Yib5bu6PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTIiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoODYsIDEzOSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqLmiLfnrqHnkIY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUQtNCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4NiwgMjQzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPue7hOe7h+acuuaehDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRS02IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDg2LCAzNDcpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+6L2m6L6G566h55CGPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1GLTkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjYyLjAzMTI1LCAxMzkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzAiIHk9Ii0yNyIgd2lkdGg9IjE0MCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a6h5om55rWB5byV5pOOPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1HLTExIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDY2Mi4wMzEyNSwgMjQzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuazleWKoeW9kuahozwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n| 模块 | 关系 |\n|---|---|\n| 合同模板管理 | 提供标准模板、风控红线/条件条款标记 |\n| 客户管理 | 乙方选择、风控等级、证照 |\n| 车辆管理 | 租赁订单品牌型号、可交车辆车牌 |\n| 审批流 | 消费标准/非标准审批类型 |\n\n---\n\n---\n\n## 12. 验收标准\n\n### 12.1 列表页\n\n- [ ] 13 项筛选(含合同模板联动标准合同名称)与 KPI 五卡片联动正确\n- [ ] 项目信息列、车辆/交车 Popover、审批流悬停\n- [ ] 操作按合同状态/类型正确显隐\n- [ ] 法务盖章上传后入口关闭\n\n### 12.2 新增页\n\n- [ ] 三块主体合同表单 + 完成度徽章\n- [ ] 甲乙档案只读同步、客户 A/B 级限制\n- [ ] 预览随表单实时更新\n- [ ] 可编辑预览改动风控红线 → 非标准审批徽章\n- [ ] 保存/提交展示审批类型\n\n### 12.3 标注与文档(原型)\n\n- [ ] 已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录可切换列表页 / 新增页,PRD 全文可阅\n\n---" }, { "type": "link", "id": "lc-link-template", "title": "合同模板管理", - "href": "../contract-template-management/index.html", + "href": "/prototypes/contract-template-management", "target": "self" }, { @@ -535,28 +526,98 @@ "type": "markdown", "id": "lc-doc-prd-full", "title": "PRD 完整文档", - "markdown": "# 租赁合同管理 · 产品需求文档(PRD)\n\n| 项 | 内容 |\n|---|---|\n| 文档版本 | v1.0 |\n| 模块名称 | 租赁合同管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 目标读者 | 前端 / 后端 / 测试 / 业务产品对接人 |\n| 交互原型 | `/prototypes/lease-contract-management` |\n| 关联原型 | [合同模板管理](/prototypes/contract-template-management)、[车辆管理](/prototypes/vehicle-management) |\n| 文档状态 | 已对齐原型,待研发排期 |\n\n---\n\n## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁业务以合同为履约核心。业务人员需要基于法务维护的标准 Word 合同模板,快速草拟租赁合同、录入订单车辆与费用条款,并走标准/非标准审批。合同全生命周期包含草稿、审批、进行中变更、到期续签、终止等状态。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 台账可视 | 多维度筛选 + KPI 卡片快速定位进行中/临期/过期合同 |\n| 草拟高效 | 左侧结构化表单 + 右侧 Word 版式实时预览 |\n| 合规可控 | 修改模板风控红线条款自动走非标准合同审批 |\n| 档案复用 | 甲乙双方信息从主数据只读带出,减少重复录入 |\n| 流程闭环 | 列表操作覆盖续签、增车、附加费用、终止、法务盖章等场景 |\n\n### 1.3 非目标(本期不做)\n\n- 完整审批流配置界面(审批节点由流程引擎提供)\n- 合同 PDF 在线签署\n- 与财务系统的自动对账接口\n- 三方合同变更页的完整表单(列表入口预留,原型 Toast)\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 业务人员 | 新增/编辑合同、提交审批、进行中变更 |\n| 法务人员 | 审批通过后上传盖章合同扫描件 |\n| 客服/运营 | 查看合同台账与车辆交车情况 |\n| 风控(间接) | 非标准合同审批识别风控条款变更 |\n\n### 2.2 核心用户故事\n\n1. **作为业务**,我希望按项目/客户/状态筛选合同,以便跟进我的在管合同。\n2. **作为业务**,我希望新增合同时选客户即带出开票与证照信息,以便快速核对乙方资料。\n3. **作为业务**,我希望右侧预览与左侧表单联动,以便确认合同正文与填写一致。\n4. **作为业务**,我若只改非红线内容,应走标准审批;改了风控红线则自动走非标准审批。\n5. **作为法务**,我希望审批通过后在列表上传盖章合同,以便归档。\n6. **作为业务**,我希望进行中的合同能续签、增车、录附加费用,以便业务持续运营。\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| 标准合同审批 | 未改动模板风控红线条款正文时的审批路径 |\n| 非标准合同审批 | 预览中风控红线条款(`data-risk-redline=\"1\"`)相对模板基线被修改 |\n| 风控红线条款 | 合同模板中标记的合规关键条款 |\n| 合同状态 | 草稿、已提交审批、变更、合同进行中、到期合同、已结束等 |\n| 审批状态 | 未提交、待审批、审批中、审批通过、审批驳回 |\n| 标准模板 | 合同模板管理中 `isDefault=true` 的已发布租赁模板 |\n\n---\n\n## 4. 信息架构\n\n```text\n租赁合同管理\n├── 列表页\n│ ├── 筛选区(12 项,默认展示 4 项)\n│ ├── KPI 卡片(5 张)\n│ ├── 工具栏(费用模板 / 导出 / 新增)\n│ └── 合同台账表\n└── 新增 / 编辑页\n ├── 主体合同信息\n │ ├── 签约信息\n │ ├── 里程标准\n │ └── 费用信息\n ├── 附件1:租赁订单\n ├── 授权委托书\n ├── 实时预览(可编辑 + 审批类型)\n └── 底栏(保存草稿 / 提交审核)\n```\n\n---\n\n## 5. 数据模型(建议)\n\n```typescript\ninterface LeaseContract {\n id: string;\n contractCode: string; // HT-ZL-YYYY-NNN\n projectId: string;\n projectName: string;\n lessorId: string; // 甲方签约主体\n customerId: string; // 乙方客户\n contractType: 'formal' | 'trial';\n contractApprovalType: 'standard' | 'nonstandard';\n approvalStatus: 'draft' | 'pending' | 'in_review' | 'approved' | 'rejected';\n contractStatus: string;\n contractEndDate: string; // YYYY-MM-DD\n businessDept: string;\n businessOwner: string;\n mileage: MileageStandard;\n feeInfo: FeeInfo;\n leaseOrder: LeaseOrder;\n powerOfAttorney: PowerOfAttorney;\n previewHtmlBaseline: string; // 用于风控比对\n previewHtmlCurrent?: string;\n legalStampedContractUploaded?: boolean;\n vehicles: LeaseVehicleRow[];\n creator: string;\n createTime: string;\n updater?: string;\n updateTime?: string;\n remark?: string;\n}\n```\n\n字段细节见新增页各表单区块说明(`.spec/requirements-prd-create.md`)。\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 乙方客户可选规则\n\n仅 **A/B 级** 风控客户可选;C/D 级在客户选择器中标记「不可选」。\n\n### 6.2 合同编号\n\n展示前缀 `HT-ZL-`,用户输入后缀;存储时拼接完整编码。\n\n### 6.3 审批类型自动判定\n\n- 基线:由 `buildLeaseContractPreviewHtml` 生成的初始 HTML。\n- 当前:用户可编辑预览合并后的 HTML。\n- 若 `isRiskRedlineContentModified(baseline, current)` 为真 → **非标准合同审批**。\n\n### 6.4 列表 KPI 与筛选\n\nKPI 卡片筛选与筛选区条件 **叠加**(先 KPI 维度,再应用筛选表单)。\n\n### 6.5 法务盖章上传\n\n- 条件:`approvalStatus = 审批通过` 且 `legalStampedContractUploaded != true`。\n- 权限:当前用户部门为法务部。\n- 上传成功后隐藏「上传盖章合同」入口。\n\n---\n\n## 7. 列表页需求\n\n> 详细字段与操作见 `src/prototypes/lease-contract-management/.spec/requirements-prd-list.md`\n\n### 7.1 筛选区\n\n12 个筛选项;默认 4 项;支持展开/收起、查询、重置。\n\n### 7.2 KPI 卡片\n\n全部合同 / 进行中 / 已终止 / 临期合同 / 过期合同。\n\n### 7.3 工具栏\n\n租赁费用模板、导出、新增。\n\n### 7.4 列表\n\n项目信息合并列、车辆数/已交车 Popover、审批流悬停、完整业务字段、操作列。\n\n### 7.5 操作\n\n查看、编辑、删除、撤回、增车、续签、被授权人、附加费用、三方变更、转正式、终止、上传盖章合同等(按状态显隐)。\n\n---\n\n## 8. 新增 / 编辑页需求\n\n> 详细见 `src/prototypes/lease-contract-management/.spec/requirements-prd-create.md`\n\n### 8.1 主体合同信息\n\n签约信息(含甲乙档案只读)、里程标准、费用信息;区块完成度徽章。\n\n### 8.2 附件1:租赁订单\n\n订单车辆表、交车信息、三者责任险等。\n\n### 8.3 授权委托书\n\n被委托人列表。\n\n### 8.4 实时预览\n\nWord 版式、可编辑正文、缩放与翻页、审批类型徽章。\n\n### 8.5 底栏\n\n取消、保存草稿、提交审核。\n\n---\n\n## 9. 与其他模块的关系\n\n```mermaid\nflowchart LR\n A[合同模板管理] -->|标准模板 HTML| B[租赁合同创建]\n C[客户管理] -->|乙方档案| B\n D[组织机构] -->|甲方档案| B\n E[车辆管理] -->|品牌型号/车牌| B\n B -->|审批类型| F[审批流引擎]\n B -->|盖章附件| G[法务归档]\n```\n\n| 模块 | 关系 |\n|---|---|\n| 合同模板管理 | 提供标准模板、风控红线/条件条款标记 |\n| 客户管理 | 乙方选择、风控等级、证照 |\n| 车辆管理 | 租赁订单品牌型号、可交车辆车牌 |\n| 审批流 | 消费标准/非标准审批类型 |\n\n---\n\n## 10. 接口需求(建议)\n\n### 10.1 列表查询\n\n```\nGET /api/lease-contracts?...filters&page=1&pageSize=10\n```\n\n### 10.2 详情\n\n```\nGET /api/lease-contracts/{id}\n```\n\n### 10.3 创建 / 更新\n\n```\nPOST /api/lease-contracts\nPUT /api/lease-contracts/{id}\n```\n\n请求体含表单各区块与 `contractApprovalType`。\n\n### 10.4 提交审批\n\n```\nPOST /api/lease-contracts/{id}/submit\n```\n\n### 10.5 上传盖章合同\n\n```\nPOST /api/lease-contracts/{id}/stamped-files\nContent-Type: multipart/form-data\n```\n\n---\n\n## 11. 非功能需求\n\n| 类别 | 要求 |\n|---|---|\n| 性能 | 预览 HTML 变更后 300ms 内更新审批类型判定 |\n| 兼容性 | Chrome / Edge 最新两版;推荐宽度 ≥ 1280px |\n| 可访问性 | 表单 `aria-label`;完成度 `role=\"status\"` |\n| 安全 | 预览 HTML 入库前 XSS 清洗 |\n\n---\n\n## 12. 验收标准\n\n### 12.1 列表页\n\n- [ ] 12 项筛选与 KPI 五卡片联动正确\n- [ ] 项目信息列、车辆/交车 Popover、审批流悬停\n- [ ] 操作按合同状态/类型正确显隐\n- [ ] 法务盖章上传后入口关闭\n\n### 12.2 新增页\n\n- [ ] 三块主体合同表单 + 完成度徽章\n- [ ] 甲乙档案只读同步、客户 A/B 级限制\n- [ ] 预览随表单实时更新\n- [ ] 可编辑预览改动风控红线 → 非标准审批徽章\n- [ ] 保存/提交展示审批类型\n\n### 12.3 标注与文档(原型)\n\n- [ ] 已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录可切换列表页 / 新增页,PRD 全文可阅\n\n---\n\n## 13. 原型实现映射\n\n| 能力 | 文件 |\n|---|---|\n| 列表主逻辑 | `LeaseContractManagement.jsx` |\n| 新增页 | `LeaseContractCreate.jsx` |\n| 主体表单 | `LeaseContractEditorForm.jsx` |\n| 预览 | `LeaseContractPreviewPanel.jsx` |\n| 预览拼装 | `lease-contract-preview-build.js` |\n| 风控检测 | `lease-contract-risk-detect.js` |\n| 表单校验 | `lease-contract-form-validation.js` |\n| 标注入口 | `index.tsx` |\n| 标注数据 | `annotation-source.json` |\n| PRD | `src/resources/lease-contract-management/PRD.md` |\n\n---\n\n## 14. 修订记录\n\n| 版本 | 日期 | 说明 |\n|---|---|---|\n| v1.0 | 2026-06-25 | 首版:对齐列表/新增原型、风控审批判定、标注与 PRD 接入 |\n", + "markdown": "# 租赁合同管理 · 产品需求文档(PRD)\n\n| 项 | 内容 |\n|---|---|\n| 文档版本 | v1.2 |\n| 模块名称 | 租赁合同管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 目标读者 | 前端 / 后端 / 测试 / 业务产品对接人 |\n| 交互原型 | `/prototypes/lease-contract-management` |\n| 关联原型 | [合同模板管理](/prototypes/contract-template-management)、[车辆管理](/prototypes/vehicle-management) |\n| 文档状态 | 已对齐原型,待研发排期 |\n\n---\n\n## 1. 背景与目标\n\n### 1.1 背景\n\n商用车租赁业务以合同为履约核心。业务人员需要基于法务维护的标准 Word 合同模板,快速草拟租赁合同、录入订单车辆与费用条款,并走标准/非标准审批。合同全生命周期包含草稿、审批、进行中变更、到期续签、终止等状态。\n\n### 1.2 产品目标\n\n| 目标 | 说明 |\n|---|---|\n| 台账可视 | 多维度筛选 + KPI 卡片快速定位进行中/临期/过期合同 |\n| 草拟高效 | 左侧结构化表单 + 右侧 Word 版式实时预览 |\n| 合规可控 | 修改模板风控红线条款自动走非标准合同审批 |\n| 档案复用 | 甲乙双方信息从主数据只读带出,减少重复录入 |\n| 流程闭环 | 列表操作覆盖续签、增车、附加费用、终止、法务盖章等场景 |\n\n### 1.3 非目标(本期不做)\n\n- 完整审批流配置界面(审批节点由流程引擎提供)\n- 合同 PDF 在线签署\n- 与财务系统的自动对账接口\n- 三方合同变更页的完整表单(列表入口预留,原型 Toast)\n\n---\n\n## 2. 用户与场景\n\n### 2.1 目标用户\n\n| 角色 | 诉求 |\n|---|---|\n| 业务人员 | 新增/编辑合同、提交审批、进行中变更 |\n| 法务人员 | 审批通过后上传盖章合同扫描件 |\n| 客服/运营 | 查看合同台账与车辆交车情况 |\n| 风控(间接) | 非标准合同审批识别风控条款变更 |\n\n### 2.2 核心用户故事\n\n1. **作为业务**,我希望按项目/客户/状态筛选合同,以便跟进我的在管合同。\n2. **作为业务**,我希望新增合同时选客户即带出开票与证照信息,以便快速核对乙方资料。\n3. **作为业务**,我希望右侧预览与左侧表单联动,以便确认合同正文与填写一致。\n4. **作为业务**,我若只改非红线内容,应走标准审批;改了风控红线则自动走非标准审批。\n5. **作为法务**,我希望审批通过后在列表上传盖章合同,以便归档。\n6. **作为业务**,我希望进行中的合同能续签、增车、录附加费用,以便业务持续运营。\n\n---\n\n## 3. 名词解释\n\n| 名词 | 定义 |\n|---|---|\n| 标准合同审批 | 未改动模板风控红线条款正文时的审批路径 |\n| 非标准合同审批 | 预览中风控红线条款(`data-risk-redline=\"1\"`)相对模板基线被修改 |\n| 风控红线条款 | 合同模板中标记的合规关键条款 |\n| 合同状态 | 草稿、已提交审批、变更、合同进行中、到期合同、已结束等 |\n| 审批状态 | 未提交、待审批、审批中、审批通过、审批驳回 |\n| 标准模板 | 合同模板管理中 `isDefault=true` 的已发布租赁模板 |\n\n---\n\n## 4. 信息架构\n\n```text\n租赁合同管理\n├── 列表页\n│ ├── 筛选区(13 项,默认展示 4 项)\n│ ├── KPI 卡片(5 张)\n│ ├── 工具栏(费用模板 / 导出 / 新增)\n│ └── 合同台账表\n└── 新增 / 编辑页\n ├── 主体合同信息\n │ ├── 签约信息\n │ ├── 里程标准\n │ └── 费用信息\n ├── 附件1:租赁订单\n ├── 授权委托书\n ├── 实时预览(可编辑 + 审批类型)\n └── 底栏(保存草稿 / 提交审核)\n```\n\n---\n\n## 5. 数据模型(建议)\n\n```typescript\ninterface LeaseContract {\n id: string;\n contractCode: string; // HT-ZL-YYYY-NNN\n projectId: string;\n projectName: string;\n lessorId: string; // 甲方签约主体\n customerId: string; // 乙方客户\n contractType: 'formal' | 'trial';\n contractApprovalType: 'standard' | 'nonstandard';\n approvalStatus: 'draft' | 'pending' | 'in_review' | 'approved' | 'rejected';\n contractStatus: string;\n contractEndDate: string; // YYYY-MM-DD\n businessDept: string;\n businessOwner: string;\n mileage: MileageStandard;\n feeInfo: FeeInfo;\n leaseOrder: LeaseOrder;\n powerOfAttorney: PowerOfAttorney;\n previewHtmlBaseline: string; // 用于风控比对\n previewHtmlCurrent?: string;\n legalStampedContractUploaded?: boolean;\n vehicles: LeaseVehicleRow[];\n creator: string;\n createTime: string;\n updater?: string;\n updateTime?: string;\n remark?: string;\n}\n```\n\n字段细节见新增页各表单区块说明(`.spec/requirements-prd-create.md`)。\n\n---\n\n## 6. 业务规则(全局)\n\n### 6.1 乙方客户可选规则\n\n仅 **A/B 级** 风控客户可选;C/D 级在客户选择器中标记「不可选」。\n\n### 6.2 合同编号\n\n展示前缀 `HT-ZL-`,用户输入后缀;存储时拼接完整编码。\n\n### 6.3 审批类型自动判定\n\n- 基线:由 `buildLeaseContractPreviewHtml` 生成的初始 HTML。\n- 当前:用户可编辑预览合并后的 HTML。\n- 若 `isRiskRedlineContentModified(baseline, current)` 为真 → **非标准合同审批**。\n\n### 6.4 列表 KPI 与筛选\n\nKPI 卡片筛选与筛选区条件 **叠加**(先 KPI 维度,再应用筛选表单)。\n\n### 6.5 法务盖章上传\n\n- 条件:`approvalStatus = 审批通过` 且 `legalStampedContractUploaded != true`。\n- 权限:当前用户部门为法务部。\n- 上传成功后隐藏「上传盖章合同」入口。\n\n### 6.6 交还与履约规则\n\n| 规则 | 说明 |\n|---|---|\n| 审批 gate | **仅审批通过**的合同才产生交车/还车履约数据;待审批、审批中、驳回、撤回等状态下,车辆不计入「已交车辆数」 |\n| 交车 | 审批通过后,经提车应收款办理实际交车;子表「交车」列展示里程、交车人、时间 |\n| 租赁账单 | 仅已交车车辆展示「正常 / 欠费」 |\n| 子表还车 | **审批通过 + 已交车 + 未还车** 时,子表操作列显示「还车」;已还车为「—」 |\n| 还车应结款 | 还车后按待提交 → 审批中 → 已完成流转;审批中审批人展示与主表「审批状态」列一致 |\n| 已终止 | 全部车辆已还车,或续签/转三方后旧合同自动终止 |\n\n> 操作流程图见 `.spec/requirements-flow-operations.md`;标注上下文见 `.spec/requirements-annotation-context.md`。\n\n---\n\n## 7. 列表页需求\n\n> 详细字段与操作见 `src/prototypes/lease-contract-management/.spec/requirements-prd-list.md`\n\n### 7.1 筛选区\n\n13 个筛选项;默认 4 项;支持展开/收起、查询、重置。\n\n其中 **合同模板** 与 **标准合同名称** 为联动单选:\n\n- 合同模板:取自合同模板管理已发布模板的「合同名称」类别(正式合同、试用合同等)\n- 标准合同名称:选定合同模板后,列出该类别下具体 Word 文档名(如 2026年标准商用车租赁合同)\n- 切换合同模板时清空标准合同名称;未选模板时标准合同名称禁用\n\n### 7.2 KPI 卡片\n\n全部合同 / 草稿 / 进行中 / 审批中 / 已终止(按合同状态分桶,与筛选区叠加)。\n\n### 7.3 工具栏\n\n租赁费用模板、导出、新增。\n\n### 7.4 列表\n\n项目信息合并列、租赁订单车辆数/已交车子表、合同签署方式、审批流悬停、费用与交车安排、整体里程、操作列。\n\n子表「还车应结款」审批中状态与主表审批人展示一致;仅审批通过合同产生交车履约数据。\n\n### 7.5 操作\n\n查看、编辑、删除、撤回、增车、续签、被授权人、附加费用、三方变更、转正式、终止、上传盖章合同等(按状态显隐)。\n\n---\n\n## 8. 新增 / 编辑页需求\n\n> 详细见 `src/prototypes/lease-contract-management/.spec/requirements-prd-create.md`\n\n### 8.1 主体合同信息\n\n签约信息(含甲乙档案只读)、里程标准、费用信息;区块完成度徽章。\n\n### 8.2 附件1:租赁订单\n\n订单车辆表、交车信息、三者责任险等。\n\n### 8.3 授权委托书\n\n被委托人列表。\n\n### 8.4 实时预览\n\nWord 版式、可编辑正文、缩放与翻页、审批类型徽章。\n\n### 8.5 底栏\n\n取消、保存草稿、提交审核。\n\n---\n\n## 9. 与其他模块的关系\n\n\n\n![流程图 1](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNzQwLjAzMXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA3NDAuMDMxMjUgMzgyIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xNjQsMzVMMTc3LjE2OSwzNUMxOTAuMzM5LDM1LDIxNi42NzcsMzUsMjUxLjAxMyw1Ni4wM0MyODUuMzUsNzcuMDYsMzI3LjY4NCwxMTkuMTIxLDM0OC44NTEsMTQwLjE1MUwzNzAuMDE4LDE2MS4xODEiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRZMExDSjVJam96Tlgwc2V5SjRJam95TkRNdU1ERTFOakkxTENKNUlqb3pOWDBzZXlKNElqb3pOekl1T0RVMU5EWTROelVzSW5raU9qRTJOSDFkIiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTQ4LDEzOUwxNjMuODM2LDEzOUMxNzkuNjcyLDEzOSwyMTEuMzQ0LDEzOSwyMzkuNzE2LDE0My4xNTJDMjY4LjA4OCwxNDcuMzA0LDI5My4xNjEsMTU1LjYwNywzMDUuNjk4LDE1OS43NTlMMzE4LjIzNCwxNjMuOTExIiBpZD0ibXktc3ZnLUxfQ19CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UUTRMQ0o1SWpveE16bDlMSHNpZUNJNk1qUXpMakF4TlRZeU5Td2llU0k2TVRNNWZTeDdJbmdpT2pNeU1pNHdNekV5TlN3aWVTSTZNVFkxTGpFMk9ERTNOVGt6Tnprd05ESTNmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTQ4LDI0M0wxNjMuODM2LDI0M0MxNzkuNjcyLDI0MywyMTEuMzQ0LDI0MywyMzkuNzE2LDIzOC44NDhDMjY4LjA4OCwyMzQuNjk2LDI5My4xNjEsMjI2LjM5MywzMDUuNjk4LDIyMi4yNDFMMzE4LjIzNCwyMTguMDg5IiBpZD0ibXktc3ZnLUxfRF9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UUTRMQ0o1SWpveU5ETjlMSHNpZUNJNk1qUXpMakF4TlRZeU5Td2llU0k2TWpRemZTeDdJbmdpT2pNeU1pNHdNekV5TlN3aWVTSTZNakUyTGpnek1UZ3lOREEyTWpBNU5UY3pmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTQ4LDM0N0wxNjMuODM2LDM0N0MxNzkuNjcyLDM0NywyMTEuMzQ0LDM0NywyNDguMzQ3LDMyNS45N0MyODUuMzUsMzA0Ljk0LDMyNy42ODQsMjYyLjg3OSwzNDguODUxLDI0MS44NDlMMzcwLjAxOCwyMjAuODE5IiBpZD0ibXktc3ZnLUxfRV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UUTRMQ0o1SWpvek5EZDlMSHNpZUNJNk1qUXpMakF4TlRZeU5Td2llU0k2TXpRM2ZTeDdJbmdpT2pNM01pNDROVFUwTmpnM05Td2llU0k2TWpFNGZWMD0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik00NzAuMTI3LDE2NEw0ODAuOTQ1LDE1OS44MzNDNDkxLjc2MiwxNTUuNjY3LDUxMy4zOTcsMTQ3LjMzMyw1MzMuMDQ3LDE0My4xNjdDNTUyLjY5OCwxMzksNTcwLjM2NSwxMzksNTc5LjE5OCwxMzlMNTg4LjAzMSwxMzkiIGlkPSJteS1zdmctTF9CX0ZfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0ZfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRjd0xqRXlOelF3TXpnME5qRTFNemdzSW5raU9qRTJOSDBzZXlKNElqbzFNelV1TURNeE1qVXNJbmtpT2pFek9YMHNleUo0SWpvMU9USXVNRE14TWpVc0lua2lPakV6T1gxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQ3MC4xMjcsMjE4TDQ4MC45NDUsMjIyLjE2N0M0OTEuNzYyLDIyNi4zMzMsNTEzLjM5NywyMzQuNjY3LDUzNC4zODEsMjM4LjgzM0M1NTUuMzY1LDI0Myw1NzUuNjk4LDI0Myw1ODUuODY1LDI0M0w1OTYuMDMxLDI0MyIgaWQ9Im15LXN2Zy1MX0JfR18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfR18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZORGN3TGpFeU56UXdNemcwTmpFMU16Z3NJbmtpT2pJeE9IMHNleUo0SWpvMU16VXVNRE14TWpVc0lua2lPakkwTTMwc2V5SjRJam8yTURBdU1ETXhNalVzSW5raU9qSTBNMzFkIiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0My4wMTU2MjUsIDM1KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTQuMDE1NjI1LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTA4LjAzMTI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7moIflh4bmqKHmnb8gSFRNTDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjQzLjAxNTYyNSwgMTM5KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5LmZ5pa55qGj5qGIPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNDMuMDE1NjI1LCAyNDMpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7nlLLmlrnmoaPmoYg8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0My4wMTU2MjUsIDM0NykiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9FX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTUyLjE5NTMxMjUsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMDQuMzkwNjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7lk4HniYzlnovlj7cv6L2m54mMPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1MzUuMDMxMjUsIDEzOSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuWuoeaJueexu+WeizwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTM1LjAzMTI1LCAyNDMpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9HXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7nm5bnq6DpmYTku7Y8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4NiwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzgiIHk9Ii0yNyIgd2lkdGg9IjE1NiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCI5ZCM5qih5p2/566h55CGPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDAwLjAzMTI1LCAxOTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzgiIHk9Ii0yNyIgd2lkdGg9IjE1NiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+56ef6LWB5ZCI5ZCM5Yib5bu6PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTIiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoODYsIDEzOSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqLmiLfnrqHnkIY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUQtNCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4NiwgMjQzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPue7hOe7h+acuuaehDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRS02IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDg2LCAzNDcpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+6L2m6L6G566h55CGPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1GLTkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjYyLjAzMTI1LCAxMzkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzAiIHk9Ii0yNyIgd2lkdGg9IjE0MCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a6h5om55rWB5byV5pOOPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1HLTExIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDY2Mi4wMzEyNSwgMjQzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuazleWKoeW9kuahozwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n| 模块 | 关系 |\n|---|---|\n| 合同模板管理 | 提供标准模板、风控红线/条件条款标记 |\n| 客户管理 | 乙方选择、风控等级、证照 |\n| 车辆管理 | 租赁订单品牌型号、可交车辆车牌 |\n| 审批流 | 消费标准/非标准审批类型 |\n\n---\n\n## 10. 接口需求(建议)\n\n### 10.1 列表查询\n\n```\nGET /api/lease-contracts?...filters&page=1&pageSize=10\n```\n\n### 10.2 详情\n\n```\nGET /api/lease-contracts/{id}\n```\n\n### 10.3 创建 / 更新\n\n```\nPOST /api/lease-contracts\nPUT /api/lease-contracts/{id}\n```\n\n请求体含表单各区块与 `contractApprovalType`。\n\n### 10.4 提交审批\n\n```\nPOST /api/lease-contracts/{id}/submit\n```\n\n### 10.5 上传盖章合同\n\n```\nPOST /api/lease-contracts/{id}/stamped-files\nContent-Type: multipart/form-data\n```\n\n---\n\n## 11. 非功能需求\n\n| 类别 | 要求 |\n|---|---|\n| 性能 | 预览 HTML 变更后 300ms 内更新审批类型判定 |\n| 兼容性 | Chrome / Edge 最新两版;推荐宽度 ≥ 1280px |\n| 可访问性 | 表单 `aria-label`;完成度 `role=\"status\"` |\n| 安全 | 预览 HTML 入库前 XSS 清洗 |\n\n---\n\n## 12. 验收标准\n\n### 12.1 列表页\n\n- [ ] 13 项筛选(含合同模板联动标准合同名称)与 KPI 五卡片联动正确\n- [ ] 项目信息列、车辆/交车 Popover、审批流悬停\n- [ ] 操作按合同状态/类型正确显隐\n- [ ] 法务盖章上传后入口关闭\n\n### 12.2 新增页\n\n- [ ] 三块主体合同表单 + 完成度徽章\n- [ ] 甲乙档案只读同步、客户 A/B 级限制\n- [ ] 预览随表单实时更新\n- [ ] 可编辑预览改动风控红线 → 非标准审批徽章\n- [ ] 保存/提交展示审批类型\n\n### 12.3 标注与文档(原型)\n\n- [ ] 已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录可切换列表页 / 新增页,PRD 全文可阅\n\n---\n\n## 13. 原型实现映射\n\n| 能力 | 文件 |\n|---|---|\n| 列表主逻辑 | `LeaseContractManagement.jsx` |\n| 新增页 | `LeaseContractCreate.jsx` |\n| 主体表单 | `LeaseContractEditorForm.jsx` |\n| 预览 | `LeaseContractPreviewPanel.jsx` |\n| 预览拼装 | `lease-contract-preview-build.js` |\n| 风控检测 | `lease-contract-risk-detect.js` |\n| 表单校验 | `lease-contract-form-validation.js` |\n| 标注入口 | `index.tsx` |\n| 标注数据 | `annotation-source.json` |\n| PRD | `src/resources/lease-contract-management/PRD.md` |\n| 操作流程图 | `.spec/requirements-flow-operations.md` |\n| 标注上下文 | `.spec/requirements-annotation-context.md` |\n\n---\n\n## 14. 修订记录\n\n| 版本 | 日期 | 说明 |\n|---|---|---|\n| v1.0 | 2026-06-25 | 首版:对齐列表/新增原型、风控审批判定、标注与 PRD 接入 |\n| v1.1 | 2026-07-12 | 补充交还车履约规则、子表还车条件、全流程操作图与标注上下文文档 |\n| v1.2 | 2026-07-12 | 筛选「合同类型」改为「合同模板 + 标准合同名称」联动;KPI/列表列与原型对齐 |\n", "markdownPath": "src/resources/lease-contract-management/PRD.md" }, { "type": "markdown", "id": "lc-doc-prd-list", "title": "列表页 PRD", - "markdown": "# 租赁合同管理 · 列表页 PRD\n\n## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 租赁合同管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 面包屑 | 业务管理 → 租赁合同管理 |\n| 目标用户 | 业务人员(主)、法务(盖章上传)、客服/运营(查看) |\n| 核心任务 | 筛选与查看租赁合同台账,发起新增/变更/续签等流程 |\n| 交互原型 | `/prototypes/lease-contract-management` |\n\n---\n\n## 4. 筛选区\n\n默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 12 项;支持「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同编码 | 可搜索下拉 | 模糊匹配合同编码 |\n| 项目名称 | 可搜索下拉 | 模糊匹配项目名称 |\n| 客户名称 | 可搜索下拉 | 模糊匹配客户名称 |\n| 签约公司 | 下拉 | 租赁合同签约公司枚举 |\n| 审批状态 | 多选下拉 | 全部 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交 |\n| 合同状态 | 多选下拉 | 全部 / 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |\n| 业务部门 | 多选下拉 | 业务相关部门 |\n| 业务负责人 | 多选下拉 | 业务组人员 |\n| 合同类型 | 多选下拉 | 全部 / 正式合同 / 试用合同 |\n| 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 |\n| 创建人 | 多选下拉 | 业务相关人员 |\n| 合同结束日期 | 日期范围 | 单输入框双日历,中文界面,开始—结束精确至日 |\n\n**交互**:\n\n- **查询**:将当前筛选条件写入「已应用筛选」,列表与 KPI 统计联动。\n- **重置**:清空为默认(多选类默认「全部」),并刷新列表。\n\n---\n\n## 5. KPI 统计卡片\n\n共 5 张卡片,点击切换列表筛选(与筛选区叠加生效)。\n\n| 卡片 | 口径 |\n|---|---|\n| 全部合同 | 不做 KPI 维度过滤 |\n| 进行中 | 至少有一辆租赁车辆已交车且尚未还车 |\n| 审批中 | 审批状态为「待审批」或「审批中」 |\n| 临期合同 | 存在在租车辆,且合同签订有效期距今日 ≤ 30 天(以最后一辆在租车辆为准) |\n| 已终止 | 全部车辆已还车;或续签/转三方产生新合同后旧合同自动标记为已终止 |\n\n卡片右上角 `?` 悬停展示口径说明。\n\n---\n\n## 6. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 租赁费用模板 | 跳转或打开费用模板配置(本期原型 Toast) |\n| 导出 | 按当前筛选结果导出(本期原型 Toast) |\n| 新增 | 进入「新增租赁合同」页 |\n\n---\n\n## 7. 列表字段说明\n\n### 7.1 项目信息(合并列)\n\n一列展示三行:\n\n1. 项目名称(主文案)\n2. 合同编码(次级)\n3. 客户名称(次级)\n\n点击整格跳转「查看」页(原型 Toast)。\n\n### 7.2 车辆与交车\n\n| 列 | 说明 |\n|---|---|\n| 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 |\n| 已交车 | 已有实际交车日期的车辆数;点击弹出已交车列表 |\n\n车辆明细字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。\n\n### 7.3 状态与业务字段\n\n| 列 | 说明 |\n|---|---|\n| 审批类型 | 标准合同 / 非标准合同;位于审批状态左侧 |\n| 审批状态 | 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交;「审批中」悬停展示审批流节点 |\n| 合同状态 | 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |\n| 签约公司 | 创建时所选甲方签约主体简称 |\n| 业务部门 / 业务负责人 | 创建时选取 |\n| 合同类型 | 正式合同 / 试用合同 |\n| 客户联系人 / 联系电话 | 来自客户档案 |\n| 被授权人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 |\n| 创建人 / 创建时间 | 精确至分钟 |\n| 最后更新 | 更新人 / 最后更新时间,无则显示 `-` |\n\n**分页**:每页 10 / 20 / 50 条可选。\n\n---\n\n## 8. 操作与弹窗\n\n### 8.1 操作列(主按钮 + 更多)\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 查看 | 始终 | 跳转合同查看页 |\n| 编辑 | 草稿;或审批驳回 | 进入编辑页 |\n| 删除合同 | 草稿 | 二次确认后删除 |\n| 撤回合同 | 已提交审批且非驳回 | 二次确认后撤回 |\n| 新增车辆 | 合同进行中 | 租赁订单内增车并重新审批 |\n| 续签合同 | 进行中 / 到期 | 跳转续签页 |\n| 添加被授权人 | 合同进行中 | 弹窗编辑被授权人列表 |\n| 附加费用 | 合同进行中 | 弹窗按车辆录入附加费用 |\n| 转三方合同 | 合同进行中 | 弹窗新增三方协议,上传协议附件与对方公函 |\n| 试用转正式 | 试用合同 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 |\n| 终止合同 | 合同进行中 | 二次确认,提交终止审批 |\n| 上传盖章合同 | 审批通过且法务未上传 | 仅法务部可见;多文件上传后关闭入口 |\n\n### 8.2 审批状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 未提交 | 仅保存草稿 |\n| 待审批 | 已提交,尚无节点通过 |\n| 审批中 | 已有节点通过,未完成终审 |\n| 审批通过 | 终审通过 |\n| 审批驳回 | 任一节点的驳回;可编辑重提 |\n\n### 8.3 合同状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 草稿 | 未提交审批 |\n| 已提交审批 | 初次提交,终审未完成 |\n| 变更 | 已通过基础上变更并重新审批中 |\n| 合同进行中 | 终审通过且在有效期内 |\n| 到期合同 | 已过结束日期 |\n| 已结束 | 终止或自然结束 |\n\n### 8.4 弹窗摘要\n\n- **删除草稿**:「是否确认删除该合同草稿」\n- **撤回**:「是否确认撤回该合同」\n- **终止**:「是否确认终止合同」→ 提交审核\n- **添加被授权人**:姓名、电话、身份证;支持增删行\n- **附加费用**:按车辆展示类型/品牌/型号/车牌,可填服务项目、费用(元,2 位小数)、生效日期\n- **转三方合同**:支持新增多条三方协议;每条可填协议名称、上传协议附件与对方公函\n- **试用转正式**:自动带出客户信息、授权人与车辆订单;车辆订单字段可编辑;可勾选「同时转三方合同」并在同一步维护三方协议\n- **上传盖章合同**:拖拽多文件;确认后标记已上传\n", + "markdown": "# 租赁合同管理 · 列表页 PRD\n\n## 1. 模块定位\n\n| 项 | 说明 |\n|---|---|\n| 模块名称 | 租赁合同管理 |\n| 所属系统 | ONE-OS 业务管理 |\n| 面包屑 | 业务管理 → 租赁合同管理 |\n| 目标用户 | 业务人员(主)、法务(盖章上传)、客服/运营(查看) |\n| 核心任务 | 筛选与查看租赁合同台账,发起新增/变更/续签等流程 |\n| 交互原型 | `/prototypes/lease-contract-management` |\n\n---\n\n## 4. 筛选区\n\n默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 **13 项**;支持「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同编码 | 可搜索下拉 | 模糊匹配合同编码 |\n| 项目名称 | 可搜索下拉 | 模糊匹配项目名称 |\n| 客户名称 | 可搜索下拉 | 模糊匹配客户名称 |\n| 签约公司 | 下拉 | 租赁合同签约公司枚举 |\n| 审批状态 | 多选下拉 | 全部 / 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 审批终止 / 撤回 |\n| 合同状态 | 多选下拉 | 全部 / 草稿 / 已提交审批 / 合同进行中 / 已终止 |\n| 业务部门 | 多选下拉 | 业务相关部门 |\n| 业务负责人 | 多选下拉 | 业务组人员 |\n| 合同模板 | 单选下拉 | 选项来自合同模板管理已发布且启用的模板「合同名称」维度(如正式合同、试用合同、现代18吨正式合同);可清空 |\n| 标准合同名称 | 单选下拉 | **联动合同模板**:仅在选择合同模板后启用;选项为该模板类别下已发布文档名(如「2026年标准商用车租赁合同」);支持搜索;可清空 |\n| 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 |\n| 创建人 | 多选下拉 | 业务相关人员 |\n| 合同结束日期 | 日期范围 | 单输入框双日历,中文界面,开始—结束精确至日 |\n\n**合同模板联动规则**:\n\n1. 未选合同模板时,「标准合同名称」禁用并提示「请先选择合同模板」。\n2. 切换合同模板时,自动清空已选标准合同名称。\n3. 查询时按合同记录的 `contractTemplateId`(或由合同类型推断的模板 ID)匹配;合同模板与标准合同名称可单独或组合使用。\n\n**交互**:\n\n- **查询**:将当前筛选条件写入「已应用筛选」,列表与 KPI 统计联动。\n- **重置**:清空为默认(多选类默认「全部」,单选类清空),并刷新列表。\n\n---\n\n## 5. KPI 统计卡片\n\n共 5 张卡片,点击切换列表筛选(与筛选区条件 **叠加** 生效)。\n\n| 卡片 | 口径(按合同状态) |\n|---|---|\n| 全部合同 | 不做 KPI 维度过滤 |\n| 草稿 | 合同状态 = 草稿 |\n| 进行中 | 合同状态 = 合同进行中 |\n| 审批中 | 合同状态 = 已提交审批 |\n| 已终止 | 合同状态 = 已终止 |\n\n卡片右上角 `?` 悬停展示口径说明。\n\n> **说明**:KPI 按合同状态快速分桶;交还车履约口径(已交未还、临期等)见 §7.2 子表与 §6.6 全局规则,不在 KPI 卡片中重复统计。\n\n---\n\n## 6. 列表工具栏\n\n| 按钮 | 行为 |\n|---|---|\n| 租赁费用模板 | 跳转或打开费用模板配置(本期原型 Toast) |\n| 导出 | 按当前筛选结果导出(本期原型 Toast) |\n| 新增 | 进入「新增租赁合同」页 |\n\n---\n\n## 7. 列表字段说明\n\n### 7.1 项目信息(合并列)\n\n一列展示三行:\n\n1. 项目名称(主文案)\n2. 合同编码(次级)\n3. 客户名称(次级)\n\n点击整格跳转「查看」页(原型 Toast)。\n\n### 7.2 车辆与交车\n\n| 列 | 说明 |\n|---|---|\n| 租赁订单 · 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 |\n| 租赁订单 · 已交车 | **审批通过**且已有实际交车日期的车辆数;点击展开已交车子表 |\n\n**履约规则**:非审批通过的合同不视为已交车;「已交车辆数」为 0 时按钮禁用。\n\n### 7.2.1 已交车子表\n\n展开「已交车辆数」后展示车辆子表,列包括:\n\n| 列 | 说明 |\n|---|---|\n| 车辆信息 | 车牌、VIN、品牌型号 |\n| 提车应收款 | 支付状态,可跳转详情 |\n| 交车 | 已交车展示里程/交车人/时间;未交车显示「未交车」 |\n| 租赁账单 | 正常 / 欠费(仅已交车) |\n| 还车 | 已还车展示还车信息;未还车显示「未还车」 |\n| 还车应结款 | 还车后展示结款状态;**审批中**时展示当前审批人(UI 与主表「审批状态」列一致,含节点悬停) |\n| 交车安排 | 区域与计划/实际交车日期 |\n| 里程要求 / 里程完成 | 有最低里程要求时展示 |\n| 操作 | **审批通过 + 已交车 + 未还车** 显示「还车」链接 |\n\n车辆明细 Popover 字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。\n\n### 7.3 状态与业务字段\n\n| 列 | 说明 |\n|---|---|\n| 合同签署方式 | 线上电子签章 / 线下人工上传;可展开查看或变更 |\n| 审批状态 | 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回等;「审批中」悬停展示审批流节点与当前处理人 |\n| 签约公司 | 创建时所选甲方签约主体简称 |\n| 业务部门 | 创建时选取 |\n| 费用信息 | 付款方式、周期、保证金等摘要 |\n| 交车安排 | 合同级交车区域与计划日期 |\n| 整体里程完成情况 | 按在租车辆里程汇总展示完成进度 |\n| 客户联系人 | 来自客户档案 |\n| 受托人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 |\n| 创建信息 | 创建人 / 创建时间,精确至分钟 |\n| 最后更新 | 更新人 / 最后更新时间,无则显示 `-` |\n\n**分页**:每页 10 / 20 / 50 条可选。\n\n---\n\n## 8. 操作与弹窗\n\n### 8.1 操作列(主按钮 + 更多)\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 查看 | 始终 | 跳转合同查看页 |\n| 编辑 | 草稿;或审批驳回 | 进入编辑页 |\n| 删除合同 | 草稿 | 二次确认后删除 |\n| 撤回合同 | 已提交审批且非驳回 | 二次确认后撤回 |\n| 新增车辆 | 合同进行中 | 租赁订单内增车并重新审批 |\n| 续签合同 | 进行中 / 到期 | 跳转续签页 |\n| 添加被授权人 | 合同进行中 | 弹窗编辑被授权人列表 |\n| 附加费用 | 合同进行中 | 弹窗按车辆录入附加费用 |\n| 转三方合同 | 合同进行中 | 弹窗新增三方协议,上传协议附件与对方公函 |\n| 试用转正式 | 合同模板为「试用合同」 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 |\n| 终止合同 | 合同进行中 | 二次确认,提交终止审批 |\n| 上传盖章合同 | 审批通过且法务未上传 | 仅法务部可见;多文件上传后关闭入口 |\n\n### 8.2 审批状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 未提交 | 仅保存草稿 |\n| 待审批 | 已提交,尚无节点通过 |\n| 审批中 | 已有节点通过,未完成终审 |\n| 审批通过 | 终审通过 |\n| 审批驳回 | 任一节点的驳回;可编辑重提 |\n| 审批终止 | 流程被终止 |\n| 撤回 | 提交人主动撤回 |\n\n### 8.3 合同状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 草稿 | 未提交审批 |\n| 已提交审批 | 初次提交,终审未完成 |\n| 合同进行中 | 终审通过且在有效期内 |\n| 已终止 | 终止、自然结束,或续签/转三方后旧合同自动终止 |\n\n### 8.4 弹窗摘要\n\n- **删除草稿**:「是否确认删除该合同草稿」\n- **撤回**:「是否确认撤回该合同」\n- **终止**:「是否确认终止合同」→ 提交审核\n- **添加被授权人**:姓名、电话、身份证;支持增删行\n- **附加费用**:按车辆展示类型/品牌/型号/车牌,可填服务项目、费用(元,2 位小数)、生效日期\n- **转三方合同**:支持新增多条三方协议;每条可填协议名称、上传协议附件与对方公函\n- **试用转正式**:自动带出客户信息、授权人与车辆订单;车辆订单字段可编辑;可勾选「同时转三方合同」并在同一步维护三方协议\n- **上传盖章合同**:拖拽多文件;确认后标记已上传\n\n### 8.5 操作流程文档\n\n从「新增合同」起的正向与逆向全流程图见:`.spec/requirements-flow-operations.md`\n", "markdownPath": "src/prototypes/lease-contract-management/.spec/requirements-prd-list.md" }, { "type": "markdown", "id": "lc-doc-prd-create", "title": "新增页 PRD", - "markdown": "# 租赁合同管理 · 新增/编辑页 PRD\n\n## 1. 页面结构\n\n左右分栏工作区:\n\n```text\n新增租赁合同\n├── 顶栏:返回列表\n├── 左侧编辑列(可滚动)\n│ ├── 主体合同信息(完成度徽章)\n│ ├── 附件1:租赁订单\n│ ├── 授权委托书\n│ └── 合同备注(独立卡片)\n├── 右侧:实时预览(V26.6 完整合同 + 审批类型徽章)\n└── 底栏:取消 / 保存草稿 / 提交审核\n```\n\n编辑页与新增页表单结构一致;编辑时回填已保存数据。\n\n---\n\n## 2. 主体合同信息\n\n卡片内三个可折叠区块,标题行右侧展示区块说明(展开时显示在标题后方)。\n\n### 2.1 签约信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 合同编号 | 是 | 前缀 `HT-ZL-` + 用户输入 |\n| 甲方 | 是 | 出租方签约主体,来自组织机构 |\n| 乙方 | 是 | 客户选择器;仅 A/B 级客户可选 |\n| 乙方负责人姓名 | 否 | 签约对接负责人,可与档案联系人不同 |\n| 乙方负责人手机号 | 否 | 11 位手机号 |\n\n选择甲方/乙方后,下方只读面板自动带出档案:\n\n**甲方**:户名、开户行、账号、通讯地址、联系人、电话、邮箱。\n\n**乙方**:企业名称、开户银行、银行账号、纳税人识别号、企业地址、企业电话、通讯地址、联系人、电话、邮箱;资质证照缩略图可点击放大,并展示 OCR 识别结果与有效期状态(有效 / 即将到期 / 已过期)。距到期 ≤ 3 个月在工作台生成业管待办;存在已过期证照时禁止提交新合同。\n\n未选双方时展示空状态引导。\n\n### 2.2 里程标准\n\n对应合同正文最低里程与租金减免条款。\n\n| 字段 | 说明 |\n|---|---|\n| 是否有最低里程要求 | 开关 |\n| 统计周期 | 有要求时必选:月 / 季 / 年 |\n| 目标里程 | 有要求时必填(公里) |\n| 里程类型 | 多选标签:实际里程 / 仪表里程 等 |\n\n### 2.3 费用信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 付款周期 | 是 | 如月付、季付 |\n| 氢费支付方式 | 是 | 自行解决 / 预付款 / 按月结算(选按月结算时预览切换月结算占位条款) |\n| 预付金额 | 条件 | 选择预付款时必填 |\n| 提前付款工作日 | 条件 | 选择预付款时必填 |\n| 还车氢量差单价 | 是 | 元 |\n\n**完成度**:上述必填项均已填则卡片显示「已完成」。\n\n---\n\n## 3. 附件1:租赁订单\n\n| 区域 | 说明 |\n|---|---|\n| 投保车辆数 / 三者责任险(百万) | 三者责任险必填 |\n| 交车区域 | 省市区级联 |\n| 计划交车日期 | 日期 |\n| 订单车辆表 | 品牌型号级联、车牌、租期、租金、氢费、增值服务等 |\n\n支持新增行、复制行、删除行;表头 KPI 展示合计。\n\n**完成度**:三者责任险已填即视为完成(原型简化口径)。\n\n---\n\n## 4. 授权委托书\n\n被委托人表格:姓名、联系方式、身份证号。\n\n支持新增行、删除行。\n\n**完成度**:至少一行且三项均非空。\n\n---\n\n## 5. 合同备注\n\n独立卡片,与授权委托书分离。\n\n提供文本域,支持自定义输入,非必填(最多 500 字);列表页不展示备注列。\n\n---\n\n## 6. 实时预览\n\n- 基于标准合同模板 V26.6 拼装 HTML,A4 分页展示。\n- 支持缩放(50%—150%)、连续预览 / 单页预览切换。\n- **可编辑预览**:合同正文 `contentEditable`;用户可直接改字。\n- 顶栏展示当前 **审批类型** 徽章。\n\n### 5.1 占位符替换\n\n左侧表单变更时,预览内 `{{contractCode}}`、`{{customerName}}` 等变量实时替换。\n\n### 5.2 条件条款与附件章节\n\n按租赁订单车辆品牌型号,过滤模板中条件条款与附件章节绑定(与合同模板管理规则一致)。\n\n---\n\n## 7. 审批类型判定\n\n| 类型 | 触发条件 |\n|---|---|\n| 标准合同 | 默认可编辑预览相对基线未改动风控红线条款 |\n| 非标准合同 | 预览中 `.ct-risk-redline[data-risk-redline=\"1\"]` 正文相对基线有增删改 |\n\n检测逻辑:`lease-contract-risk-detect.js` 对比基线 HTML 与当前合并页 HTML。\n\n保存草稿 / 提交审核时 Toast 展示将走的审批类型(原型)。\n\n---\n\n## 8. 底栏操作\n\n| 按钮 | 行为 |\n|---|---|\n| 取消 / 返回列表 | 不保存,回列表 |\n| 保存草稿 | 保存为草稿,审批类型随检测结果记录 |\n| 提交审核 | 校验必填后发起审批流程 |\n\n---\n\n## 9. 与合同模板的关系\n\n- 创建时默认引用 **标准合同模板**(合同模板管理 `isDefault=true` 的已发布版本)。\n- 模板内风控红线条款、条件条款、锁定区标记在预览中保留。\n- 业务侧仅编辑表单字段与允许编辑的预览正文;锁定区不可改(正式环境由编辑器约束)。\n", + "markdown": "# 租赁合同管理 · 新增/编辑页 PRD\n\n## 1. 页面结构\n\n左右分栏工作区:\n\n```text\n新增租赁合同\n├── 顶栏:返回列表\n├── 左侧编辑列(可滚动)\n│ ├── 主体合同信息(完成度徽章)\n│ ├── 附件1:租赁订单\n│ ├── 授权委托书\n│ └── 合同备注(独立卡片)\n├── 右侧:实时预览(V26.6 完整合同 + 审批类型徽章)\n└── 底栏:取消 / 保存草稿 / 提交审核\n```\n\n编辑页与新增页表单结构一致;编辑时回填已保存数据。\n\n---\n\n## 2. 主体合同信息\n\n卡片内三个可折叠区块,标题行右侧展示区块说明(展开时显示在标题后方)。\n\n### 2.1 签约信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 合同编号 | 是 | 前缀 `HT-ZL-` + 用户输入 |\n| 甲方 | 是 | 出租方签约主体,来自组织机构 |\n| 乙方 | 是 | 客户选择器;仅 A/B 级客户可选 |\n| 乙方负责人姓名 | 否 | 签约对接负责人,可与档案联系人不同 |\n| 乙方负责人手机号 | 否 | 11 位手机号 |\n\n选择甲方/乙方后,下方只读面板自动带出档案:\n\n**甲方**:户名、开户行、账号、通讯地址、联系人、电话、邮箱。\n\n**乙方**:企业名称、开户银行、银行账号、纳税人识别号、企业地址、企业电话、通讯地址、联系人、电话、邮箱;资质证照缩略图可点击放大。\n\n#### 客户资质证照(OCR 与有效期)\n\n对以下四类证照做 **OCR 识别**并校验有效期:\n\n| 证照 | 说明 |\n|---|---|\n| 客户营业执照 | 识别统一社会信用代码与营业期限 |\n| 法人身份证(正面) | 识别姓名、证件号与有效期 |\n| 法人身份证(反面) | 识别签发机关与有效期 |\n| 道路运输许可证 | 识别许可编号与有效期 |\n\n**业务规则:**\n\n1. 上传后自动 OCR 提取有效期,证照区展示「OCR 已识别」、有效期及状态(有效 / 即将到期 / 已过期)。\n2. 距到期 **≤ 3 个月** 时,系统在工作台提前生成待办任务,推送给业管人员更新客户档案。\n3. 若业管尚未更新,业务人员选择该客户后仍可查看证照,但 **禁止提交新合同**;提交时给出明确拦截提示。\n4. 原型演示:客户「杭州某某租赁有限公司(B)」含即将到期与已过期证照样例,可用于验证提示与拦截。\n\n未选双方时展示空状态引导。\n\n### 2.2 里程标准\n\n对应合同正文最低里程与租金减免条款。\n\n| 字段 | 说明 |\n|---|---|\n| 是否有最低里程要求 | 开关 |\n| 统计周期 | 有要求时必选:月 / 季 / 年 |\n| 目标里程 | 有要求时必填(公里) |\n| 里程类型 | 多选标签:实际里程 / 仪表里程 等 |\n\n### 2.3 费用信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 付款周期 | 是 | 如月付、季付 |\n| 氢费支付方式 | 是 | 自行解决 / 预付款 / 按月结算(选按月结算时预览切换月结算占位条款) |\n| 预付金额 | 条件 | 选择预付款时必填 |\n| 提前付款工作日 | 条件 | 选择预付款时必填 |\n| 还车氢量差单价 | 是 | 元 |\n\n**完成度**:上述必填项均已填则卡片显示「已完成」。\n\n---\n\n## 3. 附件1:租赁订单\n\n| 区域 | 说明 |\n|---|---|\n| 投保车辆数 / 三者责任险(百万) | 三者责任险必填 |\n| 交车区域 | 省市区级联 |\n| 计划交车日期 | 日期 |\n| 订单车辆表 | 品牌型号级联、车牌、租期、租金、氢费、增值服务等 |\n\n支持新增行、复制行、删除行;表头 KPI 展示合计。\n\n**完成度**:三者责任险已填即视为完成(原型简化口径)。\n\n---\n\n## 4. 授权委托书\n\n被委托人表格:姓名、联系方式、身份证号。\n\n支持新增行、删除行。\n\n**完成度**:至少一行且三项均非空。\n\n---\n\n## 5. 合同备注\n\n独立卡片,与授权委托书分离。\n\n提供文本域,支持自定义输入,非必填(最多 500 字);列表页不展示备注列。\n\n---\n\n## 5.1 用章类型(第 6 步)\n\n右侧工作区「用章类型」支持多选,可选范围:\n\n| 用章 | 说明 |\n|---|---|\n| 合同章 | 合同正文用章 |\n| 公章 | 公司公章 |\n| 法人章 | 法定代表人章 |\n\n**不含**财务章、发票章(租赁合同电子签章场景不使用)。\n\n默认选中合同章;至少选择一种用章后方可提交审核。\n\n---\n\n## 6. 实时预览\n\n- 基于标准合同模板 V26.6 拼装 HTML,A4 分页展示。\n- 支持缩放(50%—150%)、连续预览 / 单页预览切换。\n- **可编辑预览**:合同正文 `contentEditable`;用户可直接改字。\n- 顶栏展示当前 **审批类型** 徽章。\n\n### 5.1 占位符替换\n\n左侧表单变更时,预览内 `{{contractCode}}`、`{{customerName}}` 等变量实时替换。\n\n### 5.2 条件条款与附件章节\n\n按租赁订单车辆品牌型号,过滤模板中条件条款与附件章节绑定(与合同模板管理规则一致)。\n\n---\n\n## 7. 审批类型判定\n\n| 类型 | 触发条件 |\n|---|---|\n| 标准合同 | 默认可编辑预览相对基线未改动风控红线条款 |\n| 非标准合同 | 预览中 `.ct-risk-redline[data-risk-redline=\"1\"]` 正文相对基线有增删改 |\n\n检测逻辑:`lease-contract-risk-detect.js` 对比基线 HTML 与当前合并页 HTML。\n\n保存草稿 / 提交审核时 Toast 展示将走的审批类型(原型)。\n\n---\n\n## 8. 底栏操作\n\n| 按钮 | 行为 |\n|---|---|\n| 取消 / 返回列表 | 不保存,回列表 |\n| 保存草稿 | 保存为草稿,审批类型随检测结果记录 |\n| 提交审核 | 校验必填后发起审批流程 |\n\n---\n\n## 9. 与合同模板的关系\n\n- 创建时默认引用 **标准合同模板**(合同模板管理 `isDefault=true` 的已发布版本)。\n- 模板内风控红线条款、条件条款、锁定区标记在预览中保留。\n- 业务侧仅编辑表单字段与允许编辑的预览正文;锁定区不可改(正式环境由编辑器约束)。\n", "markdownPath": "src/prototypes/lease-contract-management/.spec/requirements-prd-create.md" }, { "type": "markdown", "id": "lc-doc-prd-acceptance", "title": "验收标准", - "markdown": "## 12. 验收标准\n\n### 12.1 列表页\n\n- [ ] 12 项筛选与 KPI 五卡片联动正确\n- [ ] 项目信息列、车辆/交车 Popover、审批流悬停\n- [ ] 操作按合同状态/类型正确显隐\n- [ ] 法务盖章上传后入口关闭\n\n### 12.2 新增页\n\n- [ ] 三块主体合同表单 + 完成度徽章\n- [ ] 甲乙档案只读同步、客户 A/B 级限制\n- [ ] 预览随表单实时更新\n- [ ] 可编辑预览改动风控红线 → 非标准审批徽章\n- [ ] 保存/提交展示审批类型\n\n### 12.3 标注与文档(原型)\n\n- [ ] 已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录可切换列表页 / 新增页,PRD 全文可阅\n\n---" + "markdown": "## 12. 验收标准\n\n### 12.1 列表页\n\n- [ ] 13 项筛选(含合同模板联动标准合同名称)与 KPI 五卡片联动正确\n- [ ] 项目信息列、车辆/交车 Popover、审批流悬停\n- [ ] 操作按合同状态/类型正确显隐\n- [ ] 法务盖章上传后入口关闭\n\n### 12.2 新增页\n\n- [ ] 三块主体合同表单 + 完成度徽章\n- [ ] 甲乙档案只读同步、客户 A/B 级限制\n- [ ] 预览随表单实时更新\n- [ ] 可编辑预览改动风控红线 → 非标准审批徽章\n- [ ] 保存/提交展示审批类型\n\n### 12.3 标注与文档(原型)\n\n- [ ] 已接入 `@axhub/annotation`,关键区域有 `data-annotation-id`\n- [ ] 目录可切换列表页 / 新增页,PRD 全文可阅\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-annotation-context", + "title": "标注与原型上下文", + "markdown": "# 租赁合同管理 · 标注与原型上下文\n\n> 由 `extract-annotation-source` 从运行态 `window.__AXHUB_ANNOTATION_SOURCE__` 提取,并与仓库源码对齐。 \n> 原型地址:`/prototypes/lease-contract-management`\n\n---\n\n## 1. 提取摘要\n\n| 项 | 值 |\n|---|---|\n| 标注节点数 | 22 |\n| 列表页节点 | 8(`pageId: list`) |\n| 新增页节点 | 14(`pageId: create`) |\n| 图片附件 | 无 |\n| 开发态 `source.root` | 未发布(以仓库路径为准) |\n\n### 1.1 目录结构\n\n```text\n租赁合同管理说明\n├── 模块总览\n├── 合同模板管理(链接)\n├── PRD 全文\n│ ├── PRD 完整文档\n│ ├── 列表页 PRD\n│ ├── 新增页 PRD\n│ └── 验收标准\n├── 列表页模块(route + 各标注说明)\n├── 新增页模块(route + 各标注说明)\n├── 操作流程(正向 / 逆向流程图)\n└── 标注查看提示\n```\n\n### 1.2 标注节点索引\n\n**列表页**\n\n| id | 标题 |\n|---|---|\n| `lc-list-filter` | 筛选条件 |\n| `lc-list-kpi` | KPI 统计卡片 |\n| `lc-fleet-summary` | 在租车辆概览 |\n| `lc-list-toolbar` | 列表工具栏 |\n| `lc-list-table` | 合同台账列表 |\n| `lc-list-action-more` | 更多操作 |\n| `lc-action-convert-tripartite` | 转三方合同 |\n| `lc-action-trial-to-formal` | 试用转正式 |\n\n**新增页**\n\n| id | 标题 |\n|---|---|\n| `lc-create-main-contract` | 主体合同信息 |\n| `lc-create-signing-method` | 合同签署方式 |\n| `lc-card-signing` | 签约信息 |\n| `lc-signing-customer-principal` | 乙方负责人 |\n| `lc-create-lease-order` | 附件1:租赁订单 |\n| `lc-lease-order-brand-model` | 品牌车型与在库 |\n| `lc-lease-order-rent` | 车辆租金与最低租金 |\n| `lc-lease-order-service-content` | 服务项配置 |\n| `lc-lease-order-lease-period` | 租赁期限与到期计算 |\n| `lc-create-poa` | 授权委托书 |\n| `lc-create-contract-remark` | 合同备注 |\n| `lc-create-seal` | 用章类型 |\n| `lc-create-preview` | 实时预览 |\n| `lc-create-footer` | 底栏操作 |\n\n定位器统一为:`[data-annotation-id=\"\"]`\n\n---\n\n## 2. 履约与交还车规则(列表子表)\n\n| 规则 | 说明 |\n|---|---|\n| 审批未通过 | 合同下车辆**不视为已交车**;子表「交车」显示「未交车」,无租赁账单/还车应结款 |\n| 审批通过 | 方进入履约态;有实际交车时间的车辆计入「已交车辆数」 |\n| 子表还车 | **审批通过 + 已交车 + 未还车** 时,操作列显示「还车」 |\n| 已还车 | 操作列为「—」;「还车应结款」按状态展示待提交/审批中/已完成 |\n\n子表列:车辆信息、提车应收款、交车、租赁账单、还车、还车应结款、交车安排、里程要求、里程完成、操作。\n\n---\n\n## 3. 源码入口\n\n| 能力 | 文件 |\n|---|---|\n| 标注/路由入口 | `index.tsx` |\n| 列表主逻辑 | `LeaseContractManagement.jsx` |\n| 列表数据与履约规则 | `lease-contract-list-data.js` |\n| 新增页 | `LeaseContractCreate.jsx` |\n| 主体表单 | `LeaseContractEditorForm.jsx` |\n| 标注数据构建 | `scripts/build-annotation-source.mjs` |\n| 标注数据 | `annotation-source.json` |\n| PRD 全文 | `src/resources/lease-contract-management/PRD.md` |\n\n同步命令:\n\n```bash\nnode src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs\n```\n", + "markdownPath": "src/prototypes/lease-contract-management/.spec/requirements-annotation-context.md" + } + ] + }, + { + "type": "folder", + "id": "lc-doc-flows", + "title": "操作流程", + "defaultExpanded": false, + "children": [ + { + "type": "markdown", + "id": "lc-doc-flow-overview", + "title": "全流程总览", + "markdown": "## 1. 总览:从新增到履约结束\n\n\n\n![操作流程图 1](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMjAzMS4xMnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAyMDMxLjExNzE4NzUgMTc5NiIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmaWxsOiMwMDAwMDA7fUBrZXlmcmFtZXMgZWRnZS1hbmltYXRpb24tZnJhbWV7ZnJvbXtzdHJva2UtZGFzaG9mZnNldDowO319QGtleWZyYW1lcyBkYXNoe3Rve3N0cm9rZS1kYXNob2Zmc2V0OjA7fX0jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1zbG93e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggNTBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZWRnZS1hbmltYXRpb24tZmFzdHtzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDIwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVycm9yLWljb257ZmlsbDojNTUyMjIyO30jbXktc3ZnIC5lcnJvci10ZXh0e2ZpbGw6IzU1MjIyMjtzdHJva2U6IzU1MjIyMjt9I215LXN2ZyAuZWRnZS10aGlja25lc3Mtbm9ybWFse3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLXRoaWNre3N0cm9rZS13aWR0aDozLjVweDt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLXNvbGlke3N0cm9rZS1kYXNoYXJyYXk6MDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtaW52aXNpYmxle3N0cm9rZS13aWR0aDowO2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRhc2hlZHtzdHJva2UtZGFzaGFycmF5OjM7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kb3R0ZWR7c3Ryb2tlLWRhc2hhcnJheToyO30jbXktc3ZnIC5tYXJrZXJ7ZmlsbDojNjY2O3N0cm9rZTojNjY2O30jbXktc3ZnIC5tYXJrZXIuY3Jvc3N7c3Ryb2tlOiM2NjY7fSNteS1zdmcgc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDt9I215LXN2ZyBwe21hcmdpbjowO30jbXktc3ZnIC5sYWJlbHtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3BhbiBwe2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7fSNteS1zdmcgLmxhYmVsIHRleHQsI215LXN2ZyBzcGFue2ZpbGw6IzAwMDAwMDtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5ub2RlIHJlY3QsI215LXN2ZyAubm9kZSBjaXJjbGUsI215LXN2ZyAubm9kZSBlbGxpcHNlLCNteS1zdmcgLm5vZGUgcG9seWdvbiwjbXktc3ZnIC5ub2RlIHBhdGh7ZmlsbDojZWVlO3N0cm9rZTojOTk5O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAubm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbmNob3I6bWlkZGxlO30jbXktc3ZnIC5ub2RlIC5rYXRleCBwYXRoe2ZpbGw6IzAwMDtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCwjbXktc3ZnIC5ub2RlIC5sYWJlbCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLm5vZGUuY2xpY2thYmxle2N1cnNvcjpwb2ludGVyO30jbXktc3ZnIC5yb290IC5hbmNob3IgcGF0aHtmaWxsOiM2NjYhaW1wb3J0YW50O3N0cm9rZS13aWR0aDowO3N0cm9rZTojNjY2O30jbXktc3ZnIC5hcnJvd2hlYWRQYXRoe2ZpbGw6IzMzMzMzMzt9I215LXN2ZyAuZWRnZVBhdGggLnBhdGh7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiM2NjY7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuZWRnZUxhYmVsIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt9I215LXN2ZyAuZWRnZUxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbEJrZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTt9I215LXN2ZyAuY2x1c3RlciByZWN0e2ZpbGw6aHNsKDAsIDAlLCA5OC45MjE1Njg2Mjc1JSk7c3Ryb2tlOiM3MDcwNzA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuY2x1c3RlciB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3RlciBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgZGl2Lm1lcm1haWRUb29sdGlwe3Bvc2l0aW9uOmFic29sdXRlO3RleHQtYWxpZ246Y2VudGVyO21heC13aWR0aDoyMDBweDtwYWRkaW5nOjJweDtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDpoc2woLTE2MCwgMCUsIDkzLjMzMzMzMzMzMzMlKTtib3JkZXI6MXB4IHNvbGlkICM3MDcwNzA7Ym9yZGVyLXJhZGl1czoycHg7cG9pbnRlci1ldmVudHM6bm9uZTt6LWluZGV4OjEwMDt9I215LXN2ZyAuZmxvd2NoYXJ0VGl0bGVUZXh0e3RleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MThweDtmaWxsOiMwMDAwMDA7fSNteS1zdmcgcmVjdC50ZXh0e2ZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MDt9I215LXN2ZyAuaWNvbi1zaGFwZSwjbXktc3ZnIC5pbWFnZS1zaGFwZXtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5pY29uLXNoYXBlIHAsI215LXN2ZyAuaW1hZ2Utc2hhcGUgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWwtaWNvbntkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MWVtO292ZXJmbG93OnZpc2libGU7dmVydGljYWwtYWxpZ246LTAuMTI1ZW07fSNteS1zdmcgLm5vZGUgLmxhYmVsLWljb24gcGF0aHtmaWxsOmN1cnJlbnRDb2xvcjtzdHJva2U6cmV2ZXJ0O3N0cm9rZS13aWR0aDpyZXZlcnQ7fSNteS1zdmcgLm5vZGUgLm5lby1ub2Rle3N0cm9rZTojOTk5O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmNsdXN0ZXIgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcG9seWdvbntzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uc3dpbWxhbmUuY2x1c3RlciByZWN0e2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAub3V0ZXItcGF0aHtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm5lby1saW5lIHBhdGh7c3Ryb2tlOiM5OTk7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGV7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xlIC5zdGF0ZS1zdGFydHtmaWxsOiMwMDAwMDA7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbntmaWxsOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb24tbmVvIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIDpyb290ey0tbWVybWFpZC1mb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7fTwvc3R5bGU+PGc+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjQuNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCA1IEwgMTAgMTAgTCAxMCAwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMTEuNSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTAuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDExLjUgNyBMIDAgMTQgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjEiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBvbHlnb24gcG9pbnRzPSIwLDcgMTEuNSwxNCAxMS41LDAiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjExIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZZPSI1IiByZWZYPSIxMi4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0yIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSIxMiIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iLTEiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iMTcuNyIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSItMy41IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiPjxnIGNsYXNzPSJjbHVzdGVyIiBpZD0ibXktc3ZnLWNoYW5nZSIgZGF0YS1sb29rPSJjbGFzc2ljIj48cmVjdCBzdHlsZT0iIiB4PSI3NjEuODUxNTYyNSIgeT0iOTg3IiB3aWR0aD0iNTI0IiBoZWlnaHQ9IjI1NiIvPjxnIGNsYXNzPSJjbHVzdGVyLWxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5ODMuODUxNTYyNSwgOTg3KSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPui/m+ihjOS4reWPmOabtDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImNsdXN0ZXIiIGlkPSJteS1zdmctZnVsZmlsbCIgZGF0YS1sb29rPSJjbGFzc2ljIj48cmVjdCBzdHlsZT0iIiB4PSI4IiB5PSI3NDUiIHdpZHRoPSIyNjMuMDE1NjI1IiBoZWlnaHQ9IjEwNDMiLz48ZyBjbGFzcz0iY2x1c3Rlci1sYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIzLjUwNzgxMjUsIDc0NSkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIzMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lsaXnuqY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJjbHVzdGVyIiBpZD0ibXktc3ZnLXNpZ25pbmciIGRhdGEtbG9vaz0iY2xhc3NpYyI+PHJlY3Qgc3R5bGU9IiIgeD0iMjkxLjAxNTYyNSIgeT0iNzQ1IiB3aWR0aD0iNDQwLjU3ODEyNSIgaGVpZ2h0PSI0OTgiLz48ZyBjbGFzcz0iY2x1c3Rlci1sYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDc5LjMwNDY4NzUsIDc0NSkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7nrb7nvbLpl63njq88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJjbHVzdGVyIiBpZD0ibXktc3ZnLWNyZWF0ZSIgZGF0YS1sb29rPSJjbGFzc2ljIj48cmVjdCBzdHlsZT0iIiB4PSIxMDc1LjczODI4MTI1IiB5PSI4IiB3aWR0aD0iOTQ3LjM3ODkwNjI1IiBoZWlnaHQ9IjkwNSIvPjxnIGNsYXNzPSJjbHVzdGVyLWxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxNTA5LjQyNzczNDM3NSwgOCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ojYnmi5/kuI7lrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMTQwMS42MDksODdMMTQwMS42MDksOTEuMTY3QzE0MDEuNjA5LDk1LjMzMywxNDAxLjYwOSwxMDMuNjY3LDE0MDEuNjA5LDExMS4zMzNDMTQwMS42MDksMTE5LDE0MDEuNjA5LDEyNiwxNDAxLjYwOSwxMjkuNUwxNDAxLjYwOSwxMzMiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRRd01TNDJNRGt6TnpVc0lua2lPamczZlN4N0luZ2lPakUwTURFdU5qQTVNemMxTENKNUlqb3hNVEo5TEhzaWVDSTZNVFF3TVM0Mk1Ea3pOelVzSW5raU9qRXpOMzFkIiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTQwMS42MDksMTkxTDE0MDEuNjA5LDE5NS4xNjdDMTQwMS42MDksMTk5LjMzMywxNDAxLjYwOSwyMDcuNjY3LDE0MDEuNjA5LDIxNS4zMzNDMTQwMS42MDksMjIzLDE0MDEuNjA5LDIzMCwxNDAxLjYwOSwyMzMuNUwxNDAxLjYwOSwyMzciIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRRd01TNDJNRGt6TnpVc0lua2lPakU1TVgwc2V5SjRJam94TkRBeExqWXdPVE0zTlN3aWVTSTZNakUyZlN4N0luZ2lPakUwTURFdU5qQTVNemMxTENKNUlqb3lOREY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzNjEuOTMzLDMxOS4zMjRMMTMzNS42OTMsMzMyLjEwM0MxMzA5LjQ1NCwzNDQuODgyLDEyNTYuOTc1LDM3MC40NDEsMTIzMC43MzYsMzg4LjcyMUMxMjA0LjQ5Niw0MDcsMTIwNC40OTYsNDE4LDEyMDQuNDk2LDQyMy41TDEyMDQuNDk2LDQyOSIgaWQ9Im15LXN2Zy1MX0NfWjFfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX1oxXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UTTJNUzQ1TXpJNU5qSXdNekF6T1RnMExDSjVJam96TVRrdU16SXpOVGczTURNd016azRNelo5TEhzaWVDSTZNVEl3TkM0ME9UWXdPVE0zTlN3aWVTSTZNemsyZlN4N0luZ2lPakV5TURRdU5EazJNRGt6TnpVc0lua2lPalF6TTMxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzOTcuNDExLDM1NC44MDFMMTM5Ni44ODQsMzYxLjY2OEMxMzk2LjM1OCwzNjguNTM0LDEzOTUuMzA2LDM4Mi4yNjcsMTM5NC43OCwzOTQuNjM0QzEzOTQuMjU0LDQwNywxMzk0LjI1NCw0MTgsMTM5NC4yNTQsNDIzLjVMMTM5NC4yNTQsNDI5IiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UTTVOeTQwTVRBMU16a3dOalV4TlRjekxDSjVJam96TlRRdU9EQXhNVFkwTURZMU1UVTNOSDBzZXlKNElqb3hNemswTGpJMU16a3dOakkxTENKNUlqb3pPVFo5TEhzaWVDSTZNVE01TkM0eU5UTTVNRFl5TlN3aWVTSTZORE16ZlYwPSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE0NDkuMTk5LDMxMS40MUwxNTA4LDMyNS41MDlDMTU2Ni44MDEsMzM5LjYwNywxNjg0LjQwMiwzNjcuODAzLDE3NDMuMjAzLDM4Ny40MDJDMTgwMi4wMDQsNDA3LDE4MDIuMDA0LDQxOCwxODAyLjAwNCw0MjMuNUwxODAyLjAwNCw0MjkiIGlkPSJteS1zdmctTF9DX0VfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX0VfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRRME9TNHhPVGt3T1RZeE9USTJOakkyTENKNUlqb3pNVEV1TkRFd01qYzRPREEzTXpNM00zMHNleUo0SWpveE9EQXlMakF3TXprd05qSTFMQ0o1SWpvek9UWjlMSHNpZUNJNk1UZ3dNaTR3TURNNU1EWXlOU3dpZVNJNk5ETXpmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTc0OC4wMDQsNDcxLjYxNUwxNzE2LjcxMiw0NzguMzQ2QzE2ODUuNDIxLDQ4NS4wNzcsMTYyMi44MzcsNDk4LjUzOCwxNTkxLjU0Niw1MDguNzY5QzE1NjAuMjU0LDUxOSwxNTYwLjI1NCw1MjYsMTU2MC4yNTQsNTI5LjVMMTU2MC4yNTQsNTMzIiBpZD0ibXktc3ZnLUxfRV9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UYzBPQzR3TURNNU1EWXlOU3dpZVNJNk5EY3hMall4TlRNd05UQTJOekl4T0RKOUxIc2llQ0k2TVRVMk1DNHlOVE01TURZeU5Td2llU0k2TlRFeWZTeDdJbmdpT2pFMU5qQXVNalV6T1RBMk1qVXNJbmtpT2pVek4zMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xNTA2LjI1NCw1NzEuOTk0TDE0NTYuNzExLDU3OS4zMjhDMTQwNy4xNjgsNTg2LjY2MywxMzA4LjA4Miw2MDEuMzMxLDEyNTguNTM5LDYxMi4xNjZDMTIwOC45OTYsNjIzLDEyMDguOTk2LDYzMCwxMjA4Ljk5Niw2MzMuNUwxMjA4Ljk5Niw2MzciIGlkPSJteS1zdmctTF9GX0dfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9GX0dfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRVd05pNHlOVE01TURZeU5Td2llU0k2TlRjeExqazVOREV5T0RJME5EUTNPRFo5TEhzaWVDSTZNVEl3T0M0NU9UWXdPVE0zTlN3aWVTSTZOakUyZlN4N0luZ2lPakV5TURndU9UazJNRGt6TnpVc0lua2lPalkwTVgxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE1MDYuMjU0LDU4MC44MjRMMTQ4Ny40MzcsNTg2LjY4N0MxNDY4LjYyLDU5Mi41NDksMTQzMC45ODYsNjA0LjI3NSwxNDEyLjE2OSw2MTMuNjM3QzEzOTMuMzUyLDYyMywxMzkzLjM1Miw2MzAsMTM5My4zNTIsNjMzLjVMMTM5My4zNTIsNjM3IiBpZD0ibXktc3ZnLUxfRl9IXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRl9IXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UVXdOaTR5TlRNNU1EWXlOU3dpZVNJNk5UZ3dMamd5TkRJd09UVXhOakl6TVgwc2V5SjRJam94TXprekxqTTFNVFUyTWpVc0lua2lPall4Tm4wc2V5SjRJam94TXprekxqTTFNVFUyTWpVc0lua2lPalkwTVgxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE1NjMuOTM5LDU5MUwxNTY0LjUwOCw1OTUuMTY3QzE1NjUuMDc3LDU5OS4zMzMsMTU2Ni4yMTQsNjA3LjY2NywxNTY2Ljc4Myw2MTUuMzMzQzE1NjcuMzUyLDYyMywxNTY3LjM1Miw2MzAsMTU2Ny4zNTIsNjMzLjVMMTU2Ny4zNTIsNjM3IiBpZD0ibXktc3ZnLUxfRl9JXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRl9JXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UVTJNeTQ1TXpreU1qYzNOalEwTWpNc0lua2lPalU1TVgwc2V5SjRJam94TlRZM0xqTTFNVFUyTWpVc0lua2lPall4Tm4wc2V5SjRJam94TlRZM0xqTTFNVFUyTWpVc0lua2lPalkwTVgxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE4MTAuNzU2LDQ4N0wxODEyLjEwNiw0OTEuMTY3QzE4MTMuNDU3LDQ5NS4zMzMsMTgxNi4xNTgsNTAzLjY2NywxODE3LjUwOSw1MTYuNUMxODE4Ljg1OSw1MjkuMzMzLDE4MTguODU5LDU0Ni42NjcsMTgxOC44NTksNTY0QzE4MTguODU5LDU4MS4zMzMsMTgxOC44NTksNTk4LjY2NywxODExLjUyNCw2MTEuNDEzQzE4MDQuMTg5LDYyNC4xNTgsMTc4OS41MTgsNjMyLjMxNywxNzgyLjE4Myw2MzYuMzk2TDE3NzQuODQ3LDY0MC40NzUiIGlkPSJteS1zdmctTF9FX0pfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9FX0pfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRneE1DNDNOVFUzT0RReU5UUTRNRGMyTENKNUlqbzBPRGQ5TEhzaWVDSTZNVGd4T0M0NE5Ua3pOelVzSW5raU9qVXhNbjBzZXlKNElqb3hPREU0TGpnMU9UTTNOU3dpZVNJNk5UWTBmU3g3SW5naU9qRTRNVGd1T0RVNU16YzFMQ0o1SWpvMk1UWjlMSHNpZUNJNk1UYzNNUzR6TlRFMU5qSTFMQ0o1SWpvMk5ESXVOREU1TWpRNU56STRORFkxTW4xZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE2MTAuMTUxLDU5MUwxNjE3Ljg1MSw1OTUuMTY3QzE2MjUuNTUxLDU5OS4zMzMsMTY0MC45NTEsNjA3LjY2NywxNjUzLjY0OCw2MTUuNTk5QzE2NjYuMzQ0LDYyMy41MzEsMTY3Ni4zMzcsNjMxLjA2MiwxNjgxLjMzNCw2MzQuODI3TDE2ODYuMzMsNjM4LjU5MyIgaWQ9Im15LXN2Zy1MX0ZfSl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0ZfSl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVFl4TUM0eE5UQTNOall5TWpVNU5qRTBMQ0o1SWpvMU9URjlMSHNpZUNJNk1UWTFOaTR6TlRFMU5qSTFMQ0o1SWpvMk1UWjlMSHNpZUNJNk1UWTRPUzQxTWpRMk16azBNak13Tnpjc0lua2lPalkwTVgxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE3MjUuMzUyLDY5NUwxNzI1LjM1Miw2OTkuMTY3QzE3MjUuMzUyLDcwMy4zMzMsMTcyNS4zNTIsNzExLjY2NywxNzI1LjM1Miw3MjBDMTcyNS4zNTIsNzI4LjMzMywxNzI1LjM1Miw3MzYuNjY3LDE3MjIuNTE3LDc0OS42OThDMTcxOS42ODMsNzYyLjczLDE3MTQuMDE1LDc4MC40NiwxNzExLjE4MSw3ODkuMzI1TDE3MDguMzQ2LDc5OC4xOSIgaWQ9Im15LXN2Zy1MX0pfS18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0pfS18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVGN5TlM0ek5URTFOakkxTENKNUlqbzJPVFY5TEhzaWVDSTZNVGN5TlM0ek5URTFOakkxTENKNUlqbzNNakI5TEhzaWVDSTZNVGN5TlM0ek5URTFOakkxTENKNUlqbzNORFY5TEhzaWVDSTZNVGN3Tnk0eE1qZ3lNRGczTURVek5UY3NJbmtpT2pnd01uMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMzkzLjM1Miw2OTVMMTM5My4zNTIsNjk5LjE2N0MxMzkzLjM1Miw3MDMuMzMzLDEzOTMuMzUyLDcxMS42NjcsMTM5My4zNTIsNzIwQzEzOTMuMzUyLDcyOC4zMzMsMTM5My4zNTIsNzM2LjY2NywxNDY2LjM4Nyw3NTIuODEyQzE1MzkuNDIzLDc2OC45NTcsMTY4NS40OTMsNzkyLjkxNSwxNzU4LjUyOSw4MDQuODkzTDE4MzEuNTY0LDgxNi44NzIiIGlkPSJteS1zdmctTF9IX0xfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9IX0xfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRNNU15NHpOVEUxTmpJMUxDSjVJam8yT1RWOUxIc2llQ0k2TVRNNU15NHpOVEUxTmpJMUxDSjVJam8zTWpCOUxIc2llQ0k2TVRNNU15NHpOVEUxTmpJMUxDSjVJam8zTkRWOUxIc2llQ0k2TVRnek5TNDFNVEUzTVRnM05Td2llU0k2T0RFM0xqVXhPVEl4TmpJME9EVTNPVFY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE5MTQuMTQ0LDgwMkwxOTE3LjE4MSw3OTIuNUMxOTIwLjIxOCw3ODMsMTkyNi4yOTMsNzY0LDE5MjkuMzMsNzUwLjMzM0MxOTMyLjM2Nyw3MzYuNjY3LDE5MzIuMzY3LDcyOC4zMzMsMTkzMi4zNjcsNzE1LjVDMTkzMi4zNjcsNzAyLjY2NywxOTMyLjM2Nyw2ODUuMzMzLDE5MzIuMzY3LDY2OEMxOTMyLjM2Nyw2NTAuNjY3LDE5MzIuMzY3LDYzMy4zMzMsMTkzMi4zNjcsNjE2QzE5MzIuMzY3LDU5OC42NjcsMTkzMi4zNjcsNTgxLjMzMywxOTMyLjM2Nyw1NjRDMTkzMi4zNjcsNTQ2LjY2NywxOTMyLjM2Nyw1MjkuMzMzLDE5MjAuMjU5LDUxNS44MzdDMTkwOC4xNTEsNTAyLjM0MSwxODgzLjkzNSw0OTIuNjgxLDE4NzEuODI3LDQ4Ny44NTFMMTg1OS43MTksNDgzLjAyMiIgaWQ9Im15LXN2Zy1MX0xfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0xfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVGt4TkM0eE5ETTRNek0zTURVek5UY3NJbmtpT2pnd01uMHNleUo0SWpveE9UTXlMak0yTnpFNE56VXNJbmtpT2pjME5YMHNleUo0SWpveE9UTXlMak0yTnpFNE56VXNJbmtpT2pjeU1IMHNleUo0SWpveE9UTXlMak0yTnpFNE56VXNJbmtpT2pZMk9IMHNleUo0SWpveE9UTXlMak0yTnpFNE56VXNJbmtpT2pZeE5uMHNleUo0SWpveE9UTXlMak0yTnpFNE56VXNJbmtpT2pVMk5IMHNleUo0SWpveE9UTXlMak0yTnpFNE56VXNJbmtpT2pVeE1uMHNleUo0SWpveE9EVTJMakF3TXprd05qSTFMQ0o1SWpvME9ERXVOVE01T0RBM05qSTRPVEl4Tm4xZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzOTQuMjU0LDQ4N0wxMzk0LjI1NCw0OTEuMTY3QzEzOTQuMjU0LDQ5NS4zMzMsMTM5NC4yNTQsNTAzLjY2NywxMzk0LjI1NCw1MTEuMzMzQzEzOTQuMjU0LDUxOSwxMzk0LjI1NCw1MjYsMTM5NC4yNTQsNTI5LjVMMTM5NC4yNTQsNTMzIiBpZD0ibXktc3ZnLUxfRF9NXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9NXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UTTVOQzR5TlRNNU1EWXlOU3dpZVNJNk5EZzNmU3g3SW5naU9qRXpPVFF1TWpVek9UQTJNalVzSW5raU9qVXhNbjBzZXlKNElqb3hNemswTGpJMU16a3dOakkxTENKNUlqbzFNemQ5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTExODMuODEzLDY5NUwxMTc5LjkyNyw2OTkuMTY3QzExNzYuMDQxLDcwMy4zMzMsMTE2OC4yNjksNzExLjY2NywxMTY0LjM4Miw3MjBDMTE2MC40OTYsNzI4LjMzMywxMTYwLjQ5Niw3MzYuNjY3LDEwNTUuOTc0LDc1My42NzZDOTUxLjQ1Myw3NzAuNjg1LDc0Mi40MDksNzk2LjM3MSw2MzcuODg3LDgwOS4yMTNMNTMzLjM2Niw4MjIuMDU2IiBpZD0ibXktc3ZnLUxfR19OXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfR19OXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1URTRNeTQ0TVRNME1ERTBOREl6TURjMkxDSjVJam8yT1RWOUxIc2llQ0k2TVRFMk1DNDBPVFl3T1RNM05Td2llU0k2TnpJd2ZTeDdJbmdpT2pFeE5qQXVORGsyTURrek56VXNJbmtpT2pjME5YMHNleUo0SWpvMU1qa3VNemsxTkRRNU1qZzROREl3TXl3aWVTSTZPREl5TGpVME16ZzROamM0T0RReU1ETjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDUwLjM0LDg2MS40ODhMNDQzLjMzNCw4NzAuMDc0QzQzNi4zMjgsODc4LjY1OSw0MjIuMzE2LDg5NS44MjksNDE1LjMxMSw5MTAuNTgxQzQwOC4zMDUsOTI1LjMzMyw0MDguMzA1LDkzNy42NjcsNDA4LjMwNSw5NTBDNDA4LjMwNSw5NjIuMzMzLDQwOC4zMDUsOTc0LjY2Nyw0MDguMzA1LDk4OC4zMzNDNDA4LjMwNSwxMDAyLDQwOC4zMDUsMTAxNyw0MDguMzA1LDEwMjQuNUw0MDguMzA1LDEwMzIiIGlkPSJteS1zdmctTF9OX09fMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9OX09fMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRVd0xqTXpPVGt6TmprM05UQTFPRGtzSW5raU9qZzJNUzQwT0Rnek56UTBOelV3TlRnNWZTeDdJbmdpT2pRd09DNHpNRFEyT0RjMUxDSjVJam81TVROOUxIc2llQ0k2TkRBNExqTXdORFk0TnpVc0lua2lPamsxTUgwc2V5SjRJam8wTURndU16QTBOamczTlN3aWVTSTZPVGczZlN4N0luZ2lPalF3T0M0ek1EUTJPRGMxTENKNUlqb3hNRE0yZlYwPSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQwOC4zMDUsMTA5MEw0MDguMzA1LDEwOTguMTY3QzQwOC4zMDUsMTEwNi4zMzMsNDA4LjMwNSwxMTIyLjY2Nyw0MTMuMjY2LDExMzQuNTk3QzQxOC4yMjgsMTE0Ni41MjcsNDI4LjE1LDExNTQuMDU1LDQzMy4xMTIsMTE1Ny44MTlMNDM4LjA3MywxMTYxLjU4MiIgaWQ9Im15LXN2Zy1MX09fUF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX09fUF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOREE0TGpNd05EWTROelVzSW5raU9qRXdPVEI5TEhzaWVDSTZOREE0TGpNd05EWTROelVzSW5raU9qRXhNemw5TEhzaWVDSTZORFF4TGpJMU9Ua3hOVGcyTlRNNE5EWTBMQ0o1SWpveE1UWTBmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNTEzLjg5Nyw4NTAuOTU0TDUzMS4zNDcsODYxLjI5NUM1NDguNzk2LDg3MS42MzYsNTgzLjY5NSw4OTIuMzE4LDYwMS4xNDQsOTA4LjgyNkM2MTguNTk0LDkyNS4zMzMsNjE4LjU5NCw5MzcuNjY3LDYxOC41OTQsOTUwQzYxOC41OTQsOTYyLjMzMyw2MTguNTk0LDk3NC42NjcsNjE4LjU5NCw5ODguMzMzQzYxOC41OTQsMTAwMiw2MTguNTk0LDEwMTcsNjE4LjU5NCwxMDI0LjVMNjE4LjU5NCwxMDMyIiBpZD0ibXktc3ZnLUxfTl9RXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfTl9RXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5URXpMamc1TnpNeE5ETTJNREU0TXpRc0lua2lPamcxTUM0NU5UUXlORGd4TXprNE1UWTJmU3g3SW5naU9qWXhPQzQxT1RNM05Td2llU0k2T1RFemZTeDdJbmdpT2pZeE9DNDFPVE0zTlN3aWVTSTZPVFV3ZlN4N0luZ2lPall4T0M0MU9UTTNOU3dpZVNJNk9UZzNmU3g3SW5naU9qWXhPQzQxT1RNM05Td2llU0k2TVRBek5uMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik02MTguNTk0LDEwOTBMNjE4LjU5NCwxMDk4LjE2N0M2MTguNTk0LDExMDYuMzMzLDYxOC41OTQsMTEyMi42NjcsNjA3Ljg2MiwxMTM0Ljc3QzU5Ny4xMywxMTQ2Ljg3NCw1NzUuNjY3LDExNTQuNzQ4LDU2NC45MzUsMTE1OC42ODVMNTU0LjIwNCwxMTYyLjYyMiIgaWQ9Im15LXN2Zy1MX1FfUF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX1FfUF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOakU0TGpVNU16YzFMQ0o1SWpveE1Ea3dmU3g3SW5naU9qWXhPQzQxT1RNM05Td2llU0k2TVRFek9YMHNleUo0SWpvMU5UQXVORFE0TkRZM05UUTRNRGMyT1N3aWVTSTZNVEUyTkgxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEyMDMuOTgsNjk1TDEyMDMuMjA2LDY5OS4xNjdDMTIwMi40MzIsNzAzLjMzMywxMjAwLjg4NCw3MTEuNjY3LDEyMDAuMTEsNzIwQzExOTkuMzM2LDcyOC4zMzMsMTE5OS4zMzYsNzM2LjY2NywxMDM1LjAyOSw3NTMuODU2Qzg3MC43MjIsNzcxLjA0NSw1NDIuMTA5LDc5Ny4wOTEsMzc3LjgwMiw4MTAuMTEzTDIxMy40OTUsODIzLjEzNiIgaWQ9Im15LXN2Zy1MX0dfUl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0dfUl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEl3TXk0NU9EQXlORE16T0RrME1qTXNJbmtpT2pZNU5YMHNleUo0SWpveE1UazVMak16TlRrek56VXNJbmtpT2pjeU1IMHNleUo0SWpveE1UazVMak16TlRrek56VXNJbmtpT2pjME5YMHNleUo0SWpveU1Ea3VOVEEzT0RFeU5Td2llU0k2T0RJekxqUTFNVGt6TURVNU1ERTJNRFo5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzNi4yOTQsODU2TDEzNS4xNjMsODY1LjVDMTM0LjAzMiw4NzUsMTMxLjc3LDg5NCwxMzAuNjM5LDkwOS42NjdDMTI5LjUwOCw5MjUuMzMzLDEyOS41MDgsOTM3LjY2NywxMjkuNTA4LDk1MEMxMjkuNTA4LDk2Mi4zMzMsMTI5LjUwOCw5NzQuNjY3LDEyOS41MDgsOTg4LjMzM0MxMjkuNTA4LDEwMDIsMTI5LjUwOCwxMDE3LDEyOS41MDgsMTAyNC41TDEyOS41MDgsMTAzMiIgaWQ9Im15LXN2Zy1MX1JfU18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX1JfU18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE0yTGpJNU16VXlOamM0TlRjeE5ESTRMQ0o1SWpvNE5UWjlMSHNpZUNJNk1USTVMalV3TnpneE1qVXNJbmtpT2preE0zMHNleUo0SWpveE1qa3VOVEEzT0RFeU5Td2llU0k2T1RVd2ZTeDdJbmdpT2pFeU9TNDFNRGM0TVRJMUxDSjVJam81T0RkOUxIc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakV3TXpaOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMjkuNTA4LDEwOTBMMTI5LjUwOCwxMDk4LjE2N0MxMjkuNTA4LDExMDYuMzMzLDEyOS41MDgsMTEyMi42NjcsMTI5LjUwOCwxMTM0LjMzM0MxMjkuNTA4LDExNDYsMTI5LjUwOCwxMTUzLDEyOS41MDgsMTE1Ni41TDEyOS41MDgsMTE2MCIgaWQ9Im15LXN2Zy1MX1NfVF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX1NfVF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRXdPVEI5TEhzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRXhNemw5TEhzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRXhOalI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEyOS41MDgsMTIxOEwxMjkuNTA4LDEyMjIuMTY3QzEyOS41MDgsMTIyNi4zMzMsMTI5LjUwOCwxMjM0LjY2NywxMjkuNTA4LDEyNDNDMTI5LjUwOCwxMjUxLjMzMywxMjkuNTA4LDEyNTkuNjY3LDEyOS41MDgsMTI2Ny4zMzNDMTI5LjUwOCwxMjc1LDEyOS41MDgsMTI4MiwxMjkuNTA4LDEyODUuNUwxMjkuNTA4LDEyODkiIGlkPSJteS1zdmctTF9UX1VfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9UX1VfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakV5TVRoOUxIc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakV5TkROOUxIc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakV5TmpoOUxIc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakV5T1ROOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMjkuNTA4LDEzNDdMMTI5LjUwOCwxMzUxLjE2N0MxMjkuNTA4LDEzNTUuMzMzLDEyOS41MDgsMTM2My42NjcsMTI5LjUwOCwxMzcxLjMzM0MxMjkuNTA4LDEzNzksMTI5LjUwOCwxMzg2LDEyOS41MDgsMTM4OS41TDEyOS41MDgsMTM5MyIgaWQ9Im15LXN2Zy1MX1VfVl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX1VfVl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRXpORGQ5TEhzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRXpOeko5TEhzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRXpPVGQ5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEyOS41MDgsMTQ1MUwxMjkuNTA4LDE0NTUuMTY3QzEyOS41MDgsMTQ1OS4zMzMsMTI5LjUwOCwxNDY3LjY2NywxMjkuNTA4LDE0NzUuMzMzQzEyOS41MDgsMTQ4MywxMjkuNTA4LDE0OTAsMTI5LjUwOCwxNDkzLjVMMTI5LjUwOCwxNDk3IiBpZD0ibXktc3ZnLUxfVl9XXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfVl9XXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1USTVMalV3TnpneE1qVXNJbmtpT2pFME5URjlMSHNpZUNJNk1USTVMalV3TnpneE1qVXNJbmtpT2pFME56WjlMSHNpZUNJNk1USTVMalV3TnpneE1qVXNJbmtpT2pFMU1ERjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTI5LjUwOCwxNTU1TDEyOS41MDgsMTU1OS4xNjdDMTI5LjUwOCwxNTYzLjMzMywxMjkuNTA4LDE1NzEuNjY3LDEyOS41MDgsMTU3OS4zMzNDMTI5LjUwOCwxNTg3LDEyOS41MDgsMTU5NCwxMjkuNTA4LDE1OTcuNUwxMjkuNTA4LDE2MDEiIGlkPSJteS1zdmctTF9XX1hfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9XX1hfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakUxTlRWOUxIc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakUxT0RCOUxIc2llQ0k2TVRJNUxqVXdOemd4TWpVc0lua2lPakUyTURWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMjkuNTA4LDE2NTlMMTI5LjUwOCwxNjYzLjE2N0MxMjkuNTA4LDE2NjcuMzMzLDEyOS41MDgsMTY3NS42NjcsMTI5LjU0MiwxNjgzLjMzM0MxMjkuNTc2LDE2OTEsMTI5LjY0NSwxNjk4LDEyOS42NzksMTcwMS41TDEyOS43MTMsMTcwNSIgaWQ9Im15LXN2Zy1MX1hfWV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX1hfWV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRTJOVGw5TEhzaWVDSTZNVEk1TGpVd056Z3hNalVzSW5raU9qRTJPRFI5TEhzaWVDSTZNVEk1TGpjMU1UazFNekV5TlN3aWVTSTZNVGN3T1gxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE0NS4yOTQsODU2TDE0Ny4zMjksODY1LjVDMTQ5LjM2NSw4NzUsMTUzLjQzNiw4OTQsMTU1LjQ3Miw5MDkuNjY3QzE1Ny41MDgsOTI1LjMzMywxNTcuNTA4LDkzNy42NjcsMTU3LjUwOCw5NTBDMTU3LjUwOCw5NjIuMzMzLDE1Ny41MDgsOTc0LjY2NywyNjMuNDAyLDk5MS4yOTRDMzY5LjI5NiwxMDA3LjkyMiw1ODEuMDgzLDEwMjguODQzLDY4Ni45NzcsMTAzOS4zMDRMNzkyLjg3MSwxMDQ5Ljc2NSIgaWQ9Im15LXN2Zy1MX1JfQUFfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9SX0FBXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UUTFMakk1TXpVeU5qYzROVGN4TkRJNExDSjVJam80TlRaOUxIc2llQ0k2TVRVM0xqVXdOemd4TWpVc0lua2lPamt4TTMwc2V5SjRJam94TlRjdU5UQTNPREV5TlN3aWVTSTZPVFV3ZlN4N0luZ2lPakUxTnk0MU1EYzRNVEkxTENKNUlqbzVPRGQ5TEhzaWVDSTZOemsyTGpnMU1UVTJNalVzSW5raU9qRXdOVEF1TVRVM09EZzJNVGcxTkRZMk5IMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik05MjYuODUyLDExMTRMOTI2Ljg1MiwxMTE4LjE2N0M5MjYuODUyLDExMjIuMzMzLDkyNi44NTIsMTEzMC42NjcsOTI2Ljg1MiwxMTM4LjMzM0M5MjYuODUyLDExNDYsOTI2Ljg1MiwxMTUzLDkyNi44NTIsMTE1Ni41TDkyNi44NTIsMTE2MCIgaWQ9Im15LXN2Zy1MX0FBX0FCXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQUFfQUJfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2T1RJMkxqZzFNVFUyTWpVc0lua2lPakV4TVRSOUxIc2llQ0k2T1RJMkxqZzFNVFUyTWpVc0lua2lPakV4TXpsOUxIc2llQ0k2T1RJMkxqZzFNVFUyTWpVc0lua2lPakV4TmpSOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik05ODguODUyLDExODEuNDlMMTAzNS4wMTgsMTE3NC40MDhDMTA4MS4xODUsMTE2Ny4zMjYsMTE3My41MTgsMTE1My4xNjMsMTIxOS42ODUsMTEzMy40MTVDMTI2NS44NTIsMTExMy42NjcsMTI2NS44NTIsMTA4OC4zMzMsMTI2NS44NTIsMTA2M0MxMjY1Ljg1MiwxMDM3LjY2NywxMjY1Ljg1MiwxMDEyLjMzMywxMjY1Ljg1Miw5OTMuNUMxMjY1Ljg1Miw5NzQuNjY3LDEyNjUuODUyLDk2Mi4zMzMsMTI2NS44NTIsOTUwQzEyNjUuODUyLDkzNy42NjcsMTI2NS44NTIsOTI1LjMzMywxMjY1Ljg1Miw5MDUuMTY3QzEyNjUuODUyLDg4NSwxMjY1Ljg1Miw4NTcsMTI2NS44NTIsODI5QzEyNjUuODUyLDgwMSwxMjY1Ljg1Miw3NzMsMTI2NS44NTIsNzU0LjgzM0MxMjY1Ljg1Miw3MzYuNjY3LDEyNjUuODUyLDcyOC4zMzMsMTI2MS43ODgsNzIwLjQ1QzEyNTcuNzI0LDcxMi41NjcsMTI0OS41OTYsNzA1LjEzMywxMjQ1LjUzMyw3MDEuNDE2TDEyNDEuNDY5LDY5Ny43IiBpZD0ibXktc3ZnLUxfQUJfR18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FCX0dfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2T1RnNExqZzFNVFUyTWpVc0lua2lPakV4T0RFdU5EZzVOamMxTlRFMk1qSTBNbjBzZXlKNElqb3hNalkxTGpnMU1UVTJNalVzSW5raU9qRXhNemw5TEhzaWVDSTZNVEkyTlM0NE5URTFOakkxTENKNUlqb3hNRFl6ZlN4N0luZ2lPakV5TmpVdU9EVXhOVFl5TlN3aWVTSTZPVGczZlN4N0luZ2lPakV5TmpVdU9EVXhOVFl5TlN3aWVTSTZPVFV3ZlN4N0luZ2lPakV5TmpVdU9EVXhOVFl5TlN3aWVTSTZPVEV6ZlN4N0luZ2lPakV5TmpVdU9EVXhOVFl5TlN3aWVTSTZPREk1ZlN4N0luZ2lPakV5TmpVdU9EVXhOVFl5TlN3aWVTSTZOelExZlN4N0luZ2lPakV5TmpVdU9EVXhOVFl5TlN3aWVTSTZOekl3ZlN4N0luZ2lPakV5TXpndU5URTNNakF5TlRJME1ETTROaXdpZVNJNk5qazFmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTYyLjE2OSw4NTZMMTcwLjE0Miw4NjUuNUMxNzguMTE1LDg3NSwxOTQuMDYxLDg5NCwyMDIuMDM1LDkwOS42NjdDMjEwLjAwOCw5MjUuMzMzLDIxMC4wMDgsOTM3LjY2NywyMTAuMDA4LDk1MEMyMTAuMDA4LDk2Mi4zMzMsMjEwLjAwOCw5NzQuNjY3LDM1OC44MTcsOTkyLjYyOEM1MDcuNjI3LDEwMTAuNTksODA1LjI0NSwxMDM0LjE4LDk1NC4wNTUsMTA0NS45NzVMMTEwMi44NjQsMTA1Ny43NyIgaWQ9Im15LXN2Zy1MX1JfQUNfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9SX0FDXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UWXlMakUyT0RVeU5qYzROVGN4TkRJNExDSjVJam80TlRaOUxIc2llQ0k2TWpFd0xqQXdOemd4TWpVc0lua2lPamt4TTMwc2V5SjRJam95TVRBdU1EQTNPREV5TlN3aWVTSTZPVFV3ZlN4N0luZ2lPakl4TUM0d01EYzRNVEkxTENKNUlqbzVPRGQ5TEhzaWVDSTZNVEV3Tmk0NE5URTFOakkxTENKNUlqb3hNRFU0TGpBNE5UYzBOemd3T0RJek1qWjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTE2OC44NTIsMTA5MEwxMTY4Ljg1MiwxMDk4LjE2N0MxMTY4Ljg1MiwxMTA2LjMzMywxMTY4Ljg1MiwxMTIyLjY2NywxMTY4Ljg1MiwxMTM0LjMzM0MxMTY4Ljg1MiwxMTQ2LDExNjguODUyLDExNTMsMTE2OC44NTIsMTE1Ni41TDExNjguODUyLDExNjAiIGlkPSJteS1zdmctTF9BQ19BRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FDX0FEXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1URTJPQzQ0TlRFMU5qSTFMQ0o1SWpveE1Ea3dmU3g3SW5naU9qRXhOamd1T0RVeE5UWXlOU3dpZVNJNk1URXpPWDBzZXlKNElqb3hNVFk0TGpnMU1UVTJNalVzSW5raU9qRXhOalI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTExNjguODUyLDEyMThMMTE2OC44NTIsMTIyMi4xNjdDMTE2OC44NTIsMTIyNi4zMzMsMTE2OC44NTIsMTIzNC42NjcsMTAxNS43OTQsMTI0M0M4NjIuNzM3LDEyNTEuMzMzLDU1Ni42MjIsMTI1OS42NjcsNDAzLjU2NSwxMjcyLjVDMjUwLjUwOCwxMjg1LjMzMywyNTAuNTA4LDEzMDIuNjY3LDI1MC41MDgsMTMyMEMyNTAuNTA4LDEzMzcuMzMzLDI1MC41MDgsMTM1NC42NjcsMjUwLjUwOCwxMzcyQzI1MC41MDgsMTM4OS4zMzMsMjUwLjUwOCwxNDA2LjY2NywyNTAuNTA4LDE0MjRDMjUwLjUwOCwxNDQxLjMzMywyNTAuNTA4LDE0NTguNjY3LDI1MC41MDgsMTQ3NkMyNTAuNTA4LDE0OTMuMzMzLDI1MC41MDgsMTUxMC42NjcsMjUwLjUwOCwxNTI4QzI1MC41MDgsMTU0NS4zMzMsMjUwLjUwOCwxNTYyLjY2NywyNTAuNTA4LDE1ODBDMjUwLjUwOCwxNTk3LjMzMywyNTAuNTA4LDE2MTQuNjY3LDI1MC41MDgsMTYzMkMyNTAuNTA4LDE2NDkuMzMzLDI1MC41MDgsMTY2Ni42NjcsMjQxLjQ2NSwxNjc5LjIzNkMyMzIuNDIyLDE2OTEuODA1LDIxNC4zMzcsMTY5OS42MSwyMDUuMjk0LDE3MDMuNTEzTDE5Ni4yNTEsMTcwNy40MTUiIGlkPSJteS1zdmctTF9BRF9ZXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQURfWV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEUyT0M0NE5URTFOakkxTENKNUlqb3hNakU0ZlN4N0luZ2lPakV4TmpndU9EVXhOVFl5TlN3aWVTSTZNVEkwTTMwc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVEkyT0gwc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVE15TUgwc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVE0zTW4wc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVFF5Tkgwc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVFEzTm4wc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVFV5T0gwc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVFU0TUgwc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVFl6TW4wc2V5SjRJam95TlRBdU5UQTNPREV5TlN3aWVTSTZNVFk0Tkgwc2V5SjRJam94T1RJdU5UYzRPRGMyTWpBeE9USXpNU3dpZVNJNk1UY3dPWDFkIiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9DXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIwNC40OTYwOTM3NSwgMzk2KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfWjFfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuWPlua2iDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTM5NC4yNTM5MDYyNSwgMzk2KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfRF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5L+d5a2Y6I2J56i/PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxODAyLjAwMzkwNjI1LCAzOTYpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQ19FXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7mj5DkuqTlrqHmoLg8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9FX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRl9HXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0ZfSF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9GX0lfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRV9KXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0ZfSl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9KX0tfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfSF9MXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0xfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9EX01fMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfR19OXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDA4LjMwNDY4NzUsIDk1MCkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9OX09fMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iOTYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPue6v+S4iueUteWtkOetvueroDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX09fUF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDYxOC41OTM3NSwgOTUwKSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX05fUV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDgsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+57q/5LiL5Lq65bel5LiK5LygPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfUV9QXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0dfUl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9SX1NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfU19UXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX1RfVV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9VX1ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfVl9XXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX1dfWF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9YX1lfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfUl9BQV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BQV9BQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BQl9HXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX1JfQUNfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQUNfQURfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQURfWV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxNDAxLjYwOTM3NSwgNjApIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjkuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMTM5LjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTM5Ljc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNzkuNTE1NjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5YiX6KGoIMK3IOaWsOWinjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE0MDEuNjA5Mzc1LCAxNjQpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTAuMTk1MzEyNSIgeT0iLTI3IiB3aWR0aD0iMTgwLjM5MDYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTYwLjE5NTMxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTIwLjM5MDYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaWsOWini/nvJbovpHpobXloavlhpk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUMtMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxNDAxLjYwOTM3NSwgMzAwKSI+PHBvbHlnb24gcG9pbnRzPSI1OSwwIDExOCwtNTkgNTksLTExOCAwLC01OSIgY2xhc3M9ImxhYmVsLWNvbnRhaW5lciIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU4LjUsIDU5KSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5bqV5qCP5pON5L2cPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1aMS01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyMDQuNDk2MDkzNzUsIDQ2MCkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii05My43NTc4MTI1IiB5PSItMjciIHdpZHRoPSIxODcuNTE1NjI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjMuNzU3ODEyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMjcuNTE1NjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+6L+U5Zue5YiX6KGoIMK3IOS4jeS/neWtmDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC03IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOTQuMjUzOTA2MjUsIDQ2MCkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii00NiIgeT0iLTI3IiB3aWR0aD0iOTIiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIzMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuiNieeovzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRS05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE4MDIuMDAzOTA2MjUsIDQ2MCkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii01NCIgeT0iLTI3IiB3aWR0aD0iMTA4IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjQsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNDgiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lvoXlrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUYtMTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTU2MC4yNTM5MDYyNSwgNTY0KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTU0IiB5PSItMjciIHdpZHRoPSIxMDgiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJueS4rTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRy0xMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMjA4Ljk5NjA5Mzc1LCA2NjgpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a6h5om56YCa6L+HPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ILTE1IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOTMuMzUxNTYyNSwgNjY4KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJuemps+WbnjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtSS0xNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxNTY3LjM1MTU2MjUsIDY2OCkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqHmibnnu4jmraI8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUotMTkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTcyNS4zNTE1NjI1LCA2NjgpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNDYiIHk9Ii0yNyIgd2lkdGg9IjkyIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mkqTlm548L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUstMjMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTY5OC40OTYwOTM3NSwgODI5KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg3LjAxNTYyNSIgeT0iLTI3IiB3aWR0aD0iMTc0LjAzMTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTcuMDE1NjI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExNC4wMzEyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaSpOWbniAvIOWPr+WGjee8lui+kTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtTC0yNSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxOTA1LjUxMTcxODc1LCA4MjkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzAiIHk9Ii0yNyIgd2lkdGg9IjE0MCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+57yW6L6R5ZCO6YeN5o+QPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1NLTI5IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOTQuMjUzOTA2MjUsIDU2NCkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7liKDpmaTojYnnqL88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LU4tMzEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDc2Ljg1MTU2MjUsIDgyOSkiPjxwb2x5Z29uIHBvaW50cz0iNTksMCAxMTgsLTU5IDU5LC0xMTggMCwtNTkiIGNsYXNzPSJsYWJlbC1jb250YWluZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01OC41LCA1OSkiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuetvue9suaWueW8jzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtTy0zMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MDguMzA0Njg3NSwgMTA2MykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04Mi4yODkwNjI1IiB5PSItMjciIHdpZHRoPSIxNjQuNTc4MTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTIuMjg5MDYyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMDQuNTc4MTI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+ReetvuWuneWPkemAgeS5meaWuTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtUC0zNSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0NzYuODUxNTYyNSwgMTE5MSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03OCIgeT0iLTI3IiB3aWR0aD0iMTU2IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDgsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iOTYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7nrb7nvbLlrozmiJDlvZLmoaM8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LVEtMzciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjE4LjU5Mzc1LCAxMDYzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTc4IiB5PSItMjciIHdpZHRoPSIxNTYiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPueblueroOWQiOWQjOihpeS8oDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtUi00MSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzkuNTA3ODEyNSwgODI5KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTcwIiB5PSItMjciIHdpZHRoPSIxNDAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQiOWQjOi/m+ihjOS4rTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtUy00MyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMjkuNTA3ODEyNSwgMTA2MykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03MCIgeT0iLTI3IiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDAsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iODAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mj5DovablupTmlLbmrL48L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LVQtNDUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI5LjUwNzgxMjUsIDExOTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNDYiIHk9Ii0yNyIgd2lkdGg9IjkyIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7kuqTovaY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LVUtNDciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI5LjUwNzgxMjUsIDEzMjApIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODUuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMTcxLjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU1Ljc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTExLjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWcqOennyDCtyDnp5/otYHotKbljZU8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LVYtNDkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI5LjUwNzgxMjUsIDE0MjQpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjkuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMTM5LjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTM5Ljc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNzkuNTE1NjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a2Q6KGoIMK3IOi/mOi9pjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtVy01MSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMjkuNTA3ODEyNSwgMTUyOCkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03MCIgeT0iLTI3IiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDAsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iODAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ov5jovablupTnu5PmrL48L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LVgtNTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI5LjUwNzgxMjUsIDE2MzIpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODYiIHk9Ii0yNyIgd2lkdGg9IjE3MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWFqOmDqOi9pui+huW3sui/mOi9pjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtWS01NSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzAuMDE1NjI1LCAxNzM2KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg3LjAxNTYyNSIgeT0iLTI3IiB3aWR0aD0iMTc0LjAzMTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTcuMDE1NjI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExNC4wMzEyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuW3sue7iOatoiAvIOW3sue7k+adnzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQUEtNTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOTI2Ljg1MTU2MjUsIDEwNjMpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTMwIiB5PSItNTEiIHdpZHRoPSIyNjAiIGhlaWdodD0iMTAyIi8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTAwLCAtMzYpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSI3MiI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlOyB3aGl0ZS1zcGFjZTogYnJlYWstc3BhY2VzOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7IHdpZHRoOiAyMDBweDsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPue7reetviAvIOWinui9piAvIOi9rOS4ieaWuSAvIOmZhOWKoOi0ueeUqCAvIOa3u+WKoOaOiOadg+S5piAvIOivleeUqOi9rOato+W8jzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQUItNTkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOTI2Ljg1MTU2MjUsIDExOTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5Y+Y5pu05a6h5om5PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BQy02MyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMTY4Ljg1MTU2MjUsIDEwNjMpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5Li75Yqo57uI5q2iPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BRC02NSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMTY4Ljg1MTU2MjUsIDExOTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+57uI5q2i5a6h5om5PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PC9nPjwvZz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ciIGhlaWdodD0iMTMwJSIgd2lkdGg9IjEzMCUiPjxmZURyb3BTaGFkb3cgZHg9IjQiIGR5PSI0IiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdy1zbWFsbCIgaGVpZ2h0PSIxNTAlIiB3aWR0aD0iMTUwJSI+PGZlRHJvcFNoYWRvdyBkeD0iMiIgZHk9IjIiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJteS1zdmctZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iaHNsKDAsIDAlLCA4My4zMzMzMzMzMzMzJSkiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iaHNsKDAsIDAlLCA4OC45MjE1Njg2Mjc1JSkiIHN0b3Atb3BhY2l0eT0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+)\n\n\n\n---", + "markdownPath": "src/prototypes/lease-contract-management/.spec/requirements-flow-operations.md" + }, + { + "type": "markdown", + "id": "lc-doc-flow-create-forward", + "title": "新增合同 · 正向", + "markdown": "## 2. 新增合同 · 正向流程\n\n### 2.1 保存草稿\n\n\n\n![操作流程图 2](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTYxMy44cHg7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyIgdmlld0JveD0iMCAwIDE2MTMuNzk2ODc1IDk0IiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xMzIsNDdMMTM2LjE2Nyw0N0MxNDAuMzMzLDQ3LDE0OC42NjcsNDcsMTU2LjMzMyw0N0MxNjQsNDcsMTcxLDQ3LDE3NC41LDQ3TDE3OCw0NyIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE15TENKNUlqbzBOMzBzZXlKNElqb3hOVGNzSW5raU9qUTNmU3g3SW5naU9qRTRNaXdpZVNJNk5EZDlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDQyLDQ3TDQ0Ni4xNjcsNDdDNDUwLjMzMyw0Nyw0NTguNjY3LDQ3LDQ2Ni4zMzMsNDdDNDc0LDQ3LDQ4MSw0Nyw0ODQuNSw0N0w0ODgsNDciIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRReUxDSjVJam8wTjMwc2V5SjRJam8wTmpjc0lua2lPalEzZlN4N0luZ2lPalE1TWl3aWVTSTZORGQ5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTc1Miw0N0w3NTYuMTY3LDQ3Qzc2MC4zMzMsNDcsNzY4LjY2Nyw0Nyw3NzYuMzMzLDQ3Qzc4NCw0Nyw3OTEsNDcsNzk0LjUsNDdMNzk4LDQ3IiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk56VXlMQ0o1SWpvME4zMHNleUo0SWpvM056Y3NJbmtpT2pRM2ZTeDdJbmdpT2pnd01pd2llU0k2TkRkOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik05MjYsNDdMOTMwLjE2Nyw0N0M5MzQuMzMzLDQ3LDk0Mi42NjcsNDcsOTUwLjMzMyw0N0M5NTgsNDcsOTY1LDQ3LDk2OC41LDQ3TDk3Miw0NyIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZPVEkyTENKNUlqbzBOMzBzZXlKNElqbzVOVEVzSW5raU9qUTNmU3g3SW5naU9qazNOaXdpZVNJNk5EZDlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTE0Mi43MDMsNDdMMTE0Ni44Nyw0N0MxMTUxLjAzNiw0NywxMTU5LjM3LDQ3LDExNjcuMDM2LDQ3QzExNzQuNzAzLDQ3LDExODEuNzAzLDQ3LDExODUuMjAzLDQ3TDExODguNzAzLDQ3IiBpZD0ibXktc3ZnLUxfRV9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1URTBNaTQzTURNeE1qVXNJbmtpT2pRM2ZTeDdJbmdpT2pFeE5qY3VOekF6TVRJMUxDSjVJam8wTjMwc2V5SjRJam94TVRreUxqY3dNekV5TlN3aWVTSTZORGQ5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzNzUuNDA2LDQ3TDEzNzkuNTczLDQ3QzEzODMuNzQsNDcsMTM5Mi4wNzMsNDcsMTM5OS43NCw0N0MxNDA3LjQwNiw0NywxNDE0LjQwNiw0NywxNDE3LjkwNiw0N0wxNDIxLjQwNiw0NyIgaWQ9Im15LXN2Zy1MX0ZfR18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0ZfR18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE0zTlM0ME1EWXlOU3dpZVNJNk5EZDlMSHNpZUNJNk1UUXdNQzQwTURZeU5Td2llU0k2TkRkOUxIc2llQ0k2TVRReU5TNDBNRFl5TlN3aWVTSTZORGQ5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9FXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0VfRl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9GX0dfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzAsIDQ3KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPueCueWHu+aWsOWinjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMxMiwgNDcpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTMwIiB5PSItMzkiIHdpZHRoPSIyNjAiIGhlaWdodD0iNzgiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMDAsIC0yNCkiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjQ4Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGU7IHdoaXRlLXNwYWNlOiBicmVhay1zcGFjZXM7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsgd2lkdGg6IDIwMHB4OyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5aGr5YaZ5Li75L2TL+iuouWNlS/mjojmnYPkuaYv6aKE6KeIPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjIyLCA0NykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMzAiIHk9Ii0zOSIgd2lkdGg9IjI2MCIgaGVpZ2h0PSI3OCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEwMCwgLTI0KSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iNDgiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZTsgd2hpdGUtc3BhY2U6IGJyZWFrLXNwYWNlczsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyB3aWR0aDogMjAwcHg7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ns7vnu5/liKTlrprmoIflh4Yv6Z2e5qCH5YeG5a6h5om557G75Z6LPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoODY0LCA0NykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7kv53lrZjojYnnqL88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDU5LjM1MTU2MjUsIDQ3KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTgzLjM1MTU2MjUiIHk9Ii0yNyIgd2lkdGg9IjE2Ni43MDMxMjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01My4zNTE1NjI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEwNi43MDMxMjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lkIjlkIznirbmgIE6IOiNieeovzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRi05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyODQuMDU0Njg3NSwgNDcpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTEuMzUxNTYyNSIgeT0iLTI3IiB3aWR0aD0iMTgyLjcwMzEyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTYxLjM1MTU2MjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTIyLjcwMzEyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJueeKtuaAgTog5pyq5o+Q5LqkPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1HLTExIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE1MTUuNjAxNTYyNSwgNDcpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTAuMTk1MzEyNSIgeT0iLTI3IiB3aWR0aD0iMTgwLjM5MDYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTYwLjE5NTMxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTIwLjM5MDYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWIl+ihqOWPr+e8lui+kS/liKDpmaQ8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 2.2 提交审核 → 审批通过\n\n\n\n![操作流程图 3](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNjkwLjgwNXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA2OTAuODA0Njg3NSAxMTUwIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0yNTAuNzY2LDYyTDI1MC43NjYsNjYuMTY3QzI1MC43NjYsNzAuMzMzLDI1MC43NjYsNzguNjY3LDI1MC43NjYsODYuMzMzQzI1MC43NjYsOTQsMjUwLjc2NiwxMDEsMjUwLjc2NiwxMDQuNUwyNTAuNzY2LDEwOCIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNalV3TGpjMk5UWXlOU3dpZVNJNk5qSjlMSHNpZUNJNk1qVXdMamMyTlRZeU5Td2llU0k2T0RkOUxIc2llQ0k2TWpVd0xqYzJOVFl5TlN3aWVTSTZNVEV5ZlYwPSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTIwNy45MjEsMjM1LjE1NUwxOTMuNzIyLDI0OC40NjJDMTc5LjUyMywyNjEuNzcsMTUxLjEyNSwyODguMzg1LDEzNi45MjYsMzA3LjE5MkMxMjIuNzI3LDMyNiwxMjIuNzI3LDMzNywxMjIuNzI3LDM0Mi41TDEyMi43MjcsMzQ4IiBpZD0ibXktc3ZnLUxfQl9DXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9DXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qQTNMamt5TURVNU1EVXhNRGN5TkRjMExDSjVJam95TXpVdU1UVTBPVFkxTlRFd056STBOelI5TEhzaWVDSTZNVEl5TGpjeU5qVTJNalVzSW5raU9qTXhOWDBzZXlKNElqb3hNakl1TnpJMk5UWXlOU3dpZVNJNk16VXlmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMjkzLjYxMSwyMzUuMTU1TDMwNy44MSwyNDguNDYyQzMyMi4wMDksMjYxLjc3LDM1MC40MDcsMjg4LjM4NSwzNjQuNjA2LDMwNy4xOTJDMzc4LjgwNSwzMjYsMzc4LjgwNSwzMzcsMzc4LjgwNSwzNDIuNUwzNzguODA1LDM0OCIgaWQ9Im15LXN2Zy1MX0JfRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfRF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNamt6TGpZeE1EWTFPVFE0T1RJM05USTBMQ0o1SWpveU16VXVNVFUwT1RZMU5URXdOekkwTnpSOUxIc2llQ0k2TXpjNExqZ3dORFk0TnpVc0lua2lPak14Tlgwc2V5SjRJam96TnpndU9EQTBOamczTlN3aWVTSTZNelV5ZlYwPSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTM3OC44MDUsNDA2TDM3OC44MDUsNDEwLjE2N0MzNzguODA1LDQxNC4zMzMsMzc4LjgwNSw0MjIuNjY3LDM3OC44MDUsNDMwLjMzM0MzNzguODA1LDQzOCwzNzguODA1LDQ0NSwzNzguODA1LDQ0OC41TDM3OC44MDUsNDUyIiBpZD0ibXktc3ZnLUxfRF9FXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9FXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk16YzRMamd3TkRZNE56VXNJbmtpT2pRd05uMHNleUo0SWpvek56Z3VPREEwTmpnM05Td2llU0k2TkRNeGZTeDdJbmdpT2pNM09DNDRNRFEyT0RjMUxDSjVJam8wTlRaOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zNzguODA1LDUxMEwzNzguODA1LDUxNC4xNjdDMzc4LjgwNSw1MTguMzMzLDM3OC44MDUsNTI2LjY2NywzNzguODA1LDUzNC4zMzNDMzc4LjgwNSw1NDIsMzc4LjgwNSw1NDksMzc4LjgwNSw1NTIuNUwzNzguODA1LDU1NiIgaWQ9Im15LXN2Zy1MX0VfRl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0VfRl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNemM0TGpnd05EWTROelVzSW5raU9qVXhNSDBzZXlKNElqb3pOemd1T0RBME5qZzNOU3dpZVNJNk5UTTFmU3g3SW5naU9qTTNPQzQ0TURRMk9EYzFMQ0o1SWpvMU5qQjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMzc4LjgwNSw2MTRMMzc4LjgwNSw2MTguMTY3QzM3OC44MDUsNjIyLjMzMywzNzguODA1LDYzMC42NjcsMzc4LjgwNSw2MzguMzMzQzM3OC44MDUsNjQ2LDM3OC44MDUsNjUzLDM3OC44MDUsNjU2LjVMMzc4LjgwNSw2NjAiIGlkPSJteS1zdmctTF9GX0dfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9GX0dfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXpjNExqZ3dORFk0TnpVc0lua2lPall4Tkgwc2V5SjRJam96TnpndU9EQTBOamczTlN3aWVTSTZOak01ZlN4N0luZ2lPak0zT0M0NE1EUTJPRGMxTENKNUlqbzJOalI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTM3OC44MDUsNzE4TDM3OC44MDUsNzIyLjE2N0MzNzguODA1LDcyNi4zMzMsMzc4LjgwNSw3MzQuNjY3LDM3OC44MDUsNzQyLjMzM0MzNzguODA1LDc1MCwzNzguODA1LDc1NywzNzguODA1LDc2MC41TDM3OC44MDUsNzY0IiBpZD0ibXktc3ZnLUxfR19IXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfR19IXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk16YzRMamd3TkRZNE56VXNJbmtpT2pjeE9IMHNleUo0SWpvek56Z3VPREEwTmpnM05Td2llU0k2TnpRemZTeDdJbmdpT2pNM09DNDRNRFEyT0RjMUxDSjVJam8zTmpoOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zMzYuNTYyLDg0My43NTdMMzAzLjI2OSw4NTYuOTY0QzI2OS45NzYsODcwLjE3MiwyMDMuMzksODk2LjU4NiwxNzAuMDk4LDkxNy4yOTNDMTM2LjgwNSw5MzgsMTM2LjgwNSw5NTMsMTM2LjgwNSw5NjAuNUwxMzYuODA1LDk2OCIgaWQ9Im15LXN2Zy1MX0hfSV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0hfSV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNek0yTGpVMk1qQTRNemswT1Rjd05ERXNJbmtpT2pnME15NDNOVGN6T1RZME5EazNNRFF5ZlN4N0luZ2lPakV6Tmk0NE1EUTJPRGMxTENKNUlqbzVNak45TEhzaWVDSTZNVE0yTGpnd05EWTROelVzSW5raU9qazNNbjFkIiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTM2LjgwNSwxMDI2TDEzNi44MDUsMTAzMi4xNjdDMTM2LjgwNSwxMDM4LjMzMywxMzYuODA1LDEwNTAuNjY3LDEzNi44MDUsMTA2MC4zMzNDMTM2LjgwNSwxMDcwLDEzNi44MDUsMTA3NywxMzYuODA1LDEwODAuNUwxMzYuODA1LDEwODQiIGlkPSJteS1zdmctTF9JX0pfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9JX0pfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRNMkxqZ3dORFk0TnpVc0lua2lPakV3TWpaOUxIc2llQ0k2TVRNMkxqZ3dORFk0TnpVc0lua2lPakV3TmpOOUxIc2llQ0k2TVRNMkxqZ3dORFk0TnpVc0lua2lPakV3T0RoOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zNzguODA1LDg4NkwzNzguODA1LDg5Mi4xNjdDMzc4LjgwNSw4OTguMzMzLDM3OC44MDUsOTEwLjY2NywzNzguODA1LDkyMi4zMzNDMzc4LjgwNSw5MzQsMzc4LjgwNSw5NDUsMzc4LjgwNSw5NTAuNUwzNzguODA1LDk1NiIgaWQ9Im15LXN2Zy1MX0hfS18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0hfS18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNemM0TGpnd05EWTROelVzSW5raU9qZzRObjBzZXlKNElqb3pOemd1T0RBME5qZzNOU3dpZVNJNk9USXpmU3g3SW5naU9qTTNPQzQ0TURRMk9EYzFMQ0o1SWpvNU5qQjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDIxLjA0Nyw4NDMuNzU3TDQ1NC4zNCw4NTYuOTY0QzQ4Ny42MzMsODcwLjE3Miw1NTQuMjE5LDg5Ni41ODYsNTg3LjUxMiw5MTcuMjkzQzYyMC44MDUsOTM4LDYyMC44MDUsOTUzLDYyMC44MDUsOTYwLjVMNjIwLjgwNSw5NjgiIGlkPSJteS1zdmctTF9IX0xfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9IX0xfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRJeExqQTBOekk1TVRBMU1ESTVOVGtzSW5raU9qZzBNeTQzTlRjek9UWTBORGszTURReWZTeDdJbmdpT2pZeU1DNDRNRFEyT0RjMUxDSjVJam81TWpOOUxIc2llQ0k2TmpJd0xqZ3dORFk0TnpVc0lua2lPamszTW4xZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIyLjcyNjU2MjUsIDMxNSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI0LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNDgiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuS4jemAmui/hzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzc4LjgwNDY4NzUsIDMxNSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPumAmui/hzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9FX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRl9HXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0dfSF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzNi44MDQ2ODc1LCA5MjMpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfSF9JXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNiwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7pgJrov4c8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9JX0pfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNzguODA0Njg3NSwgOTIzKSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0hfS18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIzMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+6amz5ZuePC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2MjAuODA0Njg3NSwgOTIzKSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0hfTF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIzMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+57uI5q2iPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjUwLjc2NTYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5o+Q5Lqk5a6h5qC4PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjUwLjc2NTYyNSwgMTk1KSI+PHBvbHlnb24gcG9pbnRzPSI4MywwIDE2NiwtODMgODMsLTE2NiAwLC04MyIgY2xhc3M9ImxhYmVsLWNvbnRhaW5lciIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTgyLjUsIDgzKSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuW/heWhq+S4juivgeeFp+agoemqjDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQy0zIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyMi43MjY1NjI1LCAzNzkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTE0LjcyNjU2MjUiIHk9Ii0yNyIgd2lkdGg9IjIyOS40NTMxMjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04NC43MjY1NjI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE2OS40NTMxMjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD5Ub2FzdCDmj5DnpLogwrcg5YGc55WZ5b2T5YmN6aG1PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzc4LjgwNDY4NzUsIDM3OSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii05MS4zNTE1NjI1IiB5PSItMjciIHdpZHRoPSIxODIuNzAzMTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjEuMzUxNTYyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMjIuNzAzMTI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a6h5om554q25oCBOiDlvoXlrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNzguODA0Njg3NSwgNDgzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEwNy4zNTE1NjI1IiB5PSItMjciIHdpZHRoPSIyMTQuNzAzMTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzcuMzUxNTYyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNTQuNzAzMTI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCI5ZCM54q25oCBOiDlt7Lmj5DkuqTlrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUYtOSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNzguODA0Njg3NSwgNTg3KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg2IiB5PSItMjciIHdpZHRoPSIxNzIiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01NiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMTIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ov5vlhaXlrqHmibnmtYHoioLngrk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUctMTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzc4LjgwNDY4NzUsIDY5MSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii01NCIgeT0iLTI3IiB3aWR0aD0iMTA4IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjQsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNDgiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqHmibnkuK08L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUgtMTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzc4LjgwNDY4NzUsIDgyNykiPjxwb2x5Z29uIHBvaW50cz0iNTksMCAxMTgsLTU5IDU5LC0xMTggMCwtNTkiIGNsYXNzPSJsYWJlbC1jb250YWluZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01OC41LCA1OSkiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPue7iOWuoee7k+aenDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtSS0xNSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzYuODA0Njg3NSwgOTk5KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJuemAmui/hzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtSi0xNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzYuODA0Njg3NSwgMTExNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDcuMzUxNTYyNSIgeT0iLTI3IiB3aWR0aD0iMjE0LjcwMzEyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTc3LjM1MTU2MjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTU0LjcwMzEyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQiOWQjOeKtuaAgTog5ZCI5ZCM6L+b6KGM5LitPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1LLTE5IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDM3OC44MDQ2ODc1LCA5OTkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTMwIiB5PSItMzkiIHdpZHRoPSIyNjAiIGhlaWdodD0iNzgiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMDAsIC0yNCkiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjQ4Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGU7IHdoaXRlLXNwYWNlOiBicmVhay1zcGFjZXM7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsgd2lkdGg6IDIwMHB4OyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a6h5om56amz5ZueIOKGkiDlkIjlkIzlm57ojYnnqL/lj6/nvJbovpE8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUwtMjEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjIwLjgwNDY4NzUsIDk5OSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqHmibnnu4jmraI8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 2.3 签署闭环(审批通过后)\n\n\n\n![操作流程图 4](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNTU3Ljc1OHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA1NTcuNzU3ODEyNSA3NTgiIHJvbGU9ImdyYXBoaWNzLWRvY3VtZW50IGRvY3VtZW50IiBhcmlhLXJvbGVkZXNjcmlwdGlvbj0iZmxvd2NoYXJ0LXYyIj48c3R5bGU+I215LXN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7ZmlsbDojMDAwMDAwO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzY2NjtzdHJva2U6IzY2Njt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojNjY2O30jbXktc3ZnIHN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7fSNteS1zdmcgcHttYXJnaW46MDt9I215LXN2ZyAubGFiZWx7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMwMDAwMDA7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2VlZTtzdHJva2U6Izk5OTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojNjY2IWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojNjY2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmZsb3djaGFydC1saW5re3N0cm9rZTojNjY2O2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZUxhYmVse2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmVkZ2VMYWJlbCBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWxCa2d7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7fSNteS1zdmcgLmNsdXN0ZXIgcmVjdHtmaWxsOmhzbCgwLCAwJSwgOTguOTIxNTY4NjI3NSUpO3N0cm9rZTojNzA3MDcwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2JhY2tncm91bmQ6aHNsKC0xNjAsIDAlLCA5My4zMzMzMzMzMzMzJSk7Ym9yZGVyOjFweCBzb2xpZCAjNzA3MDcwO2JvcmRlci1yYWRpdXM6MnB4O3BvaW50ZXItZXZlbnRzOm5vbmU7ei1pbmRleDoxMDA7fSNteS1zdmcgLmZsb3djaGFydFRpdGxlVGV4dHt0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE4cHg7ZmlsbDojMDAwMDAwO30jbXktc3ZnIHJlY3QudGV4dHtmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjA7fSNteS1zdmcgLmljb24tc2hhcGUsI215LXN2ZyAuaW1hZ2Utc2hhcGV7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtwYWRkaW5nOjJweDt9I215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWwgcmVjdCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIC5ub2RlIC5uZW8tbm9kZXtzdHJva2U6Izk5OTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5jbHVzdGVyIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBvbHlnb257c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLnN3aW1sYW5lLmNsdXN0ZXIgcmVjdHtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm91dGVyLXBhdGh7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5uZW8tbGluZSBwYXRoe3N0cm9rZTojOTk5O2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xle3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZSAuc3RhdGUtc3RhcnR7ZmlsbDojMDAwMDAwO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb257ZmlsbDp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29uLW5lbyBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyA6cm9vdHstLW1lcm1haWQtZm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjExLjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEwLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMS41IDcgTCAwIDE0IHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxIiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMS41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwb2x5Z29uIHBvaW50cz0iMCw3IDExLjUsMTQgMTEuNSwwIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWT0iNSIgcmVmWD0iMTIuMjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMiIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iMTIiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9Ii0xIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9IjE3LjciIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iLTMuNSIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48ZyBjbGFzcz0icm9vdCI+PGcgY2xhc3M9ImNsdXN0ZXJzIi8+PGcgY2xhc3M9ImVkZ2VQYXRocyI+PHBhdGggZD0iTTI3OC44NzksNjJMMjc4Ljg3OSw2Ni4xNjdDMjc4Ljg3OSw3MC4zMzMsMjc4Ljg3OSw3OC42NjcsMjc4Ljg3OSw4Ni4zMzNDMjc4Ljg3OSw5NCwyNzguODc5LDEwMSwyNzguODc5LDEwNC41TDI3OC44NzksMTA4IiBpZD0ibXktc3ZnLUxfQV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qYzRMamczT0Rrd05qSTFMQ0o1SWpvMk1uMHNleUo0SWpveU56Z3VPRGM0T1RBMk1qVXNJbmtpT2pnM2ZTeDdJbmdpT2pJM09DNDROemc1TURZeU5Td2llU0k2TVRFeWZWMD0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yMzcuMDk2LDIyMC4yMTdMMjIwLjU4LDIzMy4zNDhDMjA0LjA2NCwyNDYuNDc4LDE3MS4wMzIsMjcyLjczOSwxNTQuNTE2LDI5MS4zN0MxMzgsMzEwLDEzOCwzMjEsMTM4LDMyNi41TDEzOCwzMzIiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWpNM0xqQTVOak00TmpJd056TTJOVGsxTENKNUlqb3lNakF1TWpFM05EYzVPVFUzTXpZMU9UVjlMSHNpZUNJNk1UTTRMQ0o1SWpveU9UbDlMSHNpZUNJNk1UTTRMQ0o1SWpvek16WjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTM4LDQxNEwxMzgsNDE4LjE2N0MxMzgsNDIyLjMzMywxMzgsNDMwLjY2NywxMzgsNDM4LjMzM0MxMzgsNDQ2LDEzOCw0NTMsMTM4LDQ1Ni41TDEzOCw0NjAiIGlkPSJteS1zdmctTF9DX0RfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX0RfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRNNExDSjVJam8wTVRSOUxIc2llQ0k2TVRNNExDSjVJam8wTXpsOUxIc2llQ0k2TVRNNExDSjVJam8wTmpSOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMzgsNTE4TDEzOCw1MjIuMTY3QzEzOCw1MjYuMzMzLDEzOCw1MzQuNjY3LDEzOCw1NDQuMzMzQzEzOCw1NTQsMTM4LDU2NSwxMzgsNTcwLjVMMTM4LDU3NiIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE00TENKNUlqbzFNVGg5TEhzaWVDSTZNVE00TENKNUlqbzFORE45TEhzaWVDSTZNVE00TENKNUlqbzFPREI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTMyMC42NjEsMjIwLjIxN0wzMzcuMTc3LDIzMy4zNDhDMzUzLjY5NCwyNDYuNDc4LDM4Ni43MjYsMjcyLjczOSw0MDMuMjQyLDI5My4zN0M0MTkuNzU4LDMxNCw0MTkuNzU4LDMyOSw0MTkuNzU4LDMzNi41TDQxOS43NTgsMzQ0IiBpZD0ibXktc3ZnLUxfQl9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk16SXdMalkyTVRReU5qSTVNall6TkN3aWVTSTZNakl3TGpJeE56UTNPVGsxTnpNMk5UazFmU3g3SW5naU9qUXhPUzQzTlRjNE1USTFMQ0o1SWpveU9UbDlMSHNpZUNJNk5ERTVMamMxTnpneE1qVXNJbmtpT2pNME9IMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik00MTkuNzU4LDQwMkw0MTkuNzU4LDQwOC4xNjdDNDE5Ljc1OCw0MTQuMzMzLDQxOS43NTgsNDI2LjY2Nyw0MTkuNzU4LDQzNi4zMzNDNDE5Ljc1OCw0NDYsNDE5Ljc1OCw0NTMsNDE5Ljc1OCw0NTYuNUw0MTkuNzU4LDQ2MCIgaWQ9Im15LXN2Zy1MX0ZfR18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0ZfR18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOREU1TGpjMU56Z3hNalVzSW5raU9qUXdNbjBzZXlKNElqbzBNVGt1TnpVM09ERXlOU3dpZVNJNk5ETTVmU3g3SW5naU9qUXhPUzQzTlRjNE1USTFMQ0o1SWpvME5qUjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDE5Ljc1OCw1MThMNDE5Ljc1OCw1MjIuMTY3QzQxOS43NTgsNTI2LjMzMyw0MTkuNzU4LDUzNC42NjcsNDE5Ljc1OCw1NDIuMzMzQzQxOS43NTgsNTUwLDQxOS43NTgsNTU3LDQxOS43NTgsNTYwLjVMNDE5Ljc1OCw1NjQiIGlkPSJteS1zdmctTF9HX0hfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9HX0hfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRFNUxqYzFOemd4TWpVc0lua2lPalV4T0gwc2V5SjRJam8wTVRrdU56VTNPREV5TlN3aWVTSTZOVFF6ZlN4N0luZ2lPalF4T1M0M05UYzRNVEkxTENKNUlqbzFOamg5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzOCw2MzRMMTM4LDY0MC4xNjdDMTM4LDY0Ni4zMzMsMTM4LDY1OC42NjcsMTUwLjUyMSw2NjkuNDU1QzE2My4wNDIsNjgwLjI0MywxODguMDg0LDY4OS40ODcsMjAwLjYwNSw2OTQuMTA4TDIxMy4xMjYsNjk4LjczIiBpZD0ibXktc3ZnLUxfRV9JXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9JXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UTTRMQ0o1SWpvMk16UjlMSHNpZUNJNk1UTTRMQ0o1SWpvMk56RjlMSHNpZUNJNk1qRTJMamczT0Rrd05qSTFMQ0o1SWpvM01EQXVNVEUxTURrM056UXdNVGt4TTMxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQxOS43NTgsNjQ2TDQxOS43NTgsNjUwLjE2N0M0MTkuNzU4LDY1NC4zMzMsNDE5Ljc1OCw2NjIuNjY3LDQwNy4yMzcsNjcxLjQ1NUMzOTQuNzE2LDY4MC4yNDMsMzY5LjY3NCw2ODkuNDg3LDM1Ny4xNTMsNjk0LjEwOEwzNDQuNjMxLDY5OC43MyIgaWQ9Im15LXN2Zy1MX0hfSV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0hfSV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOREU1TGpjMU56Z3hNalVzSW5raU9qWTBObjBzZXlKNElqbzBNVGt1TnpVM09ERXlOU3dpZVNJNk5qY3hmU3g3SW5naU9qTTBNQzQ0TnpnNU1EWXlOU3dpZVNJNk56QXdMakV4TlRBNU56YzBNREU1TVROOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOCwgMjk5KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDgsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+57q/5LiK55S15a2Q562+56ugPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQ19EXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQxOS43NTc4MTI1LCAyOTkpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9GXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7nur/kuIvkurrlt6XkuIrkvKA8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9GX0dfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfR19IXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0VfSV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9IX0lfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjc4Ljg3ODkwNjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqHmibnpgJrov4c8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUItMSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNzguODc4OTA2MjUsIDE4NykiPjxwb2x5Z29uIHBvaW50cz0iNzUsMCAxNTAsLTc1IDc1LC0xNTAgMCwtNzUiIGNsYXNzPSJsYWJlbC1jb250YWluZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC03NC41LCA3NSkiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQiOWQjOetvue9suaWueW8jzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQy0zIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOCwgMzc1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEzMCIgeT0iLTM5IiB3aWR0aD0iMjYwIiBoZWlnaHQ9Ijc4Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTAwLCAtMjQpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSI0OCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlOyB3aGl0ZS1zcGFjZTogYnJlYWstc3BhY2VzOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7IHdpZHRoOiAyMDBweDsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQkeS5meaWuei0n+i0o+S6uuaJi+acuuWPt+WPkemAgSBF562+5a6dPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTM4LCA0OTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5LmZ5pa5562+572yPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1FLTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTM4LCA2MDcpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTQiIHk9Ii0yNyIgd2lkdGg9IjE4OCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTY0LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPueUteWtkOWQiOWQjOiHquWKqOW9kuahozwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRi05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQxOS43NTc4MTI1LCAzNzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTAxLjc1NzgxMjUiIHk9Ii0yNyIgd2lkdGg9IjIwMy41MTU2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC03MS43NTc4MTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE0My41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7liJfooaggwrcg55uW56ug5ZCI5ZCM6KGl5LygPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1HLTExIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQxOS43NTc4MTI1LCA0OTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTE4LjM2NzE4NzUiIHk9Ii0yNyIgd2lkdGg9IjIzNi43MzQzNzUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04OC4zNjcxODc1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE3Ni43MzQzNzUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ms5XliqEv5Lia5Yqh5LiK5LygIFBERi/lm77niYc8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUgtMTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDE5Ljc1NzgxMjUsIDYwNykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMzAiIHk9Ii0zOSIgd2lkdGg9IjI2MCIgaGVpZ2h0PSI3OCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEwMCwgLTI0KSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iNDgiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZTsgd2hpdGUtc3BhY2U6IGJyZWFrLXNwYWNlczsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyB3aWR0aDogMjAwcHg7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7moIforrDlt7LooaXkvKAgwrcg5p+l55yL5Y+v6aKE6KeI5LiL6L29PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1JLTE1IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI3OC44Nzg5MDYyNSwgNzIzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWxpee6puW8gOWnizwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-flow-create-reverse", + "title": "新增合同 · 逆向", + "markdown": "## 3. 新增合同 · 逆向流程\n\n### 3.1 取消草拟(不保存)\n\n\n\n![操作流程图 5](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogODQ1LjkwNnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA4NDUuOTA2MjUgNzAiIHJvbGU9ImdyYXBoaWNzLWRvY3VtZW50IGRvY3VtZW50IiBhcmlhLXJvbGVkZXNjcmlwdGlvbj0iZmxvd2NoYXJ0LXYyIj48c3R5bGU+I215LXN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7ZmlsbDojMDAwMDAwO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzY2NjtzdHJva2U6IzY2Njt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojNjY2O30jbXktc3ZnIHN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7fSNteS1zdmcgcHttYXJnaW46MDt9I215LXN2ZyAubGFiZWx7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMwMDAwMDA7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2VlZTtzdHJva2U6Izk5OTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojNjY2IWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojNjY2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmZsb3djaGFydC1saW5re3N0cm9rZTojNjY2O2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZUxhYmVse2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmVkZ2VMYWJlbCBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWxCa2d7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7fSNteS1zdmcgLmNsdXN0ZXIgcmVjdHtmaWxsOmhzbCgwLCAwJSwgOTguOTIxNTY4NjI3NSUpO3N0cm9rZTojNzA3MDcwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2JhY2tncm91bmQ6aHNsKC0xNjAsIDAlLCA5My4zMzMzMzMzMzMzJSk7Ym9yZGVyOjFweCBzb2xpZCAjNzA3MDcwO2JvcmRlci1yYWRpdXM6MnB4O3BvaW50ZXItZXZlbnRzOm5vbmU7ei1pbmRleDoxMDA7fSNteS1zdmcgLmZsb3djaGFydFRpdGxlVGV4dHt0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE4cHg7ZmlsbDojMDAwMDAwO30jbXktc3ZnIHJlY3QudGV4dHtmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjA7fSNteS1zdmcgLmljb24tc2hhcGUsI215LXN2ZyAuaW1hZ2Utc2hhcGV7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtwYWRkaW5nOjJweDt9I215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWwgcmVjdCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIC5ub2RlIC5uZW8tbm9kZXtzdHJva2U6Izk5OTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5jbHVzdGVyIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBvbHlnb257c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLnN3aW1sYW5lLmNsdXN0ZXIgcmVjdHtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm91dGVyLXBhdGh7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5uZW8tbGluZSBwYXRoe3N0cm9rZTojOTk5O2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xle3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZSAuc3RhdGUtc3RhcnR7ZmlsbDojMDAwMDAwO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb257ZmlsbDp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29uLW5lbyBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyA6cm9vdHstLW1lcm1haWQtZm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjExLjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEwLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMS41IDcgTCAwIDE0IHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxIiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMS41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwb2x5Z29uIHBvaW50cz0iMCw3IDExLjUsMTQgMTEuNSwwIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWT0iNSIgcmVmWD0iMTIuMjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMiIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iMTIiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9Ii0xIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9IjE3LjciIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iLTMuNSIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48ZyBjbGFzcz0icm9vdCI+PGcgY2xhc3M9ImNsdXN0ZXJzIi8+PGcgY2xhc3M9ImVkZ2VQYXRocyI+PHBhdGggZD0iTTExNiwzNUwxMjAuMTY3LDM1QzEyNC4zMzMsMzUsMTMyLjY2NywzNSwxNDAuMzMzLDM1QzE0OCwzNSwxNTUsMzUsMTU4LjUsMzVMMTYyLDM1IiBpZD0ibXktc3ZnLUxfQV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1URTJMQ0o1SWpvek5YMHNleUo0SWpveE5ERXNJbmtpT2pNMWZTeDdJbmdpT2pFMk5pd2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zNjIuMzkxLDM1TDM2Ni41NTcsMzVDMzcwLjcyNCwzNSwzNzkuMDU3LDM1LDM4Ni43MjQsMzVDMzk0LjM5MSwzNSw0MDEuMzkxLDM1LDQwNC44OTEsMzVMNDA4LjM5MSwzNSIgaWQ9Im15LXN2Zy1MX0JfQ18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfQ18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNell5TGpNNU1EWXlOU3dpZVNJNk16VjlMSHNpZUNJNk16ZzNMak01TURZeU5Td2llU0k2TXpWOUxIc2llQ0k2TkRFeUxqTTVNRFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTU4NC4zOTEsMzVMNTg4LjU1NywzNUM1OTIuNzI0LDM1LDYwMS4wNTcsMzUsNjA4LjcyNCwzNUM2MTYuMzkxLDM1LDYyMy4zOTEsMzUsNjI2Ljg5MSwzNUw2MzAuMzkxLDM1IiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5UZzBMak01TURZeU5Td2llU0k2TXpWOUxIc2llQ0k2TmpBNUxqTTVNRFl5TlN3aWVTSTZNelY5TEhzaWVDSTZOak0wTGpNNU1EWXlOU3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9DXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfRF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2MiwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNTQiIHk9Ii0yNyIgd2lkdGg9IjEwOCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI0LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjQ4IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5paw5aKe6aG1PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjY0LjE5NTMxMjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTk4LjE5NTMxMjUiIHk9Ii0yNyIgd2lkdGg9IjE5Ni4zOTA2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02OC4xOTUzMTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEzNi4zOTA2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ngrnlh7vlj5bmtogv6L+U5Zue5YiX6KGoPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDk4LjM5MDYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODYiIHk9Ii0yNyIgd2lkdGg9IjE3MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuS4ouW8g+acquS/neWtmOS/ruaUuTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDczNi4xNDg0Mzc1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDEuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMjAzLjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTcxLjc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTQzLjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWbnuWIsOWIl+ihqCDCtyDml6DmlrDorrDlvZU8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 3.2 删除草稿\n\n\n\n![操作流程图 6](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNzI1LjUxNnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA3MjUuNTE1NjI1IDcwIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xMzIsMzVMMTM2LjE2NywzNUMxNDAuMzMzLDM1LDE0OC42NjcsMzUsMTU2LjMzMywzNUMxNjQsMzUsMTcxLDM1LDE3NC41LDM1TDE3OCwzNSIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE15TENKNUlqb3pOWDBzZXlKNElqb3hOVGNzSW5raU9qTTFmU3g3SW5naU9qRTRNaXdpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMzUzLjUxNiwzNUwzNTcuNjgyLDM1QzM2MS44NDksMzUsMzcwLjE4MiwzNSwzNzcuODQ5LDM1QzM4NS41MTYsMzUsMzkyLjUxNiwzNSwzOTYuMDE2LDM1TDM5OS41MTYsMzUiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXpVekxqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZNemM0TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk5EQXpMalV4TlRZeU5Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik01MjcuNTE2LDM1TDUzMS42ODIsMzVDNTM1Ljg0OSwzNSw1NDQuMTgyLDM1LDU1MS44NDksMzVDNTU5LjUxNiwzNSw1NjYuNTE2LDM1LDU3MC4wMTYsMzVMNTczLjUxNiwzNSIgaWQ9Im15LXN2Zy1MX0NfRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0NfRF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOVEkzTGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk5UVXlMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2TlRjM0xqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzAsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuiNieeov+WQiOWQjDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI2Ny43NTc4MTI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04NS43NTc4MTI1IiB5PSItMjciIHdpZHRoPSIxNzEuNTE1NjI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTUuNzU3ODEyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMTEuNTE1NjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5pu05aSaIMK3IOWIoOmZpOWQiOWQjDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQy0zIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQ2NS41MTU2MjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuS6jOasoeehruiupDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDY0Ny41MTU2MjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTcwIiB5PSItMjciIHdpZHRoPSIxNDAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuS7juWPsOi0puenu+mZpDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n### 3.3 撤回已提交审批\n\n\n\n![操作流程图 7](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMjM2cHg7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyIgdmlld0JveD0iMCAwIDIzNiA0ODYiIHJvbGU9ImdyYXBoaWNzLWRvY3VtZW50IGRvY3VtZW50IiBhcmlhLXJvbGVkZXNjcmlwdGlvbj0iZmxvd2NoYXJ0LXYyIj48c3R5bGU+I215LXN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7ZmlsbDojMDAwMDAwO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzY2NjtzdHJva2U6IzY2Njt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojNjY2O30jbXktc3ZnIHN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7fSNteS1zdmcgcHttYXJnaW46MDt9I215LXN2ZyAubGFiZWx7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMwMDAwMDA7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2VlZTtzdHJva2U6Izk5OTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojNjY2IWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojNjY2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmZsb3djaGFydC1saW5re3N0cm9rZTojNjY2O2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZUxhYmVse2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmVkZ2VMYWJlbCBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWxCa2d7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7fSNteS1zdmcgLmNsdXN0ZXIgcmVjdHtmaWxsOmhzbCgwLCAwJSwgOTguOTIxNTY4NjI3NSUpO3N0cm9rZTojNzA3MDcwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2JhY2tncm91bmQ6aHNsKC0xNjAsIDAlLCA5My4zMzMzMzMzMzMzJSk7Ym9yZGVyOjFweCBzb2xpZCAjNzA3MDcwO2JvcmRlci1yYWRpdXM6MnB4O3BvaW50ZXItZXZlbnRzOm5vbmU7ei1pbmRleDoxMDA7fSNteS1zdmcgLmZsb3djaGFydFRpdGxlVGV4dHt0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE4cHg7ZmlsbDojMDAwMDAwO30jbXktc3ZnIHJlY3QudGV4dHtmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjA7fSNteS1zdmcgLmljb24tc2hhcGUsI215LXN2ZyAuaW1hZ2Utc2hhcGV7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtwYWRkaW5nOjJweDt9I215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWwgcmVjdCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIC5ub2RlIC5uZW8tbm9kZXtzdHJva2U6Izk5OTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5jbHVzdGVyIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBvbHlnb257c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLnN3aW1sYW5lLmNsdXN0ZXIgcmVjdHtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm91dGVyLXBhdGh7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5uZW8tbGluZSBwYXRoe3N0cm9rZTojOTk5O2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xle3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZSAuc3RhdGUtc3RhcnR7ZmlsbDojMDAwMDAwO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb257ZmlsbDp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29uLW5lbyBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyA6cm9vdHstLW1lcm1haWQtZm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjExLjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEwLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMS41IDcgTCAwIDE0IHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxIiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMS41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwb2x5Z29uIHBvaW50cz0iMCw3IDExLjUsMTQgMTEuNSwwIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWT0iNSIgcmVmWD0iMTIuMjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMiIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iMTIiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9Ii0xIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9IjE3LjciIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iLTMuNSIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48ZyBjbGFzcz0icm9vdCI+PGcgY2xhc3M9ImNsdXN0ZXJzIi8+PGcgY2xhc3M9ImVkZ2VQYXRocyI+PHBhdGggZD0iTTExOCw2MkwxMTgsNjYuMTY3QzExOCw3MC4zMzMsMTE4LDc4LjY2NywxMTgsODYuMzMzQzExOCw5NCwxMTgsMTAxLDExOCwxMDQuNUwxMTgsMTA4IiBpZD0ibXktc3ZnLUxfQV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1URTRMQ0o1SWpvMk1uMHNleUo0SWpveE1UZ3NJbmtpT2pnM2ZTeDdJbmdpT2pFeE9Dd2llU0k2TVRFeWZWMD0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMTgsMTY2TDExOCwxNzAuMTY3QzExOCwxNzQuMzMzLDExOCwxODIuNjY3LDExOCwxOTAuMzMzQzExOCwxOTgsMTE4LDIwNSwxMTgsMjA4LjVMMTE4LDIxMiIgaWQ9Im15LXN2Zy1MX0JfQ18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfQ18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEU0TENKNUlqb3hOalo5TEhzaWVDSTZNVEU0TENKNUlqb3hPVEY5TEhzaWVDSTZNVEU0TENKNUlqb3lNVFo5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTExOCwyNzBMMTE4LDI3NC4xNjdDMTE4LDI3OC4zMzMsMTE4LDI4Ni42NjcsMTE4LDI5NC4zMzNDMTE4LDMwMiwxMTgsMzA5LDExOCwzMTIuNUwxMTgsMzE2IiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1URTRMQ0o1SWpveU56QjlMSHNpZUNJNk1URTRMQ0o1SWpveU9UVjlMSHNpZUNJNk1URTRMQ0o1SWpvek1qQjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTE4LDM3NEwxMTgsMzc4LjE2N0MxMTgsMzgyLjMzMywxMTgsMzkwLjY2NywxMTgsMzk4LjMzM0MxMTgsNDA2LDExOCw0MTMsMTE4LDQxNi41TDExOCw0MjAiIGlkPSJteS1zdmctTF9EX0VfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9EX0VfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRFNExDSjVJam96TnpSOUxIc2llQ0k2TVRFNExDSjVJam96T1RsOUxIc2llQ0k2TVRFNExDSjVJam8wTWpSOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQ19EXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMTgsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg3LjAxNTYyNSIgeT0iLTI3IiB3aWR0aD0iMTc0LjAzMTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTcuMDE1NjI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExNC4wMzEyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuW+heWuoeaJuSAvIOWuoeaJueS4rTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDExOCwgMTM5KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg1Ljc1NzgxMjUiIHk9Ii0yNyIgd2lkdGg9IjE3MS41MTU2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01NS43NTc4MTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMS41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mm7TlpJogwrcg5pKk5Zue5ZCI5ZCMPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTE4LCAyNDMpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5LqM5qyh56Gu6K6kPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTE4LCAzNDcpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODMuMzUxNTYyNSIgeT0iLTI3IiB3aWR0aD0iMTY2LjcwMzEyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTUzLjM1MTU2MjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTA2LjcwMzEyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJueeKtuaAgTog5pKk5ZuePC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1FLTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTE4LCA0NTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTEwIiB5PSItMjciIHdpZHRoPSIyMjAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNjAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lj6/ph43mlrDnvJbovpHlkI7lho3mrKHmj5DkuqQ8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 3.4 审批驳回后重提\n\n\n\n![操作流程图 8](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTAzNS45MXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAxMDM1LjkwNjI1IDcwIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xMzIsMzVMMTM2LjE2NywzNUMxNDAuMzMzLDM1LDE0OC42NjcsMzUsMTU2LjMzMywzNUMxNjQsMzUsMTcxLDM1LDE3NC41LDM1TDE3OCwzNSIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE15TENKNUlqb3pOWDBzZXlKNElqb3hOVGNzSW5raU9qTTFmU3g3SW5naU9qRTRNaXdpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMzM3LjUxNiwzNUwzNDEuNjgyLDM1QzM0NS44NDksMzUsMzU0LjE4MiwzNSwzNjEuODQ5LDM1QzM2OS41MTYsMzUsMzc2LjUxNiwzNSwzODAuMDE2LDM1TDM4My41MTYsMzUiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXpNM0xqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZNell5TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk16ZzNMalV4TlRZeU5Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik01NTEuOTA2LDM1TDU1Ni4wNzMsMzVDNTYwLjI0LDM1LDU2OC41NzMsMzUsNTc2LjI0LDM1QzU4My45MDYsMzUsNTkwLjkwNiwzNSw1OTQuNDA2LDM1TDU5Ny45MDYsMzUiIGlkPSJteS1zdmctTF9DX0RfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX0RfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TlRVeExqa3dOakkxTENKNUlqb3pOWDBzZXlKNElqbzFOell1T1RBMk1qVXNJbmtpT2pNMWZTeDdJbmdpT2pZd01TNDVNRFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTgwNS45MDYsMzVMODEwLjA3MywzNUM4MTQuMjQsMzUsODIyLjU3MywzNSw4MzAuMjQsMzVDODM3LjkwNiwzNSw4NDQuOTA2LDM1LDg0OC40MDYsMzVMODUxLjkwNiwzNSIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZPREExTGprd05qSTFMQ0o1SWpvek5YMHNleUo0SWpvNE16QXVPVEEyTWpVc0lua2lPak0xZlN4N0luZ2lPamcxTlM0NU1EWXlOU3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9DXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfRF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9EX0VfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzAsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJuemps+WbnjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI1OS43NTc4MTI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03Ny43NTc4MTI1IiB5PSItMjciIHdpZHRoPSIxNTUuNTE1NjI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDcuNzU3ODEyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NS41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7kuLvmjInpkq4gwrcg57yW6L6RPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDY5LjcxMDkzNzUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTgyLjE5NTMxMjUiIHk9Ii0yNyIgd2lkdGg9IjE2NC4zOTA2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01Mi4xOTUzMTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEwNC4zOTA2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7kv67mlLnooajljZUv6aKE6KeIPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzAzLjkwNjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDIiIHk9Ii0yNyIgd2lkdGg9IjIwNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTcyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE0NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuS/neWtmOiNieeov+aIluaPkOS6pOWuoeaguDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRS03IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk0MS45MDYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODYiIHk9Ii0yNyIgd2lkdGg9IjE3MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPumHjeaWsOi/m+WFpeWuoeaJuea1gTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-flow-fulfill", + "title": "履约 · 交还车", + "markdown": "## 4. 履约正向:交车 → 还车 → 结清\n\n> **前置条件**:仅 `审批通过` 的合同才产生交车/还车履约数据。\n\n\n\n![操作流程图 9](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNjIyLjA3cHg7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyIgdmlld0JveD0iMCAwIDYyMi4wNzAzMTI1IDE3NDcuODc1IiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik00MTAuMTM3LDYyTDQxMC4xMzcsNjYuMTY3QzQxMC4xMzcsNzAuMzMzLDQxMC4xMzcsNzguNjY3LDQxMC4xMzcsODYuMzMzQzQxMC4xMzcsOTQsNDEwLjEzNywxMDEsNDEwLjEzNywxMDQuNUw0MTAuMTM3LDEwOCIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOREV3TGpFek5qY3hPRGMxTENKNUlqbzJNbjBzZXlKNElqbzBNVEF1TVRNMk56RTROelVzSW5raU9qZzNmU3g3SW5naU9qUXhNQzR4TXpZM01UZzNOU3dpZVNJNk1URXlmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDEwLjEzNywxNjZMNDEwLjEzNywxNzAuMTY3QzQxMC4xMzcsMTc0LjMzMyw0MTAuMTM3LDE4Mi42NjcsNDEwLjEzNywxOTAuMzMzQzQxMC4xMzcsMTk4LDQxMC4xMzcsMjA1LDQxMC4xMzcsMjA4LjVMNDEwLjEzNywyMTIiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRFd0xqRXpOamN4T0RjMUxDSjVJam94TmpaOUxIc2llQ0k2TkRFd0xqRXpOamN4T0RjMUxDSjVJam94T1RGOUxIc2llQ0k2TkRFd0xqRXpOamN4T0RjMUxDSjVJam95TVRaOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik00MTAuMTM3LDI3MEw0MTAuMTM3LDI3NC4xNjdDNDEwLjEzNywyNzguMzMzLDQxMC4xMzcsMjg2LjY2Nyw0MTAuMTM3LDI5NC4zMzNDNDEwLjEzNywzMDIsNDEwLjEzNywzMDksNDEwLjEzNywzMTIuNUw0MTAuMTM3LDMxNiIgaWQ9Im15LXN2Zy1MX0NfRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0NfRF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOREV3TGpFek5qY3hPRGMxTENKNUlqb3lOekI5TEhzaWVDSTZOREV3TGpFek5qY3hPRGMxTENKNUlqb3lPVFY5TEhzaWVDSTZOREV3TGpFek5qY3hPRGMxTENKNUlqb3pNakI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQxMC4xMzcsMzk4TDQxMC4xMzcsNDAyLjE2N0M0MTAuMTM3LDQwNi4zMzMsNDEwLjEzNyw0MTQuNjY3LDQxMC4xMzcsNDIyLjMzM0M0MTAuMTM3LDQzMCw0MTAuMTM3LDQzNyw0MTAuMTM3LDQ0MC41TDQxMC4xMzcsNDQ0IiBpZD0ibXktc3ZnLUxfRF9FXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9FXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5ERXdMakV6TmpjeE9EYzFMQ0o1SWpvek9UaDlMSHNpZUNJNk5ERXdMakV6TmpjeE9EYzFMQ0o1SWpvME1qTjlMSHNpZUNJNk5ERXdMakV6TmpjeE9EYzFMQ0o1SWpvME5EaDlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDEwLjEzNyw1MDJMNDEwLjEzNyw1MDYuMTY3QzQxMC4xMzcsNTEwLjMzMyw0MTAuMTM3LDUxOC42NjcsNDEwLjEzNyw1MjYuMzMzQzQxMC4xMzcsNTM0LDQxMC4xMzcsNTQxLDQxMC4xMzcsNTQ0LjVMNDEwLjEzNyw1NDgiIGlkPSJteS1zdmctTF9FX0ZfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9FX0ZfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRFd0xqRXpOamN4T0RjMUxDSjVJam8xTURKOUxIc2llQ0k2TkRFd0xqRXpOamN4T0RjMUxDSjVJam8xTWpkOUxIc2llQ0k2TkRFd0xqRXpOamN4T0RjMUxDSjVJam8xTlRKOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zNzcuOTk2LDYzNy44NTlMMzY0LjIwNiw2NDkuMzgzQzM1MC40MTYsNjYwLjkwNiwzMjIuODM3LDY4My45NTMsMzA5LjA0Nyw3MDAuOTc3QzI5NS4yNTgsNzE4LDI5NS4yNTgsNzI5LDI5NS4yNTgsNzM0LjVMMjk1LjI1OCw3NDAiIGlkPSJteS1zdmctTF9GX0dfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9GX0dfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXpjM0xqazVOVGN6TlRFME16UTBNall6TENKNUlqbzJNemN1T0RVNU1ERTJNemt6TkRReU5uMHNleUo0SWpveU9UVXVNalUzT0RFeU5Td2llU0k2TnpBM2ZTeDdJbmdpT2pJNU5TNHlOVGM0TVRJMUxDSjVJam8zTkRSOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yOTUuMjU4LDc5OEwyOTUuMjU4LDgwMi4xNjdDMjk1LjI1OCw4MDYuMzMzLDI5NS4yNTgsODE0LjY2NywyOTUuMjU4LDgyMi4zMzNDMjk1LjI1OCw4MzAsMjk1LjI1OCw4MzcsMjk1LjI1OCw4NDAuNUwyOTUuMjU4LDg0NCIgaWQ9Im15LXN2Zy1MX0dfSF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0dfSF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNamsxTGpJMU56Z3hNalVzSW5raU9qYzVPSDBzZXlKNElqb3lPVFV1TWpVM09ERXlOU3dpZVNJNk9ESXpmU3g3SW5naU9qSTVOUzR5TlRjNE1USTFMQ0o1SWpvNE5EaDlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMjk1LjI1OCw5MDJMMjk1LjI1OCw5MDYuMTY3QzI5NS4yNTgsOTEwLjMzMywyOTUuMjU4LDkxOC42NjcsMjk1LjI1OCw5MjYuMzMzQzI5NS4yNTgsOTM0LDI5NS4yNTgsOTQxLDI5NS4yNTgsOTQ0LjVMMjk1LjI1OCw5NDgiIGlkPSJteS1zdmctTF9IX0lfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9IX0lfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWprMUxqSTFOemd4TWpVc0lua2lPamt3TW4wc2V5SjRJam95T1RVdU1qVTNPREV5TlN3aWVTSTZPVEkzZlN4N0luZ2lPakk1TlM0eU5UYzRNVEkxTENKNUlqbzVOVEo5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTI5NS4yNTgsMTAwNkwyOTUuMjU4LDEwMTAuMTY3QzI5NS4yNTgsMTAxNC4zMzMsMjk1LjI1OCwxMDIyLjY2NywyOTUuMjU4LDEwMzAuMzMzQzI5NS4yNTgsMTAzOCwyOTUuMjU4LDEwNDUsMjk1LjI1OCwxMDQ4LjVMMjk1LjI1OCwxMDUyIiBpZD0ibXktc3ZnLUxfSV9KXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfSV9KXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qazFMakkxTnpneE1qVXNJbmtpT2pFd01EWjlMSHNpZUNJNk1qazFMakkxTnpneE1qVXNJbmtpT2pFd016RjlMSHNpZUNJNk1qazFMakkxTnpneE1qVXNJbmtpT2pFd05UWjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMjk1LjI1OCwxMTEwTDI5NS4yNTgsMTExNC4xNjdDMjk1LjI1OCwxMTE4LjMzMywyOTUuMjU4LDExMjYuNjY3LDI5NS4yNTgsMTEzNC4zMzNDMjk1LjI1OCwxMTQyLDI5NS4yNTgsMTE0OSwyOTUuMjU4LDExNTIuNUwyOTUuMjU4LDExNTYiIGlkPSJteS1zdmctTF9KX0tfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9KX0tfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWprMUxqSTFOemd4TWpVc0lua2lPakV4TVRCOUxIc2llQ0k2TWprMUxqSTFOemd4TWpVc0lua2lPakV4TXpWOUxIc2llQ0k2TWprMUxqSTFOemd4TWpVc0lua2lPakV4TmpCOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yNTMuNDYsMTIzNi4yMDJMMjIxLjU1LDEyNDkuMzM1QzE4OS42NCwxMjYyLjQ2OCwxMjUuODIsMTI4OC43MzQsOTMuOTEsMTMwNy4zNjdDNjIsMTMyNiw2MiwxMzM3LDYyLDEzNDIuNUw2MiwxMzQ4IiBpZD0ibXktc3ZnLUxfS19MXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfS19MXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qVXpMalEyTURFek56Z3dOVFE1TWprekxDSjVJam94TWpNMkxqSXdNak15TlRNd05UUTVNamw5TEhzaWVDSTZOaklzSW5raU9qRXpNVFY5TEhzaWVDSTZOaklzSW5raU9qRXpOVEo5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTI4OC45NDYsMTI3MS42ODhMMjg4LjA4MSwxMjc4LjkwN0MyODcuMjE3LDEyODYuMTI2LDI4NS40ODcsMTMwMC41NjMsMjg0LjYyMywxMzEzLjI4MUMyODMuNzU4LDEzMjYsMjgzLjc1OCwxMzM3LDI4My43NTgsMTM0Mi41TDI4My43NTgsMTM0OCIgaWQ9Im15LXN2Zy1MX0tfTV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0tfTV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNamc0TGprME5qRTRORFU1TXpBeU16STFMQ0o1SWpveE1qY3hMalk0T0RNM01qQTVNekF5TXpKOUxIc2llQ0k2TWpnekxqYzFOemd4TWpVc0lua2lPakV6TVRWOUxIc2llQ0k2TWpnekxqYzFOemd4TWpVc0lua2lPakV6TlRKOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zMzEuMTI4LDEyNDIuMTNMMzQ5Ljk2MywxMjU0LjI3NUMzNjguNzk4LDEyNjYuNDIsNDA2LjQ2NywxMjkwLjcxLDQzMi41NzIsMTMwOC42MDhDNDU4LjY3NywxMzI2LjUwNiw0NzMuMjE4LDEzMzguMDEyLDQ4MC40ODgsMTM0My43NjVMNDg3Ljc1OCwxMzQ5LjUxOCIgaWQ9Im15LXN2Zy1MX0tfTl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0tfTl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNek14TGpFeU9EQXhNekF4TXpZME5qYzFMQ0o1SWpveE1qUXlMakV5T1RjNU9UUTROak0xTXpKOUxIc2llQ0k2TkRRMExqRXpOamN4T0RjMUxDSjVJam94TXpFMWZTeDdJbmdpT2pRNU1DNDRPVFE0TXpZME1qVTNPREV5TlN3aWVTSTZNVE0xTW4xZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQ0NC44MTUsNjM1LjMyMkw0NjEuODQ4LDY0Ny4yNjhDNDc4Ljg4Miw2NTkuMjE0LDUxMi45NDksNjgzLjEwNyw1MjkuOTgyLDcwNS43MkM1NDcuMDE2LDcyOC4zMzMsNTQ3LjAxNiw3NDkuNjY3LDU0Ny4wMTYsNzY5QzU0Ny4wMTYsNzg4LjMzMyw1NDcuMDE2LDgwNS42NjcsNTQ3LjAxNiw4MjNDNTQ3LjAxNiw4NDAuMzMzLDU0Ny4wMTYsODU3LjY2Nyw1NDcuMDE2LDg3NUM1NDcuMDE2LDg5Mi4zMzMsNTQ3LjAxNiw5MDkuNjY3LDU0Ny4wMTYsOTI3QzU0Ny4wMTYsOTQ0LjMzMyw1NDcuMDE2LDk2MS42NjcsNTQ3LjAxNiw5NzlDNTQ3LjAxNiw5OTYuMzMzLDU0Ny4wMTYsMTAxMy42NjcsNTQ3LjAxNiwxMDMxQzU0Ny4wMTYsMTA0OC4zMzMsNTQ3LjAxNiwxMDY1LjY2Nyw1NDcuMDE2LDEwODNDNTQ3LjAxNiwxMTAwLjMzMyw1NDcuMDE2LDExMTcuNjY3LDU0Ny4wMTYsMTE0MC4zMzNDNTQ3LjAxNiwxMTYzLDU0Ny4wMTYsMTE5MSw1NDcuMDE2LDEyMjFDNTQ3LjAxNiwxMjUxLDU0Ny4wMTYsMTI4Myw1NDUuMTEzLDEzMDQuNTM2QzU0My4yMDksMTMyNi4wNzIsNTM5LjQwMywxMzM3LjE0NSw1MzcuNSwxMzQyLjY4MUw1MzUuNTk3LDEzNDguMjE3IiBpZD0ibXktc3ZnLUxfRl9OXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRl9OXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5EUTBMamd4TlRBMk5UVXpNREk0TURnc0lua2lPall6TlM0ek1qRTJOVE15TVRrM01Ua3lmU3g3SW5naU9qVTBOeTR3TVRVMk1qVXNJbmtpT2pjd04zMHNleUo0SWpvMU5EY3VNREUxTmpJMUxDSjVJam8zTnpGOUxIc2llQ0k2TlRRM0xqQXhOVFl5TlN3aWVTSTZPREl6ZlN4N0luZ2lPalUwTnk0d01UVTJNalVzSW5raU9qZzNOWDBzZXlKNElqbzFORGN1TURFMU5qSTFMQ0o1SWpvNU1qZDlMSHNpZUNJNk5UUTNMakF4TlRZeU5Td2llU0k2T1RjNWZTeDdJbmdpT2pVME55NHdNVFUyTWpVc0lua2lPakV3TXpGOUxIc2llQ0k2TlRRM0xqQXhOVFl5TlN3aWVTSTZNVEE0TTMwc2V5SjRJam8xTkRjdU1ERTFOakkxTENKNUlqb3hNVE0xZlN4N0luZ2lPalUwTnk0d01UVTJNalVzSW5raU9qRXlNVGw5TEhzaWVDSTZOVFEzTGpBeE5UWXlOU3dpZVNJNk1UTXhOWDBzZXlKNElqbzFNelF1TWprMk9EYzFMQ0o1SWpveE16VXlmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNTI1LjAxNiwxNDA2TDUyNS4wMTYsMTQxMC4xNjdDNTI1LjAxNiwxNDE0LjMzMyw1MjUuMDE2LDE0MjIuNjY3LDUyNS4wMTYsMTQzMC4zMzNDNTI1LjAxNiwxNDM4LDUyNS4wMTYsMTQ0NSw1MjUuMDE2LDE0NDguNUw1MjUuMDE2LDE0NTIiIGlkPSJteS1zdmctTF9OX09fMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9OX09fMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TlRJMUxqQXhOVFl5TlN3aWVTSTZNVFF3Tm4wc2V5SjRJam8xTWpVdU1ERTFOakkxTENKNUlqb3hORE14ZlN4N0luZ2lPalV5TlM0d01UVTJNalVzSW5raU9qRTBOVFo5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTUyNS4wMTYsMTYxMS44NzVMNTI1LjAxNiwxNjE4LjA0MkM1MjUuMDE2LDE2MjQuMjA4LDUyNS4wMTYsMTYzNi41NDIsNTI1LjAxNiwxNjQ4LjIwOEM1MjUuMDE2LDE2NTkuODc1LDUyNS4wMTYsMTY3MC44NzUsNTI1LjAxNiwxNjc2LjM3NUw1MjUuMDE2LDE2ODEuODc1IiBpZD0ibXktc3ZnLUxfT19QXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfT19QXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5USTFMakF4TlRZeU5Td2llU0k2TVRZeE1TNDROelY5TEhzaWVDSTZOVEkxTGpBeE5UWXlOU3dpZVNJNk1UWTBPQzQ0TnpWOUxIc2llQ0k2TlRJMUxqQXhOVFl5TlN3aWVTSTZNVFk0TlM0NE56VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9DXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfRF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9EX0VfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRV9GXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjk1LjI1NzgxMjUsIDcwNykiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9GX0dfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI0LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNDgiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuacqui/mOi9pjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0dfSF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9IX0lfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfSV9KXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0pfS18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDYyLCAxMzE1KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0tfTF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjQsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5b6F5o+Q5LqkPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyODMuNzU3ODEyNSwgMTMxNSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9LX01fMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI0LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNDgiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuWuoeaJueS4rTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDEyLjY4ODI1LCAxMjk0LjcyMTQyKSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0tfTl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjQsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5bey5a6M5oiQPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1NDcuMDE1NjI1LCA5NzkpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRl9OXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNCwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjQ4IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7lt7Lov5jovaY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9OX09fMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1MjUuMDE1NjI1LCAxNjQ4Ljg3NSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9PX1BfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTgsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5pivPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDEwLjEzNjcxODc1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03MCIgeT0iLTI3IiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDAsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iODAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lkIjlkIzov5vooYzkuK08L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUItMSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MTAuMTM2NzE4NzUsIDEzOSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04NiIgeT0iLTI3IiB3aWR0aD0iMTcyIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTYsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTEyIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5Yqe55CG5o+Q6L2m5bqU5pS25qy+PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDEwLjEzNjcxODc1LCAyNDMpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a6e6ZmF5Lqk6L2mPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDEwLjEzNjcxODc1LCAzNTkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTMwIiB5PSItMzkiIHdpZHRoPSIyNjAiIGhlaWdodD0iNzgiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMDAsIC0yNCkiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjQ4Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGU7IHdoaXRlLXNwYWNlOiBicmVhay1zcGFjZXM7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsgd2lkdGg6IDIwMHB4OyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a2Q6KGoIMK3IOS6pOi9puWIl+WxleekuumHjOeoiy/kuqTovabkurov5pe26Ze0PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1FLTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDEwLjEzNjcxODc1LCA0NzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTAzLjU0Njg3NSIgeT0iLTI3IiB3aWR0aD0iMjA3LjA5Mzc1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzMuNTQ2ODc1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE0Ny4wOTM3NSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuenn+i1gei0puWNlTog5q2j5bi4L+asoOi0uTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRi05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQxMC4xMzY3MTg3NSwgNjExKSI+PHBvbHlnb24gcG9pbnRzPSI1OSwwIDExOCwtNTkgNTksLTExOCAwLC01OSIgY2xhc3M9ImxhYmVsLWNvbnRhaW5lciIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU4LjUsIDU5KSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5piv5ZCm6L+Y6L2mPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1HLTExIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI5NS4yNTc4MTI1LCA3NzEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTMuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMTg3LjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTYzLjc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTI3LjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWtkOihqOaTjeS9nOWIlyDCtyDov5jovaY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUgtMTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjk1LjI1NzgxMjUsIDg3NSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ov5jovabnrqHnkIY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUktMTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjk1LjI1NzgxMjUsIDk3OSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMjUuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMjUxLjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTk1Ljc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTkxLjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWtkOihqCDCtyDov5jovabliJflsZXnpLrov5jovabkv6Hmga88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUotMTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjk1LjI1NzgxMjUsIDEwODMpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzAiIHk9Ii0yNyIgd2lkdGg9IjE0MCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+6L+Y6L2m5bqU57uT5qy+PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1LLTE5IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI5NS4yNTc4MTI1LCAxMjE5KSI+PHBvbHlnb24gcG9pbnRzPSI1OSwwIDExOCwtNTkgNTksLTExOCAwLC01OSIgY2xhc3M9ImxhYmVsLWNvbnRhaW5lciIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU4LjUsIDU5KSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+57uT5qy+54q25oCBPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1MLTIxIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDYyLCAxMzc5KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTU0IiB5PSItMjciIHdpZHRoPSIxMDgiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWOu+WkhOeQhjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtTS0yMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyODMuNzU3ODEyNSwgMTM3OSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMTcuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMjM1LjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTg3Ljc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTc1LjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWxleekuuWuoeaJueS6uiDCtyDlkIzkuLvooajmoLflvI88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LU4tMjUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTI1LjAxNTYyNSwgMTM3OSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mn6XnnIvnu5PmrL48L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LU8tMjkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTI1LjAxNTYyNSwgMTUzMy45Mzc1KSI+PHBvbHlnb24gcG9pbnRzPSI3Ny45Mzc1LDAgMTU1Ljg3NSwtNzcuOTM3NSA3Ny45Mzc1LC0xNTUuODc1IDAsLTc3LjkzNzUiIGNsYXNzPSJsYWJlbC1jb250YWluZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC03Ny40Mzc1LCA3Ny45Mzc1KSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTUwLjkzNzUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTAxLjg3NSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWFqOmDqOi9pui+huW3sui/mD88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LVAtMzEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTI1LjAxNTYyNSwgMTcxMi44NzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODkuMDU0Njg3NSIgeT0iLTI3IiB3aWR0aD0iMTc4LjEwOTM3NSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU5LjA1NDY4NzUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTE4LjEwOTM3NSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPktQSSDCtyDlt7Lnu4jmraLlj6PlvoQ8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 4.1 子表还车操作条件\n\n\n\n![操作流程图 10](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNTcyLjQ1M3B4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA1NzIuNDUzMTI1IDYxNy43NSIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmaWxsOiMwMDAwMDA7fUBrZXlmcmFtZXMgZWRnZS1hbmltYXRpb24tZnJhbWV7ZnJvbXtzdHJva2UtZGFzaG9mZnNldDowO319QGtleWZyYW1lcyBkYXNoe3Rve3N0cm9rZS1kYXNob2Zmc2V0OjA7fX0jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1zbG93e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggNTBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZWRnZS1hbmltYXRpb24tZmFzdHtzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDIwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVycm9yLWljb257ZmlsbDojNTUyMjIyO30jbXktc3ZnIC5lcnJvci10ZXh0e2ZpbGw6IzU1MjIyMjtzdHJva2U6IzU1MjIyMjt9I215LXN2ZyAuZWRnZS10aGlja25lc3Mtbm9ybWFse3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLXRoaWNre3N0cm9rZS13aWR0aDozLjVweDt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLXNvbGlke3N0cm9rZS1kYXNoYXJyYXk6MDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtaW52aXNpYmxle3N0cm9rZS13aWR0aDowO2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRhc2hlZHtzdHJva2UtZGFzaGFycmF5OjM7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kb3R0ZWR7c3Ryb2tlLWRhc2hhcnJheToyO30jbXktc3ZnIC5tYXJrZXJ7ZmlsbDojNjY2O3N0cm9rZTojNjY2O30jbXktc3ZnIC5tYXJrZXIuY3Jvc3N7c3Ryb2tlOiM2NjY7fSNteS1zdmcgc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDt9I215LXN2ZyBwe21hcmdpbjowO30jbXktc3ZnIC5sYWJlbHtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3BhbiBwe2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7fSNteS1zdmcgLmxhYmVsIHRleHQsI215LXN2ZyBzcGFue2ZpbGw6IzAwMDAwMDtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5ub2RlIHJlY3QsI215LXN2ZyAubm9kZSBjaXJjbGUsI215LXN2ZyAubm9kZSBlbGxpcHNlLCNteS1zdmcgLm5vZGUgcG9seWdvbiwjbXktc3ZnIC5ub2RlIHBhdGh7ZmlsbDojZWVlO3N0cm9rZTojOTk5O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAubm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbmNob3I6bWlkZGxlO30jbXktc3ZnIC5ub2RlIC5rYXRleCBwYXRoe2ZpbGw6IzAwMDtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCwjbXktc3ZnIC5ub2RlIC5sYWJlbCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLm5vZGUuY2xpY2thYmxle2N1cnNvcjpwb2ludGVyO30jbXktc3ZnIC5yb290IC5hbmNob3IgcGF0aHtmaWxsOiM2NjYhaW1wb3J0YW50O3N0cm9rZS13aWR0aDowO3N0cm9rZTojNjY2O30jbXktc3ZnIC5hcnJvd2hlYWRQYXRoe2ZpbGw6IzMzMzMzMzt9I215LXN2ZyAuZWRnZVBhdGggLnBhdGh7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiM2NjY7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuZWRnZUxhYmVsIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt9I215LXN2ZyAuZWRnZUxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbEJrZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTt9I215LXN2ZyAuY2x1c3RlciByZWN0e2ZpbGw6aHNsKDAsIDAlLCA5OC45MjE1Njg2Mjc1JSk7c3Ryb2tlOiM3MDcwNzA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuY2x1c3RlciB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3RlciBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgZGl2Lm1lcm1haWRUb29sdGlwe3Bvc2l0aW9uOmFic29sdXRlO3RleHQtYWxpZ246Y2VudGVyO21heC13aWR0aDoyMDBweDtwYWRkaW5nOjJweDtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDpoc2woLTE2MCwgMCUsIDkzLjMzMzMzMzMzMzMlKTtib3JkZXI6MXB4IHNvbGlkICM3MDcwNzA7Ym9yZGVyLXJhZGl1czoycHg7cG9pbnRlci1ldmVudHM6bm9uZTt6LWluZGV4OjEwMDt9I215LXN2ZyAuZmxvd2NoYXJ0VGl0bGVUZXh0e3RleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MThweDtmaWxsOiMwMDAwMDA7fSNteS1zdmcgcmVjdC50ZXh0e2ZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MDt9I215LXN2ZyAuaWNvbi1zaGFwZSwjbXktc3ZnIC5pbWFnZS1zaGFwZXtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5pY29uLXNoYXBlIHAsI215LXN2ZyAuaW1hZ2Utc2hhcGUgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWwtaWNvbntkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MWVtO292ZXJmbG93OnZpc2libGU7dmVydGljYWwtYWxpZ246LTAuMTI1ZW07fSNteS1zdmcgLm5vZGUgLmxhYmVsLWljb24gcGF0aHtmaWxsOmN1cnJlbnRDb2xvcjtzdHJva2U6cmV2ZXJ0O3N0cm9rZS13aWR0aDpyZXZlcnQ7fSNteS1zdmcgLm5vZGUgLm5lby1ub2Rle3N0cm9rZTojOTk5O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmNsdXN0ZXIgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcG9seWdvbntzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uc3dpbWxhbmUuY2x1c3RlciByZWN0e2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAub3V0ZXItcGF0aHtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm5lby1saW5lIHBhdGh7c3Ryb2tlOiM5OTk7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGV7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xlIC5zdGF0ZS1zdGFydHtmaWxsOiMwMDAwMDA7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbntmaWxsOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb24tbmVvIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIDpyb290ey0tbWVybWFpZC1mb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7fTwvc3R5bGU+PGc+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjQuNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCA1IEwgMTAgMTAgTCAxMCAwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMTEuNSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTAuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDExLjUgNyBMIDAgMTQgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjEiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBvbHlnb24gcG9pbnRzPSIwLDcgMTEuNSwxNCAxMS41LDAiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjExIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZZPSI1IiByZWZYPSIxMi4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0yIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSIxMiIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iLTEiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iMTcuNyIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSItMy41IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiLz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMjcwLjk2OSw2MkwyNzAuOTY5LDY2LjE2N0MyNzAuOTY5LDcwLjMzMywyNzAuOTY5LDc4LjY2NywyNzAuOTY5LDg2LjMzM0MyNzAuOTY5LDk0LDI3MC45NjksMTAxLDI3MC45NjksMTA0LjVMMjcwLjk2OSwxMDgiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWpjd0xqazJPRGMxTENKNUlqbzJNbjBzZXlKNElqb3lOekF1T1RZNE56VXNJbmtpT2pnM2ZTeDdJbmdpT2pJM01DNDVOamczTlN3aWVTSTZNVEV5ZlYwPSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTIzNS40NTUsMjAwLjM2MkwyMTkuMjEzLDIxMi40NDdDMjAyLjk3LDIyNC41MzMsMTcwLjQ4NSwyNDguNzA0LDE1NC4yNDMsMjc0LjExMkMxMzgsMjk5LjUyMSwxMzgsMzI2LjE2NywxMzgsMzM5LjQ5TDEzOCwzNTIuODEzIiBpZD0ibXktc3ZnLUxfQl9DXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9DXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qTTFMalExTlRRMk9EUXpOREUzTXpNc0lua2lPakl3TUM0ek5qRTNNVGcwTXpReE56TXpmU3g3SW5naU9qRXpPQ3dpZVNJNk1qY3lMamczTlgwc2V5SjRJam94TXpnc0lua2lPak0xTmk0NE1USTFmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMzA2LjQ4MiwyMDAuMzYyTDMyMi43MjUsMjEyLjQ0N0MzMzguOTY3LDIyNC41MzMsMzcxLjQ1MiwyNDguNzA0LDM4Ny42OTUsMjY2LjI4OUM0MDMuOTM4LDI4My44NzUsNDAzLjkzOCwyOTQuODc1LDQwMy45MzgsMzAwLjM3NUw0MDMuOTM4LDMwNS44NzUiIGlkPSJteS1zdmctTF9CX0RfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0RfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXpBMkxqUTRNakF6TVRVMk5UZ3lOamN6TENKNUlqb3lNREF1TXpZeE56RTRORE0wTVRjek0zMHNleUo0SWpvME1ETXVPVE0zTlN3aWVTSTZNamN5TGpnM05YMHNleUo0SWpvME1ETXVPVE0zTlN3aWVTSTZNekE1TGpnM05YMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0zNjYuNDEzLDQ0NC4yMjVMMzU2Ljc4NSw0NTYuNjQ2QzM0Ny4xNTgsNDY5LjA2NywzMjcuOTAzLDQ5My45MDgsMzE4LjI3Niw1MTEuODI5QzMwOC42NDgsNTI5Ljc1LDMwOC42NDgsNTQwLjc1LDMwOC42NDgsNTQ2LjI1TDMwOC42NDgsNTUxLjc1IiBpZD0ibXktc3ZnLUxfRF9FXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9FXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk16WTJMalF4TWpjeE9UY3lNakU1TWpRc0lua2lPalEwTkM0eU1qVXlNVGszTWpJeE9USTBmU3g3SW5naU9qTXdPQzQyTkRnME16YzFMQ0o1SWpvMU1UZ3VOelY5TEhzaWVDSTZNekE0TGpZME9EUXpOelVzSW5raU9qVTFOUzQzTlgxZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQ0MS40NjIsNDQ0LjIyNUw0NTEuMDksNDU2LjY0NkM0NjAuNzE3LDQ2OS4wNjcsNDc5Ljk3Miw0OTMuOTA4LDQ4OS41OTksNTExLjgyOUM0OTkuMjI3LDUyOS43NSw0OTkuMjI3LDU0MC43NSw0OTkuMjI3LDU0Ni4yNUw0OTkuMjI3LDU1MS43NSIgaWQ9Im15LXN2Zy1MX0RfRl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZORFF4TGpRMk1qSTRNREkzTnpnd056WXNJbmtpT2pRME5DNHlNalV5TVRrM01qSXhPVEkwZlN4N0luZ2lPalE1T1M0eU1qWTFOakkxTENKNUlqbzFNVGd1TnpWOUxIc2llQ0k2TkRrNUxqSXlOalUyTWpVc0lua2lPalUxTlM0M05YMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOCwgMjcyLjg3NSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTgsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5ZCmPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MDMuOTM3NSwgMjcyLjg3NSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTgsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5pivPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMDguNjQ4NDM3NSwgNTE4Ljc1KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOCwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7mmK88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQ5OS4yMjY1NjI1LCA1MTguNzUpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9GXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuWQpjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjxnIGNsYXNzPSJub2RlcyI+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQS0wIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI3MC45Njg3NSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODYiIHk9Ii0yNyIgd2lkdGg9IjE3MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWxleW8gOW3suS6pOi9puWtkOihqDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI3MC45Njg3NSwgMTczLjkzNzUpIj48cG9seWdvbiBwb2ludHM9IjYxLjkzNzUsMCAxMjMuODc1LC02MS45Mzc1IDYxLjkzNzUsLTEyMy44NzUgMCwtNjEuOTM3NSIgY2xhc3M9ImxhYmVsLWNvbnRhaW5lciIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTYxLjQzNzUsIDYxLjkzNzUpIi8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzQuOTM3NSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2OS44NzUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqHmibnpgJrov4c/PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTM4LCAzOTUuODEyNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMzAiIHk9Ii0zOSIgd2lkdGg9IjI2MCIgaGVpZ2h0PSI3OCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEwMCwgLTI0KSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iNDgiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZTsgd2hpdGUtc3BhY2U6IGJyZWFrLXNwYWNlczsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyB3aWR0aDogMjAwcHg7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7kuqTovabmmL7npLrmnKrkuqTovaYgwrcg5peg6L+Y6L2m5oyJ6ZKuPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDAzLjkzNzUsIDM5NS44MTI1KSI+PHBvbHlnb24gcG9pbnRzPSI4NS45Mzc1LDAgMTcxLjg3NSwtODUuOTM3NSA4NS45Mzc1LC0xNzEuODc1IDAsLTg1LjkzNzUiIGNsYXNzPSJsYWJlbC1jb250YWluZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04NS40Mzc1LCA4NS45Mzc1KSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU4LjkzNzUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTE3Ljg3NSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuW3suS6pOi9puS4lOacqui/mOi9pj88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMDguNjQ4NDM3NSwgNTgyLjc1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTc1LjM1MTU2MjUiIHk9Ii0yNyIgd2lkdGg9IjE1MC43MDMxMjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00NS4zNTE1NjI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjkwLjcwMzEyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaTjeS9nOWIlzog6L+Y6L2mPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1GLTkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDk5LjIyNjU2MjUsIDU4Mi43NSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02NS4yMjY1NjI1IiB5PSItMjciIHdpZHRoPSIxMzAuNDUzMTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzUuMjI2NTYyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI3MC40NTMxMjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mk43kvZzliJc6IOKAlDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-flow-fulfill-reverse", + "title": "履约 · 逆向与异常", + "markdown": "## 5. 履约逆向与异常\n\n### 5.1 非审批通过合同不出现交车\n\n\n\n![操作流程图 11](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTAzNi42OXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAxMDM2LjY4NzUgNzAiIHJvbGU9ImdyYXBoaWNzLWRvY3VtZW50IGRvY3VtZW50IiBhcmlhLXJvbGVkZXNjcmlwdGlvbj0iZmxvd2NoYXJ0LXYyIj48c3R5bGU+I215LXN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7ZmlsbDojMDAwMDAwO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzY2NjtzdHJva2U6IzY2Njt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojNjY2O30jbXktc3ZnIHN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7fSNteS1zdmcgcHttYXJnaW46MDt9I215LXN2ZyAubGFiZWx7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMwMDAwMDA7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2VlZTtzdHJva2U6Izk5OTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojNjY2IWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojNjY2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmZsb3djaGFydC1saW5re3N0cm9rZTojNjY2O2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZUxhYmVse2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmVkZ2VMYWJlbCBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWxCa2d7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7fSNteS1zdmcgLmNsdXN0ZXIgcmVjdHtmaWxsOmhzbCgwLCAwJSwgOTguOTIxNTY4NjI3NSUpO3N0cm9rZTojNzA3MDcwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2JhY2tncm91bmQ6aHNsKC0xNjAsIDAlLCA5My4zMzMzMzMzMzMzJSk7Ym9yZGVyOjFweCBzb2xpZCAjNzA3MDcwO2JvcmRlci1yYWRpdXM6MnB4O3BvaW50ZXItZXZlbnRzOm5vbmU7ei1pbmRleDoxMDA7fSNteS1zdmcgLmZsb3djaGFydFRpdGxlVGV4dHt0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE4cHg7ZmlsbDojMDAwMDAwO30jbXktc3ZnIHJlY3QudGV4dHtmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjA7fSNteS1zdmcgLmljb24tc2hhcGUsI215LXN2ZyAuaW1hZ2Utc2hhcGV7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtwYWRkaW5nOjJweDt9I215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWwgcmVjdCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIC5ub2RlIC5uZW8tbm9kZXtzdHJva2U6Izk5OTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5jbHVzdGVyIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBvbHlnb257c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLnN3aW1sYW5lLmNsdXN0ZXIgcmVjdHtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm91dGVyLXBhdGh7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5uZW8tbGluZSBwYXRoe3N0cm9rZTojOTk5O2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xle3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZSAuc3RhdGUtc3RhcnR7ZmlsbDojMDAwMDAwO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb257ZmlsbDp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29uLW5lbyBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyA6cm9vdHstLW1lcm1haWQtZm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjExLjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEwLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMS41IDcgTCAwIDE0IHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxIiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMS41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwb2x5Z29uIHBvaW50cz0iMCw3IDExLjUsMTQgMTEuNSwwIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWT0iNSIgcmVmWD0iMTIuMjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMiIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iMTIiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9Ii0xIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9IjE3LjciIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iLTMuNSIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48ZyBjbGFzcz0icm9vdCI+PGcgY2xhc3M9ImNsdXN0ZXJzIi8+PGcgY2xhc3M9ImVkZ2VQYXRocyI+PHBhdGggZD0iTTI1My4xNzIsMzVMMjU3LjMzOSwzNUMyNjEuNTA1LDM1LDI2OS44MzksMzUsMjc3LjUwNSwzNUMyODUuMTcyLDM1LDI5Mi4xNzIsMzUsMjk1LjY3MiwzNUwyOTkuMTcyLDM1IiBpZD0ibXktc3ZnLUxfQV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qVXpMakUzTVRnM05Td2llU0k2TXpWOUxIc2llQ0k2TWpjNExqRTNNVGczTlN3aWVTSTZNelY5TEhzaWVDSTZNekF6TGpFM01UZzNOU3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNTQxLjk4NCwzNUw1NDYuMTUxLDM1QzU1MC4zMTgsMzUsNTU4LjY1MSwzNSw1NjYuMzE4LDM1QzU3My45ODQsMzUsNTgwLjk4NCwzNSw1ODQuNDg0LDM1TDU4Ny45ODQsMzUiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TlRReExqazRORE0zTlN3aWVTSTZNelY5TEhzaWVDSTZOVFkyTGprNE5ETTNOU3dpZVNJNk16VjlMSHNpZUNJNk5Ua3hMams0TkRNM05Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik03NzkuOTg0LDM1TDc4NC4xNTEsMzVDNzg4LjMxOCwzNSw3OTYuNjUxLDM1LDgwNC4zMTgsMzVDODExLjk4NCwzNSw4MTguOTg0LDM1LDgyMi40ODQsMzVMODI1Ljk4NCwzNSIgaWQ9Im15LXN2Zy1MX0NfRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0NfRF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOemM1TGprNE5ETTNOU3dpZVNJNk16VjlMSHNpZUNJNk9EQTBMams0TkRNM05Td2llU0k2TXpWOUxIc2llQ0k2T0RJNUxqazRORE0zTlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTMwLjU4NTkzNzUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEyMi41ODU5Mzc1IiB5PSItMjciIHdpZHRoPSIyNDUuMTcxODc1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOTIuNTg1OTM3NSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxODUuMTcxODc1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5b6F5a6h5om5L+WuoeaJueS4rS/pqbPlm54v5pKk5ZuePC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNDIyLjU3ODEyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTE5LjQwNjI1IiB5PSItMjciIHdpZHRoPSIyMzguODEyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTg5LjQwNjI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE3OC44MTI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5Y2z5L2/5pyJIHBsYW5uZWQg5Lqk6L2m6K6h5YiSPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjg1Ljk4NDM3NSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTQiIHk9Ii0yNyIgd2lkdGg9IjE4OCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTY0LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuS4jeiuoeWFpeW3suS6pOi9pui+huaVsDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDkyOS4zMzU5Mzc1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii05OS4zNTE1NjI1IiB5PSItMjciIHdpZHRoPSIxOTguNzAzMTI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjkuMzUxNTYyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMzguNzAzMTI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5a2Q6KGo5Lqk6L2m5YiXOiDmnKrkuqTovaY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 5.2 主动终止合同\n\n\n\n![操作流程图 12](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNDAyLjM5MXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA0MDIuMzkwNjI1IDY3OCIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmaWxsOiMwMDAwMDA7fUBrZXlmcmFtZXMgZWRnZS1hbmltYXRpb24tZnJhbWV7ZnJvbXtzdHJva2UtZGFzaG9mZnNldDowO319QGtleWZyYW1lcyBkYXNoe3Rve3N0cm9rZS1kYXNob2Zmc2V0OjA7fX0jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1zbG93e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggNTBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZWRnZS1hbmltYXRpb24tZmFzdHtzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDIwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVycm9yLWljb257ZmlsbDojNTUyMjIyO30jbXktc3ZnIC5lcnJvci10ZXh0e2ZpbGw6IzU1MjIyMjtzdHJva2U6IzU1MjIyMjt9I215LXN2ZyAuZWRnZS10aGlja25lc3Mtbm9ybWFse3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLXRoaWNre3N0cm9rZS13aWR0aDozLjVweDt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLXNvbGlke3N0cm9rZS1kYXNoYXJyYXk6MDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtaW52aXNpYmxle3N0cm9rZS13aWR0aDowO2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRhc2hlZHtzdHJva2UtZGFzaGFycmF5OjM7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kb3R0ZWR7c3Ryb2tlLWRhc2hhcnJheToyO30jbXktc3ZnIC5tYXJrZXJ7ZmlsbDojNjY2O3N0cm9rZTojNjY2O30jbXktc3ZnIC5tYXJrZXIuY3Jvc3N7c3Ryb2tlOiM2NjY7fSNteS1zdmcgc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDt9I215LXN2ZyBwe21hcmdpbjowO30jbXktc3ZnIC5sYWJlbHtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3BhbiBwe2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7fSNteS1zdmcgLmxhYmVsIHRleHQsI215LXN2ZyBzcGFue2ZpbGw6IzAwMDAwMDtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5ub2RlIHJlY3QsI215LXN2ZyAubm9kZSBjaXJjbGUsI215LXN2ZyAubm9kZSBlbGxpcHNlLCNteS1zdmcgLm5vZGUgcG9seWdvbiwjbXktc3ZnIC5ub2RlIHBhdGh7ZmlsbDojZWVlO3N0cm9rZTojOTk5O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAubm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbmNob3I6bWlkZGxlO30jbXktc3ZnIC5ub2RlIC5rYXRleCBwYXRoe2ZpbGw6IzAwMDtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCwjbXktc3ZnIC5ub2RlIC5sYWJlbCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLm5vZGUuY2xpY2thYmxle2N1cnNvcjpwb2ludGVyO30jbXktc3ZnIC5yb290IC5hbmNob3IgcGF0aHtmaWxsOiM2NjYhaW1wb3J0YW50O3N0cm9rZS13aWR0aDowO3N0cm9rZTojNjY2O30jbXktc3ZnIC5hcnJvd2hlYWRQYXRoe2ZpbGw6IzMzMzMzMzt9I215LXN2ZyAuZWRnZVBhdGggLnBhdGh7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiM2NjY7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuZWRnZUxhYmVsIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt9I215LXN2ZyAuZWRnZUxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbEJrZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTt9I215LXN2ZyAuY2x1c3RlciByZWN0e2ZpbGw6aHNsKDAsIDAlLCA5OC45MjE1Njg2Mjc1JSk7c3Ryb2tlOiM3MDcwNzA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuY2x1c3RlciB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3RlciBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgZGl2Lm1lcm1haWRUb29sdGlwe3Bvc2l0aW9uOmFic29sdXRlO3RleHQtYWxpZ246Y2VudGVyO21heC13aWR0aDoyMDBweDtwYWRkaW5nOjJweDtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDpoc2woLTE2MCwgMCUsIDkzLjMzMzMzMzMzMzMlKTtib3JkZXI6MXB4IHNvbGlkICM3MDcwNzA7Ym9yZGVyLXJhZGl1czoycHg7cG9pbnRlci1ldmVudHM6bm9uZTt6LWluZGV4OjEwMDt9I215LXN2ZyAuZmxvd2NoYXJ0VGl0bGVUZXh0e3RleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MThweDtmaWxsOiMwMDAwMDA7fSNteS1zdmcgcmVjdC50ZXh0e2ZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MDt9I215LXN2ZyAuaWNvbi1zaGFwZSwjbXktc3ZnIC5pbWFnZS1zaGFwZXtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5pY29uLXNoYXBlIHAsI215LXN2ZyAuaW1hZ2Utc2hhcGUgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWwtaWNvbntkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MWVtO292ZXJmbG93OnZpc2libGU7dmVydGljYWwtYWxpZ246LTAuMTI1ZW07fSNteS1zdmcgLm5vZGUgLmxhYmVsLWljb24gcGF0aHtmaWxsOmN1cnJlbnRDb2xvcjtzdHJva2U6cmV2ZXJ0O3N0cm9rZS13aWR0aDpyZXZlcnQ7fSNteS1zdmcgLm5vZGUgLm5lby1ub2Rle3N0cm9rZTojOTk5O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmNsdXN0ZXIgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcG9seWdvbntzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uc3dpbWxhbmUuY2x1c3RlciByZWN0e2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAub3V0ZXItcGF0aHtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm5lby1saW5lIHBhdGh7c3Ryb2tlOiM5OTk7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGV7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xlIC5zdGF0ZS1zdGFydHtmaWxsOiMwMDAwMDA7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbntmaWxsOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb24tbmVvIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIDpyb290ey0tbWVybWFpZC1mb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7fTwvc3R5bGU+PGc+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjQuNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCA1IEwgMTAgMTAgTCAxMCAwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMTEuNSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTAuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDExLjUgNyBMIDAgMTQgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjEiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBvbHlnb24gcG9pbnRzPSIwLDcgMTEuNSwxNCAxMS41LDAiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjExIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZZPSI1IiByZWZYPSIxMi4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0yIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSIxMiIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iLTEiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iMTcuNyIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSItMy41IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiLz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMjE1LjI5Myw2MkwyMTUuMjkzLDY2LjE2N0MyMTUuMjkzLDcwLjMzMywyMTUuMjkzLDc4LjY2NywyMTUuMjkzLDg2LjMzM0MyMTUuMjkzLDk0LDIxNS4yOTMsMTAxLDIxNS4yOTMsMTA0LjVMMjE1LjI5MywxMDgiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWpFMUxqSTVNamsyT0RjMUxDSjVJam8yTW4wc2V5SjRJam95TVRVdU1qa3lPVFk0TnpVc0lua2lPamczZlN4N0luZ2lPakl4TlM0eU9USTVOamczTlN3aWVTSTZNVEV5ZlYwPSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTIxNS4yOTMsMTY2TDIxNS4yOTMsMTcwLjE2N0MyMTUuMjkzLDE3NC4zMzMsMjE1LjI5MywxODIuNjY3LDIxNS4yOTMsMTkwLjMzM0MyMTUuMjkzLDE5OCwyMTUuMjkzLDIwNSwyMTUuMjkzLDIwOC41TDIxNS4yOTMsMjEyIiBpZD0ibXktc3ZnLUxfQl9DXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9DXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qRTFMakk1TWprMk9EYzFMQ0o1SWpveE5qWjlMSHNpZUNJNk1qRTFMakk1TWprMk9EYzFMQ0o1SWpveE9URjlMSHNpZUNJNk1qRTFMakk1TWprMk9EYzFMQ0o1SWpveU1UWjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMjE1LjI5MywyNzBMMjE1LjI5MywyNzQuMTY3QzIxNS4yOTMsMjc4LjMzMywyMTUuMjkzLDI4Ni42NjcsMjE1LjI5MywyOTQuMzMzQzIxNS4yOTMsMzAyLDIxNS4yOTMsMzA5LDIxNS4yOTMsMzEyLjVMMjE1LjI5MywzMTYiIGlkPSJteS1zdmctTF9DX0RfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX0RfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWpFMUxqSTVNamsyT0RjMUxDSjVJam95TnpCOUxIc2llQ0k2TWpFMUxqSTVNamsyT0RjMUxDSjVJam95T1RWOUxIc2llQ0k2TWpFMUxqSTVNamsyT0RjMUxDSjVJam96TWpCOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yMTUuMjkzLDM3NEwyMTUuMjkzLDM3OC4xNjdDMjE1LjI5MywzODIuMzMzLDIxNS4yOTMsMzkwLjY2NywyMTUuMjkzLDM5OC4zMzNDMjE1LjI5Myw0MDYsMjE1LjI5Myw0MTMsMjE1LjI5Myw0MTYuNUwyMTUuMjkzLDQyMCIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNakUxTGpJNU1qazJPRGMxTENKNUlqb3pOelI5TEhzaWVDSTZNakUxTGpJNU1qazJPRGMxTENKNUlqb3pPVGw5TEhzaWVDSTZNakUxTGpJNU1qazJPRGMxTENKNUlqbzBNalI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTE4My45MDksNTEwLjYxNkwxNzAuOTU3LDUyMi4wMTNDMTU4LjAwNCw1MzMuNDExLDEzMi4xLDU1Ni4yMDUsMTE5LjE0OCw1NzMuMTAzQzEwNi4xOTUsNTkwLDEwNi4xOTUsNjAxLDEwNi4xOTUsNjA2LjVMMTA2LjE5NSw2MTIiIGlkPSJteS1zdmctTF9FX0ZfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9FX0ZfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRnekxqa3dPVEE0TVRVd01URTJOalUxTENKNUlqbzFNVEF1TmpFMk1URXlOelV4TVRZMk5UVjlMSHNpZUNJNk1UQTJMakU1TlRNeE1qVXNJbmtpT2pVM09YMHNleUo0SWpveE1EWXVNVGsxTXpFeU5Td2llU0k2TmpFMmZWMD0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yNDYuNjc3LDUxMC42MTZMMjU5LjYyOSw1MjIuMDEzQzI3Mi41ODEsNTMzLjQxMSwyOTguNDg2LDU1Ni4yMDUsMzExLjQzOCw1NzMuMTAzQzMyNC4zOTEsNTkwLDMyNC4zOTEsNjAxLDMyNC4zOTEsNjA2LjVMMzI0LjM5MSw2MTIiIGlkPSJteS1zdmctTF9FX0dfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9FX0dfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWpRMkxqWTNOamcxTlRrNU9EZ3pNelExTENKNUlqbzFNVEF1TmpFMk1URXlOelV4TVRZMk5UVjlMSHNpZUNJNk16STBMak01TURZeU5Td2llU0k2TlRjNWZTeDdJbmdpT2pNeU5DNHpPVEEyTWpVc0lua2lPall4Tm4xZCIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9FXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTA2LjE5NTMxMjUsIDU3OSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9FX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE2LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPumAmui/hzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzI0LjM5MDYyNSwgNTc5KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0VfR18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIzMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+6amz5ZuePC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjE1LjI5Mjk2ODc1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03MCIgeT0iLTI3IiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDAsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iODAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lkIjlkIzov5vooYzkuK08L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUItMSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMTUuMjkyOTY4NzUsIDEzOSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDEuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMjAzLjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTcxLjc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTQzLjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuabtOWkmiDCtyDkuLvliqjnu4jmraLlkIjlkIw8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUMtMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMTUuMjkyOTY4NzUsIDI0MykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03OCIgeT0iLTI3IiB3aWR0aD0iMTU2IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDgsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iOTYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7loavlhpnnu4jmraLkv6Hmga88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUQtNSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMTUuMjkyOTY4NzUsIDM0NykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03OCIgeT0iLTI3IiB3aWR0aD0iMTU2IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDgsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iOTYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mj5DkuqTnu4jmraLlrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyMTUuMjkyOTY4NzUsIDQ4MykiPjxwb2x5Z29uIHBvaW50cz0iNTksMCAxMTgsLTU5IDU5LC0xMTggMCwtNTkiIGNsYXNzPSJsYWJlbC1jb250YWluZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01OC41LCA1OSkiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJuee7k+aenDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRi05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwNi4xOTUzMTI1LCA2NDMpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTguMTk1MzEyNSIgeT0iLTI3IiB3aWR0aD0iMTk2LjM5MDYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTY4LjE5NTMxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTM2LjM5MDYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQiOWQjOW3sue7iOatoi/lt7Lnu5PmnZ88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUctMTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzI0LjM5MDYyNSwgNjQzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTcwIiB5PSItMjciIHdpZHRoPSIxNDAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPue7tOaMgei/m+ihjOS4rTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n### 5.3 续签 / 转三方导致旧合同终止\n\n\n\n![操作流程图 13](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTE3MnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAxMTcyIDcwIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xNjQsMzVMMTY4LjE2NywzNUMxNzIuMzMzLDM1LDE4MC42NjcsMzUsMTg4LjMzMywzNUMxOTYsMzUsMjAzLDM1LDIwNi41LDM1TDIxMCwzNSIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVFkwTENKNUlqb3pOWDBzZXlKNElqb3hPRGtzSW5raU9qTTFmU3g3SW5naU9qSXhOQ3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMzcwLDM1TDM3NC4xNjcsMzVDMzc4LjMzMywzNSwzODYuNjY3LDM1LDM5NC4zMzMsMzVDNDAyLDM1LDQwOSwzNSw0MTIuNSwzNUw0MTYsMzUiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXpjd0xDSjVJam96Tlgwc2V5SjRJam96T1RVc0lua2lPak0xZlN4N0luZ2lPalF5TUN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTYyNCwzNUw2MjguMTY3LDM1QzYzMi4zMzMsMzUsNjQwLjY2NywzNSw2NDguMzMzLDM1QzY1NiwzNSw2NjMsMzUsNjY2LjUsMzVMNjcwLDM1IiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5qSTBMQ0o1SWpvek5YMHNleUo0SWpvMk5Ea3NJbmtpT2pNMWZTeDdJbmdpT2pZM05Dd2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik04OTQsMzVMODk4LjE2NywzNUM5MDIuMzMzLDM1LDkxMC42NjcsMzUsOTE4LjMzMywzNUM5MjYsMzUsOTMzLDM1LDkzNi41LDM1TDk0MCwzNSIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZPRGswTENKNUlqb3pOWDBzZXlKNElqbzVNVGtzSW5raU9qTTFmU3g3SW5naU9qazBOQ3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9DXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfRF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9EX0VfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoODYsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTc4IiB5PSItMjciIHdpZHRoPSIxNTYiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPui/m+ihjOS4reaXp+WQiOWQjDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI5MiwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzgiIHk9Ii0yNyIgd2lkdGg9IjE1NiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+57ut562+5oiW6L2s5LiJ5pa5PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTIyLCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDIiIHk9Ii0yNyIgd2lkdGg9IjIwNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTcyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE0NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPueUn+aIkOaWsOWQiOWQjOW5tui1sOWuoeaJuTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc4NCwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTEwIiB5PSItMjciIHdpZHRoPSIyMjAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNjAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ml6flkIjlkIzoh6rliqjmoIforrDlt7Lnu4jmraI8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDU0LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMTAiIHk9Ii0yNyIgd2lkdGg9IjIyMCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTgwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE2MCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWcqOenn+e7n+iuoei9rOenu+iHs+aWsOWQiOWQjDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-flow-change-forward", + "title": "进行中变更 · 正向", + "markdown": "## 6. 进行中变更 · 正向流程\n\n### 6.1 新增车辆\n\n\n\n![操作流程图 14](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTI2NS41MnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAxMjY1LjUxNTYyNSA3MCIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmaWxsOiMwMDAwMDA7fUBrZXlmcmFtZXMgZWRnZS1hbmltYXRpb24tZnJhbWV7ZnJvbXtzdHJva2UtZGFzaG9mZnNldDowO319QGtleWZyYW1lcyBkYXNoe3Rve3N0cm9rZS1kYXNob2Zmc2V0OjA7fX0jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1zbG93e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggNTBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZWRnZS1hbmltYXRpb24tZmFzdHtzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDIwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVycm9yLWljb257ZmlsbDojNTUyMjIyO30jbXktc3ZnIC5lcnJvci10ZXh0e2ZpbGw6IzU1MjIyMjtzdHJva2U6IzU1MjIyMjt9I215LXN2ZyAuZWRnZS10aGlja25lc3Mtbm9ybWFse3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLXRoaWNre3N0cm9rZS13aWR0aDozLjVweDt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLXNvbGlke3N0cm9rZS1kYXNoYXJyYXk6MDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtaW52aXNpYmxle3N0cm9rZS13aWR0aDowO2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRhc2hlZHtzdHJva2UtZGFzaGFycmF5OjM7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kb3R0ZWR7c3Ryb2tlLWRhc2hhcnJheToyO30jbXktc3ZnIC5tYXJrZXJ7ZmlsbDojNjY2O3N0cm9rZTojNjY2O30jbXktc3ZnIC5tYXJrZXIuY3Jvc3N7c3Ryb2tlOiM2NjY7fSNteS1zdmcgc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDt9I215LXN2ZyBwe21hcmdpbjowO30jbXktc3ZnIC5sYWJlbHtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3BhbiBwe2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7fSNteS1zdmcgLmxhYmVsIHRleHQsI215LXN2ZyBzcGFue2ZpbGw6IzAwMDAwMDtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5ub2RlIHJlY3QsI215LXN2ZyAubm9kZSBjaXJjbGUsI215LXN2ZyAubm9kZSBlbGxpcHNlLCNteS1zdmcgLm5vZGUgcG9seWdvbiwjbXktc3ZnIC5ub2RlIHBhdGh7ZmlsbDojZWVlO3N0cm9rZTojOTk5O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAubm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbmNob3I6bWlkZGxlO30jbXktc3ZnIC5ub2RlIC5rYXRleCBwYXRoe2ZpbGw6IzAwMDtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCwjbXktc3ZnIC5ub2RlIC5sYWJlbCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLm5vZGUuY2xpY2thYmxle2N1cnNvcjpwb2ludGVyO30jbXktc3ZnIC5yb290IC5hbmNob3IgcGF0aHtmaWxsOiM2NjYhaW1wb3J0YW50O3N0cm9rZS13aWR0aDowO3N0cm9rZTojNjY2O30jbXktc3ZnIC5hcnJvd2hlYWRQYXRoe2ZpbGw6IzMzMzMzMzt9I215LXN2ZyAuZWRnZVBhdGggLnBhdGh7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiM2NjY7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuZWRnZUxhYmVsIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt9I215LXN2ZyAuZWRnZUxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbEJrZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTt9I215LXN2ZyAuY2x1c3RlciByZWN0e2ZpbGw6aHNsKDAsIDAlLCA5OC45MjE1Njg2Mjc1JSk7c3Ryb2tlOiM3MDcwNzA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuY2x1c3RlciB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3RlciBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgZGl2Lm1lcm1haWRUb29sdGlwe3Bvc2l0aW9uOmFic29sdXRlO3RleHQtYWxpZ246Y2VudGVyO21heC13aWR0aDoyMDBweDtwYWRkaW5nOjJweDtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDpoc2woLTE2MCwgMCUsIDkzLjMzMzMzMzMzMzMlKTtib3JkZXI6MXB4IHNvbGlkICM3MDcwNzA7Ym9yZGVyLXJhZGl1czoycHg7cG9pbnRlci1ldmVudHM6bm9uZTt6LWluZGV4OjEwMDt9I215LXN2ZyAuZmxvd2NoYXJ0VGl0bGVUZXh0e3RleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MThweDtmaWxsOiMwMDAwMDA7fSNteS1zdmcgcmVjdC50ZXh0e2ZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MDt9I215LXN2ZyAuaWNvbi1zaGFwZSwjbXktc3ZnIC5pbWFnZS1zaGFwZXtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5pY29uLXNoYXBlIHAsI215LXN2ZyAuaW1hZ2Utc2hhcGUgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWwtaWNvbntkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MWVtO292ZXJmbG93OnZpc2libGU7dmVydGljYWwtYWxpZ246LTAuMTI1ZW07fSNteS1zdmcgLm5vZGUgLmxhYmVsLWljb24gcGF0aHtmaWxsOmN1cnJlbnRDb2xvcjtzdHJva2U6cmV2ZXJ0O3N0cm9rZS13aWR0aDpyZXZlcnQ7fSNteS1zdmcgLm5vZGUgLm5lby1ub2Rle3N0cm9rZTojOTk5O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmNsdXN0ZXIgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcG9seWdvbntzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uc3dpbWxhbmUuY2x1c3RlciByZWN0e2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAub3V0ZXItcGF0aHtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm5lby1saW5lIHBhdGh7c3Ryb2tlOiM5OTk7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGV7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xlIC5zdGF0ZS1zdGFydHtmaWxsOiMwMDAwMDA7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbntmaWxsOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb24tbmVvIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIDpyb290ey0tbWVybWFpZC1mb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7fTwvc3R5bGU+PGc+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjQuNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCA1IEwgMTAgMTAgTCAxMCAwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMTEuNSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTAuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDExLjUgNyBMIDAgMTQgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjEiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBvbHlnb24gcG9pbnRzPSIwLDcgMTEuNSwxNCAxMS41LDAiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjExIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZZPSI1IiByZWZYPSIxMi4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0yIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSIxMiIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iLTEiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iMTcuNyIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSItMy41IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiLz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMTQ4LDM1TDE1Mi4xNjcsMzVDMTU2LjMzMywzNSwxNjQuNjY3LDM1LDE3Mi4zMzMsMzVDMTgwLDM1LDE4NywzNSwxOTAuNSwzNUwxOTQsMzUiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRRNExDSjVJam96Tlgwc2V5SjRJam94TnpNc0lua2lPak0xZlN4N0luZ2lPakU1T0N3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTM2OS41MTYsMzVMMzczLjY4MiwzNUMzNzcuODQ5LDM1LDM4Ni4xODIsMzUsMzkzLjg0OSwzNUM0MDEuNTE2LDM1LDQwOC41MTYsMzUsNDEyLjAxNiwzNUw0MTUuNTE2LDM1IiBpZD0ibXktc3ZnLUxfQl9DXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9DXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk16WTVMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2TXprMExqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZOREU1TGpVeE5UWXlOU3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNTkxLjUxNiwzNUw1OTUuNjgyLDM1QzU5OS44NDksMzUsNjA4LjE4MiwzNSw2MTUuODQ5LDM1QzYyMy41MTYsMzUsNjMwLjUxNiwzNSw2MzQuMDE2LDM1TDYzNy41MTYsMzUiIGlkPSJteS1zdmctTF9DX0RfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX0RfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TlRreExqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZOakUyTGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk5qUXhMalV4TlRZeU5Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik04MjkuNTE2LDM1TDgzMy42ODIsMzVDODM3Ljg0OSwzNSw4NDYuMTgyLDM1LDg1My44NDksMzVDODYxLjUxNiwzNSw4NjguNTE2LDM1LDg3Mi4wMTYsMzVMODc1LjUxNiwzNSIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZPREk1TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk9EVTBMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2T0RjNUxqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEwMDMuNTE2LDM1TDEwMDcuNjgyLDM1QzEwMTEuODQ5LDM1LDEwMjAuMTgyLDM1LDEwMjcuODQ5LDM1QzEwMzUuNTE2LDM1LDEwNDIuNTE2LDM1LDEwNDYuMDE2LDM1TDEwNDkuNTE2LDM1IiBpZD0ibXktc3ZnLUxfRV9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UQXdNeTQxTVRVMk1qVXNJbmtpT2pNMWZTeDdJbmdpT2pFd01qZ3VOVEUxTmpJMUxDSjVJam96Tlgwc2V5SjRJam94TURVekxqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9FXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0VfRl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3OCwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzAiIHk9Ii0yNyIgd2lkdGg9IjE0MCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCI5ZCM6L+b6KGM5LitPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjgzLjc1NzgxMjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg1Ljc1NzgxMjUiIHk9Ii0yNyIgd2lkdGg9IjE3MS41MTU2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01NS43NTc4MTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMS41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mm7TlpJogwrcg5paw5aKe6L2m6L6GPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTA1LjUxNTYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODYiIHk9Ii0yNyIgd2lkdGg9IjE3MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaJk+W8gOWinui9pue8lui+kemhtTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDczNS41MTU2MjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTk0IiB5PSItMjciIHdpZHRoPSIxODgiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02NCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMjgiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ku4XmlrDlop7ovabovobotbDlrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5NDEuNTE1NjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lrqHmibnpgJrov4c8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUYtOSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMTU1LjUxNTYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTAyIiB5PSItMjciIHdpZHRoPSIyMDQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC03MiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mlrDovabnlJ/miJDmj5DovablupTmlLbmrL48L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 6.2 续签合同\n\n\n\n![操作流程图 15](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMjA0cHg7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyIgdmlld0JveD0iMCAwIDIwNCA3OTgiIHJvbGU9ImdyYXBoaWNzLWRvY3VtZW50IGRvY3VtZW50IiBhcmlhLXJvbGVkZXNjcmlwdGlvbj0iZmxvd2NoYXJ0LXYyIj48c3R5bGU+I215LXN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7ZmlsbDojMDAwMDAwO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzY2NjtzdHJva2U6IzY2Njt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojNjY2O30jbXktc3ZnIHN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7fSNteS1zdmcgcHttYXJnaW46MDt9I215LXN2ZyAubGFiZWx7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMwMDAwMDA7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2VlZTtzdHJva2U6Izk5OTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojNjY2IWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojNjY2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmZsb3djaGFydC1saW5re3N0cm9rZTojNjY2O2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZUxhYmVse2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmVkZ2VMYWJlbCBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWxCa2d7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7fSNteS1zdmcgLmNsdXN0ZXIgcmVjdHtmaWxsOmhzbCgwLCAwJSwgOTguOTIxNTY4NjI3NSUpO3N0cm9rZTojNzA3MDcwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2JhY2tncm91bmQ6aHNsKC0xNjAsIDAlLCA5My4zMzMzMzMzMzMzJSk7Ym9yZGVyOjFweCBzb2xpZCAjNzA3MDcwO2JvcmRlci1yYWRpdXM6MnB4O3BvaW50ZXItZXZlbnRzOm5vbmU7ei1pbmRleDoxMDA7fSNteS1zdmcgLmZsb3djaGFydFRpdGxlVGV4dHt0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE4cHg7ZmlsbDojMDAwMDAwO30jbXktc3ZnIHJlY3QudGV4dHtmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjA7fSNteS1zdmcgLmljb24tc2hhcGUsI215LXN2ZyAuaW1hZ2Utc2hhcGV7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtwYWRkaW5nOjJweDt9I215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWwgcmVjdCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIC5ub2RlIC5uZW8tbm9kZXtzdHJva2U6Izk5OTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5jbHVzdGVyIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBvbHlnb257c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLnN3aW1sYW5lLmNsdXN0ZXIgcmVjdHtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm91dGVyLXBhdGh7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5uZW8tbGluZSBwYXRoe3N0cm9rZTojOTk5O2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xle3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZSAuc3RhdGUtc3RhcnR7ZmlsbDojMDAwMDAwO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb257ZmlsbDp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29uLW5lbyBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyA6cm9vdHstLW1lcm1haWQtZm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjExLjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEwLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMS41IDcgTCAwIDE0IHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxIiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMS41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwb2x5Z29uIHBvaW50cz0iMCw3IDExLjUsMTQgMTEuNSwwIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWT0iNSIgcmVmWD0iMTIuMjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMiIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iMTIiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9Ii0xIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9IjE3LjciIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iLTMuNSIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48ZyBjbGFzcz0icm9vdCI+PGcgY2xhc3M9ImNsdXN0ZXJzIi8+PGcgY2xhc3M9ImVkZ2VQYXRocyI+PHBhdGggZD0iTTEwMiw2MkwxMDIsNjYuMTY3QzEwMiw3MC4zMzMsMTAyLDc4LjY2NywxMDIsODYuMzMzQzEwMiw5NCwxMDIsMTAxLDEwMiwxMDQuNUwxMDIsMTA4IiBpZD0ibXktc3ZnLUxfQV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UQXlMQ0o1SWpvMk1uMHNleUo0SWpveE1ESXNJbmtpT2pnM2ZTeDdJbmdpT2pFd01pd2llU0k2TVRFeWZWMD0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMDIsMTY2TDEwMiwxNzAuMTY3QzEwMiwxNzQuMzMzLDEwMiwxODIuNjY3LDEwMiwxOTAuMzMzQzEwMiwxOTgsMTAyLDIwNSwxMDIsMjA4LjVMMTAyLDIxMiIgaWQ9Im15LXN2Zy1MX0JfQ18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfQ18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEF5TENKNUlqb3hOalo5TEhzaWVDSTZNVEF5TENKNUlqb3hPVEY5TEhzaWVDSTZNVEF5TENKNUlqb3lNVFo5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEwMiwyNzBMMTAyLDI3NC4xNjdDMTAyLDI3OC4zMzMsMTAyLDI4Ni42NjcsMTAyLDI5NC4zMzNDMTAyLDMwMiwxMDIsMzA5LDEwMiwzMTIuNUwxMDIsMzE2IiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UQXlMQ0o1SWpveU56QjlMSHNpZUNJNk1UQXlMQ0o1SWpveU9UVjlMSHNpZUNJNk1UQXlMQ0o1SWpvek1qQjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTAyLDM3NEwxMDIsMzc4LjE2N0MxMDIsMzgyLjMzMywxMDIsMzkwLjY2NywxMDIsMzk4LjMzM0MxMDIsNDA2LDEwMiw0MTMsMTAyLDQxNi41TDEwMiw0MjAiIGlkPSJteS1zdmctTF9EX0VfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9EX0VfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRBeUxDSjVJam96TnpSOUxIc2llQ0k2TVRBeUxDSjVJam96T1RsOUxIc2llQ0k2TVRBeUxDSjVJam8wTWpSOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMDIsNDc4TDEwMiw0ODIuMTY3QzEwMiw0ODYuMzMzLDEwMiw0OTQuNjY3LDEwMiw1MDIuMzMzQzEwMiw1MTAsMTAyLDUxNywxMDIsNTIwLjVMMTAyLDUyNCIgaWQ9Im15LXN2Zy1MX0VfRl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0VfRl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVEF5TENKNUlqbzBOemg5TEhzaWVDSTZNVEF5TENKNUlqbzFNRE45TEhzaWVDSTZNVEF5TENKNUlqbzFNamg5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEwMiw1ODJMMTAyLDU4Ni4xNjdDMTAyLDU5MC4zMzMsMTAyLDU5OC42NjcsMTAyLDYwNi4zMzNDMTAyLDYxNCwxMDIsNjIxLDEwMiw2MjQuNUwxMDIsNjI4IiBpZD0ibXktc3ZnLUxfRl9HXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRl9HXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UQXlMQ0o1SWpvMU9ESjlMSHNpZUNJNk1UQXlMQ0o1SWpvMk1EZDlMSHNpZUNJNk1UQXlMQ0o1SWpvMk16SjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTAyLDY4NkwxMDIsNjkwLjE2N0MxMDIsNjk0LjMzMywxMDIsNzAyLjY2NywxMDIsNzEwLjMzM0MxMDIsNzE4LDEwMiw3MjUsMTAyLDcyOC41TDEwMiw3MzIiIGlkPSJteS1zdmctTF9HX0hfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9HX0hfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRBeUxDSjVJam8yT0RaOUxIc2llQ0k2TVRBeUxDSjVJam8zTVRGOUxIc2llQ0k2TVRBeUxDSjVJam8zTXpaOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQ19EXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9FX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRl9HXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0dfSF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDIsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTkwLjE5NTMxMjUiIHk9Ii0yNyIgd2lkdGg9IjE4MC4zOTA2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02MC4xOTUzMTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEyMC4zOTA2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ov5vooYzkuK0v5Yiw5pyf5ZCI5ZCMPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAyLCAxMzkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODUuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMTcxLjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU1Ljc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTExLjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuabtOWkmiDCtyDnu63nrb7lkIjlkIw8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUMtMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDIsIDI0MykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03MCIgeT0iLTI3IiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDAsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iODAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lpI3liLbljp/lkIjlkIw8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUQtNSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDIsIDM0NykiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii05NCIgeT0iLTI3IiB3aWR0aD0iMTg4IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjQsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+6ZSB5a6a5bey5Lqk5pyq6L+Y6L2m6L6GPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1FLTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAyLCA0NTEpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODYiIHk9Ii0yNyIgd2lkdGg9IjE3MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWPr+aWsOWinui9pui+huWNoeeJhzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRi05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMiwgNTU1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTc4IiB5PSItMjciIHdpZHRoPSIxNTYiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaPkOS6pOe7reetvuWuoeaJuTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRy0xMSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDIsIDY1OSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04NiIgeT0iLTI3IiB3aWR0aD0iMTcyIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTYsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTEyIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5paw5ZCI5ZCM5a6h5om56YCa6L+HPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ILTEzIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMiwgNzYzKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg2IiB5PSItMjciIHdpZHRoPSIxNzIiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01NiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMTIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ml6flkIjlkIzoh6rliqjnu4jmraI8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 6.3 试用转正式\n\n\n\n![操作流程图 16](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNDQycHg7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyIgdmlld0JveD0iMCAwIDQ0MiA5MzEuODc1IiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0yNTMsNjJMMjUzLDY2LjE2N0MyNTMsNzAuMzMzLDI1Myw3OC42NjcsMjUzLDg2LjMzM0MyNTMsOTQsMjUzLDEwMSwyNTMsMTA0LjVMMjUzLDEwOCIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNalV6TENKNUlqbzJNbjBzZXlKNElqb3lOVE1zSW5raU9qZzNmU3g3SW5naU9qSTFNeXdpZVNJNk1URXlmVjA9IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMjUzLDE2NkwyNTMsMTcwLjE2N0MyNTMsMTc0LjMzMywyNTMsMTgyLjY2NywyNTMsMTkwLjMzM0MyNTMsMTk4LDI1MywyMDUsMjUzLDIwOC41TDI1MywyMTIiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TWpVekxDSjVJam94TmpaOUxIc2llQ0k2TWpVekxDSjVJam94T1RGOUxIc2llQ0k2TWpVekxDSjVJam95TVRaOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yNTMsMjk0TDI1MywyOTguMTY3QzI1MywzMDIuMzMzLDI1MywzMTAuNjY3LDI1MywzMTguMzMzQzI1MywzMjYsMjUzLDMzMywyNTMsMzM2LjVMMjUzLDM0MCIgaWQ9Im15LXN2Zy1MX0NfRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0NfRF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNalV6TENKNUlqb3lPVFI5TEhzaWVDSTZNalV6TENKNUlqb3pNVGw5TEhzaWVDSTZNalV6TENKNUlqb3pORFI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTI1MywzOThMMjUzLDQwMi4xNjdDMjUzLDQwNi4zMzMsMjUzLDQxNC42NjcsMjUzLDQyMi4zMzNDMjUzLDQzMCwyNTMsNDM3LDI1Myw0NDAuNUwyNTMsNDQ0IiBpZD0ibXktc3ZnLUxfRF9FXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRF9FXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qVXpMQ0o1SWpvek9UaDlMSHNpZUNJNk1qVXpMQ0o1SWpvME1qTjlMSHNpZUNJNk1qVXpMQ0o1SWpvME5EaDlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMjE2LjE2NCw1NTEuMDM5TDIwMi40Nyw1NjMuMzQ1QzE4OC43NzYsNTc1LjY1MSwxNjEuMzg4LDYwMC4yNjMsMTQ3LjY5NCw2MTguMDY5QzEzNCw2MzUuODc1LDEzNCw2NDYuODc1LDEzNCw2NTIuMzc1TDEzNCw2NTcuODc1IiBpZD0ibXktc3ZnLUxfRV9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qRTJMakUyTkRNeE5UTTFNalk1TnpFc0lua2lPalUxTVM0d016a3pNVFV6TlRJMk9UY3hmU3g3SW5naU9qRXpOQ3dpZVNJNk5qSTBMamczTlgwc2V5SjRJam94TXpRc0lua2lPalkyTVM0NE56VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMjg5LjgzNiw1NTEuMDM5TDMwMy41Myw1NjMuMzQ1QzMxNy4yMjQsNTc1LjY1MSwzNDQuNjEyLDYwMC4yNjMsMzU4LjMwNiw2MTguMDY5QzM3Miw2MzUuODc1LDM3Miw2NDYuODc1LDM3Miw2NTIuMzc1TDM3Miw2NTcuODc1IiBpZD0ibXktc3ZnLUxfRV9HXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9HXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qZzVMamd6TlRZNE5EWTBOek13TWprc0lua2lPalUxTVM0d016a3pNVFV6TlRJMk9UY3hmU3g3SW5naU9qTTNNaXdpZVNJNk5qSTBMamczTlgwc2V5SjRJam96TnpJc0lua2lPalkyTVM0NE56VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTM0LDcxNS44NzVMMTM0LDcyMC4wNDJDMTM0LDcyNC4yMDgsMTM0LDczMi41NDIsMTQyLjkyNCw3NDAuNjA4QzE1MS44NDksNzQ4LjY3NCwxNjkuNjk3LDc1Ni40NzQsMTc4LjYyMiw3NjAuMzc0TDE4Ny41NDYsNzY0LjI3MyIgaWQ9Im15LXN2Zy1MX0ZfSF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0ZfSF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE0wTENKNUlqbzNNVFV1T0RjMWZTeDdJbmdpT2pFek5Dd2llU0k2TnpRd0xqZzNOWDBzZXlKNElqb3hPVEV1TWpFeE5UTTRORFl4TlRNNE5EVXNJbmtpT2pjMk5TNDROelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTM3Miw3MTUuODc1TDM3Miw3MjAuMDQyQzM3Miw3MjQuMjA4LDM3Miw3MzIuNTQyLDM2My4wNzYsNzQwLjYwOEMzNTQuMTUxLDc0OC42NzQsMzM2LjMwMyw3NTYuNDc0LDMyNy4zNzgsNzYwLjM3NEwzMTguNDU0LDc2NC4yNzMiIGlkPSJteS1zdmctTF9HX0hfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9HX0hfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TXpjeUxDSjVJam8zTVRVdU9EYzFmU3g3SW5naU9qTTNNaXdpZVNJNk56UXdMamczTlgwc2V5SjRJam96TVRRdU56ZzRORFl4TlRNNE5EWXhOVFVzSW5raU9qYzJOUzQ0TnpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yNTMsODE5Ljg3NUwyNTMsODI0LjA0MkMyNTMsODI4LjIwOCwyNTMsODM2LjU0MiwyNTMsODQ0LjIwOEMyNTMsODUxLjg3NSwyNTMsODU4Ljg3NSwyNTMsODYyLjM3NUwyNTMsODY1Ljg3NSIgaWQ9Im15LXN2Zy1MX0hfSV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0hfSV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNalV6TENKNUlqbzRNVGt1T0RjMWZTeDdJbmdpT2pJMU15d2llU0k2T0RRMExqZzNOWDBzZXlKNElqb3lOVE1zSW5raU9qZzJPUzQ0TnpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQ19EXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzNCwgNjI0Ljg3NSkiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9FX0ZfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTgsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5pivPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNzIsIDYyNC44NzUpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRV9HXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04LCAtMTIpIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjxwPuWQpjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0ZfSF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9HX0hfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfSF9JXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjxnIGNsYXNzPSJub2RlcyI+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQS0wIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI1MywgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+6K+V55So5ZCI5ZCMPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjUzLCAxMzkpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTMuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMTg3LjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTYzLjc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTI3LjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuabtOWkmiDCtyDor5XnlKjovazmraPlvI88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUMtMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNTMsIDI1NSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMzAiIHk9Ii0zOSIgd2lkdGg9IjI2MCIgaGVpZ2h0PSI3OCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEwMCwgLTI0KSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iNDgiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZTsgd2hpdGUtc3BhY2U6IGJyZWFrLXNwYWNlczsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyB3aWR0aDogMjAwcHg7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7oh6rliqjmi4nlj5blrqLmiLcv5o6I5p2D5Lq6L+i9pui+huiuouWNlTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI1MywgMzcxKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg2IiB5PSItMjciIHdpZHRoPSIxNzIiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01NiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMTIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7ovabovoborqLljZXlj6/nvJbovpE8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNTMsIDUxNy45Mzc1KSI+PHBvbHlnb24gcG9pbnRzPSI2OS45Mzc1LDAgMTM5Ljg3NSwtNjkuOTM3NSA2OS45Mzc1LC0xMzkuODc1IDAsLTY5LjkzNzUiIGNsYXNzPSJsYWJlbC1jb250YWluZXIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02OS40Mzc1LCA2OS45Mzc1KSIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQyLjkzNzUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iODUuODc1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCM5pe26L2s5LiJ5pa5PzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRi05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzNCwgNjg4Ljg3NSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMjYiIHk9Ii0yNyIgd2lkdGg9IjI1MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTk2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE5MiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQjOS4gOatpee7tOaKpOS4ieaWueWNj+iuruS4jumZhOS7tjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRy0xMSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNzIsIDY4OC44NzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNjIiIHk9Ii0yNyIgd2lkdGg9IjEyNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5LuF6L2s5q2j5byPPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ILTEzIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI1MywgNzkyLjg3NSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mj5DkuqTlrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUktMTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjUzLCA4OTYuODc1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTc4IiB5PSItMjciIHdpZHRoPSIxNTYiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPueUn+aIkOato+W8j+WQiOWQjDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n### 6.4 转三方合同\n\n\n\n![操作流程图 17](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTQyNS41MnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAxNDI1LjUxNTYyNSA3MCIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmaWxsOiMwMDAwMDA7fUBrZXlmcmFtZXMgZWRnZS1hbmltYXRpb24tZnJhbWV7ZnJvbXtzdHJva2UtZGFzaG9mZnNldDowO319QGtleWZyYW1lcyBkYXNoe3Rve3N0cm9rZS1kYXNob2Zmc2V0OjA7fX0jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1zbG93e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggNTBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZWRnZS1hbmltYXRpb24tZmFzdHtzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDIwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVycm9yLWljb257ZmlsbDojNTUyMjIyO30jbXktc3ZnIC5lcnJvci10ZXh0e2ZpbGw6IzU1MjIyMjtzdHJva2U6IzU1MjIyMjt9I215LXN2ZyAuZWRnZS10aGlja25lc3Mtbm9ybWFse3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLXRoaWNre3N0cm9rZS13aWR0aDozLjVweDt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLXNvbGlke3N0cm9rZS1kYXNoYXJyYXk6MDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtaW52aXNpYmxle3N0cm9rZS13aWR0aDowO2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRhc2hlZHtzdHJva2UtZGFzaGFycmF5OjM7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kb3R0ZWR7c3Ryb2tlLWRhc2hhcnJheToyO30jbXktc3ZnIC5tYXJrZXJ7ZmlsbDojNjY2O3N0cm9rZTojNjY2O30jbXktc3ZnIC5tYXJrZXIuY3Jvc3N7c3Ryb2tlOiM2NjY7fSNteS1zdmcgc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDt9I215LXN2ZyBwe21hcmdpbjowO30jbXktc3ZnIC5sYWJlbHtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3BhbiBwe2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7fSNteS1zdmcgLmxhYmVsIHRleHQsI215LXN2ZyBzcGFue2ZpbGw6IzAwMDAwMDtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5ub2RlIHJlY3QsI215LXN2ZyAubm9kZSBjaXJjbGUsI215LXN2ZyAubm9kZSBlbGxpcHNlLCNteS1zdmcgLm5vZGUgcG9seWdvbiwjbXktc3ZnIC5ub2RlIHBhdGh7ZmlsbDojZWVlO3N0cm9rZTojOTk5O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAubm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbmNob3I6bWlkZGxlO30jbXktc3ZnIC5ub2RlIC5rYXRleCBwYXRoe2ZpbGw6IzAwMDtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCwjbXktc3ZnIC5ub2RlIC5sYWJlbCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLm5vZGUuY2xpY2thYmxle2N1cnNvcjpwb2ludGVyO30jbXktc3ZnIC5yb290IC5hbmNob3IgcGF0aHtmaWxsOiM2NjYhaW1wb3J0YW50O3N0cm9rZS13aWR0aDowO3N0cm9rZTojNjY2O30jbXktc3ZnIC5hcnJvd2hlYWRQYXRoe2ZpbGw6IzMzMzMzMzt9I215LXN2ZyAuZWRnZVBhdGggLnBhdGh7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiM2NjY7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuZWRnZUxhYmVsIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt9I215LXN2ZyAuZWRnZUxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbEJrZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTt9I215LXN2ZyAuY2x1c3RlciByZWN0e2ZpbGw6aHNsKDAsIDAlLCA5OC45MjE1Njg2Mjc1JSk7c3Ryb2tlOiM3MDcwNzA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuY2x1c3RlciB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3RlciBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgZGl2Lm1lcm1haWRUb29sdGlwe3Bvc2l0aW9uOmFic29sdXRlO3RleHQtYWxpZ246Y2VudGVyO21heC13aWR0aDoyMDBweDtwYWRkaW5nOjJweDtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDpoc2woLTE2MCwgMCUsIDkzLjMzMzMzMzMzMzMlKTtib3JkZXI6MXB4IHNvbGlkICM3MDcwNzA7Ym9yZGVyLXJhZGl1czoycHg7cG9pbnRlci1ldmVudHM6bm9uZTt6LWluZGV4OjEwMDt9I215LXN2ZyAuZmxvd2NoYXJ0VGl0bGVUZXh0e3RleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MThweDtmaWxsOiMwMDAwMDA7fSNteS1zdmcgcmVjdC50ZXh0e2ZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MDt9I215LXN2ZyAuaWNvbi1zaGFwZSwjbXktc3ZnIC5pbWFnZS1zaGFwZXtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5pY29uLXNoYXBlIHAsI215LXN2ZyAuaW1hZ2Utc2hhcGUgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWwtaWNvbntkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MWVtO292ZXJmbG93OnZpc2libGU7dmVydGljYWwtYWxpZ246LTAuMTI1ZW07fSNteS1zdmcgLm5vZGUgLmxhYmVsLWljb24gcGF0aHtmaWxsOmN1cnJlbnRDb2xvcjtzdHJva2U6cmV2ZXJ0O3N0cm9rZS13aWR0aDpyZXZlcnQ7fSNteS1zdmcgLm5vZGUgLm5lby1ub2Rle3N0cm9rZTojOTk5O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmNsdXN0ZXIgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcG9seWdvbntzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uc3dpbWxhbmUuY2x1c3RlciByZWN0e2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAub3V0ZXItcGF0aHtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm5lby1saW5lIHBhdGh7c3Ryb2tlOiM5OTk7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGV7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xlIC5zdGF0ZS1zdGFydHtmaWxsOiMwMDAwMDA7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbntmaWxsOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb24tbmVvIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIDpyb290ey0tbWVybWFpZC1mb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7fTwvc3R5bGU+PGc+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjQuNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCA1IEwgMTAgMTAgTCAxMCAwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMTEuNSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTAuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDExLjUgNyBMIDAgMTQgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjEiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBvbHlnb24gcG9pbnRzPSIwLDcgMTEuNSwxNCAxMS41LDAiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjExIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZZPSI1IiByZWZYPSIxMi4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0yIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSIxMiIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iLTEiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iMTcuNyIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSItMy41IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiLz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMTQ4LDM1TDE1Mi4xNjcsMzVDMTU2LjMzMywzNSwxNjQuNjY3LDM1LDE3Mi4zMzMsMzVDMTgwLDM1LDE4NywzNSwxOTAuNSwzNUwxOTQsMzUiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRRNExDSjVJam96Tlgwc2V5SjRJam94TnpNc0lua2lPak0xZlN4N0luZ2lPakU1T0N3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTM4NS41MTYsMzVMMzg5LjY4MiwzNUMzOTMuODQ5LDM1LDQwMi4xODIsMzUsNDA5Ljg0OSwzNUM0MTcuNTE2LDM1LDQyNC41MTYsMzUsNDI4LjAxNiwzNUw0MzEuNTE2LDM1IiBpZD0ibXktc3ZnLUxfQl9DXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9DXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk16ZzFMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2TkRFd0xqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZORE0xTGpVeE5UWXlOU3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNjIzLjUxNiwzNUw2MjcuNjgyLDM1QzYzMS44NDksMzUsNjQwLjE4MiwzNSw2NDcuODQ5LDM1QzY1NS41MTYsMzUsNjYyLjUxNiwzNSw2NjYuMDE2LDM1TDY2OS41MTYsMzUiIGlkPSJteS1zdmctTF9DX0RfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX0RfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TmpJekxqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZOalE0TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk5qY3pMalV4TlRZeU5Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik05MDkuNTE2LDM1TDkxMy42ODIsMzVDOTE3Ljg0OSwzNSw5MjYuMTgyLDM1LDkzMy44NDksMzVDOTQxLjUxNiwzNSw5NDguNTE2LDM1LDk1Mi4wMTYsMzVMOTU1LjUxNiwzNSIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZPVEE1TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk9UTTBMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2T1RVNUxqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTExMTUuNTE2LDM1TDExMTkuNjgyLDM1QzExMjMuODQ5LDM1LDExMzIuMTgyLDM1LDExMzkuODQ5LDM1QzExNDcuNTE2LDM1LDExNTQuNTE2LDM1LDExNTguMDE2LDM1TDExNjEuNTE2LDM1IiBpZD0ibXktc3ZnLUxfRV9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1URXhOUzQxTVRVMk1qVXNJbmtpT2pNMWZTeDdJbmdpT2pFeE5EQXVOVEUxTmpJMUxDSjVJam96Tlgwc2V5SjRJam94TVRZMUxqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9FXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0VfRl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3OCwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzAiIHk9Ii0yNyIgd2lkdGg9IjE0MCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCI5ZCM6L+b6KGM5LitPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjkxLjc1NzgxMjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTkzLjc1NzgxMjUiIHk9Ii0yNyIgd2lkdGg9IjE4Ny41MTU2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02My43NTc4MTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEyNy41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mm7TlpJogwrcg6L2s5LiJ5pa55ZCI5ZCMPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTI5LjUxNTYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItOTQiIHk9Ii0yNyIgd2lkdGg9IjE4OCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTY0LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEyOCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaWsOWinuWkmuadoeS4ieaWueWNj+iurjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc5MS41MTU2MjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTExOCIgeT0iLTI3IiB3aWR0aD0iMjM2IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtODgsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTc2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5LiK5Lyg5Y2P6K6u6ZmE5Lu25LiO5a+55pa55YWs5Ye9PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1FLTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAzNy41MTU2MjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTc4IiB5PSItMjciIHdpZHRoPSIxNTYiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaPkOS6pOWPmOabtOWuoeaJuTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRi05IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyOTEuNTE1NjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMjYiIHk9Ii0yNyIgd2lkdGg9IjI1MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTk2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE5MiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWuoeaJuemAmui/h+WQjuayv+eUqOWOn+etvue9suaWueW8jzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n### 6.5 附加费用\n\n\n\n![操作流程图 18](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTMxMy41MnB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAxMzEzLjUxNTYyNSA3MCIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmaWxsOiMwMDAwMDA7fUBrZXlmcmFtZXMgZWRnZS1hbmltYXRpb24tZnJhbWV7ZnJvbXtzdHJva2UtZGFzaG9mZnNldDowO319QGtleWZyYW1lcyBkYXNoe3Rve3N0cm9rZS1kYXNob2Zmc2V0OjA7fX0jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1zbG93e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggNTBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZWRnZS1hbmltYXRpb24tZmFzdHtzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDIwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVycm9yLWljb257ZmlsbDojNTUyMjIyO30jbXktc3ZnIC5lcnJvci10ZXh0e2ZpbGw6IzU1MjIyMjtzdHJva2U6IzU1MjIyMjt9I215LXN2ZyAuZWRnZS10aGlja25lc3Mtbm9ybWFse3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLXRoaWNre3N0cm9rZS13aWR0aDozLjVweDt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLXNvbGlke3N0cm9rZS1kYXNoYXJyYXk6MDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtaW52aXNpYmxle3N0cm9rZS13aWR0aDowO2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRhc2hlZHtzdHJva2UtZGFzaGFycmF5OjM7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kb3R0ZWR7c3Ryb2tlLWRhc2hhcnJheToyO30jbXktc3ZnIC5tYXJrZXJ7ZmlsbDojNjY2O3N0cm9rZTojNjY2O30jbXktc3ZnIC5tYXJrZXIuY3Jvc3N7c3Ryb2tlOiM2NjY7fSNteS1zdmcgc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDt9I215LXN2ZyBwe21hcmdpbjowO30jbXktc3ZnIC5sYWJlbHtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3BhbiBwe2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7fSNteS1zdmcgLmxhYmVsIHRleHQsI215LXN2ZyBzcGFue2ZpbGw6IzAwMDAwMDtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5ub2RlIHJlY3QsI215LXN2ZyAubm9kZSBjaXJjbGUsI215LXN2ZyAubm9kZSBlbGxpcHNlLCNteS1zdmcgLm5vZGUgcG9seWdvbiwjbXktc3ZnIC5ub2RlIHBhdGh7ZmlsbDojZWVlO3N0cm9rZTojOTk5O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAubm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbmNob3I6bWlkZGxlO30jbXktc3ZnIC5ub2RlIC5rYXRleCBwYXRoe2ZpbGw6IzAwMDtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCwjbXktc3ZnIC5ub2RlIC5sYWJlbCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLm5vZGUuY2xpY2thYmxle2N1cnNvcjpwb2ludGVyO30jbXktc3ZnIC5yb290IC5hbmNob3IgcGF0aHtmaWxsOiM2NjYhaW1wb3J0YW50O3N0cm9rZS13aWR0aDowO3N0cm9rZTojNjY2O30jbXktc3ZnIC5hcnJvd2hlYWRQYXRoe2ZpbGw6IzMzMzMzMzt9I215LXN2ZyAuZWRnZVBhdGggLnBhdGh7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiM2NjY7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuZWRnZUxhYmVsIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt9I215LXN2ZyAuZWRnZUxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbEJrZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTt9I215LXN2ZyAuY2x1c3RlciByZWN0e2ZpbGw6aHNsKDAsIDAlLCA5OC45MjE1Njg2Mjc1JSk7c3Ryb2tlOiM3MDcwNzA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuY2x1c3RlciB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3RlciBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgZGl2Lm1lcm1haWRUb29sdGlwe3Bvc2l0aW9uOmFic29sdXRlO3RleHQtYWxpZ246Y2VudGVyO21heC13aWR0aDoyMDBweDtwYWRkaW5nOjJweDtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDpoc2woLTE2MCwgMCUsIDkzLjMzMzMzMzMzMzMlKTtib3JkZXI6MXB4IHNvbGlkICM3MDcwNzA7Ym9yZGVyLXJhZGl1czoycHg7cG9pbnRlci1ldmVudHM6bm9uZTt6LWluZGV4OjEwMDt9I215LXN2ZyAuZmxvd2NoYXJ0VGl0bGVUZXh0e3RleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MThweDtmaWxsOiMwMDAwMDA7fSNteS1zdmcgcmVjdC50ZXh0e2ZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MDt9I215LXN2ZyAuaWNvbi1zaGFwZSwjbXktc3ZnIC5pbWFnZS1zaGFwZXtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5pY29uLXNoYXBlIHAsI215LXN2ZyAuaW1hZ2Utc2hhcGUgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWwtaWNvbntkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MWVtO292ZXJmbG93OnZpc2libGU7dmVydGljYWwtYWxpZ246LTAuMTI1ZW07fSNteS1zdmcgLm5vZGUgLmxhYmVsLWljb24gcGF0aHtmaWxsOmN1cnJlbnRDb2xvcjtzdHJva2U6cmV2ZXJ0O3N0cm9rZS13aWR0aDpyZXZlcnQ7fSNteS1zdmcgLm5vZGUgLm5lby1ub2Rle3N0cm9rZTojOTk5O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmNsdXN0ZXIgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcG9seWdvbntzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uc3dpbWxhbmUuY2x1c3RlciByZWN0e2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAub3V0ZXItcGF0aHtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm5lby1saW5lIHBhdGh7c3Ryb2tlOiM5OTk7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGV7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xlIC5zdGF0ZS1zdGFydHtmaWxsOiMwMDAwMDA7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbntmaWxsOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb24tbmVvIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIDpyb290ey0tbWVybWFpZC1mb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7fTwvc3R5bGU+PGc+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjQuNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCA1IEwgMTAgMTAgTCAxMCAwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMTEuNSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTAuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDExLjUgNyBMIDAgMTQgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjEiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBvbHlnb24gcG9pbnRzPSIwLDcgMTEuNSwxNCAxMS41LDAiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjExIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZZPSI1IiByZWZYPSIxMi4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0yIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSIxMiIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iLTEiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iMTcuNyIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSItMy41IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiLz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMTQ4LDM1TDE1Mi4xNjcsMzVDMTU2LjMzMywzNSwxNjQuNjY3LDM1LDE3Mi4zMzMsMzVDMTgwLDM1LDE4NywzNSwxOTAuNSwzNUwxOTQsMzUiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRRNExDSjVJam96Tlgwc2V5SjRJam94TnpNc0lua2lPak0xZlN4N0luZ2lPakU1T0N3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTM2OS41MTYsMzVMMzczLjY4MiwzNUMzNzcuODQ5LDM1LDM4Ni4xODIsMzUsMzkzLjg0OSwzNUM0MDEuNTE2LDM1LDQwOC41MTYsMzUsNDEyLjAxNiwzNUw0MTUuNTE2LDM1IiBpZD0ibXktc3ZnLUxfQl9DXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9DXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk16WTVMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2TXprMExqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZOREU1TGpVeE5UWXlOU3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNjU1LjUxNiwzNUw2NTkuNjgyLDM1QzY2My44NDksMzUsNjcyLjE4MiwzNSw2NzkuODQ5LDM1QzY4Ny41MTYsMzUsNjk0LjUxNiwzNSw2OTguMDE2LDM1TDcwMS41MTYsMzUiIGlkPSJteS1zdmctTF9DX0RfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9DX0RfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TmpVMUxqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZOamd3TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk56QTFMalV4TlRZeU5Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik04NjEuNTE2LDM1TDg2NS42ODIsMzVDODY5Ljg0OSwzNSw4NzguMTgyLDM1LDg4NS44NDksMzVDODkzLjUxNiwzNSw5MDAuNTE2LDM1LDkwNC4wMTYsMzVMOTA3LjUxNiwzNSIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZPRFl4TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk9EZzJMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2T1RFeExqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEwMzUuNTE2LDM1TDEwMzkuNjgyLDM1QzEwNDMuODQ5LDM1LDEwNTIuMTgyLDM1LDEwNTkuODQ5LDM1QzEwNjcuNTE2LDM1LDEwNzQuNTE2LDM1LDEwNzguMDE2LDM1TDEwODEuNTE2LDM1IiBpZD0ibXktc3ZnLUxfRV9GXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfRV9GXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UQXpOUzQxTVRVMk1qVXNJbmtpT2pNMWZTeDdJbmdpT2pFd05qQXVOVEUxTmpJMUxDSjVJam96Tlgwc2V5SjRJam94TURnMUxqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWxzIj48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQV9CXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9DX0RfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfRF9FXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0VfRl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3OCwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzAiIHk9Ii0yNyIgd2lkdGg9IjE0MCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCI5ZCM6L+b6KGM5LitPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjgzLjc1NzgxMjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg1Ljc1NzgxMjUiIHk9Ii0yNyIgd2lkdGg9IjE3MS41MTU2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01NS43NTc4MTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMS41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mm7TlpJogwrcg6ZmE5Yqg6LS555SoPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTM3LjUxNTYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTE4IiB5PSItMjciIHdpZHRoPSIyMzYiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNzYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mjInovabovoblvZXlhaXmnI3liqHpobnkuI7otLnnlKg8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUQtNSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3ODMuNTE1NjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii03OCIgeT0iLTI3IiB3aWR0aD0iMTU2IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDgsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iOTYiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lt7LmnInotLnnlKjlj6ror7s8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUUtNyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5NzMuNTE1NjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii02MiIgeT0iLTI3IiB3aWR0aD0iMTI0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iNjQiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7mj5DkuqTlrqHmibk8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUYtOSIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMTk1LjUxNTYyNSwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTEwIiB5PSItMjciIHdpZHRoPSIyMjAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNjAiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7pgJrov4flkI7mjInorqHotLnop4TliJnlhaXotKY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n### 6.6 添加授权委托书\n\n\n\n![操作流程图 19](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMTEzMS45MXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCAxMTMxLjkwNjI1IDcwIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xNDgsMzVMMTUyLjE2NywzNUMxNTYuMzMzLDM1LDE2NC42NjcsMzUsMTcyLjMzMywzNUMxODAsMzUsMTg3LDM1LDE5MC41LDM1TDE5NCwzNSIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVFE0TENKNUlqb3pOWDBzZXlKNElqb3hOek1zSW5raU9qTTFmU3g3SW5naU9qRTVPQ3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDE3LjUxNiwzNUw0MjEuNjgyLDM1QzQyNS44NDksMzUsNDM0LjE4MiwzNSw0NDEuODQ5LDM1QzQ0OS41MTYsMzUsNDU2LjUxNiwzNSw0NjAuMDE2LDM1TDQ2My41MTYsMzUiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRFM0xqVXhOVFl5TlN3aWVTSTZNelY5TEhzaWVDSTZORFF5TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk5EWTNMalV4TlRZeU5Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik02MzkuNTE2LDM1TDY0My42ODIsMzVDNjQ3Ljg0OSwzNSw2NTYuMTgyLDM1LDY2My44NDksMzVDNjcxLjUxNiwzNSw2NzguNTE2LDM1LDY4Mi4wMTYsMzVMNjg1LjUxNiwzNSIgaWQ9Im15LXN2Zy1MX0NfRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0NfRF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOak01TGpVeE5UWXlOU3dpZVNJNk16VjlMSHNpZUNJNk5qWTBMalV4TlRZeU5Td2llU0k2TXpWOUxIc2llQ0k2TmpnNUxqVXhOVFl5TlN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTkwMS45MDYsMzVMOTA2LjA3MywzNUM5MTAuMjQsMzUsOTE4LjU3MywzNSw5MjYuMjQsMzVDOTMzLjkwNiwzNSw5NDAuOTA2LDM1LDk0NC40MDYsMzVMOTQ3LjkwNiwzNSIgaWQ9Im15LXN2Zy1MX0RfRV8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0RfRV8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZPVEF4TGprd05qSTFMQ0o1SWpvek5YMHNleUo0SWpvNU1qWXVPVEEyTWpVc0lua2lPak0xZlN4N0luZ2lPamsxTVM0NU1EWXlOU3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9DXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfRF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9EX0VfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzgsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTcwIiB5PSItMjciIHdpZHRoPSIxNDAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQiOWQjOi/m+ihjOS4rTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwNy43NTc4MTI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDkuNzU3ODEyNSIgeT0iLTI3IiB3aWR0aD0iMjE5LjUxNTYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTc5Ljc1NzgxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTU5LjUxNTYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuabtOWkmiDCtyDmt7vliqDmjojmnYPlp5TmiZjkuaY8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUMtMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1NTMuNTE1NjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04NiIgeT0iLTI3IiB3aWR0aD0iMTcyIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTYsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTEyIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+57u05oqk5Y+X5omY5Lq65YiX6KGoPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzk1LjcxMDkzNzUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEwNi4xOTUzMTI1IiB5PSItMjciIHdpZHRoPSIyMTIuMzkwNjI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzYuMTk1MzEyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxNTIuMzkwNjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5LuF5o6I5p2D5Lmm6LWw5a6h5om5L+etvue9sjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRS03IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMzcuOTA2MjUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTg2IiB5PSItMjciIHdpZHRoPSIxNzIiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01NiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMTIiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7kuLvlkIjlkIzml6DpnIDph43nrb48L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48L2c+PC9nPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdyIgaGVpZ2h0PSIxMzAlIiB3aWR0aD0iMTMwJSI+PGZlRHJvcFNoYWRvdyBkeD0iNCIgZHk9IjQiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93LXNtYWxsIiBoZWlnaHQ9IjE1MCUiIHdpZHRoPSIxNTAlIj48ZmVEcm9wU2hhZG93IGR4PSIyIiBkeT0iMiIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9Im15LXN2Zy1ncmFkaWVudCIgZ3JhZGllbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDgzLjMzMzMzMzMzMzMlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJoc2woMCwgMCUsIDg4LjkyMTU2ODYyNzUlKSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48L3N2Zz4=)\n\n\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-flow-change-reverse", + "title": "进行中变更 · 逆向", + "markdown": "## 7. 进行中变更 · 逆向流程\n\n### 7.1 变更审批撤回\n\n\n\n![操作流程图 20](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogMjc2cHg7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyIgdmlld0JveD0iMCAwIDI3NiA1MTAiIHJvbGU9ImdyYXBoaWNzLWRvY3VtZW50IGRvY3VtZW50IiBhcmlhLXJvbGVkZXNjcmlwdGlvbj0iZmxvd2NoYXJ0LXYyIj48c3R5bGU+I215LXN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7ZmlsbDojMDAwMDAwO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzY2NjtzdHJva2U6IzY2Njt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojNjY2O30jbXktc3ZnIHN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7fSNteS1zdmcgcHttYXJnaW46MDt9I215LXN2ZyAubGFiZWx7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMwMDAwMDA7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2VlZTtzdHJva2U6Izk5OTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojNjY2IWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojNjY2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmZsb3djaGFydC1saW5re3N0cm9rZTojNjY2O2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZUxhYmVse2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmVkZ2VMYWJlbCBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWxCa2d7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7fSNteS1zdmcgLmNsdXN0ZXIgcmVjdHtmaWxsOmhzbCgwLCAwJSwgOTguOTIxNTY4NjI3NSUpO3N0cm9rZTojNzA3MDcwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2JhY2tncm91bmQ6aHNsKC0xNjAsIDAlLCA5My4zMzMzMzMzMzMzJSk7Ym9yZGVyOjFweCBzb2xpZCAjNzA3MDcwO2JvcmRlci1yYWRpdXM6MnB4O3BvaW50ZXItZXZlbnRzOm5vbmU7ei1pbmRleDoxMDA7fSNteS1zdmcgLmZsb3djaGFydFRpdGxlVGV4dHt0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE4cHg7ZmlsbDojMDAwMDAwO30jbXktc3ZnIHJlY3QudGV4dHtmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjA7fSNteS1zdmcgLmljb24tc2hhcGUsI215LXN2ZyAuaW1hZ2Utc2hhcGV7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtwYWRkaW5nOjJweDt9I215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWwgcmVjdCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIC5ub2RlIC5uZW8tbm9kZXtzdHJva2U6Izk5OTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5jbHVzdGVyIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBvbHlnb257c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLnN3aW1sYW5lLmNsdXN0ZXIgcmVjdHtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm91dGVyLXBhdGh7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5uZW8tbGluZSBwYXRoe3N0cm9rZTojOTk5O2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xle3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZSAuc3RhdGUtc3RhcnR7ZmlsbDojMDAwMDAwO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb257ZmlsbDp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29uLW5lbyBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyA6cm9vdHstLW1lcm1haWQtZm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjExLjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEwLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMS41IDcgTCAwIDE0IHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxIiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMS41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwb2x5Z29uIHBvaW50cz0iMCw3IDExLjUsMTQgMTEuNSwwIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWT0iNSIgcmVmWD0iMTIuMjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMiIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iMTIiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9Ii0xIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9IjE3LjciIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iLTMuNSIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48ZyBjbGFzcz0icm9vdCI+PGcgY2xhc3M9ImNsdXN0ZXJzIi8+PGcgY2xhc3M9ImVkZ2VQYXRocyI+PHBhdGggZD0iTTEzOCw4NkwxMzgsOTAuMTY3QzEzOCw5NC4zMzMsMTM4LDEwMi42NjcsMTM4LDExMC4zMzNDMTM4LDExOCwxMzgsMTI1LDEzOCwxMjguNUwxMzgsMTMyIiBpZD0ibXktc3ZnLUxfQV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UTTRMQ0o1SWpvNE5uMHNleUo0SWpveE16Z3NJbmtpT2pFeE1YMHNleUo0SWpveE16Z3NJbmtpT2pFek5uMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0xMzgsMTkwTDEzOCwxOTQuMTY3QzEzOCwxOTguMzMzLDEzOCwyMDYuNjY3LDEzOCwyMTQuMzMzQzEzOCwyMjIsMTM4LDIyOSwxMzgsMjMyLjVMMTM4LDIzNiIgaWQ9Im15LXN2Zy1MX0JfQ18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfQ18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVE00TENKNUlqb3hPVEI5TEhzaWVDSTZNVE00TENKNUlqb3lNVFY5TEhzaWVDSTZNVE00TENKNUlqb3lOREI5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzOCwyOTRMMTM4LDI5OC4xNjdDMTM4LDMwMi4zMzMsMTM4LDMxMC42NjcsMTM4LDMxOC4zMzNDMTM4LDMyNiwxMzgsMzMzLDEzOCwzMzYuNUwxMzgsMzQwIiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UTTRMQ0o1SWpveU9UUjlMSHNpZUNJNk1UTTRMQ0o1SWpvek1UbDlMSHNpZUNJNk1UTTRMQ0o1SWpvek5EUjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTM4LDM5OEwxMzgsNDAyLjE2N0MxMzgsNDA2LjMzMywxMzgsNDE0LjY2NywxMzgsNDIyLjMzM0MxMzgsNDMwLDEzOCw0MzcsMTM4LDQ0MC41TDEzOCw0NDQiIGlkPSJteS1zdmctTF9EX0VfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9EX0VfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRNNExDSjVJam96T1RoOUxIc2llQ0k2TVRNNExDSjVJam8wTWpOOUxIc2llQ0k2TVRNNExDSjVJam8wTkRoOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQ19EXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzgsIDQ3KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEzMCIgeT0iLTM5IiB3aWR0aD0iMjYwIiBoZWlnaHQ9Ijc4Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTAwLCAtMjQpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSI0OCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlOyB3aGl0ZS1zcGFjZTogYnJlYWstc3BhY2VzOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7IHdpZHRoOiAyMDBweDsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWinui9pi/nu63nrb4v6L2s5LiJ5pa5L+mZhOWKoOi0ueeUqC/mjojmnYPkuaYv57uI5q2iPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTM4LCAxNjMpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItODYiIHk9Ii0yNyIgd2lkdGg9IjE3MiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU2LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjExMiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuW3suaPkOS6pOWPmOabtOWuoeaJuTwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQy0zIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOCwgMjY3KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTYyIiB5PSItMjciIHdpZHRoPSIxMjQiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMiwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI2NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaSpOWbnuWQiOWQjDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOCwgMzcxKSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTcwIiB5PSItMjciIHdpZHRoPSIxNDAiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00MCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI4MCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWPmOabtOWNleaSpOWbnjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRS03IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzOCwgNDc1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTExMCIgeT0iLTI3IiB3aWR0aD0iMjIwIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtODAsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTYwIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5Y6f6L+b6KGM5Lit5ZCI5ZCM5L+d5oyB5LiN5Y+YPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PC9nPjwvZz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ciIGhlaWdodD0iMTMwJSIgd2lkdGg9IjEzMCUiPjxmZURyb3BTaGFkb3cgZHg9IjQiIGR5PSI0IiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdy1zbWFsbCIgaGVpZ2h0PSIxNTAlIiB3aWR0aD0iMTUwJSI+PGZlRHJvcFNoYWRvdyBkeD0iMiIgZHk9IjIiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJteS1zdmctZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iaHNsKDAsIDAlLCA4My4zMzMzMzMzMzMzJSkiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iaHNsKDAsIDAlLCA4OC45MjE1Njg2Mjc1JSkiIHN0b3Atb3BhY2l0eT0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+)\n\n\n\n### 7.2 变更审批驳回\n\n\n\n![操作流程图 21](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNzIwLjM5MXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA3MjAuMzkwNjI1IDcwIiByb2xlPSJncmFwaGljcy1kb2N1bWVudCBkb2N1bWVudCIgYXJpYS1yb2xlZGVzY3JpcHRpb249ImZsb3djaGFydC12MiI+PHN0eWxlPiNteS1zdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZpbGw6IzAwMDAwMDt9QGtleWZyYW1lcyBlZGdlLWFuaW1hdGlvbi1mcmFtZXtmcm9te3N0cm9rZS1kYXNob2Zmc2V0OjA7fX1Aa2V5ZnJhbWVzIGRhc2h7dG97c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLXNsb3d7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCA1MHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1mYXN0e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggMjBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZXJyb3ItaWNvbntmaWxsOiM1NTIyMjI7fSNteS1zdmcgLmVycm9yLXRleHR7ZmlsbDojNTUyMjIyO3N0cm9rZTojNTUyMjIyO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1ub3JtYWx7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtdGhpY2t7c3Ryb2tlLXdpZHRoOjMuNXB4O30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tc29saWR7c3Ryb2tlLWRhc2hhcnJheTowO30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy1pbnZpc2libGV7c3Ryb2tlLXdpZHRoOjA7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZGFzaGVke3N0cm9rZS1kYXNoYXJyYXk6Mzt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRvdHRlZHtzdHJva2UtZGFzaGFycmF5OjI7fSNteS1zdmcgLm1hcmtlcntmaWxsOiM2NjY7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLm1hcmtlci5jcm9zc3tzdHJva2U6IzY2Njt9I215LXN2ZyBzdmd7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O30jbXktc3ZnIHB7bWFyZ2luOjA7fSNteS1zdmcgLmxhYmVse2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFuIHB7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDt9I215LXN2ZyAubGFiZWwgdGV4dCwjbXktc3ZnIHNwYW57ZmlsbDojMDAwMDAwO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLm5vZGUgcmVjdCwjbXktc3ZnIC5ub2RlIGNpcmNsZSwjbXktc3ZnIC5ub2RlIGVsbGlwc2UsI215LXN2ZyAubm9kZSBwb2x5Z29uLCNteS1zdmcgLm5vZGUgcGF0aHtmaWxsOiNlZWU7c3Ryb2tlOiM5OTk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFuY2hvcjptaWRkbGU7fSNteS1zdmcgLm5vZGUgLmthdGV4IHBhdGh7ZmlsbDojMDAwO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsLCNteS1zdmcgLm5vZGUgLmxhYmVsLCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCwjbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbHt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAubm9kZS5jbGlja2FibGV7Y3Vyc29yOnBvaW50ZXI7fSNteS1zdmcgLnJvb3QgLmFuY2hvciBwYXRoe2ZpbGw6IzY2NiFpbXBvcnRhbnQ7c3Ryb2tlLXdpZHRoOjA7c3Ryb2tlOiM2NjY7fSNteS1zdmcgLmFycm93aGVhZFBhdGh7ZmlsbDojMzMzMzMzO30jbXktc3ZnIC5lZGdlUGF0aCAucGF0aHtzdHJva2U6IzY2NjtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5mbG93Y2hhcnQtbGlua3tzdHJva2U6IzY2NjtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2VMYWJlbHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5lZGdlTGFiZWwgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO30jbXktc3ZnIC5lZGdlTGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsQmtne2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO30jbXktc3ZnIC5jbHVzdGVyIHJlY3R7ZmlsbDpoc2woMCwgMCUsIDk4LjkyMTU2ODYyNzUlKTtzdHJva2U6IzcwNzA3MDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5jbHVzdGVyIHRleHR7ZmlsbDojMzMzO30jbXktc3ZnIC5jbHVzdGVyIHNwYW57Y29sb3I6IzMzMzt9I215LXN2ZyBkaXYubWVybWFpZFRvb2x0aXB7cG9zaXRpb246YWJzb2x1dGU7dGV4dC1hbGlnbjpjZW50ZXI7bWF4LXdpZHRoOjIwMHB4O3BhZGRpbmc6MnB4O2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTJweDtiYWNrZ3JvdW5kOmhzbCgtMTYwLCAwJSwgOTMuMzMzMzMzMzMzMyUpO2JvcmRlcjoxcHggc29saWQgIzcwNzA3MDtib3JkZXItcmFkaXVzOjJweDtwb2ludGVyLWV2ZW50czpub25lO3otaW5kZXg6MTAwO30jbXktc3ZnIC5mbG93Y2hhcnRUaXRsZVRleHR7dGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxOHB4O2ZpbGw6IzAwMDAwMDt9I215LXN2ZyByZWN0LnRleHR7ZmlsbDpub25lO3N0cm9rZS13aWR0aDowO30jbXktc3ZnIC5pY29uLXNoYXBlLCNteS1zdmcgLmltYWdlLXNoYXBle2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmljb24tc2hhcGUgcCwjbXktc3ZnIC5pbWFnZS1zaGFwZSBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoycHg7fSNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVsIHJlY3QsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbC1pY29ue2Rpc3BsYXk6aW5saW5lLWJsb2NrO2hlaWdodDoxZW07b3ZlcmZsb3c6dmlzaWJsZTt2ZXJ0aWNhbC1hbGlnbjotMC4xMjVlbTt9I215LXN2ZyAubm9kZSAubGFiZWwtaWNvbiBwYXRoe2ZpbGw6Y3VycmVudENvbG9yO3N0cm9rZTpyZXZlcnQ7c3Ryb2tlLXdpZHRoOnJldmVydDt9I215LXN2ZyAubm9kZSAubmVvLW5vZGV7c3Ryb2tlOiM5OTk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uY2x1c3RlciByZWN0LCNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwb2x5Z29ue3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5zd2ltbGFuZS5jbHVzdGVyIHJlY3R7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5vdXRlci1wYXRoe2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAubmVvLWxpbmUgcGF0aHtzdHJva2U6Izk5OTtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZXtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGUgLnN0YXRlLXN0YXJ0e2ZpbGw6IzAwMDAwMDt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29ue2ZpbGw6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbi1uZW8gcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgOnJvb3R7LS1tZXJtYWlkLWZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjt9PC9zdHlsZT48Zz48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI1IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMCA1IEwgMCAxMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNC41IiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDUgTCAxMCAxMCBMIDEwIDAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxMS41IiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMC41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTEuNSA3IEwgMCAxNCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cG9seWdvbiBwb2ludHM9IjAsNyAxMS41LDE0IDExLjUsMCIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iMTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0xIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZUVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlk9IjUiIHJlZlg9IjEyLjI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTIiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9IjEyIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0IiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSItMSIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSIxNy43IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9Ii0zLjUiIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PGcgY2xhc3M9InJvb3QiPjxnIGNsYXNzPSJjbHVzdGVycyIvPjxnIGNsYXNzPSJlZGdlUGF0aHMiPjxwYXRoIGQ9Ik0xNjQsMzVMMTY4LjE2NywzNUMxNzIuMzMzLDM1LDE4MC42NjcsMzUsMTg4LjMzMywzNUMxOTYsMzUsMjAzLDM1LDIwNi41LDM1TDIxMCwzNSIgaWQ9Im15LXN2Zy1MX0FfQl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0FfQl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNVFkwTENKNUlqb3pOWDBzZXlKNElqb3hPRGtzSW5raU9qTTFmU3g3SW5naU9qSXhOQ3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNNDU4LjM5MSwzNUw0NjIuNTU3LDM1QzQ2Ni43MjQsMzUsNDc1LjA1NywzNSw0ODIuNzI0LDM1QzQ5MC4zOTEsMzUsNDk3LjM5MSwzNSw1MDAuODkxLDM1TDUwNC4zOTEsMzUiIGlkPSJteS1zdmctTF9CX0NfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9CX0NfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TkRVNExqTTVNRFl5TlN3aWVTSTZNelY5TEhzaWVDSTZORGd6TGpNNU1EWXlOU3dpZVNJNk16VjlMSHNpZUNJNk5UQTRMak01TURZeU5Td2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9CX0NfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PGcgY2xhc3M9Im5vZGVzIj48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1BLTAiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoODYsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTc4IiB5PSItMjciIHdpZHRoPSIxNTYiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00OCwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI5NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWPmOabtOWuoeaJuemps+WbnjwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtQi0xIiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMzNi4xOTUzMTI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMjIuMTk1MzEyNSIgeT0iLTI3IiB3aWR0aD0iMjQ0LjM5MDYyNSIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTkyLjE5NTMxMjUsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTg0LjM5MDYyNSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWQiOWQjOeKtuaAgeWbnuWPmOabtC/ojYnnqL/lj6PlvoQ8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUMtMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2MTAuMzkwNjI1LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMDIiIHk9Ii0yNyIgd2lkdGg9IjIwNCIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTcyLCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE0NCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuS4muWKoeS/ruaUueWQjumHjeaWsOaPkOS6pDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-flow-delivery-edit", + "title": "交车安排编辑", + "markdown": "## 8. 交车安排编辑(列表与子表)\n\n### 8.1 正向:修改交车安排\n\n\n\n![操作流程图 22](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogNTA5Ljk1M3B4OyBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiIHZpZXdCb3g9IjAgMCA1MDkuOTUzMTI1IDU2My44NzUiIHJvbGU9ImdyYXBoaWNzLWRvY3VtZW50IGRvY3VtZW50IiBhcmlhLXJvbGVkZXNjcmlwdGlvbj0iZmxvd2NoYXJ0LXYyIj48c3R5bGU+I215LXN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7ZmlsbDojMDAwMDAwO31Aa2V5ZnJhbWVzIGVkZ2UtYW5pbWF0aW9uLWZyYW1le2Zyb217c3Ryb2tlLWRhc2hvZmZzZXQ6MDt9fUBrZXlmcmFtZXMgZGFzaHt0b3tzdHJva2UtZGFzaG9mZnNldDowO319I215LXN2ZyAuZWRnZS1hbmltYXRpb24tc2xvd3tzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDUwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVkZ2UtYW5pbWF0aW9uLWZhc3R7c3Ryb2tlLWRhc2hhcnJheTo5LDUhaW1wb3J0YW50O3N0cm9rZS1kYXNob2Zmc2V0OjkwMDthbmltYXRpb246ZGFzaCAyMHMgbGluZWFyIGluZmluaXRlO3N0cm9rZS1saW5lY2FwOnJvdW5kO30jbXktc3ZnIC5lcnJvci1pY29ue2ZpbGw6IzU1MjIyMjt9I215LXN2ZyAuZXJyb3ItdGV4dHtmaWxsOiM1NTIyMjI7c3Ryb2tlOiM1NTIyMjI7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLW5vcm1hbHtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5lZGdlLXRoaWNrbmVzcy10aGlja3tzdHJva2Utd2lkdGg6My41cHg7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1zb2xpZHtzdHJva2UtZGFzaGFycmF5OjA7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLWludmlzaWJsZXtzdHJva2Utd2lkdGg6MDtmaWxsOm5vbmU7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kYXNoZWR7c3Ryb2tlLWRhc2hhcnJheTozO30jbXktc3ZnIC5lZGdlLXBhdHRlcm4tZG90dGVke3N0cm9rZS1kYXNoYXJyYXk6Mjt9I215LXN2ZyAubWFya2Vye2ZpbGw6IzY2NjtzdHJva2U6IzY2Njt9I215LXN2ZyAubWFya2VyLmNyb3Nze3N0cm9rZTojNjY2O30jbXktc3ZnIHN2Z3tmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjE2cHg7fSNteS1zdmcgcHttYXJnaW46MDt9I215LXN2ZyAubGFiZWx7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2NvbG9yOiMwMDAwMDA7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIC5jbHVzdGVyLWxhYmVsIHNwYW4gcHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O30jbXktc3ZnIC5sYWJlbCB0ZXh0LCNteS1zdmcgc3BhbntmaWxsOiMwMDAwMDA7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAubm9kZSByZWN0LCNteS1zdmcgLm5vZGUgY2lyY2xlLCNteS1zdmcgLm5vZGUgZWxsaXBzZSwjbXktc3ZnIC5ub2RlIHBvbHlnb24sI215LXN2ZyAubm9kZSBwYXRoe2ZpbGw6I2VlZTtzdHJva2U6Izk5OTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCB0ZXh0LCNteS1zdmcgLm5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYW5jaG9yOm1pZGRsZTt9I215LXN2ZyAubm9kZSAua2F0ZXggcGF0aHtmaWxsOiMwMDA7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAucm91Z2gtbm9kZSAubGFiZWwsI215LXN2ZyAubm9kZSAubGFiZWwsI215LXN2ZyAuaW1hZ2Utc2hhcGUgLmxhYmVsLCNteS1zdmcgLmljb24tc2hhcGUgLmxhYmVse3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5ub2RlLmNsaWNrYWJsZXtjdXJzb3I6cG9pbnRlcjt9I215LXN2ZyAucm9vdCAuYW5jaG9yIHBhdGh7ZmlsbDojNjY2IWltcG9ydGFudDtzdHJva2Utd2lkdGg6MDtzdHJva2U6IzY2Njt9I215LXN2ZyAuYXJyb3doZWFkUGF0aHtmaWxsOiMzMzMzMzM7fSNteS1zdmcgLmVkZ2VQYXRoIC5wYXRoe3N0cm9rZTojNjY2O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmZsb3djaGFydC1saW5re3N0cm9rZTojNjY2O2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZUxhYmVse2JhY2tncm91bmQtY29sb3I6d2hpdGU7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLmVkZ2VMYWJlbCBwe2JhY2tncm91bmQtY29sb3I6d2hpdGU7fSNteS1zdmcgLmVkZ2VMYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWxCa2d7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7fSNteS1zdmcgLmNsdXN0ZXIgcmVjdHtmaWxsOmhzbCgwLCAwJSwgOTguOTIxNTY4NjI3NSUpO3N0cm9rZTojNzA3MDcwO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmNsdXN0ZXIgdGV4dHtmaWxsOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXIgc3Bhbntjb2xvcjojMzMzO30jbXktc3ZnIGRpdi5tZXJtYWlkVG9vbHRpcHtwb3NpdGlvbjphYnNvbHV0ZTt0ZXh0LWFsaWduOmNlbnRlcjttYXgtd2lkdGg6MjAwcHg7cGFkZGluZzoycHg7Zm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxMnB4O2JhY2tncm91bmQ6aHNsKC0xNjAsIDAlLCA5My4zMzMzMzMzMzMzJSk7Ym9yZGVyOjFweCBzb2xpZCAjNzA3MDcwO2JvcmRlci1yYWRpdXM6MnB4O3BvaW50ZXItZXZlbnRzOm5vbmU7ei1pbmRleDoxMDA7fSNteS1zdmcgLmZsb3djaGFydFRpdGxlVGV4dHt0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE4cHg7ZmlsbDojMDAwMDAwO30jbXktc3ZnIHJlY3QudGV4dHtmaWxsOm5vbmU7c3Ryb2tlLXdpZHRoOjA7fSNteS1zdmcgLmljb24tc2hhcGUsI215LXN2ZyAuaW1hZ2Utc2hhcGV7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuaWNvbi1zaGFwZSBwLCNteS1zdmcgLmltYWdlLXNoYXBlIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtwYWRkaW5nOjJweDt9I215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWwgcmVjdCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwgcmVjdHtvcGFjaXR5OjAuNTtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO2ZpbGw6d2hpdGU7fSNteS1zdmcgLmxhYmVsLWljb257ZGlzcGxheTppbmxpbmUtYmxvY2s7aGVpZ2h0OjFlbTtvdmVyZmxvdzp2aXNpYmxlO3ZlcnRpY2FsLWFsaWduOi0wLjEyNWVtO30jbXktc3ZnIC5ub2RlIC5sYWJlbC1pY29uIHBhdGh7ZmlsbDpjdXJyZW50Q29sb3I7c3Ryb2tlOnJldmVydDtzdHJva2Utd2lkdGg6cmV2ZXJ0O30jbXktc3ZnIC5ub2RlIC5uZW8tbm9kZXtzdHJva2U6Izk5OTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5jbHVzdGVyIHJlY3QsI215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBvbHlnb257c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLnN3aW1sYW5lLmNsdXN0ZXIgcmVjdHtmaWx0ZXI6bm9uZTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm91dGVyLXBhdGh7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIC5uZW8tbGluZSBwYXRoe3N0cm9rZTojOTk5O2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xle3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5ub2RlIGNpcmNsZSAuc3RhdGUtc3RhcnR7ZmlsbDojMDAwMDAwO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb257ZmlsbDp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyBbZGF0YS1sb29rPSJuZW8iXS5pY29uLXNoYXBlIC5pY29uLW5lbyBwYXRoe3N0cm9rZTp1cmwoI215LXN2Zy1ncmFkaWVudCk7ZmlsdGVyOmRyb3Atc2hhZG93KCAxcHggMnB4IDJweCByZ2JhKDE4NSwxODUsMTg1LDEpKTt9I215LXN2ZyA6cm9vdHstLW1lcm1haWQtZm9udC1mYW1pbHk6InRyZWJ1Y2hldCBtcyIsdmVyZGFuYSxhcmlhbCxzYW5zLXNlcmlmO308L3N0eWxlPjxnPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDEwIDUgTCAwIDEwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSI0LjUiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjgiIG1hcmtlckhlaWdodD0iOCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgNSBMIDEwIDEwIEwgMTAgMCB6IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjExLjUiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEwLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAwIDAgTCAxMS41IDcgTCAwIDE0IHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludFN0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMS41IDE0IiByZWZYPSIxIiByZWZZPSI3IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMS41IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxwb2x5Z29uIHBvaW50cz0iMCw3IDExLjUsMTQgMTEuNSwwIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSIxMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iLTEiIHJlZlk9IjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWT0iNSIgcmVmWD0iMTIuMjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjE0IiBtYXJrZXJIZWlnaHQ9IjE0IiBvcmllbnQ9ImF1dG8iPjxjaXJjbGUgY3g9IjUiIGN5PSI1IiByPSI1IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAwOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY2lyY2xlU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMiIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iMTIiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQiIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEgMTEiIHJlZlg9Ii0xIiByZWZZPSI1LjIiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExIiBtYXJrZXJIZWlnaHQ9IjExIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIGwgOSw5IE0gMTAsMSBsIC05LDkiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDI7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc0VuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgY3Jvc3MgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTUgMTUiIHJlZlg9IjE3LjciIHJlZlk9IjcuNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTIiIG1hcmtlckhlaWdodD0iMTIiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgTCAxNCwxNCBNIDEsMTQgTCAxNCwxIiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyLjU7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jcm9zc1N0YXJ0LW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iLTMuNSIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48ZyBjbGFzcz0icm9vdCI+PGcgY2xhc3M9ImNsdXN0ZXJzIi8+PGcgY2xhc3M9ImVkZ2VQYXRocyI+PHBhdGggZD0iTTI1OC44NTUsNjJMMjU4Ljg1NSw2Ni4xNjdDMjU4Ljg1NSw3MC4zMzMsMjU4Ljg1NSw3OC42NjcsMjU4Ljg1NSw4Ni4zMzNDMjU4Ljg1NSw5NCwyNTguODU1LDEwMSwyNTguODU1LDEwNC41TDI1OC44NTUsMTA4IiBpZD0ibXktc3ZnLUxfQV9CXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQV9CXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1qVTRMamcxTlRRMk9EYzFMQ0o1SWpvMk1uMHNleUo0SWpveU5UZ3VPRFUxTkRZNE56VXNJbmtpT2pnM2ZTeDdJbmdpT2pJMU9DNDROVFUwTmpnM05Td2llU0k2TVRFeWZWMD0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yMjcuNjIyLDE4OC42NDJMMjExLjk3OCwyMDAuMDE0QzE5Ni4zMzQsMjExLjM4NiwxNjUuMDQ2LDIzNC4xMzEsMTQ5LjQwMiwyNTEuMDAzQzEzMy43NTgsMjY3Ljg3NSwxMzMuNzU4LDI3OC44NzUsMTMzLjc1OCwyODQuMzc1TDEzMy43NTgsMjg5Ljg3NSIgaWQ9Im15LXN2Zy1MX0JfQ18wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfQ18wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNakkzTGpZeU1qTXpOVFEwTXpjNE9EazRMQ0o1SWpveE9EZ3VOalF4T0RZMk5qa3pOemc0T1RoOUxIc2llQ0k2TVRNekxqYzFOemd4TWpVc0lua2lPakkxTmk0NE56VjlMSHNpZUNJNk1UTXpMamMxTnpneE1qVXNJbmtpT2pJNU15NDROelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTEzMy43NTgsMzQ3Ljg3NUwxMzMuNzU4LDM1Mi4wNDJDMTMzLjc1OCwzNTYuMjA4LDEzMy43NTgsMzY0LjU0MiwxMzMuNzU4LDM3Mi4yMDhDMTMzLjc1OCwzNzkuODc1LDEzMy43NTgsMzg2Ljg3NSwxMzMuNzU4LDM5MC4zNzVMMTMzLjc1OCwzOTMuODc1IiBpZD0ibXktc3ZnLUxfQ19EXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQ19EXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk1UTXpMamMxTnpneE1qVXNJbmtpT2pNME55NDROelY5TEhzaWVDSTZNVE16TGpjMU56Z3hNalVzSW5raU9qTTNNaTQ0TnpWOUxIc2llQ0k2TVRNekxqYzFOemd4TWpVc0lua2lPak01Tnk0NE56VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48cGF0aCBkPSJNMTMzLjc1OCw0NTEuODc1TDEzMy43NTgsNDU2LjA0MkMxMzMuNzU4LDQ2MC4yMDgsMTMzLjc1OCw0NjguNTQyLDEzMy43NTgsNDc2LjIwOEMxMzMuNzU4LDQ4My44NzUsMTMzLjc1OCw0OTAuODc1LDEzMy43NTgsNDk0LjM3NUwxMzMuNzU4LDQ5Ny44NzUiIGlkPSJteS1zdmctTF9EX0VfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9EX0VfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRNekxqYzFOemd4TWpVc0lua2lPalExTVM0NE56VjlMSHNpZUNJNk1UTXpMamMxTnpneE1qVXNJbmtpT2pRM05pNDROelY5TEhzaWVDSTZNVE16TGpjMU56Z3hNalVzSW5raU9qVXdNUzQ0TnpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik0yOTAuMDg5LDE4OC42NDJMMzA1LjczMywyMDAuMDE0QzMyMS4zNzcsMjExLjM4NiwzNTIuNjY1LDIzNC4xMzEsMzY4LjMwOSwyNTEuMDAzQzM4My45NTMsMjY3Ljg3NSwzODMuOTUzLDI3OC44NzUsMzgzLjk1MywyODQuMzc1TDM4My45NTMsMjg5Ljg3NSIgaWQ9Im15LXN2Zy1MX0JfRl8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0JfRl8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZNamt3TGpBNE9EWXdNakExTmpJeE1Td2llU0k2TVRnNExqWTBNVGcyTmpZNU16YzRPRGs0ZlN4N0luZ2lPak00TXk0NU5UTXhNalVzSW5raU9qSTFOaTQ0TnpWOUxIc2llQ0k2TXpnekxqazFNekV5TlN3aWVTSTZNamt6TGpnM05YMWQiIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVscyI+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0FfQl8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzMy43NTc4MTI1LCAyNTYuODc1KSI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0JfQ18wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjQsIC0xMikiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PHA+5pyq6ZSB5a6aPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQ19EXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0RfRV8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJlZGdlTGFiZWwiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDM4My45NTMxMjUsIDI1Ni44NzUpIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9GXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNCwgLTEyKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjQ4IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48cD7lt7LplIHlrpo8L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNTguODU1NDY4NzUsIDM1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTkzLjc1NzgxMjUiIHk9Ii0yNyIgd2lkdGg9IjE4Ny41MTU2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02My43NTc4MTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjEyNy41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7liJfooaggwrcg5Lqk6L2m5a6J5o6S5YiXPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjU4Ljg1NTQ2ODc1LCAxNjUuOTM3NSkiPjxwb2x5Z29uIHBvaW50cz0iNTMuOTM3NSwwIDEwNy44NzUsLTUzLjkzNzUgNTMuOTM3NSwtMTA3Ljg3NSAwLC01My45Mzc1IiBjbGFzcz0ibGFiZWwtY29udGFpbmVyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTMuNDM3NSwgNTMuOTM3NSkiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNi45Mzc1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjUzLjg3NSIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuWPr+e8lui+kT88L3A+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUMtMyIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzMuNzU3ODEyNSwgMzIwLjg3NSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04Mi4xOTUzMTI1IiB5PSItMjciIHdpZHRoPSIxNjQuMzkwNjI1IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTIuMTk1MzEyNSwgLTEyKSI+PHJlY3QvPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIxMDQuMzkwNjI1IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+57yW6L6R5Yy65Z+fL+aXpeacnzwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9Im5vZGUgZGVmYXVsdCIgaWQ9Im15LXN2Zy1mbG93Y2hhcnQtRC01IiBkYXRhLWxvb2s9ImNsYXNzaWMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzMy43NTc4MTI1LCA0MjQuODc1KSI+PHJlY3QgY2xhc3M9ImJhc2ljIGxhYmVsLWNvbnRhaW5lciIgc3R5bGU9IiIgeD0iLTEwMiIgeT0iLTI3IiB3aWR0aD0iMjA0IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzIsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5ZCM5q2l5omA5pyJ5pyq5Lqk6L2m6L2m6L6GPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1FLTciIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTMzLjc1NzgxMjUsIDUyOC44NzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItMTI1Ljc1NzgxMjUiIHk9Ii0yNyIgd2lkdGg9IjI1MS41MTU2MjUiIGhlaWdodD0iNTQiLz48ZyBjbGFzcz0ibGFiZWwiIHN0eWxlPSIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC05NS43NTc4MTI1LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE5MS41MTU2MjUiIGhlaWdodD0iMjQiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0ibm9kZUxhYmVsIj48cD7lm57lhpnmj5DovablupTmlLbmrL4gwrcg5Yi35paw5b6F5YqePC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1GLTkiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzgzLjk1MzEyNSwgMzIwLjg3NSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii0xMTgiIHk9Ii0yNyIgd2lkdGg9IjIzNiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTg4LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjE3NiIgaGVpZ2h0PSIyNCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJub2RlTGFiZWwiPjxwPuaPkOekuuWcqOaPkOi9puW6lOaUtuasvuS4reiwg+aVtDwvcD48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PC9nPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0ibXktc3ZnLWRyb3Atc2hhZG93IiBoZWlnaHQ9IjEzMCUiIHdpZHRoPSIxMzAlIj48ZmVEcm9wU2hhZG93IGR4PSI0IiBkeT0iNCIgc3RkRGV2aWF0aW9uPSIwIiBmbG9vZC1vcGFjaXR5PSIwLjA2IiBmbG9vZC1jb2xvcj0iIzAwMDAwMCIvPjwvZmlsdGVyPjwvZGVmcz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ctc21hbGwiIGhlaWdodD0iMTUwJSIgd2lkdGg9IjE1MCUiPjxmZURyb3BTaGFkb3cgZHg9IjIiIGR5PSIyIiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibXktc3ZnLWdyYWRpZW50IiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODMuMzMzMzMzMzMzMyUpIiBzdG9wLW9wYWNpdHk9IjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgwLCAwJSwgODguOTIxNTY4NjI3NSUpIiBzdG9wLW9wYWNpdHk9IjEiLz48L2xpbmVhckdyYWRpZW50Pjwvc3ZnPg==)\n\n\n\n### 8.2 子表:单车交车时间\n\n\n\n![操作流程图 23](data:image/svg+xml;base64,PHN2ZyBpZD0ibXktc3ZnIiB3aWR0aD0iMTAwJSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9ImZsb3djaGFydCIgc3R5bGU9Im1heC13aWR0aDogODcwcHg7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyIgdmlld0JveD0iMCAwIDg3MCA3MCIgcm9sZT0iZ3JhcGhpY3MtZG9jdW1lbnQgZG9jdW1lbnQiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJmbG93Y2hhcnQtdjIiPjxzdHlsZT4jbXktc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDtmaWxsOiMwMDAwMDA7fUBrZXlmcmFtZXMgZWRnZS1hbmltYXRpb24tZnJhbWV7ZnJvbXtzdHJva2UtZGFzaG9mZnNldDowO319QGtleWZyYW1lcyBkYXNoe3Rve3N0cm9rZS1kYXNob2Zmc2V0OjA7fX0jbXktc3ZnIC5lZGdlLWFuaW1hdGlvbi1zbG93e3N0cm9rZS1kYXNoYXJyYXk6OSw1IWltcG9ydGFudDtzdHJva2UtZGFzaG9mZnNldDo5MDA7YW5pbWF0aW9uOmRhc2ggNTBzIGxpbmVhciBpbmZpbml0ZTtzdHJva2UtbGluZWNhcDpyb3VuZDt9I215LXN2ZyAuZWRnZS1hbmltYXRpb24tZmFzdHtzdHJva2UtZGFzaGFycmF5OjksNSFpbXBvcnRhbnQ7c3Ryb2tlLWRhc2hvZmZzZXQ6OTAwO2FuaW1hdGlvbjpkYXNoIDIwcyBsaW5lYXIgaW5maW5pdGU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fSNteS1zdmcgLmVycm9yLWljb257ZmlsbDojNTUyMjIyO30jbXktc3ZnIC5lcnJvci10ZXh0e2ZpbGw6IzU1MjIyMjtzdHJva2U6IzU1MjIyMjt9I215LXN2ZyAuZWRnZS10aGlja25lc3Mtbm9ybWFse3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLmVkZ2UtdGhpY2tuZXNzLXRoaWNre3N0cm9rZS13aWR0aDozLjVweDt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLXNvbGlke3N0cm9rZS1kYXNoYXJyYXk6MDt9I215LXN2ZyAuZWRnZS10aGlja25lc3MtaW52aXNpYmxle3N0cm9rZS13aWR0aDowO2ZpbGw6bm9uZTt9I215LXN2ZyAuZWRnZS1wYXR0ZXJuLWRhc2hlZHtzdHJva2UtZGFzaGFycmF5OjM7fSNteS1zdmcgLmVkZ2UtcGF0dGVybi1kb3R0ZWR7c3Ryb2tlLWRhc2hhcnJheToyO30jbXktc3ZnIC5tYXJrZXJ7ZmlsbDojNjY2O3N0cm9rZTojNjY2O30jbXktc3ZnIC5tYXJrZXIuY3Jvc3N7c3Ryb2tlOiM2NjY7fSNteS1zdmcgc3Zne2ZvbnQtZmFtaWx5OiJ0cmVidWNoZXQgbXMiLHZlcmRhbmEsYXJpYWwsc2Fucy1zZXJpZjtmb250LXNpemU6MTZweDt9I215LXN2ZyBwe21hcmdpbjowO30jbXktc3ZnIC5sYWJlbHtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Y29sb3I6IzAwMDAwMDt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3Rlci1sYWJlbCBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgLmNsdXN0ZXItbGFiZWwgc3BhbiBwe2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7fSNteS1zdmcgLmxhYmVsIHRleHQsI215LXN2ZyBzcGFue2ZpbGw6IzAwMDAwMDtjb2xvcjojMDAwMDAwO30jbXktc3ZnIC5ub2RlIHJlY3QsI215LXN2ZyAubm9kZSBjaXJjbGUsI215LXN2ZyAubm9kZSBlbGxpcHNlLCNteS1zdmcgLm5vZGUgcG9seWdvbiwjbXktc3ZnIC5ub2RlIHBhdGh7ZmlsbDojZWVlO3N0cm9rZTojOTk5O3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgLnJvdWdoLW5vZGUgLmxhYmVsIHRleHQsI215LXN2ZyAubm9kZSAubGFiZWwgdGV4dCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbmNob3I6bWlkZGxlO30jbXktc3ZnIC5ub2RlIC5rYXRleCBwYXRoe2ZpbGw6IzAwMDtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4O30jbXktc3ZnIC5yb3VnaC1ub2RlIC5sYWJlbCwjbXktc3ZnIC5ub2RlIC5sYWJlbCwjbXktc3ZnIC5pbWFnZS1zaGFwZSAubGFiZWwsI215LXN2ZyAuaWNvbi1zaGFwZSAubGFiZWx7dGV4dC1hbGlnbjpjZW50ZXI7fSNteS1zdmcgLm5vZGUuY2xpY2thYmxle2N1cnNvcjpwb2ludGVyO30jbXktc3ZnIC5yb290IC5hbmNob3IgcGF0aHtmaWxsOiM2NjYhaW1wb3J0YW50O3N0cm9rZS13aWR0aDowO3N0cm9rZTojNjY2O30jbXktc3ZnIC5hcnJvd2hlYWRQYXRoe2ZpbGw6IzMzMzMzMzt9I215LXN2ZyAuZWRnZVBhdGggLnBhdGh7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuZmxvd2NoYXJ0LWxpbmt7c3Ryb2tlOiM2NjY7ZmlsbDpub25lO30jbXktc3ZnIC5lZGdlTGFiZWx7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt0ZXh0LWFsaWduOmNlbnRlcjt9I215LXN2ZyAuZWRnZUxhYmVsIHB7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTt9I215LXN2ZyAuZWRnZUxhYmVsIHJlY3R7b3BhY2l0eTowLjU7YmFja2dyb3VuZC1jb2xvcjp3aGl0ZTtmaWxsOndoaXRlO30jbXktc3ZnIC5sYWJlbEJrZ3tiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTt9I215LXN2ZyAuY2x1c3RlciByZWN0e2ZpbGw6aHNsKDAsIDAlLCA5OC45MjE1Njg2Mjc1JSk7c3Ryb2tlOiM3MDcwNzA7c3Ryb2tlLXdpZHRoOjFweDt9I215LXN2ZyAuY2x1c3RlciB0ZXh0e2ZpbGw6IzMzMzt9I215LXN2ZyAuY2x1c3RlciBzcGFue2NvbG9yOiMzMzM7fSNteS1zdmcgZGl2Lm1lcm1haWRUb29sdGlwe3Bvc2l0aW9uOmFic29sdXRlO3RleHQtYWxpZ246Y2VudGVyO21heC13aWR0aDoyMDBweDtwYWRkaW5nOjJweDtmb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7Zm9udC1zaXplOjEycHg7YmFja2dyb3VuZDpoc2woLTE2MCwgMCUsIDkzLjMzMzMzMzMzMzMlKTtib3JkZXI6MXB4IHNvbGlkICM3MDcwNzA7Ym9yZGVyLXJhZGl1czoycHg7cG9pbnRlci1ldmVudHM6bm9uZTt6LWluZGV4OjEwMDt9I215LXN2ZyAuZmxvd2NoYXJ0VGl0bGVUZXh0e3RleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MThweDtmaWxsOiMwMDAwMDA7fSNteS1zdmcgcmVjdC50ZXh0e2ZpbGw6bm9uZTtzdHJva2Utd2lkdGg6MDt9I215LXN2ZyAuaWNvbi1zaGFwZSwjbXktc3ZnIC5pbWFnZS1zaGFwZXtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3RleHQtYWxpZ246Y2VudGVyO30jbXktc3ZnIC5pY29uLXNoYXBlIHAsI215LXN2ZyAuaW1hZ2Utc2hhcGUgcHtiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MnB4O30jbXktc3ZnIC5pY29uLXNoYXBlIC5sYWJlbCByZWN0LCNteS1zdmcgLmltYWdlLXNoYXBlIC5sYWJlbCByZWN0e29wYWNpdHk6MC41O2JhY2tncm91bmQtY29sb3I6d2hpdGU7ZmlsbDp3aGl0ZTt9I215LXN2ZyAubGFiZWwtaWNvbntkaXNwbGF5OmlubGluZS1ibG9jaztoZWlnaHQ6MWVtO292ZXJmbG93OnZpc2libGU7dmVydGljYWwtYWxpZ246LTAuMTI1ZW07fSNteS1zdmcgLm5vZGUgLmxhYmVsLWljb24gcGF0aHtmaWxsOmN1cnJlbnRDb2xvcjtzdHJva2U6cmV2ZXJ0O3N0cm9rZS13aWR0aDpyZXZlcnQ7fSNteS1zdmcgLm5vZGUgLm5lby1ub2Rle3N0cm9rZTojOTk5O30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmNsdXN0ZXIgcmVjdCwjbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcG9seWdvbntzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO2ZpbHRlcjpkcm9wLXNoYWRvdyggMXB4IDJweCAycHggcmdiYSgxODUsMTg1LDE4NSwxKSk7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uc3dpbWxhbmUuY2x1c3RlciByZWN0e2ZpbHRlcjpub25lO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgcGF0aHtzdHJva2U6dXJsKCNteS1zdmctZ3JhZGllbnQpO3N0cm9rZS13aWR0aDoxcHg7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSAub3V0ZXItcGF0aHtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgLm5lby1saW5lIHBhdGh7c3Ryb2tlOiM5OTk7ZmlsdGVyOm5vbmU7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0ubm9kZSBjaXJjbGV7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLm5vZGUgY2lyY2xlIC5zdGF0ZS1zdGFydHtmaWxsOiMwMDAwMDA7fSNteS1zdmcgW2RhdGEtbG9vaz0ibmVvIl0uaWNvbi1zaGFwZSAuaWNvbntmaWxsOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIFtkYXRhLWxvb2s9Im5lbyJdLmljb24tc2hhcGUgLmljb24tbmVvIHBhdGh7c3Ryb2tlOnVybCgjbXktc3ZnLWdyYWRpZW50KTtmaWx0ZXI6ZHJvcC1zaGFkb3coIDFweCAycHggMnB4IHJnYmEoMTg1LDE4NSwxODUsMSkpO30jbXktc3ZnIDpyb290ey0tbWVybWFpZC1mb250LWZhbWlseToidHJlYnVjaGV0IG1zIix2ZXJkYW5hLGFyaWFsLHNhbnMtc2VyaWY7fTwvc3R5bGU+PGc+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCIgY2xhc3M9Im1hcmtlciBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMCAxMCIgcmVmWD0iNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCAwIEwgMTAgNSBMIDAgMTAgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjQuNSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMCA1IEwgMTAgMTAgTCAxMCAwIHoiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTEuNSAxNCIgcmVmWD0iMTEuNSIgcmVmWT0iNyIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTAuNSIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDAgMCBMIDExLjUgNyBMIDAgMTQgeiIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50U3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExLjUgMTQiIHJlZlg9IjEiIHJlZlk9IjciIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjExLjUiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PHBvbHlnb24gcG9pbnRzPSIwLDcgMTEuNSwxNCAxMS41LDAiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQiIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9IjExIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNpcmNsZVN0YXJ0IiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZYPSItMSIgcmVmWT0iNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVFbmQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDEwIDEwIiByZWZZPSI1IiByZWZYPSIxMi4yNSIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTQiIG1hcmtlckhlaWdodD0iMTQiIG9yaWVudD0iYXV0byI+PGNpcmNsZSBjeD0iNSIgY3k9IjUiIHI9IjUiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IDEsIDA7Ii8+PC9tYXJrZXI+PG1hcmtlciBpZD0ibXktc3ZnX2Zsb3djaGFydC12Mi1jaXJjbGVTdGFydC1tYXJnaW4iIGNsYXNzPSJtYXJrZXIgZmxvd2NoYXJ0LXYyIiB2aWV3Qm94PSIwIDAgMTAgMTAiIHJlZlg9Ii0yIiByZWZZPSI1IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxNCIgbWFya2VySGVpZ2h0PSIxNCIgb3JpZW50PSJhdXRvIj48Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iNSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMDsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDExIDExIiByZWZYPSIxMiIgcmVmWT0iNS4yIiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMSIgbWFya2VySGVpZ2h0PSIxMSIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBsIDksOSBNIDEwLDEgbCAtOSw5IiBjbGFzcz0iYXJyb3dNYXJrZXJQYXRoIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAyOyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxtYXJrZXIgaWQ9Im15LXN2Z19mbG93Y2hhcnQtdjItY3Jvc3NTdGFydCIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxMSAxMSIgcmVmWD0iLTEiIHJlZlk9IjUuMiIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBtYXJrZXJXaWR0aD0iMTEiIG1hcmtlckhlaWdodD0iMTEiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTSAxLDEgbCA5LDkgTSAxMCwxIGwgLTksOSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMjsgc3Ryb2tlLWRhc2hhcnJheTogMSwgMDsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzRW5kLW1hcmdpbiIgY2xhc3M9Im1hcmtlciBjcm9zcyBmbG93Y2hhcnQtdjIiIHZpZXdCb3g9IjAgMCAxNSAxNSIgcmVmWD0iMTcuNyIgcmVmWT0iNy41IiBtYXJrZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIG1hcmtlcldpZHRoPSIxMiIgbWFya2VySGVpZ2h0PSIxMiIgb3JpZW50PSJhdXRvIj48cGF0aCBkPSJNIDEsMSBMIDE0LDE0IE0gMSwxNCBMIDE0LDEiIGNsYXNzPSJhcnJvd01hcmtlclBhdGgiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDIuNTsiLz48L21hcmtlcj48bWFya2VyIGlkPSJteS1zdmdfZmxvd2NoYXJ0LXYyLWNyb3NzU3RhcnQtbWFyZ2luIiBjbGFzcz0ibWFya2VyIGNyb3NzIGZsb3djaGFydC12MiIgdmlld0JveD0iMCAwIDE1IDE1IiByZWZYPSItMy41IiByZWZZPSI3LjUiIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgbWFya2VyV2lkdGg9IjEyIiBtYXJrZXJIZWlnaHQ9IjEyIiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0gMSwxIEwgMTQsMTQgTSAxLDE0IEwgMTQsMSIgY2xhc3M9ImFycm93TWFya2VyUGF0aCIgc3R5bGU9InN0cm9rZS13aWR0aDogMi41OyBzdHJva2UtZGFzaGFycmF5OiAxLCAwOyIvPjwvbWFya2VyPjxnIGNsYXNzPSJyb290Ij48ZyBjbGFzcz0iY2x1c3RlcnMiLz48ZyBjbGFzcz0iZWRnZVBhdGhzIj48cGF0aCBkPSJNMTY0LDM1TDE2OC4xNjcsMzVDMTcyLjMzMywzNSwxODAuNjY3LDM1LDE4OC4zMzMsMzVDMTk2LDM1LDIwMywzNSwyMDYuNSwzNUwyMTAsMzUiIGlkPSJteS1zdmctTF9BX0JfMCIgY2xhc3M9ImVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBmbG93Y2hhcnQtbGluayIgc3R5bGU9IjsiIGRhdGEtZWRnZT0idHJ1ZSIgZGF0YS1ldD0iZWRnZSIgZGF0YS1pZD0iTF9BX0JfMCIgZGF0YS1wb2ludHM9Ilczc2llQ0k2TVRZMExDSjVJam96Tlgwc2V5SjRJam94T0Rrc0lua2lPak0xZlN4N0luZ2lPakl4TkN3aWVTSTZNelY5WFE9PSIgZGF0YS1sb29rPSJjbGFzc2ljIiBtYXJrZXItZW5kPSJ1cmwoI215LXN2Z19mbG93Y2hhcnQtdjItcG9pbnRFbmQpIi8+PHBhdGggZD0iTTQwMiwzNUw0MDYuMTY3LDM1QzQxMC4zMzMsMzUsNDE4LjY2NywzNSw0MjYuMzMzLDM1QzQzNCwzNSw0NDEsMzUsNDQ0LjUsMzVMNDQ4LDM1IiBpZD0ibXktc3ZnLUxfQl9DXzAiIGNsYXNzPSJlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGVkZ2UtdGhpY2tuZXNzLW5vcm1hbCBlZGdlLXBhdHRlcm4tc29saWQgZmxvd2NoYXJ0LWxpbmsiIHN0eWxlPSI7IiBkYXRhLWVkZ2U9InRydWUiIGRhdGEtZXQ9ImVkZ2UiIGRhdGEtaWQ9IkxfQl9DXzAiIGRhdGEtcG9pbnRzPSJXM3NpZUNJNk5EQXlMQ0o1SWpvek5YMHNleUo0SWpvME1qY3NJbmtpT2pNMWZTeDdJbmdpT2pRMU1pd2llU0k2TXpWOVhRPT0iIGRhdGEtbG9vaz0iY2xhc3NpYyIgbWFya2VyLWVuZD0idXJsKCNteS1zdmdfZmxvd2NoYXJ0LXYyLXBvaW50RW5kKSIvPjxwYXRoIGQ9Ik02MjQsMzVMNjI4LjE2NywzNUM2MzIuMzMzLDM1LDY0MC42NjcsMzUsNjQ4LjMzMywzNUM2NTYsMzUsNjYzLDM1LDY2Ni41LDM1TDY3MCwzNSIgaWQ9Im15LXN2Zy1MX0NfRF8wIiBjbGFzcz0iZWRnZS10aGlja25lc3Mtbm9ybWFsIGVkZ2UtcGF0dGVybi1zb2xpZCBlZGdlLXRoaWNrbmVzcy1ub3JtYWwgZWRnZS1wYXR0ZXJuLXNvbGlkIGZsb3djaGFydC1saW5rIiBzdHlsZT0iOyIgZGF0YS1lZGdlPSJ0cnVlIiBkYXRhLWV0PSJlZGdlIiBkYXRhLWlkPSJMX0NfRF8wIiBkYXRhLXBvaW50cz0iVzNzaWVDSTZOakkwTENKNUlqb3pOWDBzZXlKNElqbzJORGtzSW5raU9qTTFmU3g3SW5naU9qWTNOQ3dpZVNJNk16VjlYUT09IiBkYXRhLWxvb2s9ImNsYXNzaWMiIG1hcmtlci1lbmQ9InVybCgjbXktc3ZnX2Zsb3djaGFydC12Mi1wb2ludEVuZCkiLz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbHMiPjxnIGNsYXNzPSJlZGdlTGFiZWwiPjxnIGNsYXNzPSJsYWJlbCIgZGF0YS1pZD0iTF9BX0JfMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkiPjxmb3JlaWduT2JqZWN0IHdpZHRoPSIwIiBoZWlnaHQ9IjAiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIGNsYXNzPSJsYWJlbEJrZyIgc3R5bGU9ImRpc3BsYXk6IHRhYmxlLWNlbGw7IHdoaXRlLXNwYWNlOiBub3dyYXA7IGxpbmUtaGVpZ2h0OiAxLjU7IG1heC13aWR0aDogMjAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIGNsYXNzPSJlZGdlTGFiZWwiPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0iZWRnZUxhYmVsIj48ZyBjbGFzcz0ibGFiZWwiIGRhdGEtaWQ9IkxfQl9DXzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDApIj48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMCIgaGVpZ2h0PSIwIj48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBjbGFzcz0ibGFiZWxCa2ciIHN0eWxlPSJkaXNwbGF5OiB0YWJsZS1jZWxsOyB3aGl0ZS1zcGFjZTogbm93cmFwOyBsaW5lLWhlaWdodDogMS41OyBtYXgtd2lkdGg6IDIwMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48c3BhbiBjbGFzcz0iZWRnZUxhYmVsIj48L3NwYW4+PC9kaXY+PC9mb3JlaWduT2JqZWN0PjwvZz48L2c+PGcgY2xhc3M9ImVkZ2VMYWJlbCI+PGcgY2xhc3M9ImxhYmVsIiBkYXRhLWlkPSJMX0NfRF8wIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAwKSI+PGZvcmVpZ25PYmplY3Qgd2lkdGg9IjAiIGhlaWdodD0iMCI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgY2xhc3M9ImxhYmVsQmtnIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9ImVkZ2VMYWJlbCI+PC9zcGFuPjwvZGl2PjwvZm9yZWlnbk9iamVjdD48L2c+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZXMiPjxnIGNsYXNzPSJub2RlIGRlZmF1bHQiIGlkPSJteS1zdmctZmxvd2NoYXJ0LUEtMCIgZGF0YS1sb29rPSJjbGFzc2ljIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4NiwgMzUpIj48cmVjdCBjbGFzcz0iYmFzaWMgbGFiZWwtY29udGFpbmVyIiBzdHlsZT0iIiB4PSItNzgiIHk9Ii0yNyIgd2lkdGg9IjE1NiIgaGVpZ2h0PSI1NCIvPjxnIGNsYXNzPSJsYWJlbCIgc3R5bGU9IiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LCAtMTIpIj48cmVjdC8+PGZvcmVpZ25PYmplY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5pyq5Lqk6L2m6L2m6L6G6KGMPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1CLTEiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzA4LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii05NCIgeT0iLTI3IiB3aWR0aD0iMTg4IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjQsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+57yW6L6R5pys6L2m5Lqk6L2m5pe26Ze0PC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1DLTMiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTM4LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii04NiIgeT0iLTI3IiB3aWR0aD0iMTcyIiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTYsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTEyIiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5LuF5b2x5ZON5b2T5YmN6L2m6L6GPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48ZyBjbGFzcz0ibm9kZSBkZWZhdWx0IiBpZD0ibXktc3ZnLWZsb3djaGFydC1ELTUiIGRhdGEtbG9vaz0iY2xhc3NpYyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzY4LCAzNSkiPjxyZWN0IGNsYXNzPSJiYXNpYyBsYWJlbC1jb250YWluZXIiIHN0eWxlPSIiIHg9Ii05NCIgeT0iLTI3IiB3aWR0aD0iMTg4IiBoZWlnaHQ9IjU0Ii8+PGcgY2xhc3M9ImxhYmVsIiBzdHlsZT0iIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjQsIC0xMikiPjxyZWN0Lz48Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTI4IiBoZWlnaHQ9IjI0Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogdGFibGUtY2VsbDsgd2hpdGUtc3BhY2U6IG5vd3JhcDsgbGluZS1oZWlnaHQ6IDEuNTsgbWF4LXdpZHRoOiAyMDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PHNwYW4gY2xhc3M9Im5vZGVMYWJlbCI+PHA+5LiN5b2x5ZON5bey5Lqk6L2m6L2m6L6GPC9wPjwvc3Bhbj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PC9nPjwvZz48L2c+PC9nPjwvZz48ZGVmcz48ZmlsdGVyIGlkPSJteS1zdmctZHJvcC1zaGFkb3ciIGhlaWdodD0iMTMwJSIgd2lkdGg9IjEzMCUiPjxmZURyb3BTaGFkb3cgZHg9IjQiIGR5PSI0IiBzdGREZXZpYXRpb249IjAiIGZsb29kLW9wYWNpdHk9IjAuMDYiIGZsb29kLWNvbG9yPSIjMDAwMDAwIi8+PC9maWx0ZXI+PC9kZWZzPjxkZWZzPjxmaWx0ZXIgaWQ9Im15LXN2Zy1kcm9wLXNoYWRvdy1zbWFsbCIgaGVpZ2h0PSIxNTAlIiB3aWR0aD0iMTUwJSI+PGZlRHJvcFNoYWRvdyBkeD0iMiIgZHk9IjIiIHN0ZERldmlhdGlvbj0iMCIgZmxvb2Qtb3BhY2l0eT0iMC4wNiIgZmxvb2QtY29sb3I9IiMwMDAwMDAiLz48L2ZpbHRlcj48L2RlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJteS1zdmctZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iaHNsKDAsIDAlLCA4My4zMzMzMzMzMzMzJSkiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iaHNsKDAsIDAlLCA4OC45MjE1Njg2Mjc1JSkiIHN0b3Atb3BhY2l0eT0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+)\n\n\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-flow-matrix", + "title": "操作与状态对照表", + "markdown": "## 9. 操作与状态对照表\n\n| 操作 | 显示条件 | 正向结果 | 逆向/回退 |\n|---|---|---|---|\n| 新增 | 工具栏 | 进入新增页 | 取消不保存 |\n| 保存草稿 | 新增/编辑页 | 草稿 | 删除草稿 |\n| 提交审核 | 新增/编辑页 | 进入审批 | 撤回 |\n| 编辑 | 草稿/驳回 | 修改后重提 | — |\n| 删除 | 草稿 | 记录删除 | — |\n| 撤回 | 待审批/审批中 | 审批撤回 | 可再提交 |\n| 盖章补传 | 审批通过+线下 | 附件归档 | — |\n| 交车 | 审批通过后履约 | 在租 | — |\n| 还车 | 子表·已交未还 | 还车应结款 | — |\n| 续签 | 进行中/到期 | 新合同+旧合同终止 | 撤回变更审批 |\n| 增车 | 进行中 | 变更审批 | 驳回/撤回 |\n| 转三方 | 进行中 | 变更审批 | 驳回/撤回 |\n| 试用转正式 | 试用合同 | 新正式合同 | 驳回/撤回 |\n| 附加费用 | 进行中 | 费用入账审批 | 驳回/撤回 |\n| 添加授权书 | 进行中 | 授权书审批 | 驳回/撤回 |\n| 主动终止 | 进行中 | 终止审批 | 驳回 |\n\n---" } ] }, @@ -579,13 +640,13 @@ "type": "markdown", "id": "lc-doc-node-lc-list-filter", "title": "筛选条件", - "markdown": "## 4. 筛选区\n\n默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 12 项;支持「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同编码 | 可搜索下拉 | 模糊匹配合同编码 |\n| 项目名称 | 可搜索下拉 | 模糊匹配项目名称 |\n| 客户名称 | 可搜索下拉 | 模糊匹配客户名称 |\n| 签约公司 | 下拉 | 租赁合同签约公司枚举 |\n| 审批状态 | 多选下拉 | 全部 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交 |\n| 合同状态 | 多选下拉 | 全部 / 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |\n| 业务部门 | 多选下拉 | 业务相关部门 |\n| 业务负责人 | 多选下拉 | 业务组人员 |\n| 合同类型 | 多选下拉 | 全部 / 正式合同 / 试用合同 |\n| 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 |\n| 创建人 | 多选下拉 | 业务相关人员 |\n| 合同结束日期 | 日期范围 | 单输入框双日历,中文界面,开始—结束精确至日 |\n\n**交互**:\n\n- **查询**:将当前筛选条件写入「已应用筛选」,列表与 KPI 统计联动。\n- **重置**:清空为默认(多选类默认「全部」),并刷新列表。\n\n---" + "markdown": "## 4. 筛选区\n\n默认展示 **第一行 4 个筛选项**(3 列栅格),点击「展开更多筛选项」显示全部 **13 项**;支持「重置」「查询」。\n\n| 字段 | 控件 | 说明 |\n|---|---|---|\n| 合同编码 | 可搜索下拉 | 模糊匹配合同编码 |\n| 项目名称 | 可搜索下拉 | 模糊匹配项目名称 |\n| 客户名称 | 可搜索下拉 | 模糊匹配客户名称 |\n| 签约公司 | 下拉 | 租赁合同签约公司枚举 |\n| 审批状态 | 多选下拉 | 全部 / 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回 / 审批终止 / 撤回 |\n| 合同状态 | 多选下拉 | 全部 / 草稿 / 已提交审批 / 合同进行中 / 已终止 |\n| 业务部门 | 多选下拉 | 业务相关部门 |\n| 业务负责人 | 多选下拉 | 业务组人员 |\n| 合同模板 | 单选下拉 | 选项来自合同模板管理已发布且启用的模板「合同名称」维度(如正式合同、试用合同、现代18吨正式合同);可清空 |\n| 标准合同名称 | 单选下拉 | **联动合同模板**:仅在选择合同模板后启用;选项为该模板类别下已发布文档名(如「2026年标准商用车租赁合同」);支持搜索;可清空 |\n| 审批类型 | 多选下拉 | 全部 / 标准合同 / 非标准合同 |\n| 创建人 | 多选下拉 | 业务相关人员 |\n| 合同结束日期 | 日期范围 | 单输入框双日历,中文界面,开始—结束精确至日 |\n\n**合同模板联动规则**:\n\n1. 未选合同模板时,「标准合同名称」禁用并提示「请先选择合同模板」。\n2. 切换合同模板时,自动清空已选标准合同名称。\n3. 查询时按合同记录的 `contractTemplateId`(或由合同类型推断的模板 ID)匹配;合同模板与标准合同名称可单独或组合使用。\n\n**交互**:\n\n- **查询**:将当前筛选条件写入「已应用筛选」,列表与 KPI 统计联动。\n- **重置**:清空为默认(多选类默认「全部」,单选类清空),并刷新列表。\n\n---" }, { "type": "markdown", "id": "lc-doc-node-lc-list-kpi", "title": "KPI 统计卡片", - "markdown": "## 5. KPI 统计卡片\n\n共 5 张卡片,点击切换列表筛选(与筛选区叠加生效)。\n\n| 卡片 | 口径 |\n|---|---|\n| 全部合同 | 不做 KPI 维度过滤 |\n| 进行中 | 至少有一辆租赁车辆已交车且尚未还车 |\n| 审批中 | 审批状态为「待审批」或「审批中」 |\n| 临期合同 | 存在在租车辆,且合同签订有效期距今日 ≤ 30 天(以最后一辆在租车辆为准) |\n| 已终止 | 全部车辆已还车;或续签/转三方产生新合同后旧合同自动标记为已终止 |\n\n卡片右上角 `?` 悬停展示口径说明。\n\n---" + "markdown": "## 5. KPI 统计卡片\n\n共 5 张卡片,点击切换列表筛选(与筛选区条件 **叠加** 生效)。\n\n| 卡片 | 口径(按合同状态) |\n|---|---|\n| 全部合同 | 不做 KPI 维度过滤 |\n| 草稿 | 合同状态 = 草稿 |\n| 进行中 | 合同状态 = 合同进行中 |\n| 审批中 | 合同状态 = 已提交审批 |\n| 已终止 | 合同状态 = 已终止 |\n\n卡片右上角 `?` 悬停展示口径说明。\n\n> **说明**:KPI 按合同状态快速分桶;交还车履约口径(已交未还、临期等)见 §7.2 子表与 §6.6 全局规则,不在 KPI 卡片中重复统计。\n\n---" }, { "type": "markdown", @@ -603,7 +664,7 @@ "type": "markdown", "id": "lc-doc-node-lc-list-table", "title": "合同台账列表", - "markdown": "## 7. 列表字段说明\n\n### 7.1 项目信息(合并列)\n\n一列展示三行:\n\n1. 项目名称(主文案)\n2. 合同编码(次级)\n3. 客户名称(次级)\n\n点击整格跳转「查看」页(原型 Toast)。\n\n### 7.2 车辆与交车\n\n| 列 | 说明 |\n|---|---|\n| 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 |\n| 已交车 | 已有实际交车日期的车辆数;点击弹出已交车列表 |\n\n车辆明细字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。\n\n### 7.3 状态与业务字段\n\n| 列 | 说明 |\n|---|---|\n| 审批类型 | 标准合同 / 非标准合同;位于审批状态左侧 |\n| 审批状态 | 待审批 / 审批中 / 审批通过 / 审批驳回 / 未提交;「审批中」悬停展示审批流节点 |\n| 合同状态 | 草稿 / 变更 / 合同进行中 / 到期合同 / 已提交审批 / 已结束 |\n| 签约公司 | 创建时所选甲方签约主体简称 |\n| 业务部门 / 业务负责人 | 创建时选取 |\n| 合同类型 | 正式合同 / 试用合同 |\n| 客户联系人 / 联系电话 | 来自客户档案 |\n| 被授权人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 |\n| 创建人 / 创建时间 | 精确至分钟 |\n| 最后更新 | 更新人 / 最后更新时间,无则显示 `-` |\n\n**分页**:每页 10 / 20 / 50 条可选。\n\n---\n\n---\n\n## 8. 操作与弹窗\n\n### 8.1 操作列(主按钮 + 更多)\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 查看 | 始终 | 跳转合同查看页 |\n| 编辑 | 草稿;或审批驳回 | 进入编辑页 |\n| 删除合同 | 草稿 | 二次确认后删除 |\n| 撤回合同 | 已提交审批且非驳回 | 二次确认后撤回 |\n| 新增车辆 | 合同进行中 | 租赁订单内增车并重新审批 |\n| 续签合同 | 进行中 / 到期 | 跳转续签页 |\n| 添加被授权人 | 合同进行中 | 弹窗编辑被授权人列表 |\n| 附加费用 | 合同进行中 | 弹窗按车辆录入附加费用 |\n| 转三方合同 | 合同进行中 | 弹窗新增三方协议,上传协议附件与对方公函 |\n| 试用转正式 | 试用合同 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 |\n| 终止合同 | 合同进行中 | 二次确认,提交终止审批 |\n| 上传盖章合同 | 审批通过且法务未上传 | 仅法务部可见;多文件上传后关闭入口 |\n\n### 8.2 审批状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 未提交 | 仅保存草稿 |\n| 待审批 | 已提交,尚无节点通过 |\n| 审批中 | 已有节点通过,未完成终审 |\n| 审批通过 | 终审通过 |\n| 审批驳回 | 任一节点的驳回;可编辑重提 |\n\n### 8.3 合同状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 草稿 | 未提交审批 |\n| 已提交审批 | 初次提交,终审未完成 |\n| 变更 | 已通过基础上变更并重新审批中 |\n| 合同进行中 | 终审通过且在有效期内 |\n| 到期合同 | 已过结束日期 |\n| 已结束 | 终止或自然结束 |\n\n### 8.4 弹窗摘要\n\n- **删除草稿**:「是否确认删除该合同草稿」\n- **撤回**:「是否确认撤回该合同」\n- **终止**:「是否确认终止合同」→ 提交审核\n- **添加被授权人**:姓名、电话、身份证;支持增删行\n- **附加费用**:按车辆展示类型/品牌/型号/车牌,可填服务项目、费用(元,2 位小数)、生效日期\n- **转三方合同**:支持新增多条三方协议;每条可填协议名称、上传协议附件与对方公函\n- **试用转正式**:自动带出客户信息、授权人与车辆订单;车辆订单字段可编辑;可勾选「同时转三方合同」并在同一步维护三方协议\n- **上传盖章合同**:拖拽多文件;确认后标记已上传" + "markdown": "## 7. 列表字段说明\n\n### 7.1 项目信息(合并列)\n\n一列展示三行:\n\n1. 项目名称(主文案)\n2. 合同编码(次级)\n3. 客户名称(次级)\n\n点击整格跳转「查看」页(原型 Toast)。\n\n### 7.2 车辆与交车\n\n| 列 | 说明 |\n|---|---|\n| 租赁订单 · 车辆数 | 租赁订单车辆总数;点击弹出车辆明细表 |\n| 租赁订单 · 已交车 | **审批通过**且已有实际交车日期的车辆数;点击展开已交车子表 |\n\n**履约规则**:非审批通过的合同不视为已交车;「已交车辆数」为 0 时按钮禁用。\n\n### 7.2.1 已交车子表\n\n展开「已交车辆数」后展示车辆子表,列包括:\n\n| 列 | 说明 |\n|---|---|\n| 车辆信息 | 车牌、VIN、品牌型号 |\n| 提车应收款 | 支付状态,可跳转详情 |\n| 交车 | 已交车展示里程/交车人/时间;未交车显示「未交车」 |\n| 租赁账单 | 正常 / 欠费(仅已交车) |\n| 还车 | 已还车展示还车信息;未还车显示「未还车」 |\n| 还车应结款 | 还车后展示结款状态;**审批中**时展示当前审批人(UI 与主表「审批状态」列一致,含节点悬停) |\n| 交车安排 | 区域与计划/实际交车日期 |\n| 里程要求 / 里程完成 | 有最低里程要求时展示 |\n| 操作 | **审批通过 + 已交车 + 未还车** 显示「还车」链接 |\n\n车辆明细 Popover 字段:车辆类型、品牌、型号、车牌号、实际交车日期、交车人。\n\n### 7.3 状态与业务字段\n\n| 列 | 说明 |\n|---|---|\n| 合同签署方式 | 线上电子签章 / 线下人工上传;可展开查看或变更 |\n| 审批状态 | 未提交 / 待审批 / 审批中 / 审批通过 / 审批驳回等;「审批中」悬停展示审批流节点与当前处理人 |\n| 签约公司 | 创建时所选甲方签约主体简称 |\n| 业务部门 | 创建时选取 |\n| 费用信息 | 付款方式、周期、保证金等摘要 |\n| 交车安排 | 合同级交车区域与计划日期 |\n| 整体里程完成情况 | 按在租车辆里程汇总展示完成进度 |\n| 客户联系人 | 来自客户档案 |\n| 受托人 | 显示人数;点击弹出卡片查看姓名、联系方式、身份证号 |\n| 创建信息 | 创建人 / 创建时间,精确至分钟 |\n| 最后更新 | 更新人 / 最后更新时间,无则显示 `-` |\n\n**分页**:每页 10 / 20 / 50 条可选。\n\n---\n\n---\n\n## 8. 操作与弹窗\n\n### 8.1 操作列(主按钮 + 更多)\n\n| 操作 | 显示条件 | 行为 |\n|---|---|---|\n| 查看 | 始终 | 跳转合同查看页 |\n| 编辑 | 草稿;或审批驳回 | 进入编辑页 |\n| 删除合同 | 草稿 | 二次确认后删除 |\n| 撤回合同 | 已提交审批且非驳回 | 二次确认后撤回 |\n| 新增车辆 | 合同进行中 | 租赁订单内增车并重新审批 |\n| 续签合同 | 进行中 / 到期 | 跳转续签页 |\n| 添加被授权人 | 合同进行中 | 弹窗编辑被授权人列表 |\n| 附加费用 | 合同进行中 | 弹窗按车辆录入附加费用 |\n| 转三方合同 | 合同进行中 | 弹窗新增三方协议,上传协议附件与对方公函 |\n| 试用转正式 | 合同模板为「试用合同」 | 单步弹窗:自动拉取客户/授权人/车辆订单(车辆可编辑),可选同时转三方 |\n| 终止合同 | 合同进行中 | 二次确认,提交终止审批 |\n| 上传盖章合同 | 审批通过且法务未上传 | 仅法务部可见;多文件上传后关闭入口 |\n\n### 8.2 审批状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 未提交 | 仅保存草稿 |\n| 待审批 | 已提交,尚无节点通过 |\n| 审批中 | 已有节点通过,未完成终审 |\n| 审批通过 | 终审通过 |\n| 审批驳回 | 任一节点的驳回;可编辑重提 |\n| 审批终止 | 流程被终止 |\n| 撤回 | 提交人主动撤回 |\n\n### 8.3 合同状态口径\n\n| 状态 | 定义 |\n|---|---|\n| 草稿 | 未提交审批 |\n| 已提交审批 | 初次提交,终审未完成 |\n| 合同进行中 | 终审通过且在有效期内 |\n| 已终止 | 终止、自然结束,或续签/转三方后旧合同自动终止 |\n\n### 8.4 弹窗摘要\n\n- **删除草稿**:「是否确认删除该合同草稿」\n- **撤回**:「是否确认撤回该合同」\n- **终止**:「是否确认终止合同」→ 提交审核\n- **添加被授权人**:姓名、电话、身份证;支持增删行\n- **附加费用**:按车辆展示类型/品牌/型号/车牌,可填服务项目、费用(元,2 位小数)、生效日期\n- **转三方合同**:支持新增多条三方协议;每条可填协议名称、上传协议附件与对方公函\n- **试用转正式**:自动带出客户信息、授权人与车辆订单;车辆订单字段可编辑;可勾选「同时转三方合同」并在同一步维护三方协议\n- **上传盖章合同**:拖拽多文件;确认后标记已上传\n\n### 8.5 操作流程文档\n\n从「新增合同」起的正向与逆向全流程图见:`.spec/requirements-flow-operations.md`" }, { "type": "markdown", @@ -644,7 +705,13 @@ "type": "markdown", "id": "lc-doc-node-lc-create-main-contract", "title": "主体合同信息", - "markdown": "## 2. 主体合同信息\n\n卡片内三个可折叠区块,标题行右侧展示区块说明(展开时显示在标题后方)。\n\n### 2.1 签约信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 合同编号 | 是 | 前缀 `HT-ZL-` + 用户输入 |\n| 甲方 | 是 | 出租方签约主体,来自组织机构 |\n| 乙方 | 是 | 客户选择器;仅 A/B 级客户可选 |\n| 乙方负责人姓名 | 否 | 签约对接负责人,可与档案联系人不同 |\n| 乙方负责人手机号 | 否 | 11 位手机号 |\n\n选择甲方/乙方后,下方只读面板自动带出档案:\n\n**甲方**:户名、开户行、账号、通讯地址、联系人、电话、邮箱。\n\n**乙方**:企业名称、开户银行、银行账号、纳税人识别号、企业地址、企业电话、通讯地址、联系人、电话、邮箱;资质证照缩略图可点击放大,并展示 OCR 识别结果与有效期状态(有效 / 即将到期 / 已过期)。距到期 ≤ 3 个月在工作台生成业管待办;存在已过期证照时禁止提交新合同。\n\n未选双方时展示空状态引导。\n\n### 2.2 里程标准\n\n对应合同正文最低里程与租金减免条款。\n\n| 字段 | 说明 |\n|---|---|\n| 是否有最低里程要求 | 开关 |\n| 统计周期 | 有要求时必选:月 / 季 / 年 |\n| 目标里程 | 有要求时必填(公里) |\n| 里程类型 | 多选标签:实际里程 / 仪表里程 等 |\n\n### 2.3 费用信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 付款周期 | 是 | 如月付、季付 |\n| 氢费支付方式 | 是 | 自行解决 / 预付款 / 按月结算(选按月结算时预览切换月结算占位条款) |\n| 预付金额 | 条件 | 选择预付款时必填 |\n| 提前付款工作日 | 条件 | 选择预付款时必填 |\n| 还车氢量差单价 | 是 | 元 |\n\n**完成度**:上述必填项均已填则卡片显示「已完成」。\n\n---" + "markdown": "## 2. 主体合同信息\n\n卡片内三个可折叠区块,标题行右侧展示区块说明(展开时显示在标题后方)。\n\n### 2.1 签约信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 合同编号 | 是 | 前缀 `HT-ZL-` + 用户输入 |\n| 甲方 | 是 | 出租方签约主体,来自组织机构 |\n| 乙方 | 是 | 客户选择器;仅 A/B 级客户可选 |\n| 乙方负责人姓名 | 否 | 签约对接负责人,可与档案联系人不同 |\n| 乙方负责人手机号 | 否 | 11 位手机号 |\n\n选择甲方/乙方后,下方只读面板自动带出档案:\n\n**甲方**:户名、开户行、账号、通讯地址、联系人、电话、邮箱。\n\n**乙方**:企业名称、开户银行、银行账号、纳税人识别号、企业地址、企业电话、通讯地址、联系人、电话、邮箱;资质证照缩略图可点击放大。\n\n#### 客户资质证照(OCR 与有效期)\n\n对以下四类证照做 **OCR 识别**并校验有效期:\n\n| 证照 | 说明 |\n|---|---|\n| 客户营业执照 | 识别统一社会信用代码与营业期限 |\n| 法人身份证(正面) | 识别姓名、证件号与有效期 |\n| 法人身份证(反面) | 识别签发机关与有效期 |\n| 道路运输许可证 | 识别许可编号与有效期 |\n\n**业务规则:**\n\n1. 上传后自动 OCR 提取有效期,证照区展示「OCR 已识别」、有效期及状态(有效 / 即将到期 / 已过期)。\n2. 距到期 **≤ 3 个月** 时,系统在工作台提前生成待办任务,推送给业管人员更新客户档案。\n3. 若业管尚未更新,业务人员选择该客户后仍可查看证照,但 **禁止提交新合同**;提交时给出明确拦截提示。\n4. 原型演示:客户「杭州某某租赁有限公司(B)」含即将到期与已过期证照样例,可用于验证提示与拦截。\n\n未选双方时展示空状态引导。\n\n### 2.2 里程标准\n\n对应合同正文最低里程与租金减免条款。\n\n| 字段 | 说明 |\n|---|---|\n| 是否有最低里程要求 | 开关 |\n| 统计周期 | 有要求时必选:月 / 季 / 年 |\n| 目标里程 | 有要求时必填(公里) |\n| 里程类型 | 多选标签:实际里程 / 仪表里程 等 |\n\n### 2.3 费用信息\n\n| 字段 | 必填 | 说明 |\n|---|---|:---:|\n| 付款周期 | 是 | 如月付、季付 |\n| 氢费支付方式 | 是 | 自行解决 / 预付款 / 按月结算(选按月结算时预览切换月结算占位条款) |\n| 预付金额 | 条件 | 选择预付款时必填 |\n| 提前付款工作日 | 条件 | 选择预付款时必填 |\n| 还车氢量差单价 | 是 | 元 |\n\n**完成度**:上述必填项均已填则卡片显示「已完成」。\n\n---" + }, + { + "type": "markdown", + "id": "lc-doc-node-lc-create-signing-method", + "title": "合同签署方式", + "markdown": "# 合同签署方式\n\n与「选择合同类型」同一步配置,可选:\n\n| 方式 | 说明 |\n|---|---|\n| 线上电子签章 | 审批通过后向乙方负责人手机号发送 E签宝文件并自动归档 |\n| 线下人工上传 | 不发送电子签,审批通过后通过列表「盖章合同补传」完成闭环 |" }, { "type": "markdown", @@ -652,11 +719,17 @@ "title": "签约信息", "markdown": "# 签约信息\n\n合同编号、甲乙双方与乙方负责人。" }, + { + "type": "markdown", + "id": "lc-doc-node-lc-credentials-ocr", + "title": "客户资质证照 OCR", + "markdown": "# 客户资质证照(OCR 与有效期)\n\n对 **客户营业执照**、**法人身份证正反面**、**道路运输许可证** 做 OCR 识别,校验有效期。\n\n| 规则 | 说明 |\n|---|---|\n| OCR | 上传后自动识别证照信息与有效期 |\n| 即将到期 | 距到期 ≤ 3 个月时,工作台提前生成待办推送给业管 |\n| 已过期 | 业管未更新前,禁止提交新合同 |\n| 页面展示 | 缩略图旁显示 OCR 标识、有效期与状态徽章;临期/过期时顶部展示提示条" + }, { "type": "markdown", "id": "lc-doc-node-lc-signing-customer-principal", "title": "乙方负责人", - "markdown": "# 乙方负责人\n\n选择乙方后,在签约信息中补充填写:\n\n- **乙方负责人姓名**:签约对接负责人,可与客户档案中的联系人不同\n- **乙方负责人手机号**:用于履约沟通与通知\n\n选填;用于合同履约联系,不覆盖客户档案只读信息。" + "markdown": "# 乙方负责人\n\n选择乙方后,在签约信息中补充填写:\n\n- **乙方负责人姓名**:签约对接负责人,可与客户档案中的联系人不同\n- **乙方负责人手机号**:E签宝电子合同签章时乙方经办人\n\n**签署闭环:**\n\n- **线上电子签章**:审批通过后自动向该手机号发送 E签宝电子签章文件\n- **线下人工上传**:审批通过不发送电子签,需在列表「盖章合同补传」上传 PDF/图片,查看时可预览与下载全部附件" }, { "type": "markdown", @@ -700,6 +773,12 @@ "title": "合同备注", "markdown": "## 5. 合同备注\n\n独立卡片,与授权委托书分离。\n\n提供文本域,支持自定义输入,非必填(最多 500 字);列表页不展示备注列。\n\n---" }, + { + "type": "markdown", + "id": "lc-doc-node-lc-create-seal", + "title": "用章类型", + "markdown": "# 用章类型(第 6 步)\n\n支持多选,可选范围仅包含:\n\n| 用章 | 说明 |\n|---|---|\n| 合同章 | 合同正文用章 |\n| 公章 | 公司公章 |\n| 法人章 | 法定代表人章 |\n\n**不含**财务章、发票章。\n\n默认选中合同章;至少选择一种用章后方可提交审核。\n\n**提示**:额外勾选公章、法人章会进入非标审批流程。" + }, { "type": "markdown", "id": "lc-doc-node-lc-create-preview", @@ -718,7 +797,7 @@ "type": "markdown", "id": "lc-doc-annotation-hint", "title": "标注查看提示", - "markdown": "## 标注查看提示\n\n右侧标注工具栏可浏览目录、切换主题与颜色筛选。列表页与新增页标注按当前页面自动显隐。\n\n**PRD 同步**:修改 `src/resources/lease-contract-management/PRD.md` 或 `.spec/requirements-prd-*.md` 后,运行 `node src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs` 重新生成 `annotation-source.json`。" + "markdown": "## 标注查看提示\n\n右侧标注工具栏可浏览目录、切换主题与颜色筛选。列表页与新增页标注按当前页面自动显隐。\n\n**PRD 同步**:修改 `src/resources/lease-contract-management/PRD.md` 或 `.spec/requirements-prd-*.md`、`.spec/requirements-flow-operations.md` 后,运行 `node src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs` 重新生成 `annotation-source.json`(含 Mermaid 流程图预渲染为 SVG 图片)。" } ] } diff --git a/src/prototypes/lease-contract-management/index.tsx b/src/prototypes/lease-contract-management/index.tsx index e751221..89654c0 100644 --- a/src/prototypes/lease-contract-management/index.tsx +++ b/src/prototypes/lease-contract-management/index.tsx @@ -1,18 +1,17 @@ /** * @name 租赁合同管理 */ -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import * as antd from 'antd'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; import { - AnnotationViewer, - type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import 'antd/dist/reset.css'; import '../vehicle-management/style.css'; import './styles/index.css'; @@ -83,17 +82,6 @@ export default function LeaseContractManagementPage() { const [annotationPageId, setAnnotationPageId] = useState('list'); const navigationRef = useRef({}); - const handleDirectoryRoute = useCallback((node: AnnotationDirectoryRouteNode) => { - const payload = node.payload as { pageId?: string } | undefined; - const pageId: LeaseContractPageId = payload?.pageId === 'create' ? 'create' : 'list'; - if (pageId === 'create') { - navigationRef.current.openCreate?.(); - } else { - navigationRef.current.backToList?.(); - } - setAnnotationPageId(pageId); - }, []); - const annotationOptions = useMemo( () => ({ showToolbar: true, @@ -102,9 +90,8 @@ export default function LeaseContractManagementPage() { emptyWhenNoData: false, toolbarEdge: 'right', currentPageId: annotationPageId, - onDirectoryRoute: handleDirectoryRoute, }), - [annotationPageId, handleDirectoryRoute], + [annotationPageId], ); return ( @@ -113,7 +100,7 @@ export default function LeaseContractManagementPage() { navigationRef={navigationRef} onPageViewChange={setAnnotationPageId} /> - diff --git a/src/prototypes/lease-contract-management/lease-contract-approval-rules.js b/src/prototypes/lease-contract-management/lease-contract-approval-rules.js index 1944467..fb33960 100644 --- a/src/prototypes/lease-contract-management/lease-contract-approval-rules.js +++ b/src/prototypes/lease-contract-management/lease-contract-approval-rules.js @@ -1,16 +1,47 @@ import { hasLeaseOrderRentBelowMinimum } from './lease-order-vars.js'; import { resolveContractApprovalType, + isRiskRedlineContentModified, + isPreviewNewClauseAdded, STANDARD_CONTRACT_APPROVAL, NONSTANDARD_CONTRACT_APPROVAL, } from './lease-contract-risk-detect.js'; export { STANDARD_CONTRACT_APPROVAL, NONSTANDARD_CONTRACT_APPROVAL }; +function isPostpayPaymentMethod(feeInfo) { + return feeInfo && feeInfo.paymentMethod === 'postpay'; +} + +function isNonMonthlyPaymentPeriod(feeInfo) { + var period = feeInfo && feeInfo.paymentPeriod; + return period === 3 || period === 6 || period === 12; +} + +function hasExtraOfficialSeal(sealTypes) { + var types = sealTypes || []; + return types.indexOf('official') >= 0 || types.indexOf('legal_person') >= 0; +} + /** 综合风控红线修改与租金低于下限等规则,判定审批类型 */ export function resolveLeaseContractApprovalType(baselineHtml, currentHtml, form) { if (form && hasLeaseOrderRentBelowMinimum(form.leaseOrder)) { return NONSTANDARD_CONTRACT_APPROVAL; } + if (form && isPostpayPaymentMethod(form.feeInfo)) { + return NONSTANDARD_CONTRACT_APPROVAL; + } + if (form && isNonMonthlyPaymentPeriod(form.feeInfo)) { + return NONSTANDARD_CONTRACT_APPROVAL; + } + if (form && hasExtraOfficialSeal(form.sealTypes)) { + return NONSTANDARD_CONTRACT_APPROVAL; + } + if (isPreviewNewClauseAdded(baselineHtml, currentHtml)) { + return NONSTANDARD_CONTRACT_APPROVAL; + } + if (isRiskRedlineContentModified(baselineHtml, currentHtml)) { + return NONSTANDARD_CONTRACT_APPROVAL; + } return resolveContractApprovalType(baselineHtml, currentHtml); } diff --git a/src/prototypes/lease-contract-management/lease-contract-edit-bridge.js b/src/prototypes/lease-contract-management/lease-contract-edit-bridge.js index c1b5887..9f0e5e8 100644 --- a/src/prototypes/lease-contract-management/lease-contract-edit-bridge.js +++ b/src/prototypes/lease-contract-management/lease-contract-edit-bridge.js @@ -8,6 +8,8 @@ import { createEmptyLeaseOrderRow, createEmptyDelegateRow, PLATE_ACTUAL_DELIVERY, + normalizeLeaseOrderState, + syncRowPricingFields, } from './lease-order-vars.js'; import { inferContractTemplateId, @@ -32,13 +34,15 @@ function mapVehicleToLeaseOrderRow(vehicle, index) { if (vehicle.brand && vehicle.model) { row.brandModels = [[vehicle.brand, vehicle.model]]; } + row.vehicleQty = 1; var plate = vehicle.plateNo; row.plateNos = plate && plate !== '-' ? [plate] : [PLATE_ACTUAL_DELIVERY]; row.rent = vehicle.rent != null ? vehicle.rent : 8800; row.serviceFee = vehicle.serviceFee != null ? vehicle.serviceFee : 1200; row.deposit = vehicle.deposit != null ? vehicle.deposit : 20000; + row.leasePeriodMode = 'months'; row.leasePeriodMonths = vehicle.leasePeriodMonths != null ? vehicle.leasePeriodMonths : 12; - return row; + return syncRowPricingFields(row); } function mapAuthorizedDelegatesToPoaRows(delegates) { @@ -62,18 +66,24 @@ function buildLeaseOrderFromRecord(record) { ? vehicles.map(mapVehicleToLeaseOrderRow) : defaultOrder.rows.slice(); var deliveryDate = record.deliveryDate; - if (deliveryDate && isDeliveryDateTbd(deliveryDate)) { + var deliveryDateTbd = Boolean(record.deliveryDateTbd || isDeliveryDateTbd(deliveryDate)); + if (deliveryDateTbd) { deliveryDate = null; } - return { - insuredVehicleCount: record.insuredVehicleCount != null ? record.insuredVehicleCount : rows.length || 1, + return normalizeLeaseOrderState({ thirdPartyLiabilityMillion: record.thirdPartyLiabilityMillion != null ? record.thirdPartyLiabilityMillion : defaultOrder.thirdPartyLiabilityMillion, deliveryRegion: (record.deliveryRegion || defaultOrder.deliveryRegion).slice(), + deliveryRegionMode: record.deliveryRegionTbd ? 'tbd' : 'region', + deliveryRegionTbd: Boolean(record.deliveryRegionTbd), deliveryDate: deliveryDate || null, + deliveryDateStart: record.deliveryDateStart || deliveryDate || null, + deliveryDateEnd: record.deliveryDateEnd || null, + deliveryDateMode: deliveryDateTbd ? 'unconfirmed' : 'range', + deliveryDateTbd: deliveryDateTbd, rows: rows, - }; + }); } function buildMileageFromRecord(record) { @@ -97,6 +107,7 @@ function buildFeeInfoFromRecord(record) { } var isPrepay = hydrogenPaymentMethod === 'prepay'; return Object.assign({}, DEFAULT_FEE_INFO, { + paymentMethod: record.paymentMethod || DEFAULT_FEE_INFO.paymentMethod, paymentPeriod: record.paymentPeriod != null ? record.paymentPeriod : DEFAULT_FEE_INFO.paymentPeriod, hydrogenPaymentMethod: hydrogenPaymentMethod, prepayAmount: record.prepayAmount != null ? record.prepayAmount : (isPrepay ? 50000 : null), @@ -120,6 +131,7 @@ export function buildLeaseContractEditFormState(record) { lessorId: lessorId, customerId: customerId, contractCode: extractContractCodeInput(record.contractCode), + projectName: record.projectName || '', businessDept: record.businessDept || '', businessOwner: record.businessOwner || '', mileage: buildMileageFromRecord(record), @@ -133,6 +145,9 @@ export function buildLeaseContractEditFormState(record) { contractRemark: record.poaRemark || '', customerPrincipalName: record.customerPrincipalName || '', customerPrincipalPhone: record.customerPrincipalPhone || '', + thirdPartyCustomerId: record.thirdPartyCustomerId || '', + thirdPartyPrincipalName: record.thirdPartyPrincipalName || '', + thirdPartyPrincipalPhone: record.thirdPartyPrincipalPhone || '', sealTypes: Array.isArray(record.sealTypes) && record.sealTypes.length ? record.sealTypes.slice() : ['contract'], diff --git a/src/prototypes/lease-contract-management/lease-contract-flow-bridge.js b/src/prototypes/lease-contract-management/lease-contract-flow-bridge.js new file mode 100644 index 0000000..a7e94f4 --- /dev/null +++ b/src/prototypes/lease-contract-management/lease-contract-flow-bridge.js @@ -0,0 +1,185 @@ +/** + * 列表合同 → 续签/转正式/增车/转三方 等流程表单初始态 + */ + +import { generateAutoContractCodeSuffix } from '../contract-template-management/contract-template-vars.js'; +import { getDefaultContractTemplateId } from '../contract-template-management/contract-template-catalog.js'; +import { buildLeaseContractEditFormState } from './lease-contract-edit-bridge.js'; +import { + createDefaultLeaseOrderState, + createEmptyLeaseOrderRow, + createEmptyDelegateRow, + normalizeLeaseOrderState, + syncRowPricingFields, +} from './lease-order-vars.js'; +import { canContractVehicleReturn, isContractApprovalPassed } from './lease-contract-list-data.js'; + +export var FLOW_MODE_CREATE = 'create'; +export var FLOW_MODE_EDIT = 'edit'; +export var FLOW_MODE_RENEW = 'renew'; +export var FLOW_MODE_TRIAL_TO_FORMAL = 'trialToFormal'; +export var FLOW_MODE_ADD_VEHICLE = 'addVehicle'; +export var FLOW_MODE_ADD_POA = 'addPoa'; +export var FLOW_MODE_TRIPARTITE = 'tripartite'; + +export var FLOW_MODE_LABELS = { + create: '新增租赁合同', + edit: '编辑租赁合同', + renew: '续签合同', + trialToFormal: '试用转正式', + addVehicle: '新增车辆', + addPoa: '添加授权委托书', + tripartite: '转三方合同', +}; + +export function getDeliveredNotReturnedVehicles(record) { + if (!record || !record.vehicles || !isContractApprovalPassed(record)) return []; + return record.vehicles.filter(function (vehicle) { + return canContractVehicleReturn(vehicle, record); + }); +} + +function mapLockedVehicleToRow(vehicle, index) { + var row = createEmptyLeaseOrderRow(); + row.id = 'lo-flow-locked-' + index; + row.brandModels = vehicle.brand && vehicle.model ? [[vehicle.brand, vehicle.model]] : []; + row.vehicleQty = 1; + row.plateNos = vehicle.plateNo && vehicle.plateNo !== '-' ? [vehicle.plateNo] : []; + row.rent = vehicle.rent != null ? vehicle.rent : 8800; + row.serviceFee = vehicle.serviceFee != null ? vehicle.serviceFee : 1200; + row.deposit = vehicle.deposit != null ? vehicle.deposit : 20000; + row.leasePeriodMode = 'months'; + row.leasePeriodMonths = vehicle.leasePeriodMonths != null ? vehicle.leasePeriodMonths : 12; + row._flowLocked = true; + row._flowLockedFields = ['brandModels', 'plateNos', 'vehicleQty']; + row._deliveredAt = vehicle.actualDelivery || null; + return syncRowPricingFields(row); +} + +function buildRowsFromDeliveredVehicles(record) { + var region = (record.deliveryRegion || []).slice(); + var vehicles = getDeliveredNotReturnedVehicles(record); + return vehicles.map(function (vehicle, index) { + var row = mapLockedVehicleToRow(vehicle, index); + row._deliveryRegion = region; + row._deliveredAt = vehicle.actualDelivery || null; + return row; + }); +} + +function cloneFormState(base) { + if (!base) return null; + return JSON.parse(JSON.stringify(base)); +} + +function withNewContractCode(state) { + if (!state) return state; + state.contractCode = generateAutoContractCodeSuffix(); + return state; +} + +function withLeaseOrderRows(state, rows, extra) { + if (!state) return state; + state.leaseOrder = normalizeLeaseOrderState(Object.assign({}, state.leaseOrder, extra || {}, { + rows: rows.length ? rows : [createEmptyLeaseOrderRow()], + })); + return state; +} + +/** 续签:复制原合同,锁定已交未还车辆,可新增车辆卡片 */ +export function buildRenewContractFormState(record) { + var base = cloneFormState(buildLeaseContractEditFormState(record)); + if (!base) return null; + withNewContractCode(base); + var lockedRows = buildRowsFromDeliveredVehicles(record); + withLeaseOrderRows(base, lockedRows, { + deliveryRegion: (record.deliveryRegion || []).slice(), + deliveryRegionMode: record.deliveryRegionTbd ? 'tbd' : 'region', + deliveryDateMode: record.deliveryDateMode || 'range', + deliveryDateTbd: Boolean(record.deliveryDateTbd), + }); + base._flowMode = FLOW_MODE_RENEW; + base._sourceContractCode = record.contractCode; + base._pickupReceivableHint = '已交未还车辆不生成提车应收款;新增车辆按新增合同规则生成提车应收款。'; + return base; +} + +/** 转正式:锁定已交未还车辆品牌型号车牌,租金服务项可改 */ +export function buildTrialToFormalFormState(record) { + var base = cloneFormState(buildLeaseContractEditFormState(record)); + if (!base) return null; + withNewContractCode(base); + base.contractType = 'formal'; + var lockedRows = buildRowsFromDeliveredVehicles(record); + lockedRows.forEach(function (row) { + row._flowLockedFields = ['brandModels', 'plateNos', 'vehicleQty']; + }); + withLeaseOrderRows(base, lockedRows); + base._flowMode = FLOW_MODE_TRIAL_TO_FORMAL; + base._sourceContractCode = record.contractCode; + base._pickupReceivableHint = '车辆金额变更将重新记录提车应收款;已交未还车辆完成后不再生成交车任务。'; + return base; +} + +/** 新增车辆:仅新附件1 */ +export function buildAddVehicleFormState(record) { + var base = buildLeaseContractEditFormState(record); + if (!base) return null; + base = cloneFormState(base); + withNewContractCode(base); + withLeaseOrderRows(base, [createEmptyLeaseOrderRow()]); + base._flowMode = FLOW_MODE_ADD_VEHICLE; + base._sourceContractCode = record.contractCode; + base._pickupReceivableHint = '审批通过后,新增车辆按新增合同规则生成提车应收款任务。'; + base._signingHint = '无需重新签署主合同,仅对新增附件1(租赁订单)发起审批。'; + return base; +} + +/** 添加授权委托书:仅维护受托人,预览仅授权委托书 */ +export function buildAddPowerOfAttorneyFormState(record) { + var base = buildLeaseContractEditFormState(record); + if (!base) return null; + base = cloneFormState(base); + withNewContractCode(base); + base.powerOfAttorney = { + delegates: [createEmptyDelegateRow()], + }; + base._flowMode = FLOW_MODE_ADD_POA; + base._sourceContractCode = record.contractCode; + base._signingHint = '无需重新签署主合同,仅对新增授权委托书发起审批。'; + return base; +} + +/** 转三方:模板固定转三方协议,可增车 */ +export function buildTripartiteContractFormState(record) { + var base = cloneFormState(buildLeaseContractEditFormState(record)); + if (!base) return null; + withNewContractCode(base); + var tripartiteTemplateId = getDefaultContractTemplateId('转三方协议'); + base.contractTemplateId = tripartiteTemplateId + || base.contractTemplateId + || getDefaultContractTemplateId('合同') + || ''; + base._flowMode = FLOW_MODE_TRIPARTITE; + base._sourceContractCode = record.contractCode; + base._requiresTripartiteParty = true; + base._signingHint = '审批通过后沿用原合同签署方式;线上发送 E签宝链接,线下需完成盖章合同补传。'; + base.thirdPartyCustomerId = ''; + base.thirdPartyPrincipalName = ''; + base.thirdPartyPrincipalPhone = ''; + var rows = base.leaseOrder && base.leaseOrder.rows ? base.leaseOrder.rows.slice() : [createEmptyLeaseOrderRow()]; + withLeaseOrderRows(base, rows); + return base; +} + +export function getFlowPageTitle(flowMode) { + return FLOW_MODE_LABELS[flowMode] || FLOW_MODE_LABELS.create; +} + +export function countNewPickupVehicles(leaseOrder, lockedRowPrefix) { + if (!leaseOrder || !leaseOrder.rows) return 0; + var prefix = lockedRowPrefix || 'lo-flow-locked-'; + return leaseOrder.rows.filter(function (row) { + return !row._flowLocked && String(row.id || '').indexOf(prefix) !== 0; + }).length; +} diff --git a/src/prototypes/lease-contract-management/lease-contract-form-validation.js b/src/prototypes/lease-contract-management/lease-contract-form-validation.js index 6a4187f..51da95a 100644 --- a/src/prototypes/lease-contract-management/lease-contract-form-validation.js +++ b/src/prototypes/lease-contract-management/lease-contract-form-validation.js @@ -17,15 +17,16 @@ function isMainContractFieldsComplete(form) { if (!mileage.period) return false; if (mileage.targetKm == null || mileage.targetKm === '') return false; if (mileage.reductionYuan == null || mileage.reductionYuan === '') return false; + if (!mileage.validUntil) return false; } var feeInfo = form.feeInfo || {}; + if (!feeInfo.paymentMethod) return false; if (feeInfo.paymentPeriod == null || feeInfo.paymentPeriod === '') return false; if (!feeInfo.hydrogenPaymentMethod) return false; if (feeInfo.hydrogenPaymentMethod === 'prepay') { if (feeInfo.prepayAmount == null || feeInfo.prepayAmount === '') return false; - if (feeInfo.payAheadWorkdays == null || feeInfo.payAheadWorkdays === '') return false; } if (feeInfo.returnHydrogenDiffUnitPrice == null || feeInfo.returnHydrogenDiffUnitPrice === '') { @@ -78,6 +79,14 @@ export function isSealTypeSelected(form) { return Boolean(form.sealType); } +/** 转三方:丙方客户已选且不与乙方重复 */ +export function isTripartitePartyComplete(form) { + var thirdPartyCustomerId = (form.thirdPartyCustomerId || '').trim(); + if (!thirdPartyCustomerId) return false; + if (form.customerId && thirdPartyCustomerId === form.customerId) return false; + return true; +} + /** 判断新增租赁合同左侧表单必填项是否均已填写(提交审核) */ export function isLeaseContractFormComplete(form) { return isMainContractFieldsComplete(form) diff --git a/src/prototypes/lease-contract-management/lease-contract-list-data.js b/src/prototypes/lease-contract-management/lease-contract-list-data.js index 5bca4e1..4a4d7c9 100644 --- a/src/prototypes/lease-contract-management/lease-contract-list-data.js +++ b/src/prototypes/lease-contract-management/lease-contract-list-data.js @@ -21,10 +21,15 @@ export var PAYMENT_PERIOD_CYCLE_LABELS = { /** @deprecated 列表展示请用 formatPaymentPeriodLabel */ export var PAYMENT_PERIOD_LABELS = { - 1: '1个月', - 3: '3个月', - 6: '6个月', - 12: '12个月', + 1: '月付', + 3: '季付', + 6: '半年付', + 12: '年付', +}; + +export var PAYMENT_METHOD_LABELS = { + advance: '先付后用', + postpay: '先用后付', }; export var HYDROGEN_PAYMENT_LABELS = { @@ -53,6 +58,11 @@ export function formatPaymentPeriodLabel(period) { return String(period); } +export function formatPaymentMethodLabel(method) { + if (!method) return '-'; + return PAYMENT_METHOD_LABELS[method] || String(method); +} + export function formatHydrogenPaymentLabel(method) { if (!method) return '-'; if (HYDROGEN_PAYMENT_LABELS[method]) return HYDROGEN_PAYMENT_LABELS[method]; @@ -292,6 +302,25 @@ export function resolveContractTemplateTypeLabel(record) { return getContractTypeLabel(shortType); } +/** 列表筛选:合同模板(合同名称维度,如正式合同 / 试用合同) */ +export function resolveRecordContractTemplateCategory(record) { + if (!record) return ''; + var templateId = record.contractTemplateId || inferContractTemplateId(record); + var option = templateId ? getContractTemplateOption(templateId) : null; + if (option && option.contractType) return option.contractType; + return record.contractType || ''; +} + +/** 列表筛选:标准合同名称(已发布模板文档名,如 2026年标准商用车租赁合同) */ +export function resolveRecordContractTemplateStandardName(record) { + if (!record) return ''; + var templateId = record.contractTemplateId || inferContractTemplateId(record); + if (!templateId) return ''; + var option = getContractTemplateOption(templateId); + if (option) return option.title || option.fileName || templateId; + return templateId; +} + export function getReturnSettlementStatusTone(status) { if (status === '已完成' || status === '已结算') return 'green'; if (status === '审批中') return 'amber'; @@ -322,6 +351,16 @@ export function getReturnSettlementDisplayTone(displayLabel) { return 'gray'; } +export function resolveLeaseBillStatus(vehicle) { + if (!isContractVehicleDelivered(vehicle)) return null; + return vehicle.leaseBillStatus || '正常'; +} + +export function getLeaseBillStatusTone(status) { + if (status === '欠费') return 'red'; + return 'green'; +} + export function formatMileageSummary(record) { if (!record.hasMinimumMileage) return '无最低里程要求'; var period = MILEAGE_PERIOD_LABELS[record.mileagePeriod] || ''; @@ -359,6 +398,41 @@ export function formatDeliveryRegion(region) { return region.join(' / '); } +export var DELIVERY_REGION_TBD_LABEL = '交还车时约定'; + +export var LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL = '暂未确认'; + +export function isContractDeliveryRegionTbd(record) { + if (!record) return false; + return Boolean(record.deliveryRegionTbd || record.deliveryRegionMode === 'tbd'); +} + +export function isContractDeliveryDateUnconfirmed(record) { + if (!record) return false; + return Boolean( + record.deliveryDateTbd + || record.deliveryDateMode === 'unconfirmed' + || isDeliveryDateTbd(record.deliveryDate) + || record.deliveryDate === LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL, + ); +} + +export function formatContractDeliveryRegionLabel(record) { + if (!record) return '-'; + if (isContractDeliveryRegionTbd(record)) return DELIVERY_REGION_TBD_LABEL; + return formatDeliveryRegion(record.deliveryRegion); +} + +export function canEditContractDeliveryArrangement(record) { + if (!record) return false; + if (record.deliveryPlanLocked === true || record.pickupReceivableCompleted === true) { + return false; + } + return (record.vehicles || []).some(function (vehicle) { + return !isContractVehicleDelivered(vehicle); + }); +} + var LEGACY_CONTRACT_APPROVAL_TYPE_MAP = { '标准合同审批': '标准合同', '非标准合同审批': '非标准合同', @@ -369,6 +443,11 @@ export function formatContractApprovalTypeLabel(value) { return LEGACY_CONTRACT_APPROVAL_TYPE_MAP[value] || value; } +export function getContractApprovalFlowKindLabel(record) { + var label = formatContractApprovalTypeLabel(record && record.contractApprovalType); + return label === '非标准合同' ? '非标准合同流程' : '标准合同流程'; +} + var SAMPLE_AUTHORIZED_DELEGATES = [ { name: '王明', contact: '13900139001', idNumber: '330102199001011234' }, { name: '李华', contact: '13900139002', idNumber: '330102199002022345' }, @@ -537,7 +616,7 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ ], approvalStatus: '未提交', contractStatus: '草稿', - customerName: '上海某某运输公司', + customerName: '上海某某运输有限公司', signingCompany: '上海羚牛', businessDept: '业务2部', businessOwner: '李专员', @@ -615,10 +694,12 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ vehicleCount: 2, vehicles: [ { vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '9.6米氢燃料电池车', plateNo: '-', actualDelivery: '-', deliveryPerson: '-' }, - { vehicleType: '49吨牵引车头', brand: '飞驰', model: '集卡头', plateNo: '浙C30003', actualDelivery: '2025-02-18 14:00', returnTime: '2025-04-01 09:00', deliveryPerson: '孙运维' }, + { vehicleType: '49吨牵引车头', brand: '飞驰', model: '集卡头', plateNo: '浙C30003', actualDelivery: '2025-02-18 14:00', returnTime: '2025-04-01 09:00', deliveryPerson: '孙运维', returnSettlementStatus: '审批中', returnSettlementApprover: '赵主管、王财务', leaseBillStatus: '正常' }, ], approvalStatus: '审批中', contractStatus: '已提交审批', + contractOriginFlow: 'renew', + sourceContractCode: 'HT-ZL-2024-009', customerName: '嘉兴某某物流有限公司', signingCompany: '嘉兴羚牛', businessDept: '业务1部', @@ -665,7 +746,7 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ ], approvalStatus: '审批中', contractStatus: '变更', - customerName: '上海某某运输公司', + customerName: '上海某某运输有限公司', signingCompany: '上海羚牛', businessDept: '业务2部', businessOwner: '李专员', @@ -704,7 +785,7 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ vehicles: [ { vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', plateNo: '苏A60006', actualDelivery: '2025-01-20 08:00', returnTime: '2025-06-01 10:00', deliveryPerson: '吴运维', leasePeriodMonths: 24 }, { vehicleType: '18吨厢式货车', brand: '现代', model: '18吨氢燃料电池车', plateNo: '苏A60007', actualDelivery: '2025-01-21 10:00', returnTime: '2025-06-01 10:00', deliveryPerson: '郑运维', leasePeriodMonths: 24 }, - { vehicleType: '牵引车头', brand: '飞驰', model: '集卡头', plateNo: '苏A60008', actualDelivery: '2025-01-22 14:00', returnTime: '2025-06-01 10:00', deliveryPerson: '冯运维', leasePeriodMonths: 24 }, + { vehicleType: '牵引车头', brand: '飞驰', model: '集卡头', plateNo: '苏A60008', actualDelivery: '2025-01-22 14:00', deliveryPerson: '冯运维', leasePeriodMonths: 24 }, ], approvalStatus: '审批通过', contractStatus: '合同进行中', @@ -739,6 +820,12 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ updater: '王专员', updateTime: '2025-01-25 11:00', remark: '-', + contractSigningMethod: 'offline_manual', + stampedContractFiles: [ + { uid: 'stamp-6-1', name: 'HT-ZL-2025-006-盖章合同.pdf' }, + { uid: 'stamp-6-2', name: 'HT-ZL-2025-006-授权委托书扫描件.jpg' }, + ], + offlineStampSupplementedAt: '2025-01-25 11:00', legalStampedContractUploaded: true, approvalFlowNodes: [ { nodeTitle: '法务审核', result: 'passed', operatorName: '陈法务', operatorTime: '2025-01-22 16:40:00', comment: '标准合同审批,正文无红线条款改动。' }, @@ -753,7 +840,20 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ projectName: '无锡试用租赁项目', vehicleCount: 1, vehicles: [ - { vehicleType: '小客车', brand: '宇通', model: '氢燃料电池客车', plateNo: '苏B70007', actualDelivery: '2025-02-01 09:30', returnTime: '2025-05-15 16:00', deliveryPerson: '陈运维', leasePeriodMonths: 3 }, + { + vehicleType: '小客车', + brand: '宇通', + model: '氢燃料电池客车', + plateNo: '苏B70007', + actualDelivery: '2025-02-01 09:30', + returnTime: '2025-05-15 16:00', + deliveryPerson: '陈运维', + leasePeriodMonths: 3, + extraServiceValues: ['wear-insurance'], + extraFee: '80', + extraFeeEffectiveDate: '2025-02-01', + extraFeeBillingMode: '先付后用', + }, ], approvalStatus: '审批通过', contractStatus: '合同进行中', @@ -786,6 +886,9 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ updater: '-', updateTime: '-', remark: '试用 3 个月', + contractSigningMethod: 'offline_manual', + customerPrincipalPhone: '13900139004', + customerPrincipalEmail: 'zhangsan@example.com', legalStampedContractUploaded: false, approvalFlowNodes: [ { nodeTitle: '法务审核', result: 'passed', operatorName: '周法务', operatorTime: '2025-01-30 17:10:00', comment: '试用合同条款已按非标准审批要求补充说明。' }, @@ -804,7 +907,7 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ ], approvalStatus: '审批驳回', contractStatus: '已提交审批', - customerName: '上海某某运输公司', + customerName: '上海某某运输有限公司', signingCompany: '上海羚牛', businessDept: '业务2部', businessOwner: '李专员', @@ -843,8 +946,8 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ { vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '9.6米氢燃料电池车', plateNo: '苏F90009', actualDelivery: '2024-03-01 09:00', returnTime: '2024-12-20 15:00', deliveryPerson: '褚运维', leasePeriodMonths: 12 }, { vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', plateNo: '苏F90010', actualDelivery: '2024-03-02 10:00', returnTime: '2024-12-20 15:00', deliveryPerson: '卫运维', leasePeriodMonths: 12 }, ], - approvalStatus: '审批通过', - contractStatus: '已终止', + approvalStatus: '审批终止', + contractStatus: '审批终止', terminatedBy: 'renewal', supersededByContractCode: 'HT-ZL-2025-012', customerName: '杭州某某租赁有限公司', @@ -888,6 +991,8 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ ], approvalStatus: '审批通过', contractStatus: '已结束', + contractOriginFlow: 'trialToFormal', + sourceContractCode: 'HT-ZL-2025-007', customerName: '嘉兴某某物流有限公司', signingCompany: '嘉兴羚牛', businessDept: '业务1部', @@ -918,6 +1023,7 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ updateTime: '2025-01-10 14:00', remark: '-', legalStampedContractUploaded: true, + onlineEsignCompletedAt: '2024-05-25 16:30', approvalFlowNodes: [ { nodeTitle: '业务负责人审批', result: 'passed', operatorName: '张经理', operatorTime: '2024-05-22 11:00:00', comment: '单车试用转正式合作,条款无异常。' }, { nodeTitle: '发起审批', result: 'passed', operatorName: '张经理', operatorTime: '2024-05-20 09:10:00' }, @@ -967,6 +1073,8 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ vehicles: buildOnLeaseFleetDemoVehicles(ON_LEASE_FLEET_VEHICLE_CASES), approvalStatus: '审批通过', contractStatus: '合同进行中', + contractOriginFlow: 'renew', + sourceContractCode: 'HT-ZL-2024-009', customerName: '嘉兴某某物流有限公司', signingCompany: '嘉兴羚牛', businessDept: '业务1部', @@ -998,14 +1106,90 @@ export var LEASE_CONTRACT_LIST_RECORDS = [ updateTime: '2025-02-10 09:00', remark: '在租车辆概览用例合同(品牌/型号/车型与主数据枚举对齐)', legalStampedContractUploaded: true, + onlineEsignCompletedAt: '2025-01-10 14:20', + onlineEsignContractFiles: [ + { uid: 'esign-12-1', name: 'HT-ZL-2025-012-电子签章合同.pdf' }, + { uid: 'esign-12-2', name: 'HT-ZL-2025-012-补充协议电子签章.pdf' }, + ], approvalFlowNodes: [ { nodeTitle: '法务审核', result: 'passed', operatorName: '陈法务', operatorTime: '2025-01-08 16:00:00', comment: '七车批量租赁,车型与公告型号已核对。' }, { nodeTitle: '业务负责人审批', result: 'passed', operatorName: '张经理', operatorTime: '2025-01-07 11:00:00', comment: '同意按标准合同审批。' }, { nodeTitle: '发起审批', result: 'passed', operatorName: '张经理', operatorTime: '2025-01-06 09:30:00' }, ], }, + { + id: '13', + contractCode: 'HT-ZL-2026-010', + projectName: '宁波城配新车投放项目', + vehicleCount: 4, + vehicles: [ + { vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', plateNo: '浙B88001', actualDelivery: '-', deliveryPerson: '-' }, + { vehicleType: '4.5吨厢式货车', brand: '福田', model: '欧马可S3', plateNo: '浙B88002', actualDelivery: '-', deliveryPerson: '-' }, + { vehicleType: '18吨厢式货车', brand: '东风', model: 'DFH1180', plateNo: '浙B88003', actualDelivery: '-', deliveryPerson: '-' }, + { vehicleType: '4.5吨厢式货车', brand: '江淮', model: '格尔发K5', plateNo: '浙B88004', actualDelivery: '-', deliveryPerson: '-' }, + ], + approvalStatus: '审批通过', + contractStatus: '合同进行中', + contractOriginFlow: 'tripartite', + sourceContractCode: 'HT-ZL-2025-006', + customerName: '宁波某某供应链有限公司', + signingCompany: '嘉兴羚牛', + businessDept: '业务1部', + businessOwner: '张经理', + contractType: '正式合同', + contractApprovalType: '标准合同', + paymentPeriod: 3, + hydrogenPaymentMethod: 'prepay', + deliveryRegion: ['浙江省', '宁波市'], + deliveryRegionMode: 'region', + deliveryRegionTbd: false, + deliveryDate: null, + deliveryDateMode: 'unconfirmed', + deliveryDateTbd: true, + deliveryDateStart: null, + deliveryDateEnd: null, + pickupReceivableCreated: true, + insuredVehicleCount: 4, + thirdPartyLiabilityMillion: 200, + hasMinimumMileage: true, + mileagePeriod: 'month', + mileageTargetKm: 6000, + delegateCount: 1, + poaUploaded: true, + authorizedDelegates: [ + { name: '林晓峰', contact: '13900139100', idNumber: '330102199101011234' }, + ], + poaRemark: '新车投放合同已审批通过,待业务办理提车应收款。', + contractEndDate: '2027-03-14', + contactName: '钱经理', + contactPhone: '13800138100', + creator: '张经理', + createTime: '2026-02-20 09:00', + updater: '张经理', + updateTime: '2026-02-28 16:30', + remark: '提车应收款「办理」演示用例:合同已提交审核并同步生成主记录,尚未办理提车。', + legalStampedContractUploaded: true, + onlineEsignCompletedAt: '2026-02-28 16:30', + approvalFlowNodes: [ + { nodeTitle: '法务审核', result: 'passed', operatorName: '陈法务', operatorTime: '2026-02-25 15:20:00', comment: '标准合同条款无修改,同意通过。' }, + { nodeTitle: '业务负责人审批', result: 'passed', operatorName: '张经理', operatorTime: '2026-02-24 11:00:00', comment: '宁波城配新车投放方案可行,同意通过。' }, + { nodeTitle: '发起审批', result: 'passed', operatorName: '张经理', operatorTime: '2026-02-22 10:00:00' }, + ], + }, ]; +export function getLeaseProjectNameOptions() { + var seen = {}; + var options = []; + LEASE_CONTRACT_LIST_RECORDS.forEach(function (record) { + var name = record.projectName; + if (!name || seen[name]) return; + seen[name] = true; + options.push({ value: name, label: name }); + }); + return options; +} + export function resolveSigningCompanyFullName(record) { var lessorId = record.lessorId || SIGNING_COMPANY_LESSOR_IDS[record.signingCompany]; var lessor = getLessorCompanyById(lessorId || record.signingCompany); @@ -1032,9 +1216,15 @@ export function getCurrentApproverLabel(record) { export function shouldHideCurrentApprover(approvalStatus) { return approvalStatus === '审批通过' || approvalStatus === '审批驳回' + || approvalStatus === '审批终止' || approvalStatus === '撤回'; } +/** 合同审批是否已通过(仅通过后才有交车/还车等履约数据) */ +export function isContractApprovalPassed(record) { + return Boolean(record && String(record.approvalStatus || '').trim() === '审批通过'); +} + export function formatContractHandoverMileage(mile) { if (mile == null || mile === '' || mile === '-') return ''; var num = Number(mile); @@ -1059,7 +1249,11 @@ export function isDeliveryDateTbd(value) { export function formatContractDeliveryDateLabel(record) { if (!record) return '-'; - if (record.deliveryDateTbd || isDeliveryDateTbd(record.deliveryDate)) return DELIVERY_DATE_TBD_LABEL; + if (isContractDeliveryDateUnconfirmed(record)) return LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL; + if (record.deliveryDateStart && record.deliveryDateEnd) { + return record.deliveryDateStart + ' ~ ' + record.deliveryDateEnd; + } + if (record.deliveryDateTbd || isDeliveryDateTbd(record.deliveryDate)) return LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL; return record.deliveryDate || '-'; } @@ -1067,7 +1261,7 @@ export function resolveVehicleDeliveryDateTbd(vehicle, record) { if (!vehicle || isContractVehicleDelivered(vehicle)) return false; if (vehicle.deliveryDateTbd != null) return Boolean(vehicle.deliveryDateTbd); if (!record) return false; - return Boolean(record.deliveryDateTbd || isDeliveryDateTbd(record.deliveryDate)); + return isContractDeliveryDateUnconfirmed(record); } export function resolveVehiclePlannedDeliveryDate(vehicle, record) { @@ -1087,9 +1281,9 @@ export function formatVehicleDeliveryPlanDateLabel(vehicle, record) { var deliveredAt = formatContractHandoverDateTimeMinute(vehicle.deliveryTime || vehicle.actualDelivery); return deliveredAt && deliveredAt !== '-' ? deliveredAt : '-'; } - if (resolveVehicleDeliveryDateTbd(vehicle, record)) return DELIVERY_DATE_TBD_LABEL; + if (resolveVehicleDeliveryDateTbd(vehicle, record)) return LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL; var date = resolveVehiclePlannedDeliveryDate(vehicle, record); - if (!date || isDeliveryDateTbd(date)) return DELIVERY_DATE_TBD_LABEL; + if (!date || isDeliveryDateTbd(date)) return LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL; return date; } @@ -1121,21 +1315,79 @@ export function isVehicleLeasePeriodExpired(vehicle, record, referenceDate) { return ref.getTime() > leaseEnd.getTime(); } -var LEASE_STATUS_DYNAMIC_BASE = ['合同进行中', '到期合同']; - -export function resolveContractStatusByVehicleLease(record) { +/** + * 合同状态标签:由保存/提交动作与审批状态共同决定。 + * - 仅保存 → 草稿 + * - 提交审批且未通过 → 已提交审批 + * - 提交审批且审批通过 → 合同进行中 + * - 提交审批且审批终止 → 已终止 + * - 提交审批且审批驳回 → 草稿 + */ +export function resolveContractDisplayStatus(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 '合同进行中'; + var approval = String(record.approvalStatus || '').trim(); + if (approval === '审批终止') return '已终止'; + if (approval === '审批驳回' || approval === '未提交' || approval === '撤回') return '草稿'; + if (approval === '审批通过') return '合同进行中'; + if (approval === '待审批' || approval === '审批中') return '已提交审批'; + return String(record.contractStatus || '').trim() || '草稿'; +} + +var PROJECT_INFO_ORIGIN_FLOW_LABELS = { + renew: '续签', + trialToFormal: '转正式', + tripartite: '转三方', +}; + +function hasSubmittedContractForApproval(approval) { + return Boolean(approval && approval !== '未提交'); +} + +/** 项目信息列:基础状态标签(草稿 / 已提交 / 进行中 / 已终止) */ +export function resolveProjectInfoBaseStatus(record) { + if (!record) return '草稿'; + var approval = String(record.approvalStatus || '').trim(); + if (approval === '未提交' || approval === '审批驳回' || approval === '撤回') return '草稿'; + if (approval === '审批终止') return '已终止'; + if (approval === '审批通过') return '进行中'; + if (approval === '待审批' || approval === '审批中') return '已提交'; + return '草稿'; +} + +/** 项目信息列:来源流程标签(续签 / 转正式 / 转三方),仅已提交审批后展示 */ +export function resolveProjectInfoOriginTag(record) { + if (!record) return null; + var approval = String(record.approvalStatus || '').trim(); + if (!hasSubmittedContractForApproval(approval)) return null; + var flow = String(record.contractOriginFlow || record._flowMode || '').trim(); + return PROJECT_INFO_ORIGIN_FLOW_LABELS[flow] || null; +} + +/** 项目信息列:完整标签序列(来源标签在前,基础状态在后) */ +export function resolveProjectInfoStatusTags(record) { + var tags = []; + var origin = resolveProjectInfoOriginTag(record); + if (origin) tags.push(origin); + tags.push(resolveProjectInfoBaseStatus(record)); + return tags; +} + +export function projectInfoStatusTone(label) { + switch (label) { + case '进行中': return 'green'; + case '已提交': return 'blue'; + case '已终止': return 'red'; + case '续签': + case '转正式': + case '转三方': return 'amber'; + case '草稿': + default: return 'gray'; + } +} + +/** @deprecated 使用 resolveContractDisplayStatus */ +export function resolveContractStatusByVehicleLease(record) { + return resolveContractDisplayStatus(record); } export function isContractVehicleDelivered(vehicle) { @@ -1148,7 +1400,8 @@ export function isContractVehicleReturned(vehicle) { return Boolean(time && String(time).trim() && time !== '-'); } -export function canContractVehicleReturn(vehicle) { +export function canContractVehicleReturn(vehicle, record) { + if (record && !isContractApprovalPassed(record)) return false; return isContractVehicleDelivered(vehicle) && !isContractVehicleReturned(vehicle); } @@ -1171,7 +1424,7 @@ export function getLastDeliveredNotReturnedVehicle(record) { var lastTs = -1; for (var i = 0; i < vehicles.length; i++) { var vehicle = vehicles[i]; - if (!canContractVehicleReturn(vehicle)) continue; + if (!canContractVehicleReturn(vehicle, record)) continue; var ts = getVehicleDeliveryTimestamp(vehicle); if (ts == null) ts = i; if (ts >= lastTs) { @@ -1227,11 +1480,11 @@ export function isContractTerminatedForKpi(record) { } /** 不计入在租统计的合同状态 */ -var ON_LEASE_EXCLUDED_CONTRACT_STATUSES = ['草稿', '已结束', '已终止', '未提交', '已提交审批']; +var ON_LEASE_EXCLUDED_CONTRACT_STATUSES = ['草稿', '已提交审批', '审批终止']; /** 当前在租:已交车、未还车,且合同处于履约期 */ export function isVehicleCurrentlyOnLease(vehicle, record) { - if (!canContractVehicleReturn(vehicle)) return false; + if (!canContractVehicleReturn(vehicle, record)) return false; if (!record) return false; var status = String(record.contractStatus || '').trim(); if (!status || ON_LEASE_EXCLUDED_CONTRACT_STATUSES.indexOf(status) >= 0) return false; @@ -1308,13 +1561,18 @@ export function getContractMileageForecastStatusTone(status) { } export function enrichLeaseContractVehicle(vehicle, index, recordIndex, record) { - var delivered = vehicle.actualDelivery && String(vehicle.actualDelivery).trim() && vehicle.actualDelivery !== '-'; + var approvalPassed = isContractApprovalPassed(record); + var rawDelivered = vehicle.actualDelivery && String(vehicle.actualDelivery).trim() && vehicle.actualDelivery !== '-'; + var delivered = approvalPassed && rawDelivered; 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 hasExplicitReturn = approvalPassed + && vehicle.returnTime + && String(vehicle.returnTime).trim() + && vehicle.returnTime !== '-'; var hasMinMileage = record && record.hasMinimumMileage; var progress = vehicle.mileageProgress; if (!hasMinMileage) { @@ -1375,7 +1633,17 @@ export function enrichLeaseContractVehicle(vehicle, index, recordIndex, record) } var returnSettlementApprover = vehicle.returnSettlementApprover; if (hasReturn && returnSettlementStatus === '审批中' && !returnSettlementApprover) { - returnSettlementApprover = ['王财务', '赵主管', '刘经理'][seed % 3]; + var approverPool = ['王财务', '赵主管', '刘经理', '超级用户', '金可鹏']; + if (seed % 3 === 0) { + returnSettlementApprover = approverPool[seed % approverPool.length] + + '、' + approverPool[(seed + 1) % approverPool.length]; + } else { + returnSettlementApprover = approverPool[seed % approverPool.length]; + } + } + var leaseBillStatus = vehicle.leaseBillStatus; + if (delivered && !leaseBillStatus) { + leaseBillStatus = seed % 5 === 0 ? '欠费' : '正常'; } var dailyAvgMileage7d = vehicle.dailyAvgMileage7d; if (dailyAvgMileage7d == null && delivered && hasMinMileage && record) { @@ -1396,6 +1664,7 @@ export function enrichLeaseContractVehicle(vehicle, index, recordIndex, record) vin: vehicle.vin || (vehicle.plateNo && vehicle.plateNo !== '-' ? 'LFWNKVPH8K1' + String(10000 + seed).slice(-5) : '-'), + actualDelivery: delivered ? (vehicle.actualDelivery || deliveryTime || '-') : '-', deliveryTime: deliveryTime, deliveryMileage: deliveryMileage, deliveryPerson: deliveryPerson, @@ -1414,6 +1683,7 @@ export function enrichLeaseContractVehicle(vehicle, index, recordIndex, record) periodStartMileage: periodStartMileage, returnSettlementStatus: hasReturn ? returnSettlementStatus : null, returnSettlementApprover: hasReturn && returnSettlementStatus === '审批中' ? returnSettlementApprover : null, + leaseBillStatus: delivered ? leaseBillStatus : null, leasePeriodMonths: vehicle.leasePeriodMonths != null ? vehicle.leasePeriodMonths : getVehicleLeasePeriodMonths(vehicle, record), @@ -1455,6 +1725,7 @@ export function enrichLeaseContractRecord(record, recordIndex) { var enriched = Object.assign({}, record, { lessorId: lessorId, contractTemplateId: record.contractTemplateId || templateId, + paymentMethod: record.paymentMethod || (recordIndex % 3 === 1 ? 'postpay' : 'advance'), signingCompanyFullName: resolveSigningCompanyFullName(Object.assign({}, record, { lessorId: lessorId })), updater: lastUpdateAudit.updater, updateTime: lastUpdateAudit.updateTime, @@ -1465,7 +1736,7 @@ export function enrichLeaseContractRecord(record, recordIndex) { enriched.contractTemplateTypeLabel = resolveContractTemplateTypeLabel(enriched); enriched.overallMileageProgress = computeContractOverallMileageProgress(enriched); enriched.overallMileageForecastStatus = computeContractMileageForecastStatus(enriched); - enriched.contractStatus = resolveContractStatusByVehicleLease(enriched); + enriched.contractStatus = resolveContractDisplayStatus(enriched); return enriched; } diff --git a/src/prototypes/lease-contract-management/lease-contract-preview-build.js b/src/prototypes/lease-contract-management/lease-contract-preview-build.js index 2403ee7..f632911 100644 --- a/src/prototypes/lease-contract-management/lease-contract-preview-build.js +++ b/src/prototypes/lease-contract-management/lease-contract-preview-build.js @@ -4,6 +4,7 @@ import { getContractTemplateBaseHtml } from '../contract-template-management/contract-template-catalog.js'; import { getTemplateSectionVehicleBindings } from '../contract-template-management/contract-template-store.js'; import { applyAttachmentSectionVehicleFilter, renumberAttachmentSections } from '../contract-template-management/contract-template-section-filter.js'; +import { splitMonolithicHtml } from '../contract-template-management/contract-template-section-html.js'; import { applyTemplateVars, buildLeasePreviewVars, @@ -13,7 +14,7 @@ import { CUSTOMER_CREDENTIAL_ITEMS, CONTRACT_CODE_PREFIX, } from '../contract-template-management/contract-template-vars.js'; -import { calcRentServiceSubtotal, formatExtraServicesDisplay, formatLeasePeriod, normalizeExtraServices, normalizeBrandModels, normalizePlateNos, formatBrandModelPair, PLATE_ACTUAL_DELIVERY, calcInsuredVehicleCount, getRowVehicleCountForPricing, normalizeDelegateRows } from './lease-order-vars.js'; +import { calcRentServiceSubtotal, formatExtraServicesPreviewLines, formatLeasePeriod, normalizeExtraServices, normalizeBrandModels, normalizePlateNos, formatBrandModelPair, PLATE_ACTUAL_DELIVERY, calcInsuredVehicleCount, getRowVehicleCountForPricing, normalizeDelegateRows, formatLeaseOrderDeliveryRegion, formatLeaseOrderDeliveryDate } from './lease-order-vars.js'; import { injectPrototypeVehicleClauses, collectVehicleKeysFromLeaseOrder, @@ -127,6 +128,7 @@ export function applyMileageStandardPatch(html, mileage) { export function applyPaymentPeriodPatch(html, feeInfo) { if (!html || !feeInfo) return html; var months = feeInfo.paymentPeriod != null ? String(feeInfo.paymentPeriod) : ''; + var paymentMethodLabel = feeInfo.paymentMethod === 'postpay' ? '先用后付' : '先付后用'; var start = html.indexOf('2.1.1'); if (start < 0) return html; var end = html.indexOf('2.1.2', start); @@ -134,47 +136,77 @@ export function applyPaymentPeriodPatch(html, feeInfo) { var head = html.slice(0, start); var slice = html.slice(start, end); var tail = html.slice(end); + slice = slice.replace(/(租金、服务费)(先付后用|先用后付)/, '$1' + escapeHtml(paymentMethodLabel)); slice = slice.replace(/(每【)\s*([\s\u00a0]*)(】个自然月为一个付款周期)/, '$1' + escapeHtml(months) + '$3'); return head + slice + tail; } -/** 2.2 氢费支付方式:按月结算时切换为占位条款(正式文案待法务确认) */ +/** 2.2.1 氢费支付方式:按月结算时切换为月结算条款 */ export function applyHydrogenSettlementPatch(html, feeInfo) { if (!html || !feeInfo || feeInfo.hydrogenPaymentMethod !== 'month') return html; var start = html.indexOf('2.2.1'); if (start < 0) return html; - var end = html.indexOf('2.2.3', start); - if (end < 0) end = html.indexOf('2.3', start); - if (end < 0) end = start + 2800; - var head = html.slice(0, start); - var slice = html.slice(start, end); - var tail = html.slice(end); - var placeholder = '

' - + '【原型占位 · 待法务确认】氢费按月结算:每月 1 日至当月最后一日为一个结算周期,' - + '次月 5 日前结清上月氢费;具体条款以法务定稿为准。' - + '

'; - if (slice.indexOf('lc-h2-settlement-prototype') < 0) { - slice = slice.replace(/(2\.2\.1[\s\S]*?<\/p>)/, '$1' + placeholder); - } - return head + slice + tail; -} - -/** 2.2.1 氢费支付:提前 x 个工作日 */ -export function applyFeeInfoPatch(html, feeInfo) { - if (!html || !feeInfo) return html; - var days = feeInfo.payAheadWorkdays != null ? String(feeInfo.payAheadWorkdays) : ''; - var start = html.indexOf('2.2.1'); - if (start < 0) return html; var end = html.indexOf('2.2.2', start); if (end < 0) end = html.indexOf('2.2.3', start); if (end < 0) end = start + 2200; var head = html.slice(0, start); var slice = html.slice(start, end); var tail = html.slice(end); + var monthlyClause = '如需甲方安排加氢的,氢费按月结算:每月 1 日至当月最后一日为一个结算周期,' + + '次月 5 日前结清上月氢费;乙方应按对账单确认金额及时支付,逾期支付的,甲方有权暂停安排加氢,' + + '由此造成的一切损失由乙方承担,给甲方造成损失的,乙方还应承担赔偿责任。'; + slice = slice.replace(/

/g, ''); + slice = slice.replace( + /2\.2\.1[\s\S]*?<\/p>/, + '2.2.1 ' + escapeHtml(monthlyClause) + '

', + ); + return head + slice + tail; +} + +/** 2.2.1 氢费支付:预付款走提车应收款;其他方式可回填提前工作日 */ +export function applyFeeInfoPatch(html, feeInfo) { + if (!html || !feeInfo) return html; + if (feeInfo.hydrogenPaymentMethod === 'month') return html; + var start = html.indexOf('2.2.1'); + if (start < 0) return html; + var end = html.indexOf('2.2.2', start); + if (end < 0) end = html.indexOf('2.2.3', start); + if (end < 0) end = start + 2200; + var head = html.slice(0, start); + var slice = html.slice(start, end); + var tail = html.slice(end); + if (feeInfo.hydrogenPaymentMethod === 'prepay') { + var prepayAmount = feeInfo.prepayAmount != null && feeInfo.prepayAmount !== '' + ? escapeHtml(String(feeInfo.prepayAmount)) + : '—'; + var prepayNote = '预付款金额 ' + prepayAmount + ' 元;无需单独约定支付时间,在实际提车时通过「提车应收款」分摊支付' + + '(全部提车一次性付清;分批提车可部分领取,末次提车须结清剩余金额,不可修改)。'; + slice = slice.replace(/(提前【)\s*([\s\u00a0]*)(】个工作日内)/, escapeHtml(prepayNote)); + return head + slice + tail; + } + var days = feeInfo.payAheadWorkdays != null ? String(feeInfo.payAheadWorkdays) : ''; slice = slice.replace(/(提前【)\s*([\s\u00a0]*)(】个工作日内)/, '$1' + escapeHtml(days) + '$3'); return head + slice + tail; } +/** 2.2.5 还车氢量差单价:与左侧「还车氢量差单价」表单联动 */ +export function applyReturnHydrogenDiffPatch(html, feeInfo) { + if (!html || !feeInfo) return html; + var price = feeInfo.returnHydrogenDiffUnitPrice != null && feeInfo.returnHydrogenDiffUnitPrice !== '' + ? escapeHtml(String(feeInfo.returnHydrogenDiffUnitPrice)) + : ''; + var start = html.indexOf('2.2.5'); + if (start < 0) return html; + var end = html.indexOf('2.2.6', start); + if (end < 0) end = html.indexOf('2.3', start); + if (end < 0) end = start + 600; + var head = html.slice(0, start); + var slice = html.slice(start, end); + var tail = html.slice(end); + slice = slice.replace(/(则按照【)\s*([\s\u00a0]*)(】元\/公斤)/, '$1' + price + '$3'); + return head + slice + tail; +} + /** 附件1:租赁订单编号、甲乙方与车辆列表反写 */ export function applyLeaseOrderAttachmentPatch(html, form, customer, lessor) { if (!html) return html; @@ -279,23 +311,19 @@ function applyLeaseOrderSummaryPatch(slice, leaseOrder) { } function applyLeaseOrderDeliveryPatch(slice, leaseOrder) { - var region = leaseOrder.deliveryRegion || []; - var regionText = region.length ? escapeHtml(region.join('')) : ''; - if (regionText) { + var regionText = formatLeaseOrderDeliveryRegion(leaseOrder); + if (regionText && regionText !== '-') { slice = slice.replace( /(车辆交付(交还)地点:)\s*([\s\u00a0]*)(。)/, - '$1' + regionText + '$3', + '$1' + escapeHtml(regionText) + '$3', ); } - if (leaseOrder.deliveryDate) { - var parts = String(leaseOrder.deliveryDate).split('-'); - if (parts.length === 3) { - var dateText = escapeHtml(parts[0] + '年' + parseInt(parts[1], 10) + '月' + parseInt(parts[2], 10) + '日'); - slice = slice.replace( - /(车辆交付时间:)\s*([\s\u00a0\u2002]*年\s*[\s\u00a0\u2002]*月\s*[\s\u00a0\u2002]*日)/, - '$1' + dateText, - ); - } + var dateText = formatLeaseOrderDeliveryDate(leaseOrder); + if (dateText && dateText !== '-') { + slice = slice.replace( + /(车辆交付时间:)\s*([\s\u00a0\u2002]*年\s*[\s\u00a0\u2002]*月\s*[\s\u00a0\u2002]*日)/, + '$1' + escapeHtml(dateText), + ); } return slice; } @@ -333,7 +361,9 @@ function buildLeaseOrderPreviewRow(row, brandModelPair, plate, leaseOrder) { var rent = row.rent != null && row.rent !== '' ? escapeHtml(row.rent) : ''; var serviceFee = row.serviceFee != null && row.serviceFee !== '' ? escapeHtml(row.serviceFee) : ''; var deposit = row.deposit != null && row.deposit !== '' ? escapeHtml(row.deposit) : ''; - var extra = escapeHtml(formatExtraServicesDisplay(extraServices)); + var extra = formatExtraServicesPreviewLines(extraServices).map(function (line) { + return '

' + escapeHtml(line) + '

'; + }).join(''); var leasePeriod = escapeHtml(formatLeasePeriod(row)); var border = 'border:1px solid #bfbfbf;padding:0 5px'; var brandCell = brandModelText @@ -431,6 +461,52 @@ function stripUnresolvedPlaceholders(html) { return String(html || '').replace(/\{\{[^{}]+\}\}/g, ''); } +/** 添加授权委托书等场景:从完整合同 HTML 中截取授权委托书预览片段 */ +export function extractPowerOfAttorneyPreviewHtml(html) { + if (!html) return ''; + var sections = splitMonolithicHtml(html); + var slice = String(sections.authorization || '').trim(); + if (!slice) return html; + return '
' + + '

' + + '以下为授权委托书预览,主合同及其他附件无需重新签署。' + + '

' + + slice + + '
'; +} + +/** 新增车辆等场景:从完整合同 HTML 中截取附件1(租赁订单)预览片段 */ +export function extractLeaseOrderAttachment1PreviewHtml(html) { + if (!html) return ''; + var text = String(html); + var markers = ['附件1', '1:租赁订单', '附件 1']; + var start = -1; + for (var i = 0; i < markers.length; i++) { + var pos = text.indexOf(markers[i]); + if (pos >= 0) { + start = pos; + break; + } + } + if (start < 0) return text; + + var endMarkers = ['附件2', '附件 2', '附件二']; + var end = text.length; + for (var j = 0; j < endMarkers.length; j++) { + var endPos = text.indexOf(endMarkers[j], start + 4); + if (endPos >= 0 && endPos < end) end = endPos; + } + + var slice = text.slice(start, end).trim(); + if (!slice) return text; + return '
' + + '

' + + '以下为新增租赁订单附件1预览,主合同及其他附件无需重新签署。' + + '

' + + slice + + '
'; +} + export function buildLeaseContractPreviewHtml(form) { var templateId = form && form.contractTemplateId; var base = getContractTemplateBaseHtml(templateId) || ''; @@ -449,6 +525,7 @@ export function buildLeaseContractPreviewHtml(form) { html = applyMileageStandardPatch(html, form && form.mileage); html = applyPaymentPeriodPatch(html, form && form.feeInfo); html = applyFeeInfoPatch(html, form && form.feeInfo); + html = applyReturnHydrogenDiffPatch(html, form && form.feeInfo); html = applyHydrogenSettlementPatch(html, form && form.feeInfo); html = applyLeaseOrderAttachmentPatch(html, form, customer, lessor); html = applyPowerOfAttorneyPatch(html, form); diff --git a/src/prototypes/lease-contract-management/lease-contract-risk-detect.js b/src/prototypes/lease-contract-management/lease-contract-risk-detect.js index 9af8ca0..2d7ffd3 100644 --- a/src/prototypes/lease-contract-management/lease-contract-risk-detect.js +++ b/src/prototypes/lease-contract-management/lease-contract-risk-detect.js @@ -6,6 +6,7 @@ export var STANDARD_CONTRACT_APPROVAL = '标准合同'; export var NONSTANDARD_CONTRACT_APPROVAL = '非标准合同'; var RISK_SELECTOR = '.ct-risk-redline[data-risk-redline="1"]'; +var LOCKED_SELECTOR = '[data-section-locked="1"], .ct-section-locked'; function normalizeRiskText(value) { return String(value || '') @@ -16,6 +17,13 @@ function normalizeRiskText(value) { .trim(); } +function truncatePreviewText(value, maxLen) { + var text = normalizeRiskText(value); + if (!text) return '(空)'; + if (text.length <= maxLen) return text; + return text.slice(0, maxLen) + '…'; +} + function collectRiskSnapshots(html) { if (!html || typeof document === 'undefined') return []; var div = document.createElement('div'); @@ -30,6 +38,22 @@ function collectRiskSnapshots(html) { return list; } +function collectParagraphSnapshots(html) { + if (!html || typeof document === 'undefined') return []; + var div = document.createElement('div'); + div.innerHTML = String(html || ''); + var list = []; + div.querySelectorAll('p, li, td, th').forEach(function (el, index) { + var text = normalizeRiskText(el.textContent || ''); + if (!text || text.length < 8) return; + list.push({ + id: 'block-' + index, + text: text, + }); + }); + return list; +} + /** 风控红线条款正文是否与基线不一致 */ export function isRiskRedlineContentModified(baselineHtml, currentHtml) { var baseline = collectRiskSnapshots(baselineHtml); @@ -42,12 +66,85 @@ export function isRiskRedlineContentModified(baselineHtml, currentHtml) { return false; } +/** 预览正文是否新增条款(相对基线出现新的段落块) */ +export function isPreviewNewClauseAdded(baselineHtml, currentHtml) { + var baseline = collectParagraphSnapshots(baselineHtml); + var current = collectParagraphSnapshots(currentHtml); + if (current.length > baseline.length) return true; + var baselineSet = {}; + baseline.forEach(function (item) { + baselineSet[item.text] = true; + }); + for (var i = 0; i < current.length; i++) { + if (!baselineSet[current[i].text]) return true; + } + return false; +} + +/** 收集标准条款与修改后条款记录(原型对比) */ +export function collectClauseChangeRecords(baselineHtml, currentHtml) { + if (!baselineHtml || !currentHtml || typeof document === 'undefined') return []; + var records = []; + var baselineRisks = collectRiskSnapshots(baselineHtml); + var currentRisks = collectRiskSnapshots(currentHtml); + var maxRisk = Math.max(baselineRisks.length, currentRisks.length); + for (var i = 0; i < maxRisk; i++) { + var before = baselineRisks[i]; + var after = currentRisks[i]; + var beforeText = before ? before.text : ''; + var afterText = after ? after.text : ''; + if (beforeText === afterText) continue; + records.push({ + id: (after && after.id) || (before && before.id) || ('risk-' + i), + kind: 'risk', + kindLabel: '风控红线', + standardText: truncatePreviewText(beforeText, 180), + modifiedText: truncatePreviewText(afterText, 180), + }); + } + + var baselineBlocks = collectParagraphSnapshots(baselineHtml); + var currentBlocks = collectParagraphSnapshots(currentHtml); + var baselineMap = {}; + baselineBlocks.forEach(function (item) { + baselineMap[item.text] = item; + }); + currentBlocks.forEach(function (item, index) { + if (baselineMap[item.text]) return; + records.push({ + id: 'new-' + index, + kind: 'new', + kindLabel: '新增条款', + standardText: '(标准合同无对应段落)', + modifiedText: truncatePreviewText(item.text, 180), + }); + }); + + var currentMap = {}; + currentBlocks.forEach(function (item) { + currentMap[item.text] = item; + }); + baselineBlocks.forEach(function (item, index) { + if (currentMap[item.text]) return; + records.push({ + id: 'removed-' + index, + kind: 'removed', + kindLabel: '删除段落', + standardText: truncatePreviewText(item.text, 180), + modifiedText: '(已删除)', + }); + }); + + return records.slice(0, 12); +} + export function mergePreviewPages(pages) { return (pages || []).join(''); } export function resolveContractApprovalType(baselineHtml, currentHtml) { return isRiskRedlineContentModified(baselineHtml, currentHtml) + || isPreviewNewClauseAdded(baselineHtml, currentHtml) ? NONSTANDARD_CONTRACT_APPROVAL : STANDARD_CONTRACT_APPROVAL; } diff --git a/src/prototypes/lease-contract-management/lease-contract-signing.js b/src/prototypes/lease-contract-management/lease-contract-signing.js new file mode 100644 index 0000000..8d08c17 --- /dev/null +++ b/src/prototypes/lease-contract-management/lease-contract-signing.js @@ -0,0 +1,181 @@ +import { getLeaseCustomerById } from '../contract-template-management/contract-template-vars.js'; + +export var CONTRACT_SIGNING_METHOD_ONLINE = 'online_esign'; +export var CONTRACT_SIGNING_METHOD_OFFLINE = 'offline_manual'; + +var PARTY_B_CUSTOMER_IDS_BY_NAME = { + '嘉兴某某物流有限公司': '1', + '上海某某运输有限公司': '2', + '杭州某某租赁有限公司': '3', +}; + +export var CONTRACT_SIGNING_METHOD_OPTIONS = [ + { value: CONTRACT_SIGNING_METHOD_ONLINE, label: '线上电子签章' }, + { value: CONTRACT_SIGNING_METHOD_OFFLINE, label: '线下人工上传' }, +]; + +export function formatContractSigningMethodLabel(method) { + if (method === CONTRACT_SIGNING_METHOD_OFFLINE) return '线下人工上传'; + if (method === CONTRACT_SIGNING_METHOD_ONLINE) return '线上电子签章'; + return '线上电子签章'; +} + +export function isOfflineContractSigning(method) { + return method === CONTRACT_SIGNING_METHOD_OFFLINE; +} + +export function resolveContractSigningMethod(method) { + return method || CONTRACT_SIGNING_METHOD_ONLINE; +} + +export function formatSigningStatusMinute(value) { + if (!value) return null; + var text = String(value).trim(); + if (!text || text === '-') return null; + if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/.test(text)) { + return text.slice(0, 16); + } + if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}$/.test(text)) { + return text; + } + return text; +} + +export function resolveOnlineEsignCompletedAt(record, completedAtOverride) { + if (!record) return null; + if (completedAtOverride && completedAtOverride[record.id]) { + return completedAtOverride[record.id]; + } + return record.onlineEsignCompletedAt || null; +} + +export function resolveOfflineStampSupplementedAt(record, completedAtOverride) { + if (!record) return null; + if (completedAtOverride && completedAtOverride[record.id]) { + return completedAtOverride[record.id]; + } + return record.offlineStampSupplementedAt || null; +} + +export function getContractSigningSubLabel(record, options) { + options = options || {}; + var method = resolveContractSigningMethod(record && record.contractSigningMethod); + var completedAtOverride = options.completedAtOverride || null; + if (isOfflineContractSigning(method)) { + if (!options.hasUploaded) return '待补传'; + return formatSigningStatusMinute(resolveOfflineStampSupplementedAt(record, completedAtOverride)) || '-'; + } + return formatSigningStatusMinute(resolveOnlineEsignCompletedAt(record, completedAtOverride)) || '-'; +} + +export function getOnlineEsignContractFiles(record) { + if (!record) return []; + if (record.onlineEsignContractFiles && record.onlineEsignContractFiles.length) { + return record.onlineEsignContractFiles; + } + if (resolveOnlineEsignCompletedAt(record, null)) { + var code = record.contractCode || '租赁合同'; + return [{ uid: 'esign-' + (record.id || code), name: code + '-电子签章合同.pdf' }]; + } + return []; +} + +export function hasOnlineEsignCompleted(record, completedAtOverride) { + return !!formatSigningStatusMinute(resolveOnlineEsignCompletedAt(record, completedAtOverride)); +} + +export function openOnlineEsignPreviewInNewTab(record) { + var code = record && record.contractCode ? record.contractCode : '租赁合同'; + var customer = record && record.customerName ? record.customerName : '-'; + var principalPhone = record && record.customerPrincipalPhone ? record.customerPrincipalPhone : '-'; + var previewWindow = window.open('', '_blank'); + if (!previewWindow) { + return false; + } + previewWindow.document.write( + '' + + '' + code + ' · E签宝电子签章预览' + + '
' + + '

E签宝电子签章合同预览

' + + '

合同编码:' + code + '

' + + '

乙方:' + customer + '

' + + '

乙方负责人手机号:' + principalPhone + '

' + + '

此处为线上电子签章合同正文预览(原型)。

审批通过后,系统已向乙方负责人发送 E签宝签署链接。

' + + '
', + ); + previewWindow.document.close(); + return true; +} + +export function openContractAttachmentPreviewInNewTab(file, record) { + var code = record && record.contractCode ? record.contractCode : '租赁合同'; + var fileName = file && file.name ? file.name : '附件'; + var previewWindow = window.open('', '_blank'); + if (!previewWindow) { + return false; + } + previewWindow.document.write( + '' + + '' + fileName + '' + + '
' + + '

' + fileName + '

' + + '

合同编码:' + code + '

' + + '

线下人工上传附件预览(原型)。

' + + '
', + ); + previewWindow.document.close(); + return true; +} + +export function downloadContractAttachment(file, record) { + var fileName = file && file.name ? file.name : '附件'; + var code = record && record.contractCode ? record.contractCode : '租赁合同'; + var blob = new Blob(['原型下载占位:' + code + ' / ' + fileName], { type: 'text/plain;charset=utf-8' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + return true; +} + +export function resolvePartyBEmailForStamp(record) { + if (!record) return ''; + if (record.customerPrincipalEmail) return record.customerPrincipalEmail; + if (record.partyBEmail) return record.partyBEmail; + var customerId = record.customerId || PARTY_B_CUSTOMER_IDS_BY_NAME[record.customerName] || ''; + var customer = getLeaseCustomerById(customerId); + return customer && customer.email ? customer.email : ''; +} + +export function isPartyBEmailValid(email) { + var value = (email || '').trim(); + if (!value) return false; + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); +} + +export function downloadPartyASignedContract(record) { + var code = record && record.contractCode ? record.contractCode : '租赁合同'; + var fileName = code + '-甲方已签章.pdf'; + var blob = new Blob(['原型:' + code + ' 甲方已签章合同(请手动发送给乙方)'], { type: 'application/pdf' }); + var url = URL.createObjectURL(blob); + var link = document.createElement('a'); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + return true; +} diff --git a/src/prototypes/lease-contract-management/lease-contract-view-data.js b/src/prototypes/lease-contract-management/lease-contract-view-data.js new file mode 100644 index 0000000..8b352b3 --- /dev/null +++ b/src/prototypes/lease-contract-management/lease-contract-view-data.js @@ -0,0 +1,124 @@ +/** + * 合同查看页:附件、操作记录、变更记录(原型样例) + */ + +import { openContractAttachmentPreviewInNewTab, downloadContractAttachment } from './lease-contract-signing.js'; + +export var CHANGE_RECORD_TYPES = [ + { key: 'tripartite', label: '转三方协议' }, + { key: 'poa', label: '添加授权委托书' }, + { key: 'extraFee', label: '附加费用' }, + { key: 'ownerChange', label: '变更业务负责人' }, + { key: 'terminate', label: '主动终止合同' }, +]; + +function padMinute(value) { + if (!value) return '-'; + var text = String(value).trim(); + if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/.test(text)) return text.slice(0, 16); + return text; +} + +export function buildContractAttachments(record) { + if (!record) return []; + var code = record.contractCode || '租赁合同'; + var files = []; + if (record.stampedContractFiles && record.stampedContractFiles.length) { + files = record.stampedContractFiles.slice(); + } else if (record.onlineEsignContractFiles && record.onlineEsignContractFiles.length) { + files = record.onlineEsignContractFiles.slice(); + } else if (record.onlineEsignCompletedAt || record.legalStampedContractUploaded) { + files = [{ uid: 'esign-main', name: code + '-电子签章合同.pdf' }]; + } + if (record.contractSigningMethod !== 'offline_manual' && !files.length && record.approvalStatus === '审批通过') { + files.push({ uid: 'draft-main', name: code + '-租赁合同正文.pdf' }); + } + return files; +} + +export function buildContractOperationLogs(record) { + if (!record) return []; + var logs = [ + { + id: 'op-create', + action: '创建合同', + operatorName: record.creator || '-', + operateTime: padMinute(record.createTime), + modifierName: record.updater && record.updater !== '-' ? record.updater : record.creator || '-', + modifyTime: padMinute(record.updateTime && record.updateTime !== '-' ? record.updateTime : record.createTime), + }, + ]; + if (record.approvalStatus && record.approvalStatus !== '未提交' && record.approvalStatus !== '草稿') { + logs.push({ + id: 'op-submit', + action: '提交审核', + operatorName: record.creator || '-', + operateTime: padMinute(record.createTime), + modifierName: record.updater || record.creator || '-', + modifyTime: padMinute(record.updateTime), + }); + } + if (record.approvalStatus === '审批通过') { + logs.push({ + id: 'op-approve', + action: '审批通过', + operatorName: record.businessOwner || '业务负责人', + operateTime: padMinute(record.updateTime), + modifierName: record.updater || record.businessOwner || '-', + modifyTime: padMinute(record.updateTime), + }); + } + return logs; +} + +export function buildContractChangeRecords(record) { + if (!record) return []; + var list = []; + if (record.changeRecords && record.changeRecords.length) { + return record.changeRecords.map(function (item, index) { + return Object.assign({ id: item.id || ('chg-' + index) }, item); + }); + } + if (record.poaUploaded) { + list.push({ + id: 'chg-poa', + type: 'poa', + typeLabel: '添加授权委托书', + summary: '已维护授权委托书与受托人信息', + operatorName: record.creator || '-', + operateTime: padMinute(record.createTime), + approvalStatus: '审批通过', + }); + } + if (record.contractApprovalType === '非标准合同') { + list.push({ + id: 'chg-nonstd', + type: 'extraFee', + typeLabel: '附加费用', + summary: '历史附加费用变更记录(原型样例)', + operatorName: record.businessOwner || '-', + operateTime: padMinute(record.updateTime), + approvalStatus: '审批通过', + }); + } + if (record.terminatedBy) { + list.push({ + id: 'chg-term', + type: 'terminate', + typeLabel: '主动终止合同', + summary: record.remark || '合同已终止', + operatorName: record.updater || record.businessOwner || '-', + operateTime: padMinute(record.updateTime), + approvalStatus: '审批通过', + }); + } + return list; +} + +export function previewContractAttachment(file, record) { + return openContractAttachmentPreviewInNewTab(file, record); +} + +export function downloadContractAttachmentFile(file, record) { + return downloadContractAttachment(file, record); +} diff --git a/src/prototypes/lease-contract-management/lease-contract-view-sections.js b/src/prototypes/lease-contract-management/lease-contract-view-sections.js new file mode 100644 index 0000000..1556e40 --- /dev/null +++ b/src/prototypes/lease-contract-management/lease-contract-view-sections.js @@ -0,0 +1,371 @@ +/** + * 合同查看页:对齐新增页左侧编辑区章节与字段 + */ + +import { getContractTemplateOption } from '../contract-template-management/contract-template-catalog.js'; +import { + getLessorCompanyById, + getLeaseCustomerById, + getCustomerInvoicePreview, + getLessorAccountPreview, + getLessorContactPreview, + getCustomerContactPreview, + MILEAGE_PERIOD_OPTIONS, + PAYMENT_METHOD_OPTIONS, + PAYMENT_PERIOD_OPTIONS, + HYDROGEN_PAYMENT_METHOD_OPTIONS, + CONTRACT_CODE_PREFIX, +} from '../contract-template-management/contract-template-vars.js'; +import { buildLeaseContractEditFormState } from './lease-contract-edit-bridge.js'; +import { + formatContractDeliveryRegionLabel, + formatContractDeliveryDateLabel, + formatPaymentMethodLabel, + formatPaymentPeriodLabel, + formatHydrogenPaymentLabel, + formatContractApprovalTypeLabel, + formatMileageSummary, + resolveContractDisplayStatus, + getAuthorizedDelegates, + isContractVehicleDelivered, + isContractVehicleReturned, + inferContractTemplateId, + resolveContractTemplateTypeLabel, +} from './lease-contract-list-data.js'; +import { + formatContractSigningMethodLabel, + resolveContractSigningMethod, + getContractSigningSubLabel, + isOfflineContractSigning, +} from './lease-contract-signing.js'; + +var SEAL_TYPE_LABELS = { + contract: '合同章', + official: '公章', + legal_person: '法人章', +}; + +var VIEW_SECTIONS = [ + { key: 'template', label: '选择模板', step: 1 }, + { key: 'main', label: '主体合同', step: 2 }, + { key: 'leaseOrder', label: '租赁订单', step: 3 }, + { key: 'poa', label: '授权委托', step: 4, optional: true }, + { key: 'remark', label: '合同备注', step: 5, optional: true }, + { key: 'seal', label: '用章类型', step: 6 }, + { key: 'attachments', label: '合同附件', viewExtra: true, step: 7 }, + { key: 'audit', label: '建档信息', viewExtra: true, step: 8 }, + { key: 'operations', label: '操作记录', viewExtra: true, step: 9 }, + { key: 'changes', label: '变更记录', viewExtra: true, step: 10 }, +]; + +function optionLabel(options, value) { + if (!options || !options.length) return value || '-'; + var found = options.find(function (item) { return item.value === value; }); + return found ? found.label : (value != null && value !== '' ? String(value) : '-'); +} + +function signingExtras(record) { + return { + hasUploaded: Boolean(record.legalStampedContractUploaded || record.stampedContractFiles && record.stampedContractFiles.length), + }; +} + +function resolveSigningSubLabel(record) { + var signingSub = getContractSigningSubLabel(record, signingExtras(record)); + if (!signingSub || signingSub === '-') { + var method = resolveContractSigningMethod(record.contractSigningMethod); + signingSub = isOfflineContractSigning(method) ? '待补传' : '待签署'; + } + return signingSub; +} + +export function resolveViewFormContext(record) { + if (!record) return null; + var formState = buildLeaseContractEditFormState(record); + if (!formState) return null; + var lessor = getLessorCompanyById(formState.lessorId); + var customer = getLeaseCustomerById(formState.customerId); + return { + formState: formState, + lessor: lessor, + customer: customer, + lessorAccount: getLessorAccountPreview(lessor), + lessorContact: getLessorContactPreview(lessor), + customerContact: getCustomerContactPreview(customer), + invoice: getCustomerInvoicePreview(customer), + }; +} + +export function getViewSectionNavItems() { + return VIEW_SECTIONS.slice(); +} + +export function formatSealTypesLabel(sealTypes) { + if (!sealTypes || !sealTypes.length) return '合同章'; + return sealTypes.map(function (key) { + return SEAL_TYPE_LABELS[key] || key; + }).join('、'); +} + +export function resolveVehicleLeaseStatus(vehicle) { + if (!vehicle) return { label: '待交车', tone: 'gray' }; + if (isContractVehicleReturned(vehicle)) return { label: '已还车', tone: 'gray' }; + if (isContractVehicleDelivered(vehicle)) return { label: '已交车', tone: 'green' }; + return { label: '待交车', tone: 'blue' }; +} + +export function buildContractViewSummary(record) { + if (!record) return null; + var displayStatus = resolveContractDisplayStatus(record); + var signingMethod = resolveContractSigningMethod(record.contractSigningMethod); + var signingSub = resolveSigningSubLabel(record); + return { + projectName: record.projectName || '-', + contractCode: record.contractCode || '-', + customerName: record.customerName || '-', + displayStatus: displayStatus, + approvalStatus: record.approvalStatus || '-', + showApprovalBadge: shouldShowApprovalBadge(displayStatus, record.approvalStatus), + contractType: record.contractType || '-', + contractApprovalType: formatContractApprovalTypeLabel(record.contractApprovalType), + signingMethodLabel: formatContractSigningMethodLabel(signingMethod), + signingSubLabel: signingSub, + vehicleCount: record.vehicleCount != null ? record.vehicleCount : (record.vehicles ? record.vehicles.length : 0), + contractEndDate: record.contractEndDate || '-', + businessOwner: record.businessOwner || '-', + }; +} + +function shouldShowApprovalBadge(displayStatus, approvalStatus) { + if (!approvalStatus || approvalStatus === '-') return false; + if (approvalStatus === '审批通过' && displayStatus === '合同进行中') return false; + return true; +} + +export function buildContractViewTemplateFields(record) { + if (!record) return []; + var templateId = record.contractTemplateId || inferContractTemplateId(record); + var option = templateId ? getContractTemplateOption(templateId) : null; + var templateLabel = option && option.title + ? option.title + : resolveContractTemplateTypeLabel(record); + return [ + { label: '合同模板', value: templateLabel }, + { label: '合同签署方式', value: formatContractSigningMethodLabel(resolveContractSigningMethod(record.contractSigningMethod)) }, + { label: '签署状态', value: resolveSigningSubLabel(record) }, + { label: '审批类型', value: formatContractApprovalTypeLabel(record.contractApprovalType) }, + ]; +} + +export function buildContractViewSigningFields(record, ctx) { + if (!record) return []; + var formState = ctx && ctx.formState; + var code = record.contractCode || (formState && formState.contractCode + ? CONTRACT_CODE_PREFIX + formState.contractCode + : '-'); + return [ + { label: '合同编码', value: code, mono: true }, + { label: '业务部门 / 业务人员', value: [record.businessDept, record.businessOwner].filter(Boolean).join(' / ') || '-' }, + { label: '项目名称', value: record.projectName || '-', wide: true }, + { label: '甲方', value: record.signingCompanyFullName || record.signingCompany }, + { label: '乙方', value: record.customerName }, + { label: '乙方负责人姓名', value: record.customerPrincipalName || (formState && formState.customerPrincipalName) || '-' }, + { label: '乙方负责人手机号', value: record.customerPrincipalPhone || (formState && formState.customerPrincipalPhone) || '-', mono: true }, + ]; +} + +export function buildContractViewLessorProfileFields(ctx) { + if (!ctx || !ctx.lessorAccount) return []; + var account = ctx.lessorAccount; + var contact = ctx.lessorContact || {}; + return [ + { label: '户名', value: account.accountName }, + { label: '开户行', value: account.bankName }, + { label: '账号', value: account.bankAccount, mono: true }, + { label: '通讯地址', value: contact.mailAddress, wide: true }, + { label: '联系人姓名', value: contact.contactName }, + { label: '联系人电话', value: contact.contactPhone, mono: true }, + { label: '邮箱', value: contact.email, wide: true }, + ]; +} + +export function buildContractViewCustomerProfileFields(ctx) { + if (!ctx || !ctx.invoice) return []; + var invoice = ctx.invoice; + var contact = ctx.customerContact || {}; + return [ + { label: '企业名称', value: invoice.companyName, wide: true }, + { label: '开户银行', value: invoice.bank }, + { label: '银行账号', value: invoice.bankAccount, mono: true }, + { label: '纳税人识别号', value: invoice.taxId, mono: true }, + { label: '企业地址', value: invoice.mailingAddress, wide: true }, + { label: '企业电话', value: invoice.companyPhone, mono: true }, + { label: '通讯地址', value: contact.mailAddress, wide: true }, + { label: '联系人姓名', value: contact.contactName }, + { label: '联系人电话', value: contact.contactPhone, mono: true }, + { label: '邮箱', value: contact.email, wide: true }, + ]; +} + +export function buildContractViewMileageFields(record, ctx) { + if (!record) return []; + var mileage = ctx && ctx.formState && ctx.formState.mileage; + var hasRequirement = Boolean(record.hasMinimumMileage || (mileage && mileage.hasRequirement)); + var fields = [ + { label: '是否有里程要求', value: hasRequirement ? '是' : '否' }, + ]; + if (hasRequirement) { + var periodLabel = optionLabel(MILEAGE_PERIOD_OPTIONS, record.mileagePeriod || (mileage && mileage.period)); + fields.push( + { label: '里程要求类型', value: periodLabel }, + { label: '里程要求', value: formatMileageSummary(record) }, + { label: '次月租金减免金额', value: record.mileageReductionYuan != null ? record.mileageReductionYuan + ' 元' : '-', mono: true }, + { label: '减免有效期至', value: record.mileageValidUntil || '-', mono: true }, + ); + } + return fields; +} + +export function buildContractViewFeeFields(record, ctx) { + if (!record) return []; + var feeInfo = ctx && ctx.formState && ctx.formState.feeInfo; + var paymentMethod = record.paymentMethod || (feeInfo && feeInfo.paymentMethod); + var hydrogenMethod = record.hydrogenPaymentMethod || (feeInfo && feeInfo.hydrogenPaymentMethod); + var fields = [ + { label: '付款方式', value: formatPaymentMethodLabel(paymentMethod) }, + { label: '付款周期', value: formatPaymentPeriodLabel(record.paymentPeriod != null ? record.paymentPeriod : (feeInfo && feeInfo.paymentPeriod)) }, + { label: '氢费支付方式', value: formatHydrogenPaymentLabel(hydrogenMethod) }, + ]; + if (hydrogenMethod === 'prepay' || record.prepayAmount != null) { + fields.push({ + label: '预付款金额', + value: record.prepayAmount != null ? record.prepayAmount + ' 元' : (feeInfo && feeInfo.prepayAmount != null ? feeInfo.prepayAmount + ' 元' : '-'), + mono: true, + }); + } + if (hydrogenMethod === 'month' || record.payAheadWorkdays != null) { + fields.push({ + label: '提前付款工作日', + value: record.payAheadWorkdays != null ? record.payAheadWorkdays + ' 天' : (feeInfo && feeInfo.payAheadWorkdays != null ? feeInfo.payAheadWorkdays + ' 天' : '-'), + mono: true, + }); + } + var diffPrice = record.returnHydrogenDiffUnitPrice != null + ? record.returnHydrogenDiffUnitPrice + : (feeInfo && feeInfo.returnHydrogenDiffUnitPrice); + fields.push({ + label: '还车氢量差单价', + value: diffPrice != null ? diffPrice + ' 元/kg' : '-', + mono: true, + }); + return fields; +} + +export function buildContractViewLeaseOrderMetaFields(record) { + if (!record) return []; + return [ + { label: '交车区域', value: formatContractDeliveryRegionLabel(record), wide: true }, + { label: '交车时间', value: formatContractDeliveryDateLabel(record), mono: true }, + { label: '保险金额', value: record.thirdPartyLiabilityMillion != null ? record.thirdPartyLiabilityMillion + ' 万元' : '-', mono: true }, + { label: '在保车辆数', value: record.insuredVehicleCount != null ? record.insuredVehicleCount + ' 辆' : '-', mono: true }, + ]; +} + +export function buildContractViewSealTypes(record, ctx) { + var sealTypes = record && record.sealTypes; + if ((!sealTypes || !sealTypes.length) && ctx && ctx.formState) { + sealTypes = ctx.formState.sealTypes; + } + return Array.isArray(sealTypes) && sealTypes.length ? sealTypes.slice() : ['contract']; +} + +/** @deprecated 使用 buildContractViewSigningFields + 档案分组 */ +export function buildContractViewPartyGroups(record) { + if (!record) return []; + return [ + { + title: '签约主体', + fields: [ + { label: '签约公司(甲方)', value: record.signingCompany }, + { label: '客户名称(乙方)', value: record.customerName, wide: true }, + ], + }, + { + title: '联系与业务', + fields: [ + { label: '联系人', value: record.contactName }, + { label: '联系电话', value: record.contactPhone, mono: true }, + { label: '业务部门', value: record.businessDept }, + { label: '业务负责人', value: record.businessOwner }, + ], + }, + { + title: '合同属性', + fields: [ + { label: '合同类型', value: record.contractType }, + { label: '审批类型', value: formatContractApprovalTypeLabel(record.contractApprovalType) }, + { label: '用章类型', value: formatSealTypesLabel(record.sealTypes) }, + ], + }, + { + title: '签署信息', + fields: [ + { label: '合同签署方式', value: formatContractSigningMethodLabel(resolveContractSigningMethod(record.contractSigningMethod)) }, + { label: '签署状态', value: resolveSigningSubLabel(record) }, + ], + }, + ]; +} + +/** @deprecated 使用 buildContractViewFeeFields + buildContractViewLeaseOrderMetaFields */ +export function buildContractViewFeeGroups(record) { + if (!record) return []; + return [ + { + title: '付款与结算', + fields: buildContractViewFeeFields(record, resolveViewFormContext(record)), + }, + { + title: '交车与保险', + fields: buildContractViewLeaseOrderMetaFields(record), + }, + ]; +} + +export function buildContractViewVehicleCards(record) { + if (!record || !record.vehicles) return []; + return record.vehicles.map(function (vehicle, index) { + var status = resolveVehicleLeaseStatus(vehicle); + return { + id: 'view-vehicle-' + index, + title: [vehicle.brand, vehicle.model].filter(Boolean).join(' ') || ('车辆 ' + (index + 1)), + status: status, + fields: [ + { label: '车辆类型', value: vehicle.vehicleType }, + { label: '品牌 / 型号', value: [vehicle.brand, vehicle.model].filter(Boolean).join(' / ') }, + { label: '车牌号', value: vehicle.plateNo, mono: true }, + { label: '租金', value: vehicle.rent != null ? vehicle.rent + ' 元/月' : '-', mono: true }, + { label: '服务费', value: vehicle.serviceFee != null ? vehicle.serviceFee + ' 元/月' : '-', mono: true }, + { label: '保证金', value: vehicle.deposit != null ? vehicle.deposit + ' 元' : '-', mono: true }, + { label: '租赁期限', value: vehicle.leasePeriodMonths != null ? vehicle.leasePeriodMonths + ' 个月' : '-' }, + { label: '交车时间', value: vehicle.actualDelivery || vehicle.deliveryTime || '-' }, + { label: '还车时间', value: vehicle.returnTime || '-' }, + { label: '交车负责人', value: vehicle.deliveryPerson }, + ], + }; + }); +} + +export function buildContractViewAuditFields(record) { + if (!record) return []; + return [ + { label: '创建人', value: record.creator }, + { label: '创建时间', value: record.createTime, mono: true }, + { label: '更新人', value: record.updater }, + { label: '更新时间', value: record.updateTime, mono: true }, + ]; +} + +export function buildContractPreviewFormFromRecord(record) { + return buildLeaseContractEditFormState(record); +} diff --git a/src/prototypes/lease-contract-management/lease-order-vars.js b/src/prototypes/lease-contract-management/lease-order-vars.js index 6925646..9fc3a7f 100644 --- a/src/prototypes/lease-contract-management/lease-order-vars.js +++ b/src/prototypes/lease-contract-management/lease-order-vars.js @@ -82,6 +82,34 @@ export var PROVINCE_CITY_CASCADER_OPTIONS = [ }, ]; +export function flattenProvinceCityOptions() { + var list = []; + PROVINCE_CITY_CASCADER_OPTIONS.forEach(function (prov) { + (prov.children || []).forEach(function (city) { + list.push({ + province: prov.value, + city: city.value, + label: prov.label + ' / ' + city.label, + }); + }); + }); + return list; +} + +export function matchProvinceCityOption(option, query) { + var normalized = (query || '').trim().toLowerCase(); + if (!normalized) return true; + return option.province.toLowerCase().indexOf(normalized) >= 0 + || option.city.toLowerCase().indexOf(normalized) >= 0 + || option.label.toLowerCase().indexOf(normalized) >= 0; +} + +export function formatDeliveryRegionDisplay(region) { + if (!region || !region.length) return ''; + if (region.length >= 2) return region[0] + ' / ' + region[1]; + return region[0] || ''; +} + export function buildBrandModelCascaderOptions() { return LEASE_VEHICLE_BRAND_MODEL_CATALOG.map(function (item) { return { @@ -96,19 +124,36 @@ export function buildBrandModelCascaderOptions() { export var PLATE_ACTUAL_DELIVERY = '以实际交付为准'; +export var PLATE_MODE_ACTUAL = 'actual'; + +export var PLATE_MODE_SPECIFIC = 'specific'; + +export var PLATE_SPECIFIC_LABEL = '选择特定车辆'; + +export var DELIVERY_REGION_TBD_LABEL = '交还车时约定'; + +export var DELIVERY_REGION_TBD_DISPLAY = '提车应收款或合同车辆子表还车时,按实际情况配置交还地点'; + +export var LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL = '暂未确认'; + +export var LEASE_DELIVERY_DATE_UNCONFIRMED_DISPLAY = '提车前通过提车应收款功能生成交车任务'; + export var POA_MAX_DELEGATES = 5; export function createEmptyLeaseOrderRow() { return { id: 'lo-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7), brandModels: [], + vehicleQty: 1, + plateMode: PLATE_MODE_ACTUAL, plateNos: [PLATE_ACTUAL_DELIVERY], rentServiceSubtotal: null, rent: null, serviceFee: null, deposit: null, extraServices: [], - leasePeriodMonths: null, + leasePeriodMode: 'months', + leasePeriodMonths: 1, leasePeriodStart: null, leasePeriodEnd: null, }; @@ -138,6 +183,139 @@ export function normalizePlateNos(row) { return [PLATE_ACTUAL_DELIVERY]; } +export function getPlateMode(row) { + if (!row) return PLATE_MODE_ACTUAL; + if (row.plateMode === PLATE_MODE_SPECIFIC || row.plateMode === PLATE_MODE_ACTUAL) { + return row.plateMode; + } + var plates = normalizePlateNos(row); + if (plates.length === 1 && plates[0] === PLATE_ACTUAL_DELIVERY) { + return PLATE_MODE_ACTUAL; + } + return PLATE_MODE_SPECIFIC; +} + +export function findVehicleByPlateNo(plate) { + var normalized = String(plate || '').trim().toUpperCase(); + if (!normalized || normalized === PLATE_ACTUAL_DELIVERY) return null; + var i; + for (i = 0; i < vehiclesData.length; i++) { + if (String(vehiclesData[i].plateNo || '').trim().toUpperCase() === normalized) { + return vehiclesData[i]; + } + } + for (i = 0; i < LEASE_READY_VEHICLE_FALLBACK.length; i++) { + if (String(LEASE_READY_VEHICLE_FALLBACK[i].plateNo || '').trim().toUpperCase() === normalized) { + return LEASE_READY_VEHICLE_FALLBACK[i]; + } + } + return null; +} + +export function validatePlateForLeaseOrder(plate, brandModels) { + var normalized = String(plate || '').trim().toUpperCase(); + if (!normalized) { + return { + plate: plate, + ok: false, + reason: 'empty', + message: '车牌号不能为空', + }; + } + var models = normalizeBrandModels({ brandModels: brandModels }); + if (!models.length) { + return { + plate: normalized, + ok: false, + reason: 'no_brand_model', + message: '请先选择品牌 / 型号', + }; + } + var vehicle = findVehicleByPlateNo(normalized); + if (!vehicle) { + return { + plate: normalized, + ok: false, + reason: 'not_found', + message: '未找到该车牌', + }; + } + if (vehicle.vehicleStatus !== READY_VEHICLE_STATUS) { + return { + plate: normalized, + ok: false, + reason: 'not_ready', + message: '车辆状态为「' + vehicle.vehicleStatus + '」,需为已备车', + vehicleStatus: vehicle.vehicleStatus, + }; + } + var brandMatched = models.some(function (pair) { + return vehicle.brand === pair[0] && vehicle.model === pair[1]; + }); + if (!brandMatched) { + return { + plate: normalized, + ok: false, + reason: 'brand_mismatch', + message: '品牌 / 型号不一致(车辆为 ' + formatBrandModelPair(vehicle.brand, vehicle.model) + ')', + vehicleBrand: vehicle.brand, + vehicleModel: vehicle.model, + }; + } + return { + plate: normalized, + ok: true, + reason: 'matched', + message: '校验通过', + resolvedPlate: vehicle.plateNo, + }; +} + +export function validatePlatesForLeaseOrder(plates, brandModels) { + var results = []; + var matched = []; + (plates || []).forEach(function (plate) { + var result = validatePlateForLeaseOrder(plate, brandModels); + results.push(result); + if (result.ok && result.resolvedPlate && matched.indexOf(result.resolvedPlate) < 0) { + matched.push(result.resolvedPlate); + } + }); + return { results: results, matched: matched }; +} + +export function resolvePlateAgainstAssets(plate, brandModels) { + var result = validatePlateForLeaseOrder(plate, brandModels); + return result.ok ? result.resolvedPlate : null; +} + +export function matchPlatesAgainstAssets(plates, brandModels) { + var matched = []; + (plates || []).forEach(function (plate) { + var resolved = resolvePlateAgainstAssets(plate, brandModels); + if (resolved && matched.indexOf(resolved) < 0) matched.push(resolved); + }); + return matched; +} + +export function syncRowPlateFields(row) { + if (!row) return row; + var next = Object.assign({}, row); + var brandModels = normalizeBrandModels(next); + var plateMode = getPlateMode(next); + next.plateMode = plateMode; + if (plateMode === PLATE_MODE_ACTUAL) { + next.plateNos = [PLATE_ACTUAL_DELIVERY]; + return next; + } + var matched = matchPlatesAgainstAssets(normalizePlateNos(next), brandModels); + next.plateNos = matched; + var qty = next.vehicleQty != null ? Number(next.vehicleQty) : 1; + if (!Number.isFinite(qty) || qty <= 0) qty = 1; + if (matched.length > qty) next.vehicleQty = matched.length; + return next; +} + export function formatBrandModelPair(brand, model) { if (!brand && !model) return ''; if (!model) return brand; @@ -151,24 +329,33 @@ export function formatBrandModelsDisplay(brandModels) { }).filter(Boolean); } -export function getRowVehicleCountForPricing(row, order) { +export function getRowVehicleQty(row) { + if (!row) return 0; var plates = normalizePlateNos(row); var actualPlates = plates.filter(function (plate) { return plate !== PLATE_ACTUAL_DELIVERY; }); if (actualPlates.length > 0) return actualPlates.length; - if (plates.indexOf(PLATE_ACTUAL_DELIVERY) >= 0) { - var rows = (order && order.rows) || []; - var allActualOnly = rows.length > 0 && rows.every(function (item) { - var itemPlates = normalizePlateNos(item); - return itemPlates.length === 1 && itemPlates[0] === PLATE_ACTUAL_DELIVERY; - }); - if (allActualOnly && rows.length === 1) { - return calcInsuredVehicleCount(order) || 1; - } - return 1; - } - return 0; + var qty = row.vehicleQty != null ? Number(row.vehicleQty) : 1; + if (!Number.isFinite(qty) || qty <= 0) return 1; + return Math.floor(qty); +} + +export function getRowVehicleCountForPricing(row) { + return getRowVehicleQty(row) || 1; +} + +export function calcInsuredVehicleCount(order) { + var rows = (order && order.rows) || []; + if (!rows.length) return 0; + return rows.reduce(function (sum, row) { + return sum + getRowVehicleQty(row); + }, 0); +} + +/** @deprecated 车辆数由各行数量自动汇总,不再支持手动编辑 */ +export function isInsuredVehicleCountEditable() { + return false; } export function countNationalInStockByBrandModel(brand, model) { @@ -180,6 +367,67 @@ export function countNationalInStockByBrandModel(brand, model) { }).length; } +var IN_STOCK_MUNICIPALITIES = ['北京市', '上海市', '天津市', '重庆市']; + +function parseInStockProvinceCity(location) { + if (!location) return { province: '未登记', city: '未登记' }; + var loc = String(location).trim(); + var i; + for (i = 0; i < IN_STOCK_MUNICIPALITIES.length; i++) { + var municipality = IN_STOCK_MUNICIPALITIES[i]; + if (loc.indexOf(municipality) === 0) { + return { province: municipality, city: municipality }; + } + } + var provMatch = loc.match(/^(.*?(?:省|自治区))/); + if (!provMatch) { + var cityOnly = loc.match(/^(.+?市)/); + if (cityOnly) return { province: cityOnly[1], city: cityOnly[1] }; + return { province: '未登记', city: '未登记' }; + } + var province = provMatch[1]; + var rest = loc.slice(provMatch[1].length); + var cityMatch = rest.match(/^(.+?市)/) || rest.match(/^(.+?(?:州|盟|地区))/); + return { + province: province, + city: cityMatch ? cityMatch[1] : '省内其他', + }; +} + +/** 按品牌型号统计在库车辆,并按省、市分组 */ +export function getInStockBreakdownByBrandModel(brand, model) { + if (!brand || !model) { + return { total: 0, regions: [] }; + } + var grouped = {}; + vehiclesData.forEach(function (vehicle) { + if (vehicle.brand !== brand || vehicle.model !== model) return; + if (vehicle.operateStatus !== '可运营' && vehicle.operateStatus !== '待运营') return; + var parts = parseInStockProvinceCity(vehicle.location); + var province = parts.province; + var city = parts.city; + if (!grouped[province]) grouped[province] = {}; + grouped[province][city] = (grouped[province][city] || 0) + 1; + }); + var regions = Object.keys(grouped).sort(function (a, b) { + return a.localeCompare(b, 'zh-CN'); + }).map(function (province) { + var cities = Object.keys(grouped[province]).sort(function (a, b) { + return a.localeCompare(b, 'zh-CN'); + }).map(function (city) { + return { city: city, count: grouped[province][city] }; + }); + var provinceTotal = cities.reduce(function (sum, item) { + return sum + item.count; + }, 0); + return { province: province, total: provinceTotal, cities: cities }; + }); + return { + total: countNationalInStockByBrandModel(brand, model), + regions: regions, + }; +} + /** 车型最低租金配置(原型);键为 brand|model */ export var LEASE_VEHICLE_MIN_RENT_BY_MODEL = { '飞驰|集卡头': 15000, @@ -215,48 +463,16 @@ export function hasLeaseOrderRentBelowMinimum(order) { }); } -export function calcInsuredVehicleCount(order) { - var rows = (order && order.rows) || []; - var autoFromPlates = 0; - var hasActualOnly = false; - rows.forEach(function (row) { - var plates = normalizePlateNos(row); - var actualPlates = plates.filter(function (plate) { - return plate !== PLATE_ACTUAL_DELIVERY; - }); - if (actualPlates.length > 0) { - autoFromPlates += actualPlates.length; - return; - } - if (plates.indexOf(PLATE_ACTUAL_DELIVERY) >= 0) { - hasActualOnly = true; - } - }); - if (autoFromPlates > 0) return autoFromPlates; - if (hasActualOnly) { - var manual = order && order.insuredVehicleCount != null ? Number(order.insuredVehicleCount) : 0; - if (Number.isFinite(manual) && manual > 0) return Math.floor(manual); - return 1; - } - return 0; -} - -export function isInsuredVehicleCountEditable(order) { - var rows = (order && order.rows) || []; - return rows.some(function (row) { - var plates = normalizePlateNos(row); - return plates.length === 1 && plates[0] === PLATE_ACTUAL_DELIVERY; - }) && !rows.some(function (row) { - return normalizePlateNos(row).some(function (plate) { - return plate !== PLATE_ACTUAL_DELIVERY; - }); - }); -} - export function formatLeasePeriod(row) { if (!row) return ''; + if (row.leasePeriodMode === 'fixed' || (row.leasePeriodStart && row.leasePeriodEnd && !row.leasePeriodMonths)) { + if (row.leasePeriodStart && row.leasePeriodEnd) { + return row.leasePeriodStart + ' ~ ' + row.leasePeriodEnd; + } + return row.leasePeriodStart || row.leasePeriodEnd || ''; + } if (row.leasePeriodMonths != null && row.leasePeriodMonths !== '') { - return String(row.leasePeriodMonths) + ' 个月'; + return '提车起算 ' + String(row.leasePeriodMonths) + ' 个月'; } if (row.leasePeriodStart && row.leasePeriodEnd) { return row.leasePeriodStart + ' ~ ' + row.leasePeriodEnd; @@ -264,44 +480,196 @@ export function formatLeasePeriod(row) { return row.leasePeriodStart || row.leasePeriodEnd || ''; } -/** 租赁订单附加服务(按类目分组;fee 仅作原型参考,不在选择器中展示) */ +export function normalizeExtraServices(row) { + if (!row) return []; + var values = []; + if (Array.isArray(row.extraServices)) values = row.extraServices.slice(); + else if (row.extraService) values = [row.extraService]; + return values.filter(function (value) { + return Boolean(getExtraServiceByValue(value)); + }); +} + +export function calcRowServiceFee(row) { + return sumFixedExtraServiceFees(normalizeExtraServices(row)); +} + +export function formatServiceFeeAmount(value) { + var num = value != null && value !== '' ? Number(value) : 0; + if (!Number.isFinite(num)) num = 0; + return num.toFixed(2); +} + +export function syncRowPricingFields(row) { + var next = Object.assign({}, row); + next.serviceFee = calcRowServiceFee(next); + next.rentServiceSubtotal = calcRentServiceSubtotal( + next.rent, + next.serviceFee, + getRowVehicleCountForPricing(next), + ); + return next; +} + +export function formatLeaseOrderDeliveryRegion(order) { + if (!order) return '-'; + if (order.deliveryRegionMode === 'tbd' || order.deliveryRegionTbd) { + return DELIVERY_REGION_TBD_LABEL; + } + var region = order.deliveryRegion || []; + if (!region.length) return '-'; + return region.join(' / '); +} + +export function formatLeaseOrderDeliveryDate(order) { + if (!order) return '-'; + if (order.deliveryDateMode === 'unconfirmed' || order.deliveryDateTbd) { + return LEASE_DELIVERY_DATE_UNCONFIRMED_LABEL; + } + if (order.deliveryDateStart && order.deliveryDateEnd) { + return order.deliveryDateStart + ' ~ ' + order.deliveryDateEnd; + } + if (order.deliveryDate) return order.deliveryDate; + return '-'; +} + +export function normalizeLeaseOrderState(order) { + var base = order || createDefaultLeaseOrderState(); + var rows = (base.rows || [createEmptyLeaseOrderRow()]).map(function (row) { + var next = Object.assign({}, createEmptyLeaseOrderRow(), row); + if (!next.leasePeriodMode) { + next.leasePeriodMode = (next.leasePeriodStart && next.leasePeriodEnd && !next.leasePeriodMonths) + ? 'fixed' + : 'months'; + } + if (next.leasePeriodMode === 'months' && (next.leasePeriodMonths == null || next.leasePeriodMonths === '')) { + next.leasePeriodMonths = 1; + } + return syncRowPricingFields(syncRowPlateFields(next)); + }); + var normalized = Object.assign({}, createDefaultLeaseOrderState(), base, { + rows: rows.length ? rows : [createEmptyLeaseOrderRow()], + deliveryRegionMode: base.deliveryRegionMode + || (base.deliveryRegionTbd ? 'tbd' : ((base.deliveryRegion && base.deliveryRegion.length) ? 'region' : 'tbd')), + deliveryDateMode: base.deliveryDateMode + || (base.deliveryDateTbd ? 'unconfirmed' : ((base.deliveryDateStart && base.deliveryDateEnd) ? 'range' : 'unconfirmed')), + deliveryDateStart: base.deliveryDateStart || null, + deliveryDateEnd: base.deliveryDateEnd || null, + deliveryDateTbd: Boolean(base.deliveryDateTbd || base.deliveryDateMode === 'unconfirmed'), + deliveryRegionTbd: Boolean(base.deliveryRegionTbd || base.deliveryRegionMode === 'tbd'), + }); + normalized.insuredVehicleCount = calcInsuredVehicleCount(normalized); + return normalized; +} + +/** 租赁订单服务项:固定费用 / 浮动费用 */ +export var LEASE_SERVICE_FEE_TYPE = { + FIXED: 'fixed', + FLOATING: 'floating', +}; + export var LEASE_EXTRA_SERVICE_TREE = [ { - category: '交还车服务', - items: [ - { value: 'door-delivery', name: '送车服务费', fee: 300 }, - { value: 'door-pickup', name: '接车服务费', fee: 300 }, + category: '固定费用', + feeType: LEASE_SERVICE_FEE_TYPE.FIXED, + subCategories: [ + { + name: '无忧包', + items: [ + { value: 'wear-insurance', name: '易损保', fee: 80, unitLabel: '元/车/月' }, + { value: 'maintenance-insurance', name: '养护保', fee: 200, unitLabel: '元/车/月' }, + { value: 'tire-insurance', name: '轮胎保', fee: 100, unitLabel: '元/车/月' }, + ], + }, + { + name: '交还车服务', + items: [ + { value: 'door-delivery', name: '送车服务费', fee: 300, unitLabel: '元/车/月' }, + { value: 'door-pickup', name: '接车服务费', fee: 300, unitLabel: '元/车/月' }, + ], + }, + { + name: '车辆加装', + items: [ + { value: 'tailgate', name: '尾板', fee: 150, unitLabel: '元/车/月' }, + ], + }, ], }, { - category: '增值保障', - items: [ - { value: 'carefree-pack', name: '无忧包', fee: 500 }, - { value: 'tire-insurance', name: '轮胎保', fee: 200 }, - { value: 'maintenance-insurance', name: '养护保', fee: 180 }, - ], - }, - { - category: '车辆加装', - items: [ - { value: 'tailgate', name: '尾板', fee: 150 }, + category: '浮动费用', + feeType: LEASE_SERVICE_FEE_TYPE.FLOATING, + subCategories: [ + { + name: '里程计费', + items: [ + { + value: 'mileage-maintenance', + name: '维保费', + billingRule: '0.10 元/公里,按实际行驶里程计入租赁账单', + }, + { + value: 'transport-risk', + name: '运保费', + billingRule: '0.08 元/公里,按实际行驶里程计入租赁账单', + }, + ], + }, + { + name: '使用期计费', + items: [ + { + value: 'usage-extra', + name: '超期使用费', + billingRule: '按车辆实际使用天数核算,计入后续租赁账单', + }, + { + value: 'energy-adjust', + name: '能源费补缴', + billingRule: '按氢量/电量实际用量与合同约定单价核算', + }, + ], + }, ], }, ]; -export var LEASE_EXTRA_SERVICE_CATALOG = LEASE_EXTRA_SERVICE_TREE.reduce(function (list, group) { - return list.concat(group.items); -}, []); +function forEachExtraServiceItem(callback) { + LEASE_EXTRA_SERVICE_TREE.forEach(function (root) { + (root.subCategories || []).forEach(function (sub) { + (sub.items || []).forEach(function (item) { + callback(item, root, sub); + }); + }); + }); +} + +function buildExtraServiceCatalog() { + var list = []; + forEachExtraServiceItem(function (item, root, sub) { + list.push(Object.assign({}, item, { + feeType: root.feeType, + category: root.category, + subCategory: sub.name, + })); + }); + return list; +} + +export var LEASE_EXTRA_SERVICE_CATALOG = buildExtraServiceCatalog(); export function flattenExtraServiceOptions() { var options = []; - LEASE_EXTRA_SERVICE_TREE.forEach(function (group) { - group.items.forEach(function (item) { - options.push({ - value: item.value, - name: item.name, - category: group.category, - }); + forEachExtraServiceItem(function (item, root, sub) { + options.push({ + value: item.value, + name: item.name, + category: root.category, + subCategory: sub.name, + feeType: root.feeType, + fee: item.fee, + unitLabel: item.unitLabel, + billingRule: item.billingRule, }); }); return options; @@ -311,19 +679,19 @@ export function matchExtraServiceOption(option, query) { var normalized = (query || '').trim().toLowerCase(); if (!normalized) return true; return option.name.toLowerCase().indexOf(normalized) >= 0 - || option.category.toLowerCase().indexOf(normalized) >= 0; + || (option.category || '').toLowerCase().indexOf(normalized) >= 0 + || (option.subCategory || '').toLowerCase().indexOf(normalized) >= 0 + || (option.billingRule || '').toLowerCase().indexOf(normalized) >= 0; } export function getExtraServiceCategoryByValue(value) { - for (var i = 0; i < LEASE_EXTRA_SERVICE_TREE.length; i++) { - var group = LEASE_EXTRA_SERVICE_TREE[i]; - for (var j = 0; j < group.items.length; j++) { - if (group.items[j].value === value) { - return group.category; - } - } - } - return ''; + var item = getExtraServiceByValue(value); + return item ? item.category : ''; +} + +export function getExtraServiceSubCategoryByValue(value) { + var item = getExtraServiceByValue(value); + return item ? item.subCategory : ''; } export var LEASE_EXTRA_SERVICE_OPTIONS = LEASE_EXTRA_SERVICE_CATALOG.map(function (item) { @@ -334,15 +702,13 @@ export var LEASE_EXTRA_SERVICE_OPTIONS = LEASE_EXTRA_SERVICE_CATALOG.map(functio }); export function getExtraServiceFeeByValue(value) { - for (var i = 0; i < LEASE_EXTRA_SERVICE_CATALOG.length; i++) { - if (LEASE_EXTRA_SERVICE_CATALOG[i].value === value) { - return LEASE_EXTRA_SERVICE_CATALOG[i].fee; - } - } - return 0; + var item = getExtraServiceByValue(value); + if (!item || item.feeType !== LEASE_SERVICE_FEE_TYPE.FIXED) return 0; + return item.fee != null ? Number(item.fee) : 0; } export function getExtraServiceByValue(value) { + if (!value) return null; for (var i = 0; i < LEASE_EXTRA_SERVICE_CATALOG.length; i++) { if (LEASE_EXTRA_SERVICE_CATALOG[i].value === value) { return LEASE_EXTRA_SERVICE_CATALOG[i]; @@ -359,14 +725,63 @@ export function getExtraServiceNames(selectedValues) { }); } -export function normalizeExtraServices(row) { - if (!row) return []; - if (Array.isArray(row.extraServices)) return row.extraServices; - if (row.extraService) return [row.extraService]; - return []; +export function formatExtraServiceUnitPrice(item) { + if (!item || item.feeType !== LEASE_SERVICE_FEE_TYPE.FIXED) return ''; + var fee = item.fee != null ? item.fee : 0; + return fee.toLocaleString('zh-CN') + (item.unitLabel || '元/车/月'); } -export function sumExtraServiceFees(selectedValues) { +export function getExtraFeeLockedServiceValues(existingRows) { + var values = []; + (existingRows || []).forEach(function (row) { + var rowValues = row.serviceValues || []; + if (!rowValues.length && row.serviceValue) { + rowValues = [row.serviceValue]; + } + rowValues.forEach(function (value) { + if (value && values.indexOf(value) < 0) values.push(value); + }); + }); + return values; +} + +export function getExtraFeeNewServiceValues(selectedValues, lockedValues) { + return (selectedValues || []).filter(function (value) { + return (lockedValues || []).indexOf(value) < 0; + }); +} + +export function formatExtraFeeExistingServiceLabel(serviceValues) { + var names = getExtraServiceNames(serviceValues || []); + return names.length ? names.join('、') : '历史附加服务项'; +} + +export function formatExtraServicesDisplay(selectedValues) { + if (!selectedValues || !selectedValues.length) return ''; + return selectedValues.map(function (value) { + var item = getExtraServiceByValue(value); + if (!item) return value; + if (item.feeType === LEASE_SERVICE_FEE_TYPE.FIXED) { + return item.name + '(' + formatExtraServiceUnitPrice(item) + ')'; + } + return item.name + '(' + (item.billingRule || '浮动计费') + ')'; + }).join('、'); +} + +/** 合同预览:服务内容每行一条 */ +export function formatExtraServicesPreviewLines(selectedValues) { + if (!selectedValues || !selectedValues.length) return []; + return selectedValues.map(function (value) { + var item = getExtraServiceByValue(value); + if (!item) return value; + if (item.feeType === LEASE_SERVICE_FEE_TYPE.FIXED) { + return item.name + '(' + formatExtraServiceUnitPrice(item) + ')'; + } + return item.name + ':' + (item.billingRule || '浮动计费'); + }); +} + +export function sumFixedExtraServiceFees(selectedValues) { if (!selectedValues || !selectedValues.length) return 0; var total = 0; selectedValues.forEach(function (value) { @@ -375,9 +790,9 @@ export function sumExtraServiceFees(selectedValues) { return total; } -export function formatExtraServicesDisplay(selectedValues) { - if (!selectedValues || !selectedValues.length) return ''; - return getExtraServiceNames(selectedValues).join('、'); +/** @deprecated 仅统计固定费用;浮动费用不计入合同初始服务费 */ +export function sumExtraServiceFees(selectedValues) { + return sumFixedExtraServiceFees(selectedValues); } export function calcRentServiceSubtotal(rent, serviceFee, vehicleCount) { @@ -408,7 +823,7 @@ export function calcLeaseOrderKpis(rows, order) { var hasSubtotal = false; var hasDeposit = false; (rows || []).forEach(function (row) { - var vehicleCount = getRowVehicleCountForPricing(row, order || { rows: rows }); + var vehicleCount = getRowVehicleCountForPricing(row); var subtotal = calcRentServiceSubtotal(row.rent, row.serviceFee, vehicleCount); if (subtotal != null) { rentServiceTaxTotal += subtotal; @@ -476,7 +891,13 @@ export function createDefaultLeaseOrderState() { insuredVehicleCount: 1, thirdPartyLiabilityMillion: null, deliveryRegion: [], + deliveryRegionMode: 'tbd', + deliveryRegionTbd: true, deliveryDate: null, + deliveryDateStart: null, + deliveryDateEnd: null, + deliveryDateMode: 'unconfirmed', + deliveryDateTbd: true, rows: [createEmptyLeaseOrderRow()], }; } diff --git a/src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs b/src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs index db4e415..b44141d 100644 --- a/src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs +++ b/src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs @@ -1,6 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { renderMermaidInMarkdown } from './render-mermaid-blocks.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const protoRoot = path.resolve(__dirname, '..'); @@ -10,10 +11,19 @@ const resourcesPrd = path.resolve(protoRoot, '../../resources/lease-contract-man const listPrd = fs.readFileSync(path.join(specRoot, 'requirements-prd-list.md'), 'utf8'); const createPrd = fs.readFileSync(path.join(specRoot, 'requirements-prd-create.md'), 'utf8'); const fullPrd = fs.readFileSync(resourcesPrd, 'utf8'); +const flowPrdRaw = fs.readFileSync(path.join(specRoot, 'requirements-flow-operations.md'), 'utf8'); +const annotationContextPrd = fs.readFileSync(path.join(specRoot, 'requirements-annotation-context.md'), 'utf8'); + +const [flowPrd, fullPrdRendered, listPrdRendered, createPrdRendered] = await Promise.all([ + renderMermaidInMarkdown(flowPrdRaw, '操作流程图'), + renderMermaidInMarkdown(fullPrd, '流程图'), + renderMermaidInMarkdown(listPrd, '流程图'), + renderMermaidInMarkdown(createPrd, '流程图'), +]); const NODE_DEFS = [ - { id: 'lc-list-filter', title: '筛选条件', pageId: 'list', color: '#2563eb', aiPrompt: '列表筛选区字段与查询逻辑。', annotationText: '默认展示 4 项,可展开至 12 项;查询与 KPI 联动。' }, - { id: 'lc-list-kpi', title: 'KPI 统计卡片', pageId: 'list', color: '#64748b', aiPrompt: '五张 KPI 卡片口径与点击筛选。', annotationText: '全部 / 进行中 / 审批中 / 临期 / 已终止。' }, + { id: 'lc-list-filter', title: '筛选条件', pageId: 'list', color: '#2563eb', aiPrompt: '列表筛选区字段与查询逻辑。', annotationText: '默认展示 4 项,可展开至 13 项;合同模板联动标准合同名称;查询与 KPI 联动。' }, + { id: 'lc-list-kpi', title: 'KPI 统计卡片', pageId: 'list', color: '#64748b', aiPrompt: '五张 KPI 卡片口径与点击筛选。', annotationText: '全部 / 草稿 / 进行中 / 审批中 / 已终止。' }, { id: 'lc-fleet-summary', title: '在租车辆概览', pageId: 'list', color: '#32a06e', aiPrompt: '按品牌型号统计在租车辆,支持品牌标签筛选。', annotationText: '顶部展示在租总数;品牌标签筛选车型卡片;点击卡片查看车辆明细。' }, { id: 'lc-list-toolbar', title: '列表工具栏', pageId: 'list', color: '#0f766e', aiPrompt: '费用模板、导出、新增入口。', annotationText: '「新增」进入租赁合同创建页。' }, { id: 'lc-list-table', title: '合同台账列表', pageId: 'list', color: '#2563eb', aiPrompt: '台账表格列与操作。', annotationText: '' }, @@ -21,7 +31,9 @@ const NODE_DEFS = [ { id: 'lc-action-convert-tripartite', title: '转三方合同', pageId: 'list', color: '#7c3aed', aiPrompt: '进行中合同转三方。', annotationText: '' }, { id: 'lc-action-trial-to-formal', title: '试用转正式', pageId: 'list', color: '#0f766e', aiPrompt: '试用合同一步转正式。', annotationText: '' }, { id: 'lc-create-main-contract', title: '主体合同信息', pageId: 'create', color: '#32a06e', aiPrompt: '签约、里程、费用三块表单。', annotationText: '完成度徽章;甲乙档案只读同步。' }, + { id: 'lc-create-signing-method', title: '合同签署方式', pageId: 'create', color: '#0369a1', aiPrompt: '线上电子签章或线下人工上传。', annotationText: '影响电子签发送或盖章合同补传闭环。' }, { id: 'lc-card-signing', title: '签约信息', pageId: 'create', color: '#0d9488', aiPrompt: '合同编号、甲乙双方与乙方负责人。', annotationText: '' }, + { id: 'lc-credentials-ocr', title: '客户资质证照 OCR', pageId: 'create', color: '#dc2626', aiPrompt: '客户证照 OCR 识别与有效期校验。', annotationText: '' }, { id: 'lc-signing-customer-principal', title: '乙方负责人', pageId: 'create', color: '#0d9488', aiPrompt: '乙方签约对接负责人。', annotationText: '' }, { id: 'lc-create-lease-order', title: '附件1:租赁订单', pageId: 'create', color: '#2563eb', aiPrompt: '订单车辆与交车信息。', annotationText: '三者责任险等为完成度必填项。' }, { id: 'lc-lease-order-brand-model', title: '品牌车型与在库', pageId: 'create', color: '#0f766e', aiPrompt: '选择车型后展示全国在库台数。', annotationText: '运营状态为「可运营」「待运营」计为在库;数据来自车辆管理主数据。' }, @@ -30,11 +42,12 @@ const NODE_DEFS = [ { id: 'lc-lease-order-lease-period', title: '租赁期限与到期计算', pageId: 'create', color: '#0369a1', aiPrompt: '租期两种计算模式与合同到期规则。', annotationText: '' }, { id: 'lc-create-poa', title: '授权委托书', pageId: 'create', color: '#7c3aed', aiPrompt: '受托人列表编辑。', annotationText: '至少一行且姓名/联系方式/身份证完整。' }, { id: 'lc-create-contract-remark', title: '合同备注', pageId: 'create', color: '#64748b', aiPrompt: '独立卡片填写合同备注。', annotationText: '选填,最多 500 字;与授权委托书分离。' }, + { id: 'lc-create-seal', title: '用章类型', pageId: 'create', color: '#7c3aed', aiPrompt: '电子签章用章类型多选。', annotationText: '' }, { id: 'lc-create-preview', title: '实时预览', pageId: 'create', color: '#0ea5e9', aiPrompt: 'Word 版式预览与可编辑正文。', annotationText: '改动风控红线条款将触发非标准合同。' }, { id: 'lc-create-footer', title: '底栏操作', pageId: 'create', color: '#64748b', aiPrompt: '取消、保存草稿、提交审核。', annotationText: '保存/提交时展示将走的审批类型。' }, ]; -const prdByKey = { list: listPrd, create: createPrd }; +const prdByKey = { list: listPrdRendered, create: createPrdRendered }; function extractSection(md, startHeading) { const lines = md.split('\n'); @@ -110,9 +123,52 @@ const CUSTOM_NODE_MARKDOWN = { 选择乙方后,在签约信息中补充填写: - **乙方负责人姓名**:签约对接负责人,可与客户档案中的联系人不同 -- **乙方负责人手机号**:用于履约沟通与通知 +- **乙方负责人手机号**:E签宝电子合同签章时乙方经办人 -选填;用于合同履约联系,不覆盖客户档案只读信息。`, +**签署闭环:** + +- **线上电子签章**:审批通过后自动向该手机号发送 E签宝电子签章文件 +- **线下人工上传**:审批通过不发送电子签,需在列表「盖章合同补传」上传 PDF/图片,查看时可预览与下载全部附件`, + 'lc-fleet-summary': `# 在租车辆概览 + +按品牌型号统计在租车辆,支持品牌标签筛选。 + +## 说明 + +顶部展示在租总数;品牌标签筛选车型卡片;点击卡片查看车辆明细。`, + 'lc-credentials-ocr': `# 客户资质证照(OCR 与有效期) + +对 **客户营业执照**、**法人身份证正反面**、**道路运输许可证** 做 OCR 识别,校验有效期。 + +| 规则 | 说明 | +|---|---| +| OCR | 上传后自动识别证照信息与有效期 | +| 即将到期 | 距到期 ≤ 3 个月时,工作台提前生成待办推送给业管 | +| 已过期 | 业管未更新前,禁止提交新合同 | +| 页面展示 | 缩略图旁显示 OCR 标识、有效期与状态徽章;临期/过期时顶部展示提示条`, + 'lc-create-seal': `# 用章类型(第 6 步) + +支持多选,可选范围仅包含: + +| 用章 | 说明 | +|---|---| +| 合同章 | 合同正文用章 | +| 公章 | 公司公章 | +| 法人章 | 法定代表人章 | + +**不含**财务章、发票章。 + +默认选中合同章;至少选择一种用章后方可提交审核。 + +**提示**:额外勾选公章、法人章会进入非标审批流程。`, + 'lc-create-signing-method': `# 合同签署方式 + +与「选择合同类型」同一步配置,可选: + +| 方式 | 说明 | +|---|---| +| 线上电子签章 | 审批通过后向乙方负责人手机号发送 E签宝文件并自动归档 | +| 线下人工上传 | 不发送电子签,审批通过后通过列表「盖章合同补传」完成闭环 |`, 'lc-lease-order-lease-period': `# 租赁期限与到期计算 租赁到期时间支持两种计算模式: @@ -204,7 +260,32 @@ const markdownMap = Object.fromEntries(nodes.map((node) => [node.id, buildNodeMa const listPageNodes = nodes.filter((n) => n.pageId === 'list'); const createPageNodes = nodes.filter((n) => n.pageId === 'create'); -const overviewMd = extractSection(fullPrd, '## 1. 背景与目标') ?? '# 租赁合同管理'; +function buildOverviewMarkdown(fullPrdText, listPrdText) { + const parts = [ + '# 租赁合同管理 · 模块总览', + '商用车租赁合同全生命周期管理:台账筛选与 KPI 监控、结构化草拟与实时预览、标准/非标准审批判定、进行中变更与法务归档。', + ]; + + const modulePosition = extractSection(listPrdText, '## 1. 模块定位'); + if (modulePosition) parts.push(modulePosition); + + for (const heading of [ + '## 1. 背景与目标', + '## 2. 用户与场景', + '## 3. 名词解释', + '## 4. 信息架构', + '## 6. 业务规则(全局)', + '## 9. 与其他模块的关系', + '## 12. 验收标准', + ]) { + const body = extractSection(fullPrdText, heading); + if (body) parts.push(body); + } + + return parts.join('\n\n---\n\n').trim() || '# 租赁合同管理'; +} + +const overviewMd = buildOverviewMarkdown(fullPrdRendered, listPrdRendered); const source = { documentVersion: 1, @@ -249,28 +330,98 @@ const source = { type: 'markdown', id: 'lc-doc-prd-full', title: 'PRD 完整文档', - markdown: fullPrd, + markdown: fullPrdRendered, markdownPath: 'src/resources/lease-contract-management/PRD.md', }, { type: 'markdown', id: 'lc-doc-prd-list', title: '列表页 PRD', - markdown: listPrd, + markdown: listPrdRendered, markdownPath: 'src/prototypes/lease-contract-management/.spec/requirements-prd-list.md', }, { type: 'markdown', id: 'lc-doc-prd-create', title: '新增页 PRD', - markdown: createPrd, + markdown: createPrdRendered, markdownPath: 'src/prototypes/lease-contract-management/.spec/requirements-prd-create.md', }, { type: 'markdown', id: 'lc-doc-prd-acceptance', title: '验收标准', - markdown: extractSection(fullPrd, '## 12. 验收标准') ?? '## 验收标准', + markdown: extractSection(fullPrdRendered, '## 12. 验收标准') ?? '## 验收标准', + }, + { + type: 'markdown', + id: 'lc-doc-annotation-context', + title: '标注与原型上下文', + markdown: annotationContextPrd, + markdownPath: 'src/prototypes/lease-contract-management/.spec/requirements-annotation-context.md', + }, + ], + }, + { + type: 'folder', + id: 'lc-doc-flows', + title: '操作流程', + defaultExpanded: false, + children: [ + { + type: 'markdown', + id: 'lc-doc-flow-overview', + title: '全流程总览', + markdown: extractSection(flowPrd, '## 1. 总览:从新增到履约结束') ?? flowPrd, + markdownPath: 'src/prototypes/lease-contract-management/.spec/requirements-flow-operations.md', + }, + { + type: 'markdown', + id: 'lc-doc-flow-create-forward', + title: '新增合同 · 正向', + markdown: extractSection(flowPrd, '## 2. 新增合同 · 正向流程') ?? '', + }, + { + type: 'markdown', + id: 'lc-doc-flow-create-reverse', + title: '新增合同 · 逆向', + markdown: extractSection(flowPrd, '## 3. 新增合同 · 逆向流程') ?? '', + }, + { + type: 'markdown', + id: 'lc-doc-flow-fulfill', + title: '履约 · 交还车', + markdown: extractSection(flowPrd, '## 4. 履约正向:交车 → 还车 → 结清') ?? '', + }, + { + type: 'markdown', + id: 'lc-doc-flow-fulfill-reverse', + title: '履约 · 逆向与异常', + markdown: extractSection(flowPrd, '## 5. 履约逆向与异常') ?? '', + }, + { + type: 'markdown', + id: 'lc-doc-flow-change-forward', + title: '进行中变更 · 正向', + markdown: extractSection(flowPrd, '## 6. 进行中变更 · 正向流程') ?? '', + }, + { + type: 'markdown', + id: 'lc-doc-flow-change-reverse', + title: '进行中变更 · 逆向', + markdown: extractSection(flowPrd, '## 7. 进行中变更 · 逆向流程') ?? '', + }, + { + type: 'markdown', + id: 'lc-doc-flow-delivery-edit', + title: '交车安排编辑', + markdown: extractSection(flowPrd, '## 8. 交车安排编辑(列表与子表)') ?? '', + }, + { + type: 'markdown', + id: 'lc-doc-flow-matrix', + title: '操作与状态对照表', + markdown: extractSection(flowPrd, '## 9. 操作与状态对照表') ?? '', }, ], }, @@ -320,7 +471,7 @@ const source = { type: 'markdown', id: 'lc-doc-annotation-hint', title: '标注查看提示', - markdown: '## 标注查看提示\n\n右侧标注工具栏可浏览目录、切换主题与颜色筛选。列表页与新增页标注按当前页面自动显隐。\n\n**PRD 同步**:修改 `src/resources/lease-contract-management/PRD.md` 或 `.spec/requirements-prd-*.md` 后,运行 `node src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs` 重新生成 `annotation-source.json`。', + markdown: '## 标注查看提示\n\n右侧标注工具栏可浏览目录、切换主题与颜色筛选。列表页与新增页标注按当前页面自动显隐。\n\n**PRD 同步**:修改 `src/resources/lease-contract-management/PRD.md` 或 `.spec/requirements-prd-*.md`、`.spec/requirements-flow-operations.md` 后,运行 `node src/prototypes/lease-contract-management/scripts/build-annotation-source.mjs` 重新生成 `annotation-source.json`(含 Mermaid 流程图预渲染为 SVG 图片)。', }, ], }, diff --git a/src/prototypes/lease-contract-management/scripts/render-mermaid-blocks.mjs b/src/prototypes/lease-contract-management/scripts/render-mermaid-blocks.mjs new file mode 100644 index 0000000..774097a --- /dev/null +++ b/src/prototypes/lease-contract-management/scripts/render-mermaid-blocks.mjs @@ -0,0 +1,79 @@ +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, '../../../..'); +const mmdcBin = path.join(projectRoot, 'node_modules/.bin/mmdc'); + +const MERMAID_BLOCK_RE = /```mermaid\n([\s\S]*?)```/g; + +function renderMermaidToSvg(code) { + if (!fs.existsSync(mmdcBin)) { + throw new Error('mmdc not found. Run: npm install -D @mermaid-js/mermaid-cli'); + } + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lc-mermaid-')); + const inputPath = path.join(tempDir, 'diagram.mmd'); + const outputPath = path.join(tempDir, 'diagram.svg'); + try { + fs.writeFileSync(inputPath, code.trim(), 'utf8'); + execFileSync( + mmdcBin, + ['-i', inputPath, '-o', outputPath, '-b', 'transparent', '-t', 'neutral'], + { stdio: 'pipe' }, + ); + return fs.readFileSync(outputPath, 'utf8'); + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }); + } +} + +function svgToMarkdownImage(svg, alt = '流程图') { + const base64 = Buffer.from(svg, 'utf8').toString('base64'); + return `\n\n![${alt}](data:image/svg+xml;base64,${base64})\n\n`; +} + +/** + * Replace ```mermaid fenced blocks with inline SVG images (data URI). + * Annotation markdown renderer supports ![alt](data:image/svg+xml;base64,...). + */ +export async function renderMermaidInMarkdown(markdown, alt = '流程图') { + if (!markdown || !markdown.includes('```mermaid')) { + return markdown; + } + + const blocks = []; + let match; + const re = new RegExp(MERMAID_BLOCK_RE.source, 'g'); + while ((match = re.exec(markdown)) !== null) { + blocks.push({ start: match.index, end: match.index + match[0].length, code: match[1] }); + } + + if (blocks.length === 0) return markdown; + + const rendered = blocks.map((block, index) => { + try { + const svg = renderMermaidToSvg(block.code); + return { ...block, image: svgToMarkdownImage(svg, `${alt} ${index + 1}`) }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.warn(`[mermaid] render failed for block ${index + 1}: ${message}`); + return { + ...block, + image: `\n\n> ⚠️ 流程图渲染失败:${message}\n\n\`\`\`mermaid\n${block.code}\`\`\`\n\n`, + }; + } + }); + + let result = ''; + let cursor = 0; + for (const block of rendered) { + result += markdown.slice(cursor, block.start); + result += block.image; + cursor = block.end; + } + result += markdown.slice(cursor); + return result; +} diff --git a/src/prototypes/lease-contract-management/styles/lease-contract-create.css b/src/prototypes/lease-contract-management/styles/lease-contract-create.css index 5931ee1..f184aa1 100644 --- a/src/prototypes/lease-contract-management/styles/lease-contract-create.css +++ b/src/prototypes/lease-contract-management/styles/lease-contract-create.css @@ -1,7 +1,59 @@ /** * 新增租赁合同 — 左右分栏布局 + * 表单控件边框/高度 token 见 lease-contract.css(.vm-page.lc-page) */ +.vm-page.lc-create-page { + --lc-create-footer-height: 72px; + display: flex; + flex-direction: column; + height: 100vh; + min-height: 100vh; + max-height: 100vh; + overflow: hidden; + padding-bottom: 0; +} + +/* 新增/编辑页:表单控件统一高度 */ +.vm-page.lc-create-page :is( + .lc-form-input.ant-input, + .lc-form-select.ant-select, + .lc-form-cascader.ant-select, + .lc-form-datepicker.ant-picker, + .lc-form-number.ant-input-number, + .lc-form-field .ant-input:not(textarea), + .lc-form-field .ant-input-affix-wrapper, + .lc-template-select.ant-select, + .lc-template-signing-row__select.ant-select, + .vm-filter-picker-control, + .lc-customer-picker__trigger, + .lc-lease-order-editor__table .lc-form-select.ant-select, + .lc-lease-order-editor__delivery .lc-form-cascader.ant-select, + .lc-lease-order-editor__delivery .lc-form-datepicker.ant-picker, + .lc-lease-order-editor__summary .lc-form-number.ant-input-number, + .lc-lease-order-editor__summary .ant-space-compact, + .lc-lease-order-period-input.ant-input-number, + #lc-card-fee-body .ant-select, + #lc-card-fee-body .ant-input-number, + .lc-poa-editor__fields--inline .ant-input:not(textarea), + .lc-poa-editor__fields--inline .ant-input-affix-wrapper +) { + min-height: var(--lc-control-height); + height: var(--lc-control-height); + box-sizing: border-box; +} + +.vm-page.lc-create-page .lc-form-select .ant-select-selector, +.vm-page.lc-create-page .lc-form-cascader .ant-select-selector, +.vm-page.lc-create-page .lc-template-select .ant-select-selector, +.vm-page.lc-create-page .lc-template-signing-row__select .ant-select-selector, +.vm-page.lc-create-page .lc-lease-order-editor__table .lc-form-select .ant-select-selector { + min-height: calc(var(--lc-control-height) - 2px) !important; + height: calc(var(--lc-control-height) - 2px); + display: flex; + align-items: center; +} + .vm-page.lc-create-page .lc-form-completion-badge { display: inline-flex; align-items: center; @@ -40,7 +92,21 @@ } .vm-page.lc-create-page .lc-create-section { - scroll-margin-top: 72px; + scroll-margin-top: 12px; +} + +.vm-page.lc-create-page .lc-create-step-nav { + position: sticky; + top: 0; + z-index: 6; + flex-shrink: 0; + margin: 0 0 12px; + 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-section__lock { @@ -60,6 +126,11 @@ } .vm-page.lc-create-page .lc-create-section__lock-text { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 4px; margin: 0; max-width: 18rem; font-size: 0.875rem; @@ -72,20 +143,6 @@ 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; @@ -140,14 +197,32 @@ justify-content: center; width: 22px; height: 22px; - border-radius: 9999px; - background: var(--ln-surface-strong); - font-size: 0.6875rem; - font-weight: 700; + flex-shrink: 0; color: var(--ln-muted); } +.vm-page.lc-create-page .lc-create-step-icon { + display: block; + flex-shrink: 0; +} + +.vm-page.lc-create-page .lc-create-step-icon__ring { + fill: var(--ln-surface-strong); + stroke: var(--ln-hairline-strong); + stroke-width: 1.5; +} + +.vm-page.lc-create-page .lc-create-step-icon__digit { + fill: currentColor; + font-size: 11px; + font-weight: 700; + font-family: var(--vm-font, system-ui, sans-serif); + text-anchor: middle; + dominant-baseline: central; +} + .vm-page.lc-create-page .lc-create-step-nav__item--done .lc-create-step-nav__index { + border-radius: 9999px; background: var(--ln-primary-soft); color: var(--ln-primary); } @@ -156,11 +231,21 @@ 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); +.vm-page.lc-create-page .lc-create-step-nav__index .lc-create-step-title__badge.lc-create-step-icon { + width: 22px; + height: 22px; color: var(--ln-ink); } +.vm-page.lc-create-page .lc-create-step-nav__index .lc-create-step-title__badge .lc-create-step-icon__ring { + fill: var(--ln-primary-soft); + stroke: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline)); +} + +.vm-page.lc-create-page .lc-create-step-nav__index .lc-create-step-title__badge .lc-create-step-icon__digit { + fill: var(--ln-primary); +} + .vm-page.lc-create-page .lc-create-step-nav__item--locked .lc-create-step-nav__btn { opacity: 0.72; } @@ -179,6 +264,11 @@ color: var(--ln-muted); } +.vm-page.lc-create-page .lc-create-step-nav__tag--required { + background: color-mix(in srgb, var(--ln-primary) 10%, var(--ln-canvas-soft)); + color: var(--ln-primary); +} + .vm-page.lc-create-page .lc-create-step-nav__sep { flex: 0 0 12px; width: 12px; @@ -228,6 +318,79 @@ .vm-page.lc-create-page .lc-template-step-panel { padding: 12px 16px 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.vm-page.lc-create-page .lc-template-step-fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + align-items: stretch; +} + +.vm-page.lc-create-page .lc-template-step-fields .lc-form-field { + display: grid; + grid-template-rows: auto var(--lc-control-height); + gap: 6px; + min-width: 0; +} + +.vm-page.lc-create-page .lc-template-step-fields .lc-form-field__control { + min-height: var(--lc-control-height); +} + +.vm-page.lc-create-page .lc-template-step-fields .lc-form-field__control > .ant-select { + width: 100%; + height: var(--lc-control-height) !important; + min-height: var(--lc-control-height) !important; + max-height: var(--lc-control-height) !important; +} + +.vm-page.lc-create-page .lc-template-step-fields .lc-form-field__control > .ant-select .ant-select-selector { + height: 100% !important; + min-height: 100% !important; + box-sizing: border-box; +} + +/* Ant Design 5:模板行 Select 无 .ant-select-selector,统一 content 高度 */ +.vm-page.lc-create-page .lc-template-step-fields .lc-form-field__control > .ant-select .ant-select-content { + display: flex; + align-items: center; + flex: 1 1 auto; + min-height: calc(var(--lc-control-height) - 8px); + line-height: calc(var(--lc-control-height) - 8px); +} + +.vm-page.lc-create-page .lc-template-step-fields .lc-form-field__control > .ant-select .ant-select-placeholder { + line-height: calc(var(--lc-control-height) - 8px); +} + +.vm-page.lc-create-page .lc-template-step-fields .lc-form-select.ant-select, +.vm-page.lc-create-page .lc-template-step-fields .lc-template-select.ant-select, +.vm-page.lc-create-page .lc-template-step-fields .lc-template-signing-row__select.ant-select { + width: 100%; + flex-shrink: 0; +} + +.vm-page.lc-create-page .lc-template-step-fields .lc-form-select .ant-select-selector, +.vm-page.lc-create-page .lc-template-step-fields .lc-template-select .ant-select-selector, +.vm-page.lc-create-page .lc-template-step-fields .lc-template-signing-row__select .ant-select-selector { + align-items: center; +} + +@media (max-width: 768px) { + .vm-page.lc-create-page .lc-template-step-fields { + grid-template-columns: 1fr; + } +} + +.vm-page.lc-create-page .lc-template-select .ant-select-selector, +.vm-page.lc-create-page .lc-template-signing-row__select .ant-select-selector { + align-items: center; + border-radius: var(--ln-radius-control); + cursor: pointer; } .vm-page.lc-create-page .lc-template-step-card__head { @@ -238,12 +401,56 @@ display: flex; align-items: center; flex-wrap: wrap; + justify-content: flex-start; gap: 6px 10px; - flex: 1; + flex: 0 1 auto; + min-width: 0; +} + +.vm-page.lc-create-page .lc-template-step-card__title-row .ct-panel-head__title.lc-create-step-title, +.vm-page.lc-create-page .ct-panel-head__title.lc-create-step-title { + display: inline-flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; + min-width: 0; +} + +.vm-page.lc-create-page .lc-create-step-title__badge.lc-create-step-icon { + width: 22px; + height: 22px; + color: var(--ln-ink); +} + +.vm-page.lc-create-page .lc-create-step-title__badge .lc-create-step-icon__ring { + fill: var(--ln-primary-soft); + stroke: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline)); +} + +.vm-page.lc-create-page .lc-create-step-title__badge .lc-create-step-icon__digit { + fill: var(--ln-primary); +} + +.vm-page.lc-create-page .lc-create-section__lock-step-icon { + color: var(--ln-primary); + vertical-align: middle; +} + +.vm-page.lc-create-page .lc-create-section__lock-step-icon .lc-create-step-icon__ring { + fill: var(--ln-primary-soft); + stroke: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline)); +} + +.vm-page.lc-create-page .lc-create-section__lock-step-icon .lc-create-step-icon__digit { + fill: var(--ln-primary); +} + +.vm-page.lc-create-page .lc-create-step-title__text { min-width: 0; } .vm-page.lc-create-page .lc-template-step-card__title-row .ct-panel-head__title { + flex: 0 0 auto; flex-shrink: 0; } @@ -272,13 +479,6 @@ 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; @@ -313,7 +513,7 @@ gap: 4px; width: 100%; padding: 12px 14px; - border: 1px solid var(--ln-line); + border: 1px solid var(--ln-hairline); border-radius: 10px; background: #fff; text-align: left; @@ -433,15 +633,22 @@ } .vm-page.lc-create-page .lc-signing-core { - padding: 14px; + padding: 16px; border-radius: var(--ln-radius-control); background: var(--ln-canvas-soft); border: 1px solid var(--ln-hairline); } +.vm-page.lc-create-page .lc-signing-core-fields { + display: flex; + flex-direction: column; + gap: 14px; +} + .vm-page.lc-create-page .lc-signing-core .lc-form-field__label-row { - flex-wrap: nowrap; + flex-wrap: wrap; align-items: center; + gap: 4px 8px; } .vm-page.lc-create-page .lc-signing-core .lc-form-field__label { @@ -449,9 +656,82 @@ } .vm-page.lc-create-page .lc-signing-core .lc-form-field__inline-hint { - flex: 0 1 auto; + flex: 1 1 auto; min-width: 0; - white-space: nowrap; +} + +.vm-page.lc-create-page .lc-signing-meta-row, +.vm-page.lc-create-page .lc-signing-project-row, +.vm-page.lc-create-page .lc-signing-core-fields > .lc-form-grid--pair { + display: grid; + gap: 12px 16px; + align-items: stretch; +} + +.vm-page.lc-create-page .lc-signing-meta-row { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.vm-page.lc-create-page .lc-signing-project-row { + grid-template-columns: minmax(0, 1fr); +} + +.vm-page.lc-create-page .lc-signing-core-fields > .lc-form-grid--pair { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.vm-page.lc-create-page .lc-signing-meta-row .lc-form-field, +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field, +.vm-page.lc-create-page .lc-signing-core-fields > .lc-form-grid--pair .lc-form-field { + display: grid; + grid-template-rows: auto var(--lc-control-height); + gap: 6px; + min-width: 0; + margin: 0; +} + +.vm-page.lc-create-page .lc-signing-meta-row .lc-form-field > :not(.lc-form-field__label):not(.lc-form-field__label-row):not(.lc-form-field__hint), +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field > :not(.lc-form-field__label):not(.lc-form-field__label-row):not(.lc-form-field__hint), +.vm-page.lc-create-page .lc-signing-core-fields > .lc-form-grid--pair .lc-form-field > :not(.lc-form-field__label):not(.lc-form-field__label-row):not(.lc-form-field__hint) { + align-self: stretch; + min-width: 0; + min-height: var(--lc-control-height); +} + +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field__control { + display: flex; + align-items: stretch; + width: 100%; + min-height: var(--lc-control-height); +} + +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field__control > .lc-form-input, +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field__control > .ant-input-affix-wrapper { + flex: 1 1 auto; + width: 100%; + min-width: 0; +} + +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field__control .ant-input-affix-wrapper { + display: inline-flex; + align-items: center; + padding-block: 0; + box-sizing: border-box; +} + +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field__control .ant-input-affix-wrapper > input.ant-input { + height: auto; + min-height: 0; + line-height: 22px; +} + +.vm-page.lc-create-page .lc-signing-meta-row .lc-form-field .lc-form-readonly, +.vm-page.lc-create-page .lc-signing-meta-row .lc-form-field .lc-business-dept-picker, +.vm-page.lc-create-page .lc-signing-meta-row .lc-form-field .lc-business-dept-picker__trigger, +.vm-page.lc-create-page .lc-signing-project-row .lc-form-field .lc-form-input, +.vm-page.lc-create-page .lc-signing-core-fields > .lc-form-grid--pair .lc-form-field .lc-form-select, +.vm-page.lc-create-page .lc-signing-core-fields > .lc-form-grid--pair .lc-form-field .lc-customer-picker { + width: 100%; } .vm-page.lc-create-page .lc-party-profiles { @@ -706,7 +986,7 @@ .vm-page.lc-create-page .lc-lease-order-kpi-row { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin: 0; } @@ -758,12 +1038,30 @@ .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-business-dept-picker__trigger { - min-height: 36px; - height: auto; - align-items: flex-start; - padding-top: 6px; - padding-bottom: 6px; +.vm-page.lc-create-page .lc-business-dept-picker__trigger, +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__trigger { + display: flex; + align-items: center; + min-height: var(--lc-control-height); + height: var(--lc-control-height); + padding-top: 0; + padding-bottom: 0; + gap: 6px; + overflow: hidden; +} + +.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger .vm-filter-picker-chevron, +.vm-page.lc-create-page .lc-lease-order-plate-picker__trigger .vm-filter-picker-chevron, +.vm-page.lc-create-page .lc-business-dept-picker__trigger .vm-filter-picker-chevron, +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__trigger .vm-filter-picker-chevron { + flex-shrink: 0; + align-self: center; +} + +.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger .vm-filter-picker-clear, +.vm-page.lc-create-page .lc-lease-order-plate-picker__trigger .vm-filter-picker-clear, +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__trigger .vm-filter-picker-clear { + flex-shrink: 0; } .vm-page.lc-create-page .lc-business-dept-picker { @@ -772,6 +1070,26 @@ .vm-page.lc-create-page .lc-business-dept-picker__trigger { width: 100%; + text-align: left; + font: inherit; + color: inherit; + background: var(--ln-surface-card, #fff); + cursor: pointer; +} + +.vm-page.lc-create-page .lc-business-dept-picker__trigger .lc-business-dept-picker__summary { + flex: 1; + min-width: 0; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.vm-page.lc-create-page .lc-business-dept-picker__trigger .vm-filter-picker-input { + height: auto; + line-height: 1.35; } .vm-page.lc-create-page .lc-business-dept-picker__summary { @@ -781,22 +1099,47 @@ .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 { display: flex; - flex-direction: column; - gap: 2px; + flex-direction: row; + flex-wrap: nowrap; flex: 1; min-width: 0; + gap: 4px; + overflow: hidden; } .vm-page.lc-create-page .lc-lease-order-brand-model-picker__summary-line, .vm-page.lc-create-page .lc-lease-order-plate-picker__summary-line { display: block; - font-size: 0.8125rem; + flex: 1 1 auto; + min-width: 0; + font-size: 0.875rem; line-height: 1.35; color: var(--ln-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - max-width: 220px; +} + +.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger .lc-lease-order-brand-model-picker__summary-list, +.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger .lc-lease-order-brand-model-picker__summary { + flex: 1; + min-width: 0; +} + +.vm-page.lc-create-page .lc-lease-order-brand-model-picker__trigger .vm-filter-picker-input, +.vm-page.lc-create-page .lc-lease-order-plate-picker__trigger .vm-filter-picker-input { + height: auto; + line-height: 1.35; +} + +.vm-page.lc-create-page .lc-lease-order-brand-model-picker__summary.is-placeholder { + display: block; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; } .lc-lease-order-brand-model-picker__selected-list { @@ -824,6 +1167,91 @@ gap: 12px; } +.lc-lease-order-plate-popover .ant-popover-inner { + padding: 0; + border-radius: 12px; + box-shadow: 0 12px 40px rgba(15, 23, 42, 0.14); +} + +.lc-lease-order-plate-popover__mode .lc-lease-order-mode-toggle--segmented { + width: 100%; +} + +.vm-page.lc-create-page .lc-lease-order-plate-control { + display: flex; + align-items: stretch; + width: 100%; + min-width: 0; + min-height: var(--lc-control-height); + height: var(--lc-control-height); +} + +.vm-page.lc-create-page .lc-lease-order-plate-control > .ant-popover-open, +.vm-page.lc-create-page .lc-lease-order-plate-control > span { + display: flex; + align-items: stretch; + width: 100%; + min-height: var(--lc-control-height); + height: var(--lc-control-height); +} + +.vm-page.lc-create-page .lc-lease-order-plate-control__surface { + display: flex; + align-items: stretch; + width: 100%; + min-height: var(--lc-control-height); + height: var(--lc-control-height); +} + +.lc-lease-order-plate-popover__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.lc-lease-order-plate-popover__title { + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-lease-order-plate-popover__clear { + border: 0; + background: transparent; + padding: 4px 6px; + font-size: 0.75rem; + color: var(--ln-muted); + cursor: pointer; + border-radius: var(--ln-radius-xs); +} + +.lc-lease-order-plate-popover__clear:hover { + color: var(--ln-ink); + background: var(--ln-canvas-soft); +} + +.lc-lease-order-plate-popover__actual-note { + margin: 0; + padding: 10px 12px; + border-radius: var(--ln-radius-control); + background: var(--ln-canvas-soft); + font-size: 0.8125rem; + line-height: 1.45; + color: var(--ln-muted); +} + +.lc-lease-order-plate-popover__hint { + margin: 0; + font-size: 0.8125rem; + line-height: 1.45; + color: var(--ln-muted); +} + +.lc-lease-order-plate-popover__hint--warn { + color: #c2410c; +} + .lc-lease-order-plate-popover__bulk { display: flex; flex-direction: column; @@ -838,20 +1266,168 @@ .lc-lease-order-plate-popover__bulk-input { width: 100%; - min-height: 72px; - padding: 8px 10px; + min-height: 96px; + padding: 10px 12px; border: 1px solid var(--ln-hairline-strong); border-radius: var(--ln-radius-control); + background: var(--ln-surface-card); font-size: 0.875rem; + line-height: 1.45; resize: vertical; } -.lc-lease-order-plate-popover__bulk-btn { - align-self: flex-end; +.lc-lease-order-plate-popover__bulk-input:disabled { + cursor: not-allowed; + opacity: 0.72; + background: var(--ln-canvas-soft); } -.lc-lease-order-plate-popover__actual { - align-self: flex-start; +.lc-lease-order-plate-popover__matched { + display: flex; + flex-direction: column; + gap: 8px; +} + +.lc-lease-order-plate-popover__matched-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.lc-lease-order-plate-popover__matched-title { + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-lease-order-plate-popover__matched-count { + font-size: 0.75rem; + font-weight: 600; + color: var(--ln-primary); +} + +.lc-lease-order-plate-popover__chip-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.lc-lease-order-plate-popover__chip { + display: inline-flex; + align-items: center; + gap: 4px; + max-width: 100%; + padding: 4px 6px 4px 10px; + border: 1px solid color-mix(in srgb, var(--ln-primary) 24%, var(--ln-hairline)); + border-radius: var(--ln-radius-pill); + background: color-mix(in srgb, var(--ln-primary) 8%, var(--ln-surface-card)); + color: var(--ln-ink); +} + +.lc-lease-order-plate-popover__chip-text { + font-size: 0.8125rem; + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.lc-lease-order-plate-popover__chip-remove { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + flex-shrink: 0; + border: 0; + border-radius: var(--ln-radius-xs); + background: transparent; + color: var(--ln-muted); + cursor: pointer; +} + +.lc-lease-order-plate-popover__chip-remove:hover { + background: color-mix(in srgb, var(--ln-primary) 12%, transparent); + color: var(--ln-ink); +} + +.lc-lease-order-plate-popover__chip-remove:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 1px; +} + +.lc-lease-order-plate-popover__results { + display: flex; + flex-direction: column; + gap: 8px; + padding: 10px 12px; + border-radius: var(--ln-radius-control); + background: var(--ln-canvas-soft); +} + +.lc-lease-order-plate-popover__results-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.lc-lease-order-plate-popover__results-title { + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-lease-order-plate-popover__results-summary { + font-size: 0.75rem; + font-weight: 600; + color: var(--ln-muted); +} + +.lc-lease-order-plate-popover__result-list { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 6px; +} + +.lc-lease-order-plate-popover__result-item { + display: grid; + grid-template-columns: minmax(88px, auto) minmax(0, 1fr); + gap: 8px; + align-items: start; + font-size: 0.8125rem; + line-height: 1.45; +} + +.lc-lease-order-plate-popover__result-plate { + font-weight: 600; + color: var(--ln-ink); + font-variant-numeric: tabular-nums; +} + +.lc-lease-order-plate-popover__result-message { + color: var(--ln-muted); +} + +.lc-lease-order-plate-popover__result-item.is-ok .lc-lease-order-plate-popover__result-message { + color: var(--ln-primary-focus); +} + +.lc-lease-order-plate-popover__result-item.is-fail .lc-lease-order-plate-popover__result-message { + color: #c2410c; +} + +.lc-lease-order-plate-popover__actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 2px; +} + +.lc-lease-order-plate-popover__actions .ant-btn { + min-width: 64px; } .vm-page.lc-create-page .lc-poa-editor .lc-form-hint { @@ -865,33 +1441,259 @@ @media (max-width: 768px) { .vm-page.lc-create-page .lc-lease-order-kpi-row { - grid-template-columns: 1fr; + grid-template-columns: repeat(2, minmax(0, 1fr)); } } .vm-page.lc-create-page .lc-lease-order-editor__summary, .vm-page.lc-create-page .lc-lease-order-editor__delivery { + gap: 16px; +} + +.vm-page.lc-create-page .lc-lease-order-delivery-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.vm-page.lc-create-page .lc-lease-order-delivery-panel { + display: flex; + flex-direction: column; gap: 12px; + padding: 14px 16px; + border: 1px solid var(--ln-hairline); + border-radius: 12px; + background: var(--ln-surface-card, #fff); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); } -.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-inline-sentence { +.vm-page.lc-create-page .lc-lease-order-delivery-panel__header { + display: flex; flex-wrap: wrap; + align-items: center; + gap: 6px 10px; } -.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-inline-sentence__input.ant-input-number { - width: 88px !important; - min-width: 88px; +.vm-page.lc-create-page .lc-lease-order-delivery-panel__title { + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); + line-height: 1.35; } -.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-inline-sentence__readonly { - width: 88px; - min-width: 88px; +.vm-page.lc-create-page .lc-lease-order-delivery-field__control { + min-width: 0; +} + +.vm-page.lc-create-page .lc-lease-order-delivery-field__control .lc-form-readonly--computed { + height: auto; + min-height: var(--lc-control-height); + padding-top: 8px; + padding-bottom: 8px; + line-height: 1.45; + white-space: normal; +} + +.vm-page.lc-create-page .lc-lease-order-delivery-field__control .lc-form-cascader.ant-select, +.vm-page.lc-create-page .lc-lease-order-delivery-field__control .lc-form-datepicker.ant-picker, +.vm-page.lc-create-page .lc-lease-order-delivery-field__control .lc-form-readonly, +.vm-page.lc-create-page .lc-lease-order-delivery-field__control .lc-lease-order-delivery-region-picker { + width: 100%; +} + +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__trigger { + width: 100%; + padding-left: 12px; + padding-right: 8px; + gap: 8px; + cursor: pointer; +} + +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__trigger .vm-filter-picker-input { + height: auto; + line-height: 1.35; +} + +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__trigger.has-value { + border-color: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-primary) 4%, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__icon { flex-shrink: 0; + align-self: center; + color: var(--ln-primary-focus, #15803d); } -.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-inline-sentence__readonly .lc-form-readonly--synced { +.vm-page.lc-create-page .lc-lease-order-delivery-region-picker__summary { + flex: 1; + min-width: 0; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + font-size: 0.875rem; +} + +.lc-lease-order-delivery-region-popover .ant-popover-inner { + padding: 0; + border-radius: 12px; + box-shadow: 0 12px 40px rgba(15, 23, 42, 0.14); +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__panel { + width: min(520px, calc(100vw - 32px)); + max-height: min(400px, calc(100vh - 120px)); + padding: 12px 14px; + overflow: hidden; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__search { + margin-bottom: 2px; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__selected { + margin-top: 0; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__selected-value { + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__main { + min-height: 240px; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__province-chip { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-height: 40px; + padding: 8px 10px; + text-align: left; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__province-chip.is-current:not(.active) { + border-color: color-mix(in srgb, var(--ln-primary) 22%, var(--ln-hairline)); +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__chip-arrow { + flex-shrink: 0; + color: var(--ln-primary-focus, #15803d); +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__city-chips { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + align-content: start; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__city-chip { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + min-height: 44px; + padding: 8px 12px; + text-align: left; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__city-name { + font-size: 0.875rem; + font-weight: 600; + color: inherit; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__city-province { + font-size: 0.6875rem; + font-weight: 500; + color: var(--ln-muted); + margin-left: auto; +} + +.lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__city-check { + flex-shrink: 0; + color: var(--ln-primary-focus, #15803d); +} + +@media (max-width: 560px) { + .lc-lease-order-delivery-region-popover .lc-lease-order-delivery-region-popover__city-chips { + grid-template-columns: 1fr; + } + + .lc-lease-order-delivery-region-popover .vm-operate-city-edit-main { + grid-template-columns: minmax(0, 1fr); + } + + .lc-lease-order-delivery-region-popover .vm-operate-city-edit-step--province { + padding-right: 0; + border-right: none; + padding-bottom: 12px; + border-bottom: 1px solid var(--ln-hairline); + } + + .lc-lease-order-delivery-region-popover .vm-operate-city-edit-step--province .vm-operate-city-edit-chips { + flex-direction: row; + flex-wrap: wrap; + max-height: 120px; + } +} + +.vm-page.lc-create-page .lc-lease-order-editor__summary .ant-space-compact { + display: inline-flex; + align-items: stretch; + width: 100%; +} + +.vm-page.lc-create-page .lc-lease-order-editor__summary .ant-space-compact > .ant-input-number, +.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-readonly-compact > .lc-form-readonly { + flex: 1 1 auto; + min-width: 0; + width: auto; +} + +.vm-page.lc-create-page .lc-lease-order-editor__summary .ant-space-compact .ant-space-addon, +.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-readonly-compact .ant-space-addon { + display: inline-flex; + align-items: center; justify-content: center; - text-align: center; + box-sizing: border-box; + height: var(--lc-control-height); + min-height: var(--lc-control-height); + padding: 0 11px; + line-height: 1.5715; + white-space: nowrap; +} + +.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-readonly-compact { + border: 1px dashed var(--ln-hairline); + background: var(--ln-surface-strong); +} + +.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-readonly-compact > .lc-form-readonly { + height: var(--lc-control-height); + min-height: var(--lc-control-height); + border: none !important; + background: transparent; +} + +.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-readonly-compact > .ant-space-addon { + border: none !important; + border-inline-start: 1px solid var(--lc-control-border) !important; + background: rgba(0, 0, 0, 0.04); + color: var(--ln-body); +} + +.vm-page.lc-create-page .lc-lease-order-editor__summary .lc-form-readonly-compact > .lc-form-readonly.lc-form-readonly--synced { + justify-content: flex-start; + text-align: left; + padding-inline: 11px; } .vm-page.lc-create-page .lc-lease-order-editor__price-cell { @@ -909,28 +1711,121 @@ width: 100%; } +.vm-page.lc-create-page .lc-lease-order-plate-picker { + width: 100%; +} + +.vm-page.lc-create-page .lc-lease-order-plate-picker__trigger { + 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-form-field { + width: 100%; } -.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); +.vm-page.lc-create-page .lc-lease-order-service-picker__btn--label { + min-height: 28px; + padding: 0 10px; + font-size: 0.75rem; +} + +.vm-page.lc-create-page .lc-lease-order-service-field { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 8px; + min-width: 0; + width: 100%; +} + +.vm-page.lc-create-page .lc-lease-order-card__row--service-content .lc-lease-order-service-field { + margin-top: 4px; +} + +.vm-page.lc-create-page .lc-lease-order-service-selected { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__item { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + grid-template-rows: auto auto; + gap: 2px 8px; + padding: 6px 8px; + border-radius: var(--ln-radius-control); + border: 1px solid var(--ln-hairline); + background: var(--ln-surface-card, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__item.is-fixed { + border-color: color-mix(in srgb, var(--ln-primary) 18%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-primary) 4%, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__item.is-floating { + border-color: color-mix(in srgb, #2563eb 18%, var(--ln-hairline)); + background: color-mix(in srgb, #2563eb 4%, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__badge { + grid-row: 1 / span 2; + align-self: center; + padding: 2px 6px; + border-radius: 9999px; + font-size: 0.6875rem; + font-weight: 600; + line-height: 1.2; white-space: nowrap; - max-width: 180px; - overflow: hidden; - text-overflow: ellipsis; +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__item.is-fixed .lc-lease-order-service-selected__badge { + color: var(--ln-primary-focus, #15803d); + background: color-mix(in srgb, var(--ln-primary) 12%, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__item.is-floating .lc-lease-order-service-selected__badge { + color: #1d4ed8; + background: color-mix(in srgb, #2563eb 12%, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__name { + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-ink); + line-height: 1.3; +} + +.vm-page.lc-create-page .lc-lease-order-service-selected__meta { + grid-column: 2; + font-size: 0.75rem; + line-height: 1.35; + color: var(--ln-muted); +} + +.vm-page.lc-create-page .lc-lease-order-service-fee-label-hint { + font-size: 0.75rem; + font-weight: 500; + line-height: 1.3; + color: var(--ln-muted); + white-space: nowrap; +} + +.vm-page.lc-create-page .lc-form-readonly--computed { + background: color-mix(in srgb, var(--ln-primary) 5%, #f8fafc); + border: 1px dashed color-mix(in srgb, var(--ln-primary) 24%, var(--ln-hairline)); + color: color-mix(in srgb, var(--ln-ink) 88%, var(--ln-primary-focus)); + font-weight: 600; +} + +.vm-page.lc-create-page .lc-lease-order-card__row--pricing .lc-form-readonly--computed { + letter-spacing: 0.01em; } .vm-page.lc-create-page .lc-lease-order-service-picker__btn { @@ -984,44 +1879,167 @@ pointer-events: none; } -.vm-page.lc-create-page .lc-lease-order-service-picker__summary.is-placeholder { - color: var(--ln-muted-soft); -} - .lc-lease-order-service-popover .ant-popover-inner { padding: 0; + border-radius: 12px; + box-shadow: 0 12px 40px rgba(15, 23, 42, 0.14); } .lc-lease-order-service-popover .lc-lease-order-service-popover__panel { - width: min(560px, calc(100vw - 32px)); - max-height: min(420px, calc(100vh - 120px)); - padding: 12px; + display: flex; + flex-direction: column; + gap: 12px; + width: min(640px, calc(100vw - 32px)); + max-height: min(520px, calc(100vh - 120px)); + padding: 14px 16px 12px; overflow: hidden; } -.lc-lease-order-service-popover .vm-operate-city-edit-selected { - align-items: flex-start; +.lc-lease-order-service-popover .lc-lease-order-service-popover__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__title { + margin: 0; + font-size: 0.9375rem; + font-weight: 600; + color: var(--ln-ink); + line-height: 1.3; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__clear-all { + flex-shrink: 0; + min-height: 32px; + padding: 0 10px; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card, #fff); + color: var(--ln-muted); + font-size: 0.75rem; + font-weight: 500; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__clear-all:hover { + color: var(--ln-ink); + border-color: color-mix(in srgb, var(--ln-ink) 18%, var(--ln-hairline)); + background: var(--ln-canvas-soft, #f8fafc); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__search { + display: flex; + align-items: center; + gap: 8px; + min-height: 40px; + padding: 0 12px; + border: 1px solid var(--ln-hairline-strong, var(--ln-hairline)); + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card, #fff); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__search:focus-within { + border-color: var(--vm-focus-border-color, var(--ln-primary)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary) 14%, transparent); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__search-icon { + flex-shrink: 0; + color: var(--ln-muted); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__search-input { + flex: 1; + min-width: 0; + border: none; + background: transparent; + font-size: 0.875rem; + color: var(--ln-ink); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__search-input:focus { + outline: none; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__search-clear { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: none; + border-radius: var(--ln-radius-control); + background: transparent; + color: var(--ln-muted); + cursor: pointer; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__search-clear:hover { + background: var(--ln-canvas-soft, #f1f5f9); + color: var(--ln-ink); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__selected { + display: flex; + flex-direction: column; + gap: 8px; + padding: 10px 12px; + border-radius: var(--ln-radius-control); + background: color-mix(in srgb, var(--ln-primary) 6%, #fff); + border: 1px solid color-mix(in srgb, var(--ln-primary) 20%, var(--ln-hairline)); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__selected-label { + font-size: 0.75rem; + font-weight: 600; + color: var(--ln-primary-focus, #15803d); } .lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tags { display: flex; flex-wrap: wrap; gap: 6px; - flex: 1; min-width: 0; } .lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tag { display: inline-flex; align-items: center; - gap: 4px; - padding: 2px 8px; + gap: 6px; + padding: 4px 8px; border-radius: 9999px; - background: var(--ln-surface-card); + background: var(--ln-surface-card, #fff); border: 1px solid color-mix(in srgb, var(--ln-primary) 22%, transparent); font-size: 0.8125rem; font-weight: 600; color: var(--ln-ink); + max-width: 100%; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tag.is-floating { + border-color: color-mix(in srgb, #2563eb 22%, transparent); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tag-name { + flex-shrink: 0; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tag-meta { + font-size: 0.75rem; + font-weight: 500; + color: var(--ln-primary-focus, #15803d); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 200px; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tag.is-floating .lc-lease-order-service-popover__selected-tag-meta { + color: #1d4ed8; } .lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tag button { @@ -1043,31 +2061,367 @@ color: var(--ln-ink); } -.lc-lease-order-service-popover .vm-operate-city-picker-panel { - padding: 0; - gap: 14px; +.lc-lease-order-service-popover .lc-lease-order-service-popover__tabs { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; } -.lc-lease-order-service-popover .vm-operate-city-edit-main { - min-height: 220px; +.lc-lease-order-service-popover .lc-lease-order-service-popover__tab { + min-height: 40px; + padding: 0 12px; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card, #fff); + color: var(--ln-muted); + font-size: 0.8125rem; + font-weight: 600; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__tab:hover { + border-color: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline)); + color: var(--ln-ink); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__tab.is-active { + border-color: color-mix(in srgb, var(--ln-primary) 45%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-primary) 8%, #fff); + color: var(--ln-primary-focus, #15803d); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ln-primary) 18%, transparent); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__tab:focus-visible { + outline: 2px solid color-mix(in srgb, var(--ln-primary) 45%, transparent); + outline-offset: 2px; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__hint { + margin: 0; + padding: 8px 10px; + border-radius: var(--ln-radius-control); + background: var(--ln-canvas-soft, #f8fafc); + font-size: 0.75rem; + line-height: 1.4; + color: var(--ln-muted); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__scroll { + flex: 1; + min-height: 0; + max-height: 300px; + overflow-y: auto; + overflow-x: hidden; + padding-right: 2px; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__section-label { + margin: 0 0 8px; + font-size: 0.75rem; + font-weight: 600; + color: var(--ln-muted); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__empty { + margin: 0; + padding: 24px 12px; + text-align: center; + font-size: 0.8125rem; + color: var(--ln-muted); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding-top: 10px; + border-top: 1px solid var(--ln-hairline); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__footer-count { + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__footer-total { + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-primary-focus, #15803d); +} + +/* 品牌型号弹层:每行一个型号,右侧全国在库(点击数字看停车场省市分布) */ +.lc-lease-order-service-popover .lc-brand-model-option-chips { + display: flex; + flex-direction: column; + gap: 8px; + align-items: stretch; + align-content: flex-start; +} + +.lc-lease-order-service-popover .lc-brand-model-option-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + width: 100%; + min-width: 0; +} + +.lc-lease-order-service-popover .lc-brand-model-option-chip { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + width: 100%; + min-width: 0; + min-height: 40px; + padding: 8px 12px; + white-space: normal; + border-radius: var(--ln-radius-control); + text-align: left; +} + +.lc-lease-order-service-popover .lc-brand-model-option-chip__model { + flex: 1 1 auto; + min-width: 0; + line-height: 1.35; +} + +.lc-lease-order-service-popover .lc-brand-model-option-stock-meta { + flex-shrink: 0; + font-size: 0.75rem; + font-weight: 500; + line-height: 1.4; + color: var(--ln-muted); + white-space: nowrap; +} + +.lc-lease-order-service-popover .lc-brand-model-option-stock-meta.is-empty { + color: var(--ln-muted-soft); +} + +.lc-lease-order-service-popover .lc-brand-model-option-stock-count { + margin: 0; + padding: 0 2px; + border: none; + background: none; + font-size: inherit; + font-weight: 700; + line-height: inherit; + color: #0f766e; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 2px; +} + +.lc-lease-order-service-popover .lc-brand-model-option-stock-count:hover { + color: var(--ln-primary-hover, #15803d); +} + +.lc-lease-order-service-popover .lc-brand-model-option-stock-count:focus-visible { + outline: 2px solid color-mix(in srgb, var(--ln-primary) 45%, transparent); + outline-offset: 2px; + border-radius: 2px; +} + +.lc-lease-order-service-popover .lc-lease-order-service-subgroups { + display: flex; + flex-direction: column; + gap: 14px; + min-width: 0; +} + +.lc-lease-order-service-popover .lc-lease-order-service-subgroup__title { + margin: 0 0 8px; + padding-left: 2px; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.02em; + color: var(--ln-muted); + text-transform: none; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option-list { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option { + display: grid; + grid-template-columns: 24px minmax(0, 1fr) minmax(156px, 40%); + align-items: center; + gap: 10px 12px; + width: 100%; + min-height: 48px; + padding: 10px 12px; + border: 1px solid var(--ln-hairline); + border-radius: 10px; + background: var(--ln-surface-card, #fff); + text-align: left; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option:hover { + border-color: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-primary) 4%, #fff); +} + +.lc-lease-order-service-popover .lc-lease-order-service-option.is-selected { + border-color: color-mix(in srgb, var(--ln-primary) 45%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-primary) 8%, #fff); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ln-primary) 20%, transparent); +} + +.lc-lease-order-service-popover .lc-lease-order-service-option.is-floating.is-selected { + border-color: color-mix(in srgb, #2563eb 40%, var(--ln-hairline)); + background: color-mix(in srgb, #2563eb 6%, #fff); + box-shadow: inset 0 0 0 1px color-mix(in srgb, #2563eb 16%, transparent); +} + +.lc-lease-order-service-popover .lc-lease-order-service-option.is-locked { + cursor: not-allowed; + opacity: 0.92; + border-color: color-mix(in srgb, var(--ln-muted, #64748b) 35%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-muted, #64748b) 6%, #fff); + box-shadow: none; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option.is-locked:hover { + border-color: color-mix(in srgb, var(--ln-muted, #64748b) 35%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-muted, #64748b) 6%, #fff); +} + +.lc-lease-order-service-popover .lc-lease-order-service-option.is-locked .lc-lease-order-service-option__check { + width: auto; + min-width: 20px; + padding: 0 6px; + border-color: color-mix(in srgb, var(--ln-muted, #64748b) 45%, var(--ln-hairline)); + background: color-mix(in srgb, var(--ln-muted, #64748b) 12%, #fff); + color: var(--ln-muted, #64748b); + font-size: 0.6875rem; + font-weight: 600; + border-radius: 9999px; +} + +.lc-lease-order-service-popover .lc-lease-order-service-popover__selected-tag.is-locked { + border-style: dashed; + background: color-mix(in srgb, var(--ln-muted, #64748b) 6%, #fff); +} + +.lc-lease-order-service-selected__item.is-locked { + border-style: dashed; + background: color-mix(in srgb, var(--ln-muted, #64748b) 5%, #fff); +} + +.lc-lease-order-service-popover .lc-lease-order-service-option:focus-visible { + outline: 2px solid color-mix(in srgb, var(--ln-primary) 45%, transparent); + outline-offset: 2px; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option__check { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 9999px; + border: 1.5px solid var(--ln-hairline-strong, var(--ln-hairline)); + color: #fff; + background: var(--ln-surface-card, #fff); + flex-shrink: 0; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option.is-selected .lc-lease-order-service-option__check { + border-color: var(--ln-primary-focus, #15803d); + background: var(--ln-primary-focus, #15803d); +} + +.lc-lease-order-service-popover .lc-lease-order-service-option.is-floating.is-selected .lc-lease-order-service-option__check { + border-color: #2563eb; + background: #2563eb; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option__body { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option__name { + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); + line-height: 1.35; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option__context { + font-size: 0.6875rem; + line-height: 1.3; + color: var(--ln-muted); +} + +.lc-lease-order-service-popover .lc-lease-order-service-option__price-pill { + grid-column: 3; + justify-self: end; + align-self: center; + padding: 4px 8px; + border-radius: 9999px; + background: color-mix(in srgb, var(--ln-primary) 10%, #fff); + border: 1px solid color-mix(in srgb, var(--ln-primary) 22%, transparent); + font-size: 0.75rem; + font-weight: 700; + color: var(--ln-primary-focus, #15803d); + white-space: nowrap; + font-variant-numeric: tabular-nums; + text-align: right; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option__meta { + grid-column: 3; + justify-self: end; + align-self: center; + max-width: 100%; + font-size: 0.75rem; + line-height: 1.35; + text-align: right; +} + +.lc-lease-order-service-popover .lc-lease-order-service-option__meta--rule { + color: var(--ln-muted); } @media (max-width: 560px) { - .lc-lease-order-service-popover .vm-operate-city-edit-main { - grid-template-columns: minmax(0, 1fr); + .lc-lease-order-service-popover .lc-lease-order-service-popover__panel { + width: calc(100vw - 24px); + padding: 12px; } - .lc-lease-order-service-popover .vm-operate-city-edit-step--province { - padding-right: 0; - border-right: none; - padding-bottom: 12px; - border-bottom: 1px solid var(--ln-hairline); + .lc-lease-order-service-popover .lc-lease-order-service-option { + grid-template-columns: 22px minmax(0, 1fr); + grid-template-rows: auto auto; + align-items: start; } - .lc-lease-order-service-popover .vm-operate-city-edit-step--province .vm-operate-city-edit-chips { - flex-direction: row; - flex-wrap: wrap; - max-height: 120px; + .lc-lease-order-service-popover .lc-lease-order-service-option__price-pill, + .lc-lease-order-service-popover .lc-lease-order-service-option__meta { + grid-column: 2; + justify-self: end; + width: 100%; + max-width: 100%; + text-align: right; + margin-top: 2px; + } + + .lc-lease-order-service-popover .lc-lease-order-service-popover__footer { + flex-direction: column; + align-items: flex-start; } } @@ -1078,7 +2432,7 @@ gap: 8px; min-height: 36px; padding: 4px 11px; - border: 1px solid var(--vm-border, #d4d4d8); + border: 1px solid var(--lc-control-border); border-radius: 8px; background: #fff; cursor: pointer; @@ -1090,7 +2444,11 @@ } .vm-page.lc-create-page .lc-customer-picker__placeholder { - color: #a1a1aa; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .vm-page.lc-create-page .lc-customer-picker__value { @@ -1355,7 +2713,19 @@ width: 100%; min-width: 0; min-height: 36px; - align-items: center; + align-items: stretch; + gap: 0; + border: 1px solid var(--lc-control-border); + border-radius: var(--lc-control-radius); + overflow: hidden; + background: #fff; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.vm-page.lc-create-page .lc-lease-order-table__amount:has(.ant-input-number-focused), +.vm-page.lc-create-page .lc-lease-order-table__amount:focus-within { + border-color: var(--vm-focus-border-color) !important; + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary) 14%, transparent); } .vm-page.lc-create-page .lc-lease-order-table__amount-field { @@ -1368,8 +2738,30 @@ width: 100%; } +.vm-page.lc-create-page .lc-lease-order-table__amount-field .lc-form-number.ant-input-number:not(.ant-input-number-borderless), +.vm-page.lc-create-page .lc-lease-order-table__amount-field .lc-form-readonly--synced { + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; +} + .vm-page.lc-create-page .lc-lease-order-table__amount .lc-form-inline-sentence__text { flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-height: var(--lc-control-height); + height: var(--lc-control-height); + padding: 0 11px; + margin: 0; + border: none; + border-inline-start: 1px solid var(--lc-control-border); + border-radius: 0; + background: rgba(0, 0, 0, 0.04); + color: var(--ln-body); + line-height: 1.5715; + white-space: nowrap; } .vm-page.lc-create-page .lc-form-datepicker.ant-picker { @@ -1378,7 +2770,6 @@ min-height: 36px; padding: 0 11px; border-radius: var(--ln-radius-control); - border-color: var(--ln-hairline-strong); } .vm-page.lc-create-page .lc-form-datepicker .ant-picker-input { @@ -1432,6 +2823,20 @@ min-width: 0; } +.vm-page.lc-create-page .lc-lease-order-editor__th-qty { + width: 108px; + min-width: 108px; +} + +.vm-page.lc-create-page .lc-lease-order-editor__qty-cell { + width: 108px; + min-width: 108px; +} + +.vm-page.lc-create-page .lc-lease-order-editor__qty-cell .ant-space-compact { + width: 100%; +} + .vm-page.lc-create-page .lc-lease-order-editor__brand-cell { min-width: 0; vertical-align: middle; @@ -1451,6 +2856,311 @@ margin: 0 0 12px; } +/* 租赁订单:车型卡片布局 */ +.vm-page.lc-create-page .lc-lease-order-editor__cards { + display: flex; + flex-direction: column; + gap: 12px; +} + +.vm-page.lc-create-page .lc-lease-order-card { + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card); + overflow: hidden; +} + +.vm-page.lc-create-page .lc-lease-order-card__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 14px; + border-bottom: 1px solid var(--ln-hairline); + background: var(--ln-canvas-soft); +} + +.vm-page.lc-create-page .lc-lease-order-card__title { + margin: 0; + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); +} + +.vm-page.lc-create-page .lc-lease-order-card__remove { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-xs); + background: var(--ln-surface-card); + color: var(--ln-body); + font-size: 0.75rem; + cursor: pointer; +} + +.vm-page.lc-create-page .lc-lease-order-card__remove:hover { + color: #c2410c; + border-color: #fdba74; + background: #fff7ed; +} + +.vm-page.lc-create-page .lc-lease-order-card__row { + display: grid; + gap: 12px 16px; + padding: 14px; +} + +.vm-page.lc-create-page .lc-lease-order-card__row .lc-form-field { + min-width: 0; +} + +.vm-page.lc-create-page .lc-lease-order-card__row--primary .lc-form-field { + display: grid; + grid-template-rows: auto var(--lc-control-height); + gap: 6px; + align-content: start; +} + +.vm-page.lc-create-page .lc-lease-order-card__row--primary .lc-form-field > :not(.lc-form-field__label):not(.lc-form-field__label-row) { + display: flex; + align-items: center; + align-self: stretch; + width: 100%; + min-height: var(--lc-control-height); + height: var(--lc-control-height); +} + +.vm-page.lc-create-page .lc-lease-order-card__row--primary .ant-space-compact { + width: 100%; + height: var(--lc-control-height); +} + +.vm-page.lc-create-page .lc-lease-order-card__row--primary, +.vm-page.lc-create-page .lc-lease-order-card__row--pricing { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.vm-page.lc-create-page .lc-lease-order-card__row--pricing { + border-top: 1px solid var(--ln-hairline); +} + +.vm-page.lc-create-page .lc-lease-order-card__row--service-content, +.vm-page.lc-create-page .lc-lease-order-card__row--lease-period { + grid-template-columns: minmax(0, 1fr); + border-top: 1px solid var(--ln-hairline); +} + +.vm-page.lc-create-page .lc-lease-order-card__row--lease-period .lc-lease-order-mode-field { + max-width: none; +} + +.vm-page.lc-create-page .lc-form-grid__span-full { + grid-column: 1 / -1; +} + +@media (max-width: 960px) { + .vm-page.lc-create-page .lc-lease-order-delivery-grid { + grid-template-columns: 1fr; + } + + .vm-page.lc-create-page .lc-lease-order-mode-field.lc-lease-order-period-field--inline { + flex-direction: column; + align-items: stretch; + } + + .vm-page.lc-create-page .lc-lease-order-mode-field .lc-lease-order-mode-toggle--segmented { + min-width: 0; + } + + .vm-page.lc-create-page .lc-lease-order-card__row--primary, + .vm-page.lc-create-page .lc-lease-order-card__row--pricing { + grid-template-columns: 1fr; + } +} + +.vm-page.lc-create-page .lc-lease-order-mode-toggle { + display: inline-flex; + flex-wrap: wrap; + gap: 6px; +} + +.vm-page.lc-create-page .lc-lease-order-mode-toggle--segmented { + display: grid; + grid-template-columns: repeat(var(--mode-count, 2), minmax(0, 1fr)); + gap: 4px; + width: 100%; + padding: 4px; + border: 1px solid var(--ln-hairline); + border-radius: 10px; + background: color-mix(in srgb, var(--ln-ink) 3%, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-mode-toggle__btn { + min-height: 34px; + padding: 0 10px; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-xs); + background: var(--ln-surface-card); + color: var(--ln-body); + font-size: 0.8125rem; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; +} + +.vm-page.lc-create-page .lc-lease-order-mode-toggle--segmented .lc-lease-order-mode-toggle__btn { + min-height: 36px; + border: none; + border-radius: 8px; + background: transparent; + font-weight: 500; +} + +.vm-page.lc-create-page .lc-lease-order-mode-toggle__btn.active { + border-color: var(--ln-primary); + background: var(--ln-primary-soft); + color: var(--ln-primary-focus); + font-weight: 600; +} + +.vm-page.lc-create-page .lc-lease-order-mode-toggle--segmented .lc-lease-order-mode-toggle__btn.active { + border: none; + background: var(--ln-surface-card, #fff); + color: var(--ln-primary-focus); + font-weight: 600; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.1); +} + +.vm-page.lc-create-page .lc-lease-order-plate-control .lc-lease-order-mode-toggle--segmented { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 3px; + flex: 1; + width: 100%; + min-height: var(--lc-control-height); + height: var(--lc-control-height); + padding: 2px; + box-sizing: border-box; + border-radius: var(--ln-radius-control); + border-color: var(--lc-control-border, var(--ln-hairline-strong)); + background: var(--ln-surface-card); +} + +.vm-page.lc-create-page .lc-lease-order-plate-control .lc-lease-order-mode-toggle--segmented .lc-lease-order-mode-toggle__btn { + min-height: 0; + height: 100%; + padding: 0 6px; + font-size: 0.8125rem; + line-height: 1.25; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + align-items: center; + justify-content: center; +} + +.vm-page.lc-create-page .lc-lease-order-mode-field { + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px 14px; + border: 1px solid color-mix(in srgb, var(--ln-primary) 14%, var(--ln-hairline)); + border-radius: 10px; + background: color-mix(in srgb, var(--ln-primary) 3%, #fff); +} + +.vm-page.lc-create-page .lc-lease-order-mode-field.lc-lease-order-period-field--inline { + flex-direction: row; + align-items: center; + gap: 12px; +} + +.vm-page.lc-create-page .lc-lease-order-mode-field .lc-lease-order-mode-toggle--segmented { + flex: 0 0 auto; + min-width: 220px; +} + +.vm-page.lc-create-page .lc-lease-order-mode-field .lc-lease-order-period-field__input { + flex: 1; + min-width: 160px; +} + +.vm-page.lc-create-page .lc-lease-order-mode-toggle__btn:focus-visible { + outline: var(--vm-focus-border-color, var(--ln-primary)) solid 2px; + outline-offset: 2px; +} + +.vm-page.lc-create-page .lc-lease-order-period-field, +.vm-page.lc-create-page .lc-lease-order-delivery-field { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.vm-page.lc-create-page .lc-lease-order-period-field--inline { + flex-direction: row; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.vm-page.lc-create-page .lc-lease-order-period-field--inline .lc-lease-order-mode-toggle { + flex-shrink: 0; + margin-bottom: 0; +} + +.vm-page.lc-create-page .lc-lease-order-period-field--inline .lc-lease-order-period-field__input { + flex: 1; + min-width: 140px; +} + +.vm-page.lc-create-page .lc-lease-order-period-field--inline .lc-lease-order-period-input, +.vm-page.lc-create-page .lc-lease-order-period-field--inline .lc-form-datepicker { + width: 100%; +} + +.vm-page.lc-create-page .lc-lease-order-period-field__months { + display: flex; + align-items: center; + gap: 8px 10px; + flex-wrap: wrap; + width: 100%; + min-width: 0; +} + +.vm-page.lc-create-page .lc-lease-order-period-field__hint { + 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-lease-order-period-field__hint-value { + font-weight: 600; + color: var(--ln-ink); +} + +.vm-page.lc-create-page .lc-lease-order-period-field__months .lc-lease-order-period-input { + flex: 0 0 auto; + width: auto; + min-width: 108px; +} + +.vm-page.lc-create-page .lc-lease-order-period-field__months .lc-lease-order-period-field__hint { + flex: 1 1 auto; + min-width: 0; +} + +.vm-page.lc-create-page .lc-lease-order-period-field__months .ant-input-number-group-wrapper, +.vm-page.lc-create-page .lc-lease-order-period-field__months .ant-input-number { + width: auto; +} + .vm-page.lc-create-page .lc-lease-order-editor__clause-hint .lc-brand-model-clause-hint { margin: 0; } @@ -1751,14 +3461,6 @@ height: 36px !important; min-height: 36px !important; border-radius: var(--ln-radius-control) !important; - border-color: var(--ln-hairline-strong) !important; -} - -.vm-page.lc-create-page { - display: flex; - flex-direction: column; - min-height: 100vh; - padding-bottom: 24px; } .vm-page.lc-create-page .lc-create-topbar { @@ -1778,23 +3480,44 @@ .vm-page.lc-create-page .lc-create-editor-page .ct-editor-workspace { flex: 1; - min-height: calc(100vh - 200px); + min-height: 0; + overflow: hidden; } -/* 左侧多卡片列:与右侧预览等高,内部纵向滚动 */ -.vm-page.lc-create-page .ct-editor-workspace > .ant-row > .ant-col > .lc-create-editor-column { +.vm-page.lc-create-page .ct-editor-workspace > .ant-row { + flex: 1 1 0; + min-height: 0; + max-height: 100%; + overflow: hidden; +} + +.vm-page.lc-create-page .ct-editor-workspace > .ant-row > .ant-col { + display: flex; + flex-direction: column; + min-height: 0; + max-height: 100%; + overflow: hidden; +} + +.vm-page.lc-create-page .lc-create-editor-column-shell { + display: flex; + flex-direction: column; flex: 1; width: 100%; min-height: 0; + max-height: 100%; + overflow: hidden; } .vm-page.lc-create-page .lc-create-editor-column { display: flex; flex-direction: column; gap: 16px; + flex: 1; min-height: 0; overflow-x: hidden; overflow-y: auto; + padding-bottom: var(--lc-create-footer-height); scrollbar-width: thin; scrollbar-color: #cbd5e1 transparent; } @@ -1972,6 +3695,215 @@ .vm-page.lc-create-page .lc-preview-page__body--editable .ct-risk-redline { cursor: text; + background: rgba(254, 226, 226, 0.55); + border: 1px dashed #f87171; + border-radius: 3px; + padding: 0 2px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; +} + +.vm-page.lc-create-page .ct-editor-column-card--preview-attachment1 .ct-preview-panel, +.vm-page.lc-create-page .ct-editor-column-card--preview-poa .ct-preview-panel { + border-top: 1px solid color-mix(in srgb, var(--ln-primary, #32a06e) 18%, transparent); +} + +.vm-page.lc-create-page .lc-preview-scoped-only__banner, +.vm-page.lc-create-page .lc-preview-attachment1-only__banner { + margin: 0 0 12px; + padding: 8px 10px; + border-radius: 6px; + background: color-mix(in srgb, var(--ln-primary, #32a06e) 8%, transparent); + color: var(--ln-ink, #0f172a); + font-size: 13px; + line-height: 1.5; +} + +.vm-page.lc-create-page .lc-preview-scoped-only .ct-preview-page__body, +.vm-page.lc-create-page .lc-preview-attachment1-only .ct-preview-page__body { + cursor: default; + user-select: text; +} + +.vm-page.lc-create-page .lc-preview-attachment1-hint .lc-form-card__hint-text, +.vm-page.lc-create-page .lc-preview-poa-hint .lc-form-card__hint-text { + max-width: 28rem; +} + +.vm-page.lc-create-page .ct-preview-page__body .ct-risk-redline, +.vm-page.lc-create-page .ct-preview-page__body--editable .ct-risk-redline { + background: rgba(254, 226, 226, 0.55); + border: 1px dashed #f87171; + border-radius: 3px; + padding: 0 2px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; +} + +.vm-page.lc-create-page .ct-preview-page__body [data-section-locked="1"], +.vm-page.lc-create-page .ct-preview-page__body .ct-section-locked, +.vm-page.lc-create-page .ct-preview-page__body--editable [data-section-locked="1"], +.vm-page.lc-create-page .ct-preview-page__body--editable .ct-section-locked { + background: rgba(226, 232, 240, 0.65); + border: 1px dashed #94a3b8; + border-radius: 3px; + padding: 0 2px; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; +} + +.lc-preview-approval-hint { + display: inline-flex; + align-items: center; + gap: 5px; + margin-right: 8px; + font-size: 0.75rem; + color: var(--ln-muted); + cursor: help; +} + +.vm-page.lc-create-page .ct-preview-pager__group--clause-record { + margin-left: auto; +} + +.vm-page.lc-create-page .lc-preview-clause-record-btn { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 32px; + padding: 0 12px; + cursor: pointer; +} + +.vm-page.lc-create-page .lc-preview-clause-record-btn.is-active, +.vm-page.lc-create-page .lc-preview-clause-record-btn.has-changes { + border-color: color-mix(in srgb, var(--ln-primary) 35%, var(--ln-hairline)); +} + +.vm-page.lc-create-page .lc-preview-clause-record-btn__label { + font-size: 0.8125rem; + line-height: 1; +} + +.vm-page.lc-create-page .lc-preview-clause-record-btn__badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: 999px; + font-size: 0.6875rem; + font-weight: 600; + line-height: 1; + color: #b45309; + background: #fff7ed; +} + +.lc-preview-clause-record-popover .ant-popover-inner { + padding: 0; + border-radius: 10px; + overflow: hidden; +} + +.lc-preview-clause-record-panel { + width: min(520px, calc(100vw - 32px)); + max-height: min(420px, calc(100vh - 120px)); + display: flex; + flex-direction: column; +} + +.lc-preview-clause-record-panel__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 12px 14px; + border-bottom: 1px solid var(--ln-hairline); + background: color-mix(in srgb, var(--ln-primary) 4%, #fff); +} + +.lc-preview-clause-record-panel__title { + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-preview-clause-record-panel__count { + font-size: 0.75rem; + color: var(--ln-muted); +} + +.lc-preview-clause-record-panel__list { + display: flex; + flex-direction: column; + gap: 8px; + margin: 0; + padding: 12px 14px; + list-style: none; + overflow: auto; +} + +.lc-preview-clause-record-panel__empty { + margin: 0; + padding: 20px 14px; + font-size: 0.8125rem; + line-height: 1.5; + color: var(--ln-muted); +} + +.lc-preview-clause-record-panel__item { + padding: 8px 10px; + border-radius: 8px; + border: 1px solid var(--ln-hairline); + background: #fff; +} + +.lc-preview-clause-record-panel__item--risk { + border-color: color-mix(in srgb, #f87171 35%, var(--ln-hairline)); +} + +.lc-preview-clause-record-panel__item--new { + border-color: color-mix(in srgb, #f59e0b 35%, var(--ln-hairline)); +} + +.lc-preview-clause-record-panel__kind { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 0.6875rem; + font-weight: 600; + color: #b45309; + background: #fff7ed; + margin-bottom: 6px; +} + +.lc-preview-clause-record-panel__item--risk .lc-preview-clause-record-panel__kind { + color: #b91c1c; + background: #fef2f2; +} + +.lc-preview-clause-record-panel__diff { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.lc-preview-clause-record-panel__label { + display: block; + margin-bottom: 4px; + font-size: 0.6875rem; + font-weight: 600; + color: var(--ln-muted); +} + +.lc-preview-clause-record-panel__text { + margin: 0; + font-size: 0.75rem; + line-height: 1.45; + color: var(--ln-ink); + word-break: break-word; } .lc-approval-type-badge { @@ -2169,7 +4101,8 @@ min-width: 0; } -.vm-page.lc-create-page .lc-create-section-poa-head .lc-create-poa-hint { +.vm-page.lc-create-page .lc-create-section-poa-head .lc-create-poa-hint, +.vm-page.lc-create-page .lc-create-section-seal-head .lc-create-seal-hint { display: inline-flex; align-items: center; gap: 5px; @@ -2181,7 +4114,8 @@ color: var(--ln-muted); } -.vm-page.lc-create-page .lc-create-section-poa-head .lc-form-card__hint-icon { +.vm-page.lc-create-page .lc-create-section-poa-head .lc-form-card__hint-icon, +.vm-page.lc-create-page .lc-create-section-seal-head .lc-form-card__hint-icon { flex-shrink: 0; margin-top: 0; color: color-mix(in srgb, var(--ln-primary) 72%, var(--ln-muted)); @@ -2412,6 +4346,10 @@ grid-template-columns: repeat(2, minmax(0, 1fr)); } +.vm-page.lc-create-page .lc-form-grid--cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + .vm-page.lc-create-page .lc-attach-gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -2472,9 +4410,16 @@ @media (max-width: 768px) { .vm-page.lc-create-page .lc-form-subblock--two-col, .vm-page.lc-create-page .lc-form-grid--cols-2, + .vm-page.lc-create-page .lc-form-grid--cols-3, .vm-page.lc-create-page .lc-attach-gallery { grid-template-columns: 1fr; } + + .vm-page.lc-create-page .lc-signing-meta-row, + .vm-page.lc-create-page .lc-signing-core-fields > .lc-form-grid--pair { + grid-template-columns: 1fr; + gap: 12px; + } } .vm-page.lc-create-page .lc-form-subblock__title { @@ -2544,10 +4489,61 @@ .vm-page.lc-create-page #lc-card-fee-body .ant-select, .vm-page.lc-create-page #lc-card-fee-body .ant-space-compact, -.vm-page.lc-create-page #lc-card-fee-body .lc-form-inline-sentence { +.vm-page.lc-create-page #lc-card-fee-body .lc-form-inline-sentence, +.vm-page.lc-create-page .lc-mileage-reduction .ant-space-compact, +.vm-page.lc-create-page #lc-card-mileage-body .ant-space-compact, +.vm-page.lc-create-page .lc-lease-order-editor__summary .ant-space-compact { width: 100%; } +.vm-page.lc-create-page #lc-card-fee-body .ant-space-compact, +.vm-page.lc-create-page .lc-mileage-reduction .ant-space-compact, +.vm-page.lc-create-page #lc-card-mileage-body .ant-space-compact { + display: inline-flex; + align-items: stretch; + width: 100%; +} + +.vm-page.lc-create-page #lc-card-fee-body .ant-space-compact > .ant-input-number, +.vm-page.lc-create-page .lc-mileage-reduction .ant-space-compact > .ant-input-number, +.vm-page.lc-create-page #lc-card-mileage-body .ant-space-compact > .ant-input-number { + flex: 1 1 auto; + min-width: 0; + width: auto; + height: var(--lc-control-height); + min-height: var(--lc-control-height); + box-sizing: border-box; +} + +.vm-page.lc-create-page #lc-card-fee-body .ant-space-compact .ant-input-number .ant-input-number-input-wrap, +.vm-page.lc-create-page .lc-mileage-reduction .ant-space-compact .ant-input-number .ant-input-number-input-wrap, +.vm-page.lc-create-page #lc-card-mileage-body .ant-space-compact .ant-input-number .ant-input-number-input-wrap { + height: 100%; +} + +.vm-page.lc-create-page #lc-card-fee-body .ant-space-compact .ant-input-number .ant-input-number-input, +.vm-page.lc-create-page .lc-mileage-reduction .ant-space-compact .ant-input-number .ant-input-number-input, +.vm-page.lc-create-page #lc-card-mileage-body .ant-space-compact .ant-input-number .ant-input-number-input { + height: 100%; +} + +.vm-page.lc-create-page #lc-card-fee-body .ant-space-compact .ant-space-addon, +.vm-page.lc-create-page #lc-card-fee-body .ant-space-compact .ant-input-number-addon, +.vm-page.lc-create-page .lc-mileage-reduction .ant-space-compact .ant-space-addon, +.vm-page.lc-create-page .lc-mileage-reduction .ant-space-compact .ant-input-number-addon, +.vm-page.lc-create-page #lc-card-mileage-body .ant-space-compact .ant-space-addon, +.vm-page.lc-create-page #lc-card-mileage-body .ant-space-compact .ant-input-number-addon { + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + height: var(--lc-control-height); + min-height: var(--lc-control-height); + padding: 0 11px; + line-height: 1.5715; + white-space: nowrap; +} + .vm-page.lc-create-page .lc-form-inline-sentence { display: flex; align-items: center; @@ -2810,15 +4806,33 @@ color: var(--ln-muted); } +.vm-page.lc-create-page .ct-editor-workspace .ct-preview-pages-wrap { + padding-bottom: var(--lc-create-footer-height); +} + .vm-page.lc-create-page .lc-create-footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 100; display: flex; justify-content: flex-end; align-items: center; gap: 8px; - margin-top: 16px; - padding-top: 16px; + margin: 0; + padding: 14px 32px; border-top: 1px solid var(--ln-hairline); - flex-shrink: 0; + background: color-mix(in srgb, var(--ln-surface-card) 94%, transparent); + backdrop-filter: blur(8px); + box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.06); +} + +@media (max-width: 640px) { + .vm-page.lc-create-page .lc-create-footer { + padding-left: 16px; + padding-right: 16px; + } } @media (max-width: 992px) { @@ -2832,23 +4846,24 @@ } /* 合同编号:系统自动生成(新建) */ -.vm-page.lc-create-page .lc-contract-code-auto { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px 12px; +.vm-page.lc-create-page .lc-contract-code-display { + margin: 0; min-height: 36px; - padding: 6px 12px; - border: 1px solid var(--ln-hairline); - border-radius: 8px; - background: var(--ln-surface-muted, #f8fafc); + display: flex; + align-items: center; + font-size: 0.875rem; + font-weight: 500; + line-height: 1.4; + color: var(--ln-ink); } -.vm-page.lc-create-page .lc-contract-code-auto__prefix { - font-size: 0.875rem; - font-weight: 600; - color: var(--ln-muted); +.vm-page.lc-create-page .lc-contract-code-display__text { font-variant-numeric: tabular-nums; + letter-spacing: -0.01em; +} + +.vm-page.lc-create-page .lc-signing-core .lc-form-field__label-row .lc-contract-code-auto__tag { + flex-shrink: 0; } .vm-page.lc-create-page .lc-contract-code-auto__tag { @@ -2876,12 +4891,137 @@ grid-column: 1 / -1; } -/* 品牌车型在库提示 */ -.vm-page.lc-create-page .lc-brand-model-stock-hint { - margin: 6px 0 0; - font-size: 0.8125rem; - line-height: 1.5; +/* 品牌车型在库:型号标签与在库提示分列展示 */ +.vm-page.lc-create-page .lc-brand-model-option-chips { + display: flex; + flex-direction: column; + gap: 8px; + align-items: stretch; +} + +.vm-page.lc-create-page .lc-brand-model-option-row { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; +} + +.vm-page.lc-create-page .lc-brand-model-option-chip { + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + min-width: 0; + max-width: 100%; + white-space: normal; + border-radius: var(--ln-radius-control); + text-align: left; + flex: 0 1 auto; +} + +.vm-page.lc-create-page .lc-brand-model-option-chip__model { + flex: 1 1 auto; + min-width: 0; +} + +.vm-page.lc-create-page .lc-brand-model-option-stock-hint { + flex: 0 0 auto; + margin: 0; + padding: 0; + border: none; + background: none; + font-size: 0.75rem; + font-weight: 500; + line-height: 1.4; color: #0f766e; + white-space: nowrap; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 2px; +} + +.vm-page.lc-create-page .lc-brand-model-option-stock-hint:hover { + color: var(--ln-primary-hover); +} + +.vm-page.lc-create-page .lc-brand-model-option-stock-hint:focus-visible { + outline: var(--vm-focus-border-color, var(--ln-primary)) solid 2px; + outline-offset: 2px; + border-radius: 2px; +} + +.lc-brand-model-stock-popover-overlay .ant-popover-inner { + padding: 0; +} + +.lc-brand-model-stock-popover { + min-width: 220px; + max-width: 280px; + padding: 12px 14px; +} + +.lc-brand-model-stock-popover__title { + margin: 0 0 10px; + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-brand-model-stock-popover__empty { + margin: 0; + font-size: 0.8125rem; + color: var(--ln-muted); +} + +.lc-brand-model-stock-popover__list { + display: flex; + flex-direction: column; + gap: 10px; + max-height: 240px; + overflow: auto; +} + +.lc-brand-model-stock-popover__region-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 4px; +} + +.lc-brand-model-stock-popover__region-name { + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-ink); +} + +.lc-brand-model-stock-popover__region-total { + font-size: 0.75rem; + font-weight: 600; + color: #0f766e; +} + +.lc-brand-model-stock-popover__cities { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 4px; +} + +.lc-brand-model-stock-popover__city { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + font-size: 0.75rem; + color: var(--ln-body); +} + +.lc-brand-model-stock-popover__city-count { + font-weight: 600; + color: var(--ln-ink); } /* 租金低于最低值警告 */ @@ -2902,4 +5042,1380 @@ .vm-page.lc-create-page .lc-contract-remark-section .lc-contract-remark__textarea { resize: vertical; + min-height: 220px; +} + +/* 流程页顶栏标题与提示 */ +.vm-page.lc-create-page .lc-create-topbar { + align-items: center; +} + +.vm-page.lc-create-page .lc-create-topbar__title, +.vm-page.lc-view-page .lc-view-topbar__heading { + margin: 0; + font-size: 1.125rem; + font-weight: 600; + color: var(--ln-ink); +} + +.vm-page.lc-create-page .lc-flow-banner { + margin: -8px 0 16px; + padding: 12px 16px; + border-radius: var(--ln-radius-control); + background: color-mix(in srgb, var(--ln-primary) 8%, var(--ln-surface-card)); + border: 1px solid color-mix(in srgb, var(--ln-primary) 18%, var(--ln-hairline)); +} + +.vm-page.lc-create-page .lc-flow-banner__line { + margin: 0; + font-size: 0.875rem; + line-height: 1.5; + color: var(--ln-body); +} + +.vm-page.lc-create-page .lc-flow-banner__line + .lc-flow-banner__line { + margin-top: 4px; +} + +/* 查看页 — 全宽单列 + 章节导航 */ +.vm-page.lc-view-page { + display: flex; + flex-direction: column; + width: 100%; + max-width: none; + box-sizing: border-box; + min-height: 0; + height: 100vh; + max-height: 100vh; + overflow: hidden; + padding-bottom: 0; + background: var(--ln-canvas-parchment); +} + +.vm-page.lc-view-page .lc-view-hero { + flex: 0 0 auto; + width: 100%; + margin-bottom: 10px; + padding: 12px 14px; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-card); + background: var(--ln-surface-card); + box-shadow: var(--ln-shadow-soft); + transition: box-shadow 0.2s ease; +} + +.vm-page.lc-view-page .lc-view-hero__bar { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.vm-page.lc-view-page .lc-view-hero__back { + flex: 0 0 auto; +} + +.vm-page.lc-view-page .lc-view-hero__identity { + flex: 1 1 220px; + min-width: 0; +} + +.vm-page.lc-view-page .lc-view-hero__title { + margin: 0; + font-size: 1.125rem; + font-weight: 600; + line-height: 1.35; + color: var(--ln-ink); +} + +.vm-page.lc-view-page .lc-view-hero__code { + margin: 2px 0 0; + font-size: 0.8125rem; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-hero__tags { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 8px; + margin-left: auto; +} + +.vm-page.lc-view-page .lc-view-hero__meta-tag { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 10px; + border-radius: var(--ln-radius-pill); + font-size: 0.75rem; + color: var(--ln-body); + background: var(--ln-surface-strong); + border: 1px solid var(--ln-hairline); +} + +.vm-page.lc-view-page .lc-view-hero__kpi-row { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 8px; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--ln-hairline); +} + +.vm-page.lc-view-page .lc-view-kpi-card { + display: flex; + align-items: flex-start; + gap: 8px; + min-width: 0; + padding: 10px 12px; + border-radius: var(--ln-radius-control); + border: 1px solid var(--ln-hairline); + background: var(--ln-surface-card); + transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; +} + +.vm-page.lc-view-page .lc-view-kpi-card:hover { + border-color: var(--ln-hairline-strong); + box-shadow: var(--ln-shadow-soft); +} + +.vm-page.lc-view-page .lc-view-kpi-card__icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 28px; + height: 28px; + border-radius: var(--ln-radius-control); + color: var(--ln-primary); + background: var(--ln-primary-soft); +} + +.vm-page.lc-view-page .lc-view-kpi-card__main { + display: flex; + flex-direction: column; + min-width: 0; + gap: 2px; +} + +.vm-page.lc-view-page .lc-view-kpi-card__label { + font-size: 0.6875rem; + line-height: 1.3; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-kpi-card__value { + font-size: 0.8125rem; + font-weight: 600; + line-height: 1.35; + color: var(--ln-ink); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.vm-page.lc-view-page .lc-view-workspace { + flex: 1 1 0; + width: 100%; + min-height: 0; + overflow: hidden; +} + +.vm-page.lc-view-page .lc-view-workspace--single { + display: flex; + flex-direction: column; + min-height: 0; + width: 100%; +} + +.vm-page.lc-view-page .lc-view-workspace--single .lc-view-detail-shell { + flex: 1 1 0; + min-height: 0; + width: 100%; + max-width: none; + margin: 0; +} + +.vm-page.lc-view-page .lc-view-workspace__row { + height: 100%; + min-height: 0; +} + +.vm-page.lc-view-page .lc-view-workspace__detail-col, +.vm-page.lc-view-page .lc-view-workspace__preview-col { + display: flex; + flex-direction: column; + min-height: 0; + height: 100%; +} + +.vm-page.lc-view-page .lc-view-detail-shell { + display: flex; + flex-direction: column; + min-height: 0; + height: 100%; + width: 100%; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-card); + background: var(--ln-surface-card); + overflow: hidden; + box-shadow: var(--ln-shadow-soft); +} + +.vm-page.lc-view-page .lc-view-section-nav { + flex: 0 0 auto; + padding: 8px 10px; + border-bottom: 1px solid var(--ln-hairline); + background: var(--ln-surface-card); + overflow-x: auto; + -webkit-overflow-scrolling: touch; + transition: box-shadow 0.2s ease; +} + +.vm-page.lc-view-page .lc-view-section-nav.is-scrolled { + box-shadow: var(--ln-shadow-soft); +} + +.vm-page.lc-view-page .lc-view-section-nav__list { + display: flex; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; + min-width: max-content; +} + +.vm-page.lc-view-page .lc-view-section-nav__btn { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 44px; + padding: 0 14px; + border: 1px solid transparent; + border-radius: 999px; + background: transparent; + color: var(--ln-body); + font-size: 0.8125rem; + cursor: pointer; + white-space: nowrap; + transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; +} + +.vm-page.lc-view-page .lc-view-section-nav__btn svg { + flex: 0 0 auto; + opacity: 0.72; +} + +.vm-page.lc-view-page .lc-view-section-nav__btn:hover { + background: var(--ln-canvas-soft); + color: var(--ln-ink); +} + +.vm-page.lc-view-page .lc-view-section-nav__btn.is-active { + background: var(--ln-primary-soft); + border-color: color-mix(in srgb, var(--ln-primary) 28%, var(--ln-hairline)); + color: var(--ln-primary-focus); + box-shadow: none; + font-weight: 600; +} + +.vm-page.lc-view-page .lc-view-section-nav__btn.is-active svg { + opacity: 1; + color: var(--ln-primary); +} + +.vm-page.lc-view-page .lc-view-section-nav__btn:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 2px; +} + +.vm-page.lc-view-page .lc-view-detail-column { + flex: 1 1 0; + min-height: 0; + overflow: auto; + padding: 12px 16px 20px; + scroll-behavior: smooth; + background: var(--ln-canvas-parchment); +} + +.vm-page.lc-view-page .lc-view-section { + margin-bottom: 12px; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-card); + background: var(--ln-surface-card); + overflow: hidden; + transition: border-color 0.25s ease, box-shadow 0.25s ease; +} + +.vm-page.lc-view-page .lc-view-section.is-active { + border-color: var(--ln-primary); + box-shadow: + 0 0 0 1px var(--ln-primary), + 0 4px 14px rgba(50, 160, 110, 0.14), + 0 2px 6px rgba(50, 160, 110, 0.08); +} + +.vm-page.lc-view-page .lc-view-section:last-child { + margin-bottom: 0; +} + +.vm-page.lc-view-page .lc-view-section__head { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 10px 12px; + border-bottom: 1px solid var(--ln-hairline); + background: var(--ln-surface-strong); +} + +.vm-page.lc-view-page .lc-view-section__icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 30px; + height: 30px; + margin-top: 1px; + border-radius: var(--ln-radius-control); + color: var(--ln-primary); + background: var(--ln-primary-soft); +} + +.vm-page.lc-view-page .lc-view-section__head-text { + flex: 1 1 auto; + min-width: 0; +} + +.vm-page.lc-view-page .lc-view-section__title { + margin: 0; + font-size: 0.9375rem; + font-weight: 600; + line-height: 1.35; + color: var(--ln-ink); +} + +.vm-page.lc-view-page .lc-view-section__subtitle { + margin: 2px 0 0; + font-size: 0.75rem; + line-height: 1.4; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-section__count, +.vm-page.lc-view-page .lc-view-section__jump { + flex: 0 0 auto; + align-self: center; + font-size: 0.75rem; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-section__jump { + border: none; + background: transparent; + cursor: pointer; + padding: 4px 8px; + border-radius: var(--ln-radius-pill); + color: var(--ln-primary); + font-weight: 500; +} + +.vm-page.lc-view-page .lc-view-section__jump:hover { + background: var(--ln-primary-soft); + color: var(--ln-primary-focus); +} + +.vm-page.lc-view-page .lc-view-section__body { + padding: 10px 12px 12px; +} + +.vm-page.lc-view-page .lc-view-section__empty { + margin: 0; + font-size: 0.875rem; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-field-group + .lc-view-field-group { + margin-top: 12px; + padding-top: 12px; + border-top: 1px dashed var(--ln-hairline); +} + +.vm-page.lc-view-page .lc-view-field-groups--grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px 16px; +} + +.vm-page.lc-view-page .lc-view-field-groups--grid .lc-view-field-group + .lc-view-field-group { + margin-top: 0; + padding-top: 0; + border-top: none; +} + +.vm-page.lc-view-page .lc-view-field-groups--grid .lc-view-field-group { + padding: 10px 12px; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.vm-page.lc-view-page .lc-view-field-groups--grid .lc-view-field-group:hover { + border-color: var(--ln-hairline-strong); + box-shadow: var(--ln-shadow-soft); +} + +.vm-page.lc-view-page .lc-view-field-group__title { + margin: 0 0 6px; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.02em; + color: var(--ln-muted); + text-transform: none; +} + +.vm-page.lc-view-page .lc-view-field-list { + display: flex; + flex-direction: column; +} + +.vm-page.lc-view-page .lc-view-field-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px 14px; +} + +.vm-page.lc-view-page .lc-view-field-grid--audit .lc-view-field { + padding: 8px 10px; + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card); + border: 1px solid var(--ln-hairline); + transition: border-color 0.2s ease, background-color 0.2s ease; +} + +.vm-page.lc-view-page .lc-view-field-grid--audit .lc-view-field:hover { + border-color: color-mix(in srgb, var(--ln-primary) 24%, var(--ln-hairline)); + background: var(--ln-primary-soft); +} + +.vm-page.lc-view-page .lc-view-field { + min-width: 0; +} + +.vm-page.lc-view-page .lc-view-field--wide { + grid-column: 1 / -1; +} + +.vm-page.lc-view-page .lc-view-field--row { + display: grid; + grid-template-columns: 108px minmax(0, 1fr); + gap: 10px; + align-items: baseline; + padding: 7px 0; + border-bottom: 1px solid color-mix(in srgb, var(--ln-hairline) 80%, transparent); +} + +.vm-page.lc-view-page .lc-view-field-list .lc-view-field--row:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.vm-page.lc-view-page .lc-view-field__label { + display: block; + font-size: 0.75rem; + line-height: 1.4; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-field--row .lc-view-field__label { + margin-bottom: 0; +} + +.vm-page.lc-view-page .lc-view-field__value { + display: block; + font-size: 0.875rem; + line-height: 1.45; + color: var(--ln-ink); + word-break: break-word; +} + +.vm-page.lc-view-page .lc-view-vehicle-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 10px; +} + +.vm-page.lc-view-page .lc-view-vehicle-card { + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-control); + background: var(--ln-surface-card); + overflow: hidden; + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.vm-page.lc-view-page .lc-view-vehicle-card:hover { + border-color: var(--ln-hairline-strong); + box-shadow: var(--ln-shadow-soft); +} + +.vm-page.lc-view-page .lc-view-vehicle-card__head { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + border-bottom: 1px solid var(--ln-hairline); + background: var(--ln-surface-strong); +} + +.vm-page.lc-view-page .lc-view-vehicle-card__index { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 28px; + height: 22px; + padding: 0 6px; + border-radius: var(--ln-radius-sm); + font-size: 0.6875rem; + font-weight: 600; + color: var(--ln-muted); + background: var(--ln-surface-strong); +} + +.vm-page.lc-view-page .lc-view-vehicle-card__title { + margin: 0; + flex: 1 1 auto; + min-width: 0; + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); +} + +.vm-page.lc-view-page .lc-view-field-list--vehicle { + padding: 4px 10px 8px; +} + +.vm-page.lc-view-page .lc-view-field-list--vehicle .lc-view-field--row { + grid-template-columns: 92px minmax(0, 1fr); + padding: 5px 0; +} + +.vm-page.lc-view-page .lc-view-remark-text { + margin: 0; + padding: 10px 12px; + font-size: 0.875rem; + line-height: 1.6; + color: var(--ln-body); + white-space: pre-wrap; + border-radius: var(--ln-radius-control); + background: var(--ln-surface-strong); + border: 1px solid var(--ln-hairline); +} + +.vm-page.lc-view-page .lc-view-table .ant-table { + font-size: 0.8125rem; +} + +.vm-page.lc-view-page .lc-view-table .ant-table-thead > tr > th { + background: var(--ln-surface-strong) !important; + font-weight: 600; +} + +.vm-page.lc-view-page .lc-view-timeline { + margin: 0; + padding: 0; + list-style: none; +} + +.vm-page.lc-view-page .lc-view-timeline__item { + display: grid; + grid-template-columns: 14px minmax(0, 1fr); + gap: 10px; + padding-bottom: 12px; + position: relative; +} + +.vm-page.lc-view-page .lc-view-timeline__item:not(:last-child)::before { + content: ''; + position: absolute; + left: 6px; + top: 14px; + bottom: 0; + width: 2px; + background: var(--ln-hairline); +} + +.vm-page.lc-view-page .lc-view-timeline__dot { + width: 10px; + height: 10px; + margin-top: 4px; + border-radius: 50%; + background: var(--ln-primary); + box-shadow: 0 0 0 3px var(--ln-primary-soft); +} + +.vm-page.lc-view-page .lc-view-timeline__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.vm-page.lc-view-page .lc-view-timeline__title { + font-size: 0.875rem; + font-weight: 600; + color: var(--ln-ink); +} + +.vm-page.lc-view-page .lc-view-timeline__badge { + font-size: 0.6875rem; + padding: 2px 8px; + border-radius: var(--ln-radius-pill); + color: var(--ln-body); + background: var(--ln-surface-strong); + border: 1px solid var(--ln-hairline); +} + +.vm-page.lc-view-page .lc-view-timeline__summary { + margin: 4px 0 0; + font-size: 0.8125rem; + line-height: 1.5; + color: var(--ln-body); +} + +.vm-page.lc-view-page .lc-view-timeline__meta { + display: flex; + flex-wrap: wrap; + gap: 8px 12px; + margin-top: 6px; + font-size: 0.75rem; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-attachment-actions { + display: flex; + gap: 4px; +} + +.vm-page.lc-view-page .lc-view-preview-panel { + display: flex; + flex-direction: column; + min-height: 0; + height: 100%; + border: 1px solid var(--ln-hairline); + border-radius: var(--ln-radius-card); + background: var(--ln-surface-card); + overflow: hidden; + box-shadow: 0 4px 18px color-mix(in srgb, var(--ln-ink) 8%, transparent); +} + +.vm-page.lc-view-page .lc-view-preview-panel__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 10px 14px; + border-bottom: 1px solid var(--ln-hairline); + background: linear-gradient( + 180deg, + color-mix(in srgb, var(--ln-primary) 4%, var(--ln-surface-card)) 0%, + var(--ln-surface-card) 100% + ); +} + +.vm-page.lc-view-page .lc-view-preview-panel__head-main { + min-width: 0; +} + +.vm-page.lc-view-page .lc-view-preview-panel__title { + margin: 0; + font-size: 0.9375rem; + font-weight: 600; + color: var(--ln-ink); +} + +.vm-page.lc-view-page .lc-view-preview-panel__hint { + display: block; + margin-top: 2px; + font-size: 0.75rem; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-preview-panel__badge { + flex: 0 0 auto; + padding: 3px 10px; + border-radius: 999px; + font-size: 0.6875rem; + font-weight: 600; + color: var(--ln-primary-focus); + background: color-mix(in srgb, var(--ln-primary) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--ln-primary) 24%, var(--ln-hairline)); +} + +.vm-page.lc-view-page .lc-view-preview-panel__badge.is-muted { + color: var(--ln-muted); + background: color-mix(in srgb, var(--ln-ink) 4%, transparent); + border-color: var(--ln-hairline); +} + +.vm-page.lc-view-page .lc-view-preview-panel__body { + flex: 1 1 0; + min-height: 0; + overflow: hidden; + background: color-mix(in srgb, var(--ln-ink) 3.5%, var(--ln-surface-card)); +} + +.vm-page.lc-view-page .lc-view-preview-panel__body .ct-preview-doc, +.vm-page.lc-view-page .lc-view-preview-panel__body .ct-preview-panel { + height: 100%; +} + +.vm-page.lc-view-page .lc-view-preview-panel__body .ct-preview-body-shell { + max-height: 100%; +} + +.vm-page.lc-view-page .lc-view-preview-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 280px; + padding: 24px; + text-align: center; + color: var(--ln-muted); +} + +.vm-page.lc-view-page .lc-view-preview-empty__sub { + margin: 0; + font-size: 0.8125rem; +} + +.vm-page.lc-view-page .lc-view-page__empty { + padding: 24px; + color: var(--ln-muted); +} + +@media (min-width: 1400px) { + .vm-page.lc-view-page .lc-view-field-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .vm-page.lc-view-page .lc-view-field-groups--grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 1399px) { + .vm-page.lc-view-page .lc-view-hero__kpi-row { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (max-width: 1199px) { + .vm-page.lc-view-page .lc-view-hero__kpi-row { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .vm-page.lc-view-page .lc-view-field-grid { + grid-template-columns: 1fr; + } + + .vm-page.lc-view-page .lc-view-field-groups--grid { + grid-template-columns: 1fr; + } + + .vm-page.lc-view-page .lc-view-field--row { + grid-template-columns: 96px minmax(0, 1fr); + } +} + +@media (max-width: 767px) { + .vm-page.lc-view-page { + padding: 16px 16px 0; + } + + .vm-page.lc-view-page .lc-view-detail-column { + padding: 10px 12px 16px; + } + + .vm-page.lc-view-page .lc-view-vehicle-list { + grid-template-columns: 1fr; + } +} + +@media (prefers-reduced-motion: reduce) { + .vm-page.lc-view-page .lc-view-section-nav__btn, + .vm-page.lc-view-page .lc-view-detail-column, + .vm-page.lc-view-page .lc-view-section-nav, + .vm-page.lc-view-page .lc-view-kpi-card, + .vm-page.lc-view-page .lc-view-vehicle-card, + .vm-page.lc-view-page .lc-view-section, + .vm-page.lc-view-page .lc-view-field-groups--grid .lc-view-field-group { + transition: none; + } + + .vm-page.lc-view-page .lc-view-kpi-card:hover, + .vm-page.lc-view-page .lc-view-vehicle-card:hover { + box-shadow: var(--ln-shadow-soft); + } +} + +.vm-page.lc-create-page .lc-lease-order-card__lock-tag { + margin-left: auto; + margin-right: 8px; + padding: 2px 8px; + font-size: 0.75rem; + border-radius: 999px; + background: color-mix(in srgb, var(--ln-primary) 12%, transparent); + color: var(--ln-primary-focus); +} + +/* 查看页 — 复用新增页左侧编辑区布局(单列全宽) */ +.vm-page.lc-view-page.lc-create-page { + display: flex; + flex-direction: column; + width: 100%; + max-width: none; + min-height: 0; + height: 100vh; + max-height: 100vh; + overflow: hidden; + padding-bottom: 0; + background: var(--ln-canvas-parchment); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar { + flex: 0 0 auto; + display: flex; + flex-direction: column; + align-items: stretch; + gap: 8px; + margin-bottom: 10px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar__hero { + width: 100%; + min-width: 0; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar__title { + margin: 0; + font-size: 1.125rem; + font-weight: 600; + line-height: 1.35; + color: var(--ln-ink); + letter-spacing: -0.01em; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar__bar { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px 12px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar__main { + flex: 1 1 240px; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar__code { + font-size: 0.8125rem; + line-height: 1.4; + color: var(--ln-muted); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar__tags { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px; + margin-top: 4px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-topbar__meta-tag { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 8px; + border-radius: 999px; + font-size: 0.75rem; + color: var(--ln-muted); + background: var(--ln-surface-strong); + border: 1px solid var(--ln-hairline); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-editor-page { + flex: 1 1 0; + min-height: 0; + display: flex; + flex-direction: column; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-editor-workspace { + flex: 1 1 0; + min-height: 0; + width: 100%; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-editor-column-shell { + width: 100%; + max-width: none; + height: 100%; + min-height: 0; + display: flex; + flex-direction: column; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-tabs { + flex: 0 0 auto; + position: sticky; + top: 0; + z-index: 4; + background: var(--ln-canvas-parchment); + border-bottom: 1px solid var(--ln-hairline); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-tabs__scroll { + display: flex; + gap: 4px; + overflow-x: auto; + padding: 8px 12px; + scrollbar-width: thin; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-tabs__tab { + flex: 0 0 auto; + min-height: 36px; + padding: 0 14px; + border: 1px solid transparent; + border-radius: var(--ln-radius-control); + background: transparent; + font-size: 0.875rem; + line-height: 1.2; + color: var(--ln-muted); + cursor: pointer; + white-space: nowrap; + transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-tabs__tab:hover { + color: var(--ln-ink); + background: var(--ln-surface-card); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-tabs__tab.is-active { + color: var(--ln-primary-focus); + background: var(--ln-primary-soft); + border-color: color-mix(in srgb, var(--ln-primary) 24%, transparent); + font-weight: 600; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-tabs__tab:focus-visible { + outline: 2px solid var(--ln-primary); + outline-offset: 2px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-editor-column { + flex: 1 1 0; + min-height: 0; + overflow: auto; + scroll-behavior: smooth; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-head { + padding: 12px 14px; + border-bottom: 1px solid var(--ln-hairline); + background: var(--ln-surface-strong); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-section-head .ct-panel-head__title { + margin: 0; + font-size: 0.9375rem; + font-weight: 600; + line-height: 1.35; + color: var(--ln-ink); +} + +.vm-page.lc-view-page.lc-create-page .lc-party-profiles { + margin-top: 12px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-signing-layout { + display: block; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-signing-layout__grid { + gap: 16px 24px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field__label { + display: block; + font-size: 0.75rem; + line-height: 1.4; + color: var(--ln-muted); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field__value { + display: block; + font-size: 0.875rem; + line-height: 1.45; + color: var(--ln-ink); + font-weight: 500; + word-break: break-word; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field__value.is-empty { + color: var(--ln-muted-soft); + font-weight: 400; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field__value.is-computed { + color: var(--ln-muted); + font-weight: 400; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px 24px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field-grid--cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field--wide { + grid-column: 1 / -1; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-field--span-2 { + grid-column: span 2; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-subsection + .lc-view-subsection { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid var(--ln-hairline); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-subsection__title { + margin: 0 0 14px; + padding-left: 10px; + border-left: 3px solid var(--ln-primary); + font-size: 0.875rem; + font-weight: 600; + line-height: 1.35; + color: var(--ln-ink); + letter-spacing: -0.01em; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-subsection__body { + display: flex; + flex-direction: column; + gap: 0; +} + +.vm-page.lc-view-page.lc-create-page .lc-main-contract-form { + display: flex; + flex-direction: column; + gap: 0; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-template-fields { + padding: 4px 0; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-party-section { + margin-top: 18px; + padding-top: 18px; + border-top: 1px dashed var(--ln-hairline); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-party-section__title { + margin: 0 0 12px; + padding-left: 10px; + border-left: 3px solid color-mix(in srgb, var(--ln-primary) 55%, transparent); + font-size: 0.8125rem; + font-weight: 600; + color: var(--ln-ink); + letter-spacing: -0.01em; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-party-section__body { + display: flex; + flex-direction: column; + gap: 12px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-party-section .lc-view-party-fields .lc-view-field--span-2 { + grid-column: span 2; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-party-section .lc-view-party-fields .lc-view-field--wide { + grid-column: 1 / -1; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-credentials-block--embedded .lc-attach-gallery { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-credentials-block--embedded { + margin-top: 4px; + padding: 0; + border: none; + background: transparent; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-credentials-block .lc-credentials-block__head { + margin-bottom: 8px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-seal-type-chip { + cursor: default; + pointer-events: none; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-attachment-capsules { + display: flex; + flex-direction: column; + gap: 8px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-attachment-capsule { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 44px; + padding: 8px 14px; + border-radius: var(--ln-radius-pill); + border: 1px solid var(--ln-hairline); + background: color-mix(in srgb, var(--ln-canvas-soft) 88%, #fff); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-attachment-capsule__name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.875rem; + font-weight: 500; + color: var(--ln-ink); +} + +.vm-page.lc-view-page.lc-create-page .lc-view-attachment-capsule__actions { + display: inline-flex; + align-items: center; + gap: 2px; + flex-shrink: 0; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-poa-remark { + margin-top: 10px; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-vehicle-card { + width: 100%; +} + +.vm-page.lc-view-page.lc-create-page .lc-view-vehicle-card__fields { + padding: 12px 14px 14px; +} + +.vm-page.lc-view-page.lc-create-page .lc-lease-order-editor__summary { + margin-bottom: 12px; +} + +@media (max-width: 767px) { + .vm-page.lc-view-page.lc-create-page .lc-view-topbar__bar { + flex-direction: column; + align-items: flex-start; + } + + .vm-page.lc-view-page.lc-create-page .lc-view-field-grid, + .vm-page.lc-view-page.lc-create-page .lc-view-field-grid--cols-3 { + grid-template-columns: 1fr; + } + + .vm-page.lc-view-page.lc-create-page .lc-view-field--span-2, + .vm-page.lc-view-page.lc-create-page .lc-view-field--wide { + grid-column: auto; + } + + .vm-page.lc-view-page.lc-create-page .lc-view-party-section .lc-view-party-fields .lc-view-field--span-2, + .vm-page.lc-view-page.lc-create-page .lc-view-party-section .lc-view-party-fields .lc-view-field--wide { + grid-column: auto; + } + + .vm-page.lc-view-page.lc-create-page .lc-view-credentials-block--embedded .lc-attach-gallery { + grid-template-columns: 1fr; + } +} + +/* 合同详情页 — 对齐车辆资产 vm-detail 布局 */ +.vm-page.lc-view-page.vm-detail.lc-view-detail-page { + display: flex; + flex-direction: column; + height: 100vh; + max-height: 100vh; + overflow: hidden; + padding-bottom: 0; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-shell { + flex: 1 1 0; + min-height: 0; + display: flex; + flex-direction: column; + border: none; + border-radius: 0; + background: transparent; + box-shadow: none; + overflow: hidden; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-topbar { + flex: 0 0 auto; + margin-bottom: 12px; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-header { + flex: 0 0 auto; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-header-card { + margin-bottom: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: none; + padding-bottom: 0; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-header-card .vm-tabs { + margin-bottom: 0; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-body { + flex: 1 1 0; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + background: var(--ln-surface-card); + border: 1px solid var(--ln-hairline); + border-top: none; + border-radius: 0 0 var(--ln-radius-card) var(--ln-radius-card); + box-shadow: var(--ln-shadow-soft); + padding: 16px 24px 24px; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-anchor { + scroll-margin-top: 12px; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-anchor + .lc-view-detail-anchor { + margin-top: 16px; +} + +.vm-page.lc-view-page.vm-detail { + padding-bottom: 24px; +} + +.vm-page.lc-view-page.vm-detail .lc-view-hero__title { + font-size: 1.375rem; + line-height: 1.3; + word-break: break-word; +} + +.vm-page.lc-view-page.vm-detail .lc-view-hero-tags { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 6px; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-tabs { + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: thin; +} + +.vm-page.lc-view-page.vm-detail .lc-view-detail-tabs .vm-tab { + flex: 0 0 auto; + white-space: nowrap; +} + +.vm-page.lc-view-page.vm-detail .vm-model-param-item--span-2 { + grid-column: span 2; +} + +.vm-page.lc-view-page.vm-detail .lc-view-vehicle-card__status { + margin: -6px 0 10px; +} + +.vm-page.lc-view-page.vm-detail .lc-view-credentials-block--embedded { + margin-top: 12px; +} + +.vm-page.lc-view-page.vm-detail .lc-view-credentials-block--embedded .lc-attach-gallery { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +@media (max-width: 991px) { + .vm-page.lc-view-page.vm-detail .vm-model-param-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .vm-page.lc-view-page.vm-detail .vm-model-param-item--span-2, + .vm-page.lc-view-page.vm-detail .vm-model-param-item--wide { + grid-column: auto; + } +} + +.vm-page.lc-view-page.vm-detail.lc-view-detail-page { + padding-bottom: 0; +} + +@media (max-width: 767px) { + .vm-page.lc-view-page.vm-detail .lc-view-detail-body { + padding: 12px 16px 20px; + } + + .vm-page.lc-view-page.vm-detail .vm-detail-aside { + text-align: left; + max-width: none; + } + + .vm-page.lc-view-page.vm-detail .lc-view-hero-tags { + justify-content: flex-start; + } + + .vm-page.lc-view-page.vm-detail .vm-model-param-grid { + grid-template-columns: 1fr; + } + + .vm-page.lc-view-page.vm-detail .lc-view-credentials-block--embedded .lc-attach-gallery { + grid-template-columns: 1fr; + } } diff --git a/src/prototypes/lease-contract-management/styles/lease-contract.css b/src/prototypes/lease-contract-management/styles/lease-contract.css index 4319112..03dd1a1 100644 --- a/src/prototypes/lease-contract-management/styles/lease-contract.css +++ b/src/prototypes/lease-contract-management/styles/lease-contract.css @@ -3,33 +3,336 @@ * 列表壳层与台账页对齐说明见 ledger-shared/DESIGN.md(ldb-page / lc-page) */ -/* 筛选区:Ant Select / DatePicker 与 vm-input 同高 */ +/* ========================================================================== + 全模块表单控件统一(列表筛选 + 新增/编辑) + ========================================================================== */ + +.vm-page.lc-page { + --lc-control-height: 36px; + --lc-control-border: var(--ln-hairline-strong); + --lc-control-border-hover: color-mix(in srgb, var(--ln-ink) 12%, var(--ln-hairline-strong)); + --lc-control-radius: var(--ln-radius-control); + --lc-placeholder-color: var(--ln-muted-soft, #a1a1aa); + --lc-placeholder-font-size: 0.875rem; + --lc-placeholder-font-weight: 400; + --lc-placeholder-line-height: 1.5715; + --ant-color-text-placeholder: var(--lc-placeholder-color); + --ant-input-placeholder-color: var(--lc-placeholder-color); +} + +/* 占位/提示文案:输入框、下拉、日期、自定义选择器统一 */ +.vm-page.lc-page input::placeholder, +.vm-page.lc-page textarea::placeholder, +.vm-page.lc-page .ant-input::placeholder, +.vm-page.lc-page .ant-input-affix-wrapper input.ant-input::placeholder, +.vm-page.lc-page .ant-input-outlined::placeholder, +.vm-page.lc-page .ant-input-affix-wrapper.ant-input-outlined input.ant-input::placeholder, +.vm-page.lc-page .ant-picker .ant-picker-input > input::placeholder, +.vm-page.lc-page .ant-input-number .ant-input-number-input::placeholder, +.vm-page.lc-page .ant-input-number-input::placeholder, +.vm-page.lc-page .vm-filter-picker-input::placeholder, +.vm-page.lc-page .vm-operate-city-edit-search-input::placeholder { + color: var(--lc-placeholder-color) !important; + -webkit-text-fill-color: var(--lc-placeholder-color) !important; + font-size: var(--lc-placeholder-font-size) !important; + font-weight: var(--lc-placeholder-font-weight) !important; + line-height: var(--lc-placeholder-line-height) !important; + opacity: 1 !important; +} + +.vm-page.lc-page .ant-select .ant-select-placeholder, +.vm-page.lc-page .ant-select-selection-placeholder, +.vm-page.lc-page .ant-cascader .ant-select-placeholder { + color: var(--lc-placeholder-color) !important; + font-size: var(--lc-placeholder-font-size) !important; + font-weight: var(--lc-placeholder-font-weight) !important; + line-height: var(--lc-placeholder-line-height) !important; +} + +.vm-page.lc-page .lc-customer-picker__placeholder, +.vm-page.lc-page [class*="__summary"].is-placeholder, +.vm-page.lc-page [class*="__summary-line"].is-placeholder, +.vm-page.lc-page .vm-filter-picker-input.is-placeholder { + color: var(--lc-placeholder-color) !important; + font-size: var(--lc-placeholder-font-size) !important; + font-weight: var(--lc-placeholder-font-weight) !important; + line-height: var(--lc-placeholder-line-height) !important; +} + +:is( + .lc-lease-order-service-popover, + .lc-business-dept-picker-popover, + .lc-lease-order-brand-model-picker-popover, + .lc-lease-order-plate-picker-popover, + .lc-lease-order-delivery-region-popover, + .lc-customer-picker-modal +) { + --lc-placeholder-color: var(--ln-muted-soft, #a1a1aa); + --lc-placeholder-font-size: 0.875rem; + --lc-placeholder-font-weight: 400; + --lc-placeholder-line-height: 1.5715; +} + +:is( + .lc-lease-order-service-popover, + .lc-business-dept-picker-popover, + .lc-lease-order-brand-model-picker-popover, + .lc-lease-order-plate-picker-popover, + .lc-lease-order-delivery-region-popover, + .lc-customer-picker-modal +) input::placeholder, +:is( + .lc-lease-order-service-popover, + .lc-business-dept-picker-popover, + .lc-lease-order-brand-model-picker-popover, + .lc-lease-order-plate-picker-popover, + .lc-lease-order-delivery-region-popover, + .lc-customer-picker-modal +) textarea::placeholder, +:is( + .lc-lease-order-service-popover, + .lc-business-dept-picker-popover, + .lc-lease-order-brand-model-picker-popover, + .lc-lease-order-plate-picker-popover, + .lc-lease-order-delivery-region-popover, + .lc-customer-picker-modal +) .vm-operate-city-edit-search-input::placeholder, +:is( + .lc-lease-order-service-popover, + .lc-business-dept-picker-popover, + .lc-lease-order-brand-model-picker-popover, + .lc-lease-order-plate-picker-popover, + .lc-lease-order-delivery-region-popover, + .lc-customer-picker-modal +) .lc-lease-order-service-popover__search-input::placeholder, +:is( + .lc-lease-order-service-popover, + .lc-business-dept-picker-popover, + .lc-lease-order-brand-model-picker-popover, + .lc-lease-order-plate-picker-popover, + .lc-lease-order-delivery-region-popover, + .lc-customer-picker-modal +) .lc-customer-picker-modal__search::placeholder { + color: var(--lc-placeholder-color, var(--ln-muted-soft, #a1a1aa)) !important; + -webkit-text-fill-color: var(--lc-placeholder-color, var(--ln-muted-soft, #a1a1aa)); + font-size: var(--lc-placeholder-font-size, 0.875rem) !important; + font-weight: var(--lc-placeholder-font-weight, 400) !important; + line-height: var(--lc-placeholder-line-height, 1.5715) !important; + opacity: 1 !important; +} + +/* 边框与圆角:Ant Design 4 结构 */ +.vm-page.lc-page :is( + .ant-input:not(textarea):not(.ant-input-borderless), + .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled), + .ant-input-number:not(.ant-input-number-borderless), + .ant-input-number .ant-input-number-group-wrapper, + .ant-select:not(.ant-select-borderless), + .ant-select .ant-select-selector, + .ant-cascader .ant-select-selector, + .ant-picker:not(.ant-picker-borderless), + .vm-filter-picker-control, + .lc-customer-picker__trigger, + .vm-input:not(textarea) +) { + border-color: var(--lc-control-border) !important; +} + +.vm-page.lc-page :is( + .ant-input:not(textarea):not(.ant-input-borderless), + .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled), + .ant-input-number:not(.ant-input-number-borderless), + .ant-input-number .ant-input-number-group-wrapper, + .ant-select:not(.ant-select-borderless), + .ant-select .ant-select-selector, + .ant-cascader .ant-select-selector, + .ant-picker:not(.ant-picker-borderless), + .vm-filter-picker-control, + .lc-customer-picker__trigger, + .vm-input:not(textarea) +):not([class*="-compact-item"]) { + border-radius: var(--lc-control-radius) !important; +} + +/* 边框:Ant Design 5 outlined */ +.vm-page.lc-page :is( + .ant-select-outlined:not(.ant-select-borderless), + .ant-picker-outlined:not(.ant-picker-borderless), + .ant-input-number-outlined:not(.ant-input-number-borderless), + .ant-input-outlined:not(.ant-input-borderless), + .ant-input-affix-wrapper-outlined +) { + border-color: var(--lc-control-border) !important; +} + +.vm-page.lc-page :is( + .ant-select-outlined:not(.ant-select-borderless), + .ant-picker-outlined:not(.ant-picker-borderless), + .ant-input-number-outlined:not(.ant-input-number-borderless), + .ant-input-outlined:not(.ant-input-borderless), + .ant-input-affix-wrapper-outlined +):not([class*="-compact-item"]) { + border-radius: var(--lc-control-radius) !important; +} + +/* 输入框 + 后缀/前缀单位:外层统一边框,内侧仅保留分割线 */ +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon), +.vm-page.lc-page .ant-space-compact:has(> .ant-input-number-addon), +.vm-page.lc-page .lc-form-readonly-compact { + display: inline-flex; + align-items: stretch; + max-width: 100%; + vertical-align: top; + box-sizing: border-box; + min-height: var(--lc-control-height); + height: var(--lc-control-height); + border: 1px solid var(--lc-control-border); + border-radius: var(--lc-control-radius); + overflow: hidden; + background: #fff; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon) > .ant-input-number, +.vm-page.lc-page .ant-space-compact:has(> .ant-input-number-addon) > .ant-input-number, +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon) > .ant-input, +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon) > .ant-input-affix-wrapper, +.vm-page.lc-page .lc-form-readonly-compact > :first-child { + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; + margin: 0 !important; + outline: none !important; +} + +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon) > .ant-space-addon, +.vm-page.lc-page .ant-space-compact:has(> .ant-input-number-addon) > .ant-input-number-addon, +.vm-page.lc-page .lc-form-readonly-compact > .ant-space-addon:last-child { + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; + margin: 0 !important; + border-inline-start: 1px solid var(--lc-control-border) !important; + background: rgba(0, 0, 0, 0.04); + color: var(--ln-body); +} + +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon):has(.ant-input-number:not(.ant-input-number-disabled):hover), +.vm-page.lc-page .ant-space-compact:has(> .ant-input-number-addon):has(.ant-input-number:not(.ant-input-number-disabled):hover), +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon):has(.ant-input:not(:disabled):hover), +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon):has(.ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover) { + border-color: var(--lc-control-border-hover) !important; +} + +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon):has(.ant-input-number-focused), +.vm-page.lc-page .ant-space-compact:has(> .ant-input-number-addon):has(.ant-input-number-focused), +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon):focus-within:has(.ant-input:focus), +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon):focus-within:has(.ant-input-affix-wrapper-focused) { + border-color: var(--vm-focus-border-color) !important; + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary) 14%, transparent); +} + +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon) .ant-input-number-focused, +.vm-page.lc-page .ant-space-compact:has(> .ant-input-number-addon) .ant-input-number-focused, +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon) .ant-input:focus, +.vm-page.lc-page .ant-space-compact:has(> .ant-space-addon) .ant-input-affix-wrapper-focused { + border-color: transparent !important; + box-shadow: none !important; +} + +/* 兼容 ant-space-compact-item 旧类名 */ +.vm-page.lc-page .ant-space-compact > .ant-space-compact-item.ant-space-compact-first-item:not(.ant-space-compact-last-item):is( + .ant-input-number, + .ant-input-number-outlined, + .ant-input, + .ant-input-outlined, + .ant-input-affix-wrapper, + .ant-input-affix-wrapper-outlined +) { + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; + margin: 0 !important; +} + +.vm-page.lc-page .ant-space-compact > .ant-space-compact-item.ant-space-compact-last-item:not(.ant-space-compact-first-item):is( + .ant-space-addon, + .ant-input-number-addon +) { + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; + margin: 0 !important; + border-inline-start: 1px solid var(--lc-control-border) !important; +} + +.vm-page.lc-page .ant-input-group > .ant-input-group-addon:first-child { + border-start-end-radius: 0 !important; + border-end-end-radius: 0 !important; +} + +.vm-page.lc-page .ant-input-group > .ant-input-group-addon:first-child + :is(.ant-input, .ant-input-affix-wrapper), +.vm-page.lc-page .ant-input-group > :is(.ant-input, .ant-input-affix-wrapper):first-child:not(:last-child) { + border-start-end-radius: 0 !important; + border-end-end-radius: 0 !important; +} + +.vm-page.lc-page .ant-input-group > .ant-input-group-addon:last-child:not(:first-child), +.vm-page.lc-page .ant-input-group > :is(.ant-input, .ant-input-affix-wrapper):last-child:not(:first-child) { + border-start-start-radius: 0 !important; + border-end-start-radius: 0 !important; +} + +/* 筛选区:控件同高同边框 */ .vm-page.lc-page .vm-filter-field .ant-select, -.vm-page.lc-page .vm-filter-field .ant-picker { +.vm-page.lc-page .vm-filter-field .ant-picker, +.vm-page.lc-page .vm-filter-field .ant-input:not(textarea), +.vm-page.lc-page .vm-filter-field .ant-input-affix-wrapper { width: 100%; } -.vm-page.lc-page .vm-filter-field .ant-select .ant-select-selector, -.vm-page.lc-page .vm-filter-field .ant-picker { - min-height: var(--vm-control-height) !important; - border-radius: var(--ln-radius-control) !important; - border-color: var(--ln-hairline-strong) !important; +.vm-page.lc-page .vm-filter-field :is( + .ant-select, + .ant-picker, + .ant-input:not(textarea), + .ant-input-affix-wrapper +) { + min-height: var(--lc-control-height) !important; + height: var(--lc-control-height); font-size: 0.875rem; + box-sizing: border-box; } .vm-page.lc-page .vm-filter-field .ant-select .ant-select-selector { + min-height: calc(var(--lc-control-height) - 2px) !important; + height: calc(var(--lc-control-height) - 2px); + display: flex; align-items: center; + padding-inline: 11px; +} + +.vm-page.lc-page .vm-filter-field .ant-select-multiple.ant-select { + height: auto; + min-height: var(--lc-control-height) !important; } .vm-page.lc-page .vm-filter-field .ant-select-multiple .ant-select-selector { - min-height: var(--vm-control-height) !important; + height: auto !important; + min-height: calc(var(--lc-control-height) - 2px) !important; padding-top: 2px; padding-bottom: 2px; + align-items: center; +} + +.vm-page.lc-page .vm-filter-field .ant-picker .ant-picker-input > input { + font-size: 0.875rem; + line-height: calc(var(--lc-control-height) - 2px); } /* 筛选区:合同结束日期 RangePicker */ .vm-page.lc-page .lc-filter-date-range.ant-picker { width: 100%; + padding: 0 11px; } .vm-page.lc-page .lc-filter-date-range.ant-picker .ant-picker-input > input { @@ -37,10 +340,6 @@ color: var(--ln-ink); } -.vm-page.lc-page .lc-filter-date-range.ant-picker .ant-picker-input > input::placeholder { - color: var(--ln-muted-soft); -} - .vm-page.lc-page .lc-filter-date-range.ant-picker .ant-picker-separator { color: var(--ln-muted); font-size: 0.8125rem; @@ -50,9 +349,51 @@ color: var(--ln-muted-soft); } -.vm-page.lc-page .vm-filter-field .lc-filter-date-range.ant-picker:hover, -.vm-page.lc-page .vm-filter-field .lc-filter-date-range.ant-picker.ant-picker-focused { - border-color: color-mix(in srgb, var(--ln-primary) 42%, var(--ln-hairline-strong)) !important; +/* 悬停 / 聚焦 */ +.vm-page.lc-page :is( + .ant-select:not(.ant-select-disabled):not(.ant-select-borderless):hover, + .ant-picker:not(.ant-picker-disabled):not(.ant-picker-borderless):hover, + .ant-input-number:not(.ant-input-number-disabled):not(.ant-input-number-borderless):hover, + .ant-input:not(textarea):not(:disabled):not(.ant-input-borderless):hover, + .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover, + .vm-filter-picker-control:hover, + .lc-customer-picker__trigger:hover +) { + border-color: var(--lc-control-border-hover) !important; +} + +.vm-page.lc-page :is( + .ant-select-outlined:not(.ant-select-disabled):hover, + .ant-picker-outlined:not(.ant-picker-disabled):hover, + .ant-input-number-outlined:not(.ant-input-number-disabled):hover, + .ant-input-outlined:not(:disabled):hover +) { + border-color: var(--lc-control-border-hover) !important; +} + +.vm-page.lc-page :is( + .ant-select-focused:not(.ant-select-borderless), + .ant-select.ant-select-open:not(.ant-select-borderless), + .ant-picker-focused:not(.ant-picker-borderless), + .ant-input-number-focused:not(.ant-input-number-borderless), + .ant-input:focus:not(.ant-input-borderless), + .ant-input-affix-wrapper-focused, + .vm-filter-picker-control.open, + .vm-filter-picker-control:focus-within, + .lc-customer-picker__trigger:focus-visible +) { + border-color: var(--vm-focus-border-color) !important; + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary) 14%, transparent); +} + +.vm-page.lc-page :is( + .ant-select-outlined.ant-select-focused, + .ant-select-outlined.ant-select-open, + .ant-picker-outlined.ant-picker-focused, + .ant-input-number-outlined.ant-input-number-focused, + .ant-input-outlined:focus +) { + border-color: var(--vm-focus-border-color) !important; box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary) 14%, transparent); } @@ -74,6 +415,13 @@ background: var(--ln-primary, #32a06e); } +.vm-page.lc-page .vm-filter-picker-control { + min-height: var(--lc-control-height); + height: var(--lc-control-height); + border-color: var(--lc-control-border) !important; + border-radius: var(--lc-control-radius) !important; +} + /* 列表工具栏:上下间距一致(以下方 16px 为准) */ .vm-page.lc-page .vm-filter-card { margin-bottom: 16px; @@ -226,8 +574,16 @@ gap: 2px; } -.vm-page.lc-page.lc-page--list-dense .lc-approval-status-cell__approver { - font-size: 0.75rem; +.vm-page.lc-page.lc-page--list-dense .lc-approval-status-cell__inline-name { + font-size: 11px; + max-width: 4.5em; +} + +.vm-page.lc-page.lc-page--list-dense .lc-approval-status-cell__count-badge { + min-width: 16px; + height: 16px; + padding: 0 4px; + font-size: 10px; } .vm-page.lc-page.lc-page--list-dense .vm-list-table .ant-btn-link { @@ -341,6 +697,45 @@ color: var(--ln-muted, #64748b); } +.vm-page.lc-page .lc-flow-modal--extra-fee .lc-lease-order-service-picker--inline { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.vm-page.lc-page .lc-flow-modal--extra-fee .lc-lease-order-service-picker__btn, +.vm-page.lc-page .lc-flow-modal--extra-fee .lc-lease-order-service-picker__btn--inline { + 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; +} + +.vm-page.lc-page .lc-flow-modal--extra-fee .lc-lease-order-service-picker__btn--inline { + width: 100%; + min-height: 36px; +} + +.vm-page.lc-page .lc-flow-modal--extra-fee .lc-lease-order-service-field--inline { + margin-top: 0; +} + +.vm-page.lc-page .lc-flow-modal--extra-fee .lc-lease-order-service-selected { + gap: 6px; +} + .vm-page.lc-page .lc-tripartite-agreements__add { width: 100%; margin-top: 8px; @@ -478,6 +873,7 @@ .vm-page.lc-page .lc-list-table--nested .ant-table-tbody > tr:not(.ant-table-expanded-row) > td.ant-table-cell-fix-end:not(.ant-table-cell-scrollbar), .vm-page.lc-page .lc-list-table--nested .ant-table-tbody > tr:not(.ant-table-expanded-row) > td.ant-table-cell-fix-right:not(.ant-table-cell-scrollbar) { z-index: 3; + overflow: visible; background: var(--ln-surface-card) !important; } @@ -647,6 +1043,45 @@ padding: 12px 14px; } +.lc-approval-flow__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 12px; + padding-bottom: 10px; + border-bottom: 1px solid var(--ln-hairline, #e2e8f0); +} + +.lc-approval-flow__head-label { + font-size: 13px; + font-weight: 600; + color: var(--ln-ink, #0f172a); +} + +.lc-approval-flow__kind { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + line-height: 1.4; + white-space: nowrap; +} + +.lc-approval-flow__kind--standard { + color: #166534; + background: color-mix(in srgb, #22c55e 12%, #fff); + border: 1px solid color-mix(in srgb, #22c55e 28%, transparent); +} + +.lc-approval-flow__kind--nonstandard { + color: #b45309; + background: color-mix(in srgb, #f59e0b 12%, #fff); + border: 1px solid color-mix(in srgb, #f59e0b 30%, transparent); +} + .lc-approval-flow { width: min(380px, calc(100vw - 48px)); max-height: min(420px, calc(100vh - 120px)); @@ -724,6 +1159,13 @@ .vm-page.lc-page .vm-list-table .lc-project-info-col { white-space: normal !important; vertical-align: middle; + min-width: 320px !important; +} + +.vm-page.lc-page .lc-project-info-btn .vm-stack .primary, +.vm-page.lc-page .lc-project-info-btn .vm-stack .sub { + overflow-wrap: anywhere; + word-break: break-word; } .vm-page.lc-page .lc-project-info-cell { @@ -741,9 +1183,12 @@ .vm-page.lc-page .lc-project-info-cell__status { flex-shrink: 0; display: flex; + flex-wrap: wrap; align-items: center; - justify-content: center; + justify-content: flex-end; + gap: 4px; align-self: center; + max-width: 108px; } .vm-page.lc-page .lc-project-info-btn { @@ -828,6 +1273,19 @@ background: color-mix(in srgb, var(--ln-primary, #32a06e) 8%, transparent); } +.vm-page.lc-page .lc-lease-order-counts__item--active { + background: color-mix(in srgb, var(--ln-primary, #32a06e) 12%, transparent); +} + +.vm-page.lc-page .lc-lease-order-counts__item.is-disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.vm-page.lc-page .lc-lease-order-counts__item.is-disabled:hover { + background: transparent; +} + .vm-page.lc-page .lc-lease-order-counts__item:focus-visible { outline: 2px solid var(--ln-primary, #32a06e); outline-offset: 1px; @@ -854,17 +1312,442 @@ flex-shrink: 0; } -/* 列表「费用信息」列:付款周期 / 氢费支付 左右均分展示 */ +.lc-stamp-upload-modal .ant-modal-body { + padding-top: 16px; +} + +.lc-stamp-modal { + display: flex; + flex-direction: column; + gap: 18px; +} + +.lc-stamp-modal__hint { + margin: 0; + font-size: 13px; + color: var(--ln-muted, #64748b); + line-height: 1.5; +} + +.lc-stamp-modal__meta { + margin: 0; + font-size: 14px; + color: var(--ln-muted, #64748b); +} + +.lc-stamp-modal__meta-value { + color: var(--ln-body, #334155); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.lc-stamp-modal__hero { + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px 14px; + border-radius: 10px; + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border: 1px solid var(--ln-hairline, #e2e8f0); +} + +.lc-stamp-modal__hero-main { + display: flex; + align-items: baseline; + gap: 8px; + flex-wrap: wrap; +} + +.lc-stamp-modal__hero-label { + font-size: 12px; + color: var(--ln-muted, #64748b); +} + +.lc-stamp-modal__hero-code { + font-size: 15px; + font-weight: 600; + color: var(--ln-body, #1e293b); + font-variant-numeric: tabular-nums; +} + +.lc-stamp-modal__hero-sub { + margin: 0; + font-size: 13px; + color: var(--ln-muted, #64748b); + line-height: 1.45; +} + +.lc-stamp-modal__steps { + display: flex; + align-items: center; + gap: 10px; +} + +.lc-stamp-modal__step { + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; + color: var(--ln-muted, #94a3b8); +} + +.lc-stamp-modal__step.is-active { + color: var(--ln-body, #334155); +} + +.lc-stamp-modal__step-num { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 50%; + font-size: 12px; + font-weight: 600; + background: #e2e8f0; + color: #64748b; + flex-shrink: 0; + font-variant-numeric: tabular-nums; +} + +.lc-stamp-modal__step.is-active .lc-stamp-modal__step-num { + background: var(--ln-primary, #32a06e); + color: #fff; +} + +.lc-stamp-modal__step-text { + font-size: 13px; + font-weight: 500; + white-space: nowrap; +} + +.lc-stamp-modal__step-line { + flex: 1; + height: 1px; + min-width: 24px; + background: var(--ln-hairline, #e2e8f0); +} + +.lc-stamp-modal__section { + display: flex; + flex-direction: column; + gap: 12px; +} + +.lc-stamp-modal__section-head { + display: flex; + flex-direction: column; + gap: 4px; +} + +.lc-stamp-modal__section-title { + margin: 0; + font-size: 14px; + font-weight: 600; + color: var(--ln-body, #334155); +} + +.lc-stamp-modal__section-desc { + margin: 0; + font-size: 13px; + color: var(--ln-muted, #64748b); + line-height: 1.5; +} + +.lc-stamp-modal__method-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.lc-stamp-modal__method-card { + display: flex; + flex-direction: column; + gap: 8px; + padding: 14px; + border-radius: 10px; + background: #fff; + border: 1px solid var(--ln-hairline, #e2e8f0); + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.lc-stamp-modal__method-card:hover { + border-color: color-mix(in srgb, var(--ln-primary, #32a06e) 35%, #e2e8f0); + box-shadow: 0 4px 14px rgba(15, 23, 42, 0.05); +} + +.lc-stamp-modal__method-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.lc-stamp-modal__method-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + color: var(--ln-primary-focus, #15803d); + background: var(--ln-primary-soft, #e8f5ee); +} + +.lc-stamp-modal__method-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + color: var(--ln-primary, #32a06e); + background: var(--ln-primary-soft, #e8f5ee); +} + +.lc-stamp-modal__method-title { + margin: 0; + font-size: 14px; + font-weight: 600; + color: var(--ln-body, #334155); +} + +.lc-stamp-modal__method-desc { + margin: 0; + font-size: 12px; + color: var(--ln-muted, #64748b); + line-height: 1.5; + flex: 1; +} + +.lc-stamp-modal__method-btn { + align-self: flex-start; + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 36px; + margin-top: 4px; + cursor: pointer; +} + +.lc-stamp-modal__method-btn .lc-stamp-modal__icon { + width: 16px; + height: 16px; +} + +.lc-stamp-modal__email-field { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 4px; +} + +.lc-stamp-modal__email-label { + font-size: 12px; + font-weight: 500; + color: var(--ln-body, #475569); +} + +.lc-stamp-modal__email-row { + display: flex; + gap: 8px; + align-items: flex-start; +} + +.lc-stamp-modal__email-input { + flex: 1; + min-width: 0; +} + +.lc-stamp-modal__email-send-btn { + flex-shrink: 0; + min-width: 72px; + min-height: 36px; + cursor: pointer; +} + +.lc-stamp-modal__email-error { + margin: 0; + font-size: 12px; + color: #dc2626; + line-height: 1.4; +} + +.lc-stamp-modal__sent-banner { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + border-radius: 8px; + font-size: 13px; + color: #166534; + background: #ecfdf5; + border: 1px solid #bbf7d0; +} + +.lc-stamp-modal__sent-banner .lc-stamp-modal__icon { + flex-shrink: 0; + color: #16a34a; +} + +.lc-stamp-modal__upload-panel .ant-upload-drag { + border-radius: 10px; + border-color: var(--ln-hairline, #e2e8f0); + background: #fafbfc; + transition: border-color 0.15s ease, background 0.15s ease; +} + +.lc-stamp-modal__upload-panel .ant-upload-drag:hover, +.lc-stamp-modal__upload-panel .ant-upload-drag.ant-upload-drag-hover { + border-color: color-mix(in srgb, var(--ln-primary, #32a06e) 45%, #e2e8f0); + background: var(--ln-primary-soft, #f0fdf4); +} + +.lc-stamp-modal__dropzone { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 18px 12px; +} + +.lc-stamp-modal__dropzone-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + color: var(--ln-primary, #32a06e); + background: var(--ln-primary-soft, #e8f5ee); +} + +.lc-stamp-modal__dropzone-icon .lc-stamp-modal__icon { + width: 20px; + height: 20px; +} + +.lc-stamp-modal__dropzone-title { + margin: 0; + font-size: 14px; + font-weight: 500; + color: var(--ln-body, #334155); +} + +.lc-stamp-modal__dropzone-hint { + margin: 0; + font-size: 12px; + color: var(--ln-muted, #64748b); +} + +.lc-stamp-modal__file-summary { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + border-radius: 8px; + font-size: 13px; + color: var(--ln-body, #475569); + background: #f8fafc; + border: 1px solid var(--ln-hairline, #e2e8f0); +} + +.lc-stamp-modal__file-summary .lc-stamp-modal__icon { + flex-shrink: 0; + color: var(--ln-primary, #32a06e); +} + +.lc-stamp-modal__icon { + display: block; + flex-shrink: 0; +} + +@media (max-width: 680px) { + .lc-stamp-modal__method-grid { + grid-template-columns: 1fr; + } + + .lc-stamp-modal__steps { + flex-wrap: wrap; + } + + .lc-stamp-modal__step-text { + white-space: normal; + } + + .lc-stamp-modal__email-row { + flex-direction: column; + align-items: stretch; + } + + .lc-stamp-modal__email-send-btn { + width: 100%; + } +} + +.lc-contract-view-attachments__meta { + margin: 0 0 12px; + font-size: 14px; + color: var(--ln-body, #334155); +} + +.lc-contract-view-attachments__list { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 8px; +} + +.lc-contract-view-attachments__item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + border: 1px solid var(--ln-hairline, #e2e8f0); + border-radius: 8px; + background: var(--ln-surface-muted, #f8fafc); +} + +.lc-contract-view-attachments__name { + min-width: 0; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 13px; +} + +.lc-contract-view-attachments__actions { + display: flex; + align-items: center; + gap: 4px; + flex-shrink: 0; +} + +.lc-contract-view-attachments__empty { + margin: 0; + font-size: 13px; + color: var(--ln-muted, #64748b); +} + +/* 列表「费用信息」列:付款方式 / 付款周期 / 氢费支付 三栏展示 */ +.vm-page.lc-page .vm-list-table th.lc-fee-info-col { + text-align: center !important; +} + .vm-page.lc-page .vm-list-table .lc-fee-info-col { white-space: normal !important; - min-width: 192px !important; + min-width: 248px !important; padding-left: 6px !important; padding-right: 6px !important; } .vm-page.lc-page .lc-fee-info-cell { display: grid; - grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr); + grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr) 1px minmax(0, 1fr); align-items: stretch; width: 100%; min-width: 0; @@ -913,6 +1796,16 @@ color: var(--ln-primary, #32a06e); } +.vm-page.lc-page .lc-fee-info-cell__side--payment .lc-fee-info-cell__value { + background: color-mix(in srgb, var(--ln-primary, #32a06e) 12%, transparent); + color: var(--ln-primary, #32a06e); +} + +.vm-page.lc-page .lc-fee-info-cell__side--payment.is-postpay .lc-fee-info-cell__value { + background: color-mix(in srgb, var(--ln-error, #e5484d) 10%, transparent); + color: var(--ln-error, #e5484d); +} + .vm-page.lc-page .lc-fee-info-cell__side--hydrogen .lc-fee-info-cell__value { background: var(--ln-surface-strong, #f1f5f9); color: var(--ln-body, #334155); @@ -1048,6 +1941,44 @@ color: var(--ln-muted, #64748b); } +.lc-delivery-change__mode-toggle { + display: inline-flex; + gap: 6px; + flex-wrap: wrap; +} + +.lc-delivery-change__mode-btn { + min-height: 28px; + padding: 0 10px; + border: 1px solid var(--ln-hairline, #e2e8f0); + border-radius: 6px; + background: #fff; + color: var(--ln-body, #334155); + font-size: 12px; + cursor: pointer; +} + +.lc-delivery-change__mode-btn.is-active { + border-color: rgba(50, 160, 110, 0.45); + background: rgba(50, 160, 110, 0.08); + color: #166534; +} + +.lc-delivery-change__readonly { + margin: 0; + padding: 8px 10px; + border-radius: 8px; + background: #f8fafc; + color: var(--ln-muted, #64748b); + font-size: 12px; + line-height: 1.45; +} + +.vm-page.lc-page .lc-delivery-cell__region--tbd, +.vm-page.lc-page .lc-delivery-cell__date--tbd { + color: var(--ln-muted, #64748b); +} + .lc-delivery-change__tbd { display: flex; align-items: center; @@ -1157,6 +2088,160 @@ font-weight: 500; } +.vm-page.lc-page .lc-signing-method-cell__btn { + display: inline-flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; + min-height: 44px; + padding: 4px 0; + border: none; + background: transparent; + text-align: left; + cursor: pointer; + border-radius: var(--ln-radius-xs, 4px); + transition: color 0.15s ease, background-color 0.15s ease; +} + +.vm-page.lc-page .lc-signing-method-cell__btn:hover, +.vm-page.lc-page .lc-signing-method-cell__btn:focus-visible { + color: var(--ln-link, var(--ln-primary, #32a06e)); + background: color-mix(in srgb, var(--ln-primary, #32a06e) 8%, transparent); + outline: none; +} + +.vm-page.lc-page .lc-signing-method-cell__btn:focus-visible { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary, #32a06e) 35%, transparent); +} + +.vm-page.lc-page .lc-signing-method-cell__label { + font-size: 13px; + font-weight: 500; + color: var(--ln-body, #1e293b); + line-height: 1.35; +} + +.vm-page.lc-page .lc-signing-method-cell__btn:hover .lc-signing-method-cell__label, +.vm-page.lc-page .lc-signing-method-cell__btn:focus-visible .lc-signing-method-cell__label { + color: var(--ln-link, var(--ln-primary, #32a06e)); + text-decoration: underline; + text-underline-offset: 2px; +} + +.vm-page.lc-page .lc-signing-method-cell__sub { + font-size: 11px; + color: var(--ln-muted, #64748b); + line-height: 1.3; + max-width: 148px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.vm-page.lc-page .lc-signing-method-cell__sub.is-pending, +.vm-page.lc-page .lc-signing-method-cell__btn.is-offline.is-pending .lc-signing-method-cell__sub { + color: #b45309; + font-weight: 500; +} + +.vm-page.lc-page .lc-signing-method-cell__btn.is-offline.is-uploaded .lc-signing-method-cell__sub, +.vm-page.lc-page .lc-signing-method-cell__btn.is-online.is-completed .lc-signing-method-cell__sub { + color: var(--ln-body, #475569); +} + +.vm-page.lc-page .lc-signing-method-cell__static { + display: inline-flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; + min-height: 44px; + padding: 4px 0; +} + +.lc-signing-method-popover-wrap .ant-popover-inner { + padding: 10px 12px; +} + +.lc-signing-method-popover { + width: min(360px, calc(100vw - 48px)); +} + +.lc-signing-method-popover__title { + font-size: 13px; + font-weight: 600; + color: var(--ln-ink, #0f172a); +} + +.lc-signing-method-popover__sub { + margin-top: 2px; + font-size: 12px; + color: var(--ln-muted, #64748b); +} + +.lc-signing-method-popover__list { + margin: 10px 0 0; + padding: 0; + list-style: none; +} + +.lc-signing-method-popover__row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + min-height: 40px; + padding: 8px 10px; + border: 1px solid var(--ln-hairline, #e2e8f0); + border-radius: 8px; + background: var(--ln-surface-strong, #f8fafc); +} + +.lc-signing-method-popover__name { + flex: 1; + min-width: 0; + font-size: 13px; + color: var(--ln-body, #334155); + line-height: 1.4; + word-break: break-all; +} + +.lc-signing-method-popover__actions { + display: inline-flex; + align-items: center; + gap: 6px; + flex-shrink: 0; +} + +.lc-signing-method-popover__action { + min-height: 28px; + padding: 0 6px; + border: none; + background: transparent; + color: var(--ln-link, var(--ln-primary, #32a06e)); + font-size: 12px; + font-weight: 500; + cursor: pointer; + border-radius: var(--ln-radius-xs, 4px); + transition: color 0.15s ease, background-color 0.15s ease; +} + +.lc-signing-method-popover__action:hover, +.lc-signing-method-popover__action:focus-visible { + color: var(--ln-primary-hover, #278a5c); + background: color-mix(in srgb, var(--ln-primary, #32a06e) 8%, transparent); + outline: none; +} + +.lc-signing-method-popover__list li + li { + margin-top: 8px; +} + +.lc-signing-method-popover__empty { + margin-top: 10px; + font-size: 12px; + color: var(--ln-muted, #64748b); +} + .vm-page.lc-page .lc-delegate-count-btn { display: inline-flex; align-items: center; @@ -1316,24 +2401,65 @@ } .vm-page.lc-page .lc-approval-status-cell { - align-items: flex-start; + display: inline-flex; + align-items: center; + gap: 6px; + line-height: 1.35; + max-width: 100%; +} + +.vm-page.lc-page .lc-approval-status-cell--inline { + flex-wrap: nowrap; +} + +.vm-page.lc-page .lc-approval-status-cell__inline-approver { + display: inline-flex; + align-items: center; + gap: 4px; + min-width: 0; +} + +.vm-page.lc-page .lc-approval-status-cell__inline-approver--multi { + cursor: help; +} + +.vm-page.lc-page .lc-approval-status-cell__inline-name { + font-size: 12px; + color: var(--ln-body, #475569); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + max-width: 5em; +} + +.vm-page.lc-page .lc-approval-status-cell__count-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + color: var(--ln-primary, #32a06e); + background: var(--ln-primary-soft, #e8f5ee); + border: 1px solid color-mix(in srgb, var(--ln-primary, #32a06e) 28%, transparent); + font-variant-numeric: tabular-nums; + flex-shrink: 0; + line-height: 1; } .vm-page.lc-page .lc-approval-status-cell__tag-wrap { display: inline-flex; + flex-shrink: 0; } .vm-page.lc-page .lc-approval-status-cell__tag-wrap--interactive { cursor: help; } -.vm-page.lc-page .lc-approval-status-cell__approver { - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - .vm-page.lc-page .lc-signing-company-col { max-width: 220px; } @@ -1429,6 +2555,51 @@ border: 1px solid color-mix(in srgb, var(--ln-primary, #32a06e) 16%, transparent); } +.lc-vehicle-popover__summary-wrap { + padding: 12px 16px 16px; + overflow: auto; +} + +.lc-vehicle-popover__summary-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +.lc-vehicle-popover__summary-table th, +.lc-vehicle-popover__summary-table td { + padding: 8px 10px; + text-align: left; + border-bottom: 1px solid var(--ln-hairline, #e2e8f0); + vertical-align: middle; +} + +.lc-vehicle-popover__summary-table th { + font-size: 12px; + font-weight: 600; + color: var(--ln-muted, #64748b); + background: color-mix(in srgb, var(--ln-surface-muted, #f8fafc) 80%, #fff); +} + +.lc-vehicle-popover__summary-table th.is-count, +.lc-vehicle-popover__summary-table td.is-count { + width: 72px; + text-align: right; + white-space: nowrap; +} + +.lc-vehicle-popover__summary-table tbody tr:last-child td { + border-bottom: none; +} + +.lc-vehicle-popover__summary-table td { + color: var(--ln-ink, #0f172a); + max-width: 160px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .lc-vehicle-popover__list { display: flex; flex-direction: column; @@ -1650,11 +2821,15 @@ } .vm-page.lc-page .lc-contract-vehicle-native-table__handover { - width: 9%; + width: 8%; +} + +.vm-page.lc-page .lc-contract-vehicle-native-table__lease-bill { + width: 7%; } .vm-page.lc-page .lc-contract-vehicle-native-table__return-settlement { - width: 9%; + width: 10%; } .vm-page.lc-page .lc-contract-vehicle-native-table__delivery-plan { @@ -1678,6 +2853,8 @@ .vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-contract-vehicle-info, .vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-contract-pickup-payment, .vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-contract-return-settlement, +.vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-approval-status-cell, +.vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-contract-lease-bill, .vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-contract-handover-situation, .vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-delivery-cell, .vm-page.lc-page .lc-contract-vehicle-native-table tbody td > .lc-contract-mileage-req-row, @@ -1704,6 +2881,18 @@ text-align: left; } +.vm-page.lc-page .lc-contract-vehicle-native-table .lc-approval-status-cell { + min-height: 0; + justify-content: flex-start; + align-items: center; + text-align: left; +} + +.vm-page.lc-page .lc-contract-vehicle-native-table .lc-approval-status-cell__tag-wrap .lc-contract-status-tag-btn { + line-height: 1; + align-items: center; +} + .vm-page.lc-page .lc-contract-vehicle-native-table .lc-contract-handover-situation--clickable, .vm-page.lc-page .lc-contract-vehicle-native-table .lc-contract-handover-situation--empty { text-align: left; @@ -1956,30 +3145,22 @@ min-height: 0; } -.vm-page.lc-page .lc-contract-return-settlement--stacked { - flex-direction: column; - align-items: flex-start; - justify-content: center; - gap: 4px; +.vm-page.lc-page .lc-approval-status-cell__tag-wrap .lc-contract-status-tag-btn { + line-height: 1; + align-items: center; +} + +.vm-page.lc-page .lc-contract-lease-bill { + display: flex; + align-items: center; + justify-content: flex-start; + min-height: 32px; +} + +.vm-page.lc-page .lc-contract-lease-bill--empty { min-height: 0; } -.vm-page.lc-page .lc-contract-return-settlement__approver { - font-size: 11px; - line-height: 1.3; - color: var(--ln-muted, #64748b); - max-width: 100%; - word-break: break-word; -} - -.vm-page.lc-page .lc-contract-vehicle-native-table .lc-contract-return-settlement--stacked { - align-items: flex-start; -} - -.vm-page.lc-page .lc-contract-vehicle-native-table .lc-contract-return-settlement__approver { - font-size: 10px; -} - .vm-page.lc-page .lc-contract-pickup-payment--empty { font-size: 13px; color: var(--ln-muted, #64748b); @@ -1987,13 +3168,14 @@ .vm-page.lc-page .lc-contract-status-tag-btn { display: inline-flex; + align-items: center; padding: 0; border: none; background: transparent; cursor: pointer; border-radius: var(--ln-radius-pill, 999px); font: inherit; - line-height: 0; + line-height: 1; } .vm-page.lc-page .lc-contract-status-tag-btn:hover { @@ -2263,6 +3445,13 @@ color: var(--ln-muted); } +.vm-page.lc-page .lc-contract-vehicle-expand__title { + padding: 4px 0 8px; + font-size: 12px; + font-weight: 600; + color: var(--ln-muted, #64748b); +} + .vm-page.lc-page .lc-vehicle-mileage-progress { display: flex; align-items: center; diff --git a/src/prototypes/ledger-shared/DESIGN.md b/src/prototypes/ledger-shared/DESIGN.md index 0f7ee13..dd12d79 100644 --- a/src/prototypes/ledger-shared/DESIGN.md +++ b/src/prototypes/ledger-shared/DESIGN.md @@ -61,7 +61,7 @@ vm-page ldb-page - 底部分页:使用 `src/common/TablePagination.tsx`,布局见 `vm-shared/DESIGN.md` - 表头排序:`ldb-th-sort`;列宽:`ldb-col-resize-handle` - 金额列:`ldb-col-money` + `tabular-nums`;**与文本列统一左对齐**(含明细弹层触发按钮 `ldb-detail-popover-trigger`) -- 操作列:`ldb-action-btn--edit` / `--delete`,右冻结 `sticky-col-right` +- 操作列:使用 `src/common/OperationActions`(详情/编辑外显 + ⋮ 更多),右冻结 `sticky-col-right`;规范见 [`vm-shared/DESIGN.md`](../vm-shared/DESIGN.md) OperationActions 章节。`ldb-action-btn--*` 仅用于表单内嵌行操作等例外场景 - 勾选列:左冻结 `sticky-col-left` - 租赁业务台账:自勾选列至「客户名称」(`LEFT_STICKY_LAST_COLUMN_KEY`)均为左冻结,「增值服务」及右侧列参与横向滚动;末列冻结列加 `sticky-col-left-last` 分隔阴影,列类名 `ldb-col-check`(全局规范见 [`vm-shared/DESIGN.md`](../vm-shared/DESIGN.md) 多选框章节) - 工具栏 / 弹窗内多选:为 `input` 添加 `vm-checkbox` 类名 diff --git a/src/prototypes/oneos-prototype-nav/annotation-source.json b/src/prototypes/oneos-prototype-nav/annotation-source.json index 847cc0b..b9a5f08 100644 --- a/src/prototypes/oneos-prototype-nav/annotation-source.json +++ b/src/prototypes/oneos-prototype-nav/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "oneos-prototype-nav", "pageId": "nav", - "updatedAt": 1783600000000, + "updatedAt": 1783800844967, "nodes": [ { "id": "opn-hero", diff --git a/src/prototypes/oneos-prototype-nav/index.tsx b/src/prototypes/oneos-prototype-nav/index.tsx index 578b1fa..d0b290e 100644 --- a/src/prototypes/oneos-prototype-nav/index.tsx +++ b/src/prototypes/oneos-prototype-nav/index.tsx @@ -30,6 +30,8 @@ import { iconForLink, loadNavSections, loadRecentUpdates, + resolvePrototypeHref, + linkHref, type NavLinkItem, type NavSection, type NavSubGroup, @@ -317,6 +319,13 @@ function ChangelogEntryRow({

{entry.summary}

+ {entry.details && entry.details.length > 0 && ( +
    + {entry.details.map((line) => ( +
  • {line}
  • + ))} +
+ )} {entry.files.length > 0 && (
@@ -402,7 +411,7 @@ function ChangelogDetailPanel({
@@ -474,7 +483,7 @@ function RecentUpdatesPanel({ onClick={() => onSelectLink({ id: item.prototypeId, title: item.title, - href: `/prototypes/${item.prototypeId}`, + href: resolvePrototypeHref(item.prototypeId), prototypeId: item.prototypeId, version: item.version, })} @@ -482,6 +491,11 @@ function RecentUpdatesPanel({ {item.title} {item.summary} + {item.details && item.details.length > 0 && ( + + {item.details[0]} + + )} {item.version} @@ -501,7 +515,21 @@ function RecentUpdatesPanel({ export default function OneosPrototypeNavApp() { const [isAuthed, setIsAuthed] = useState(readAuthSession); - const sections = useMemo(() => loadNavSections(), []); + const sections = useMemo(() => { + const loaded = loadNavSections(); + const patchLink = (link: NavLinkItem): NavLinkItem => ({ + ...link, + href: linkHref(link), + }); + return loaded.map((section) => ({ + ...section, + links: section.links.map(patchLink), + subGroups: section.subGroups.map((group) => ({ + ...group, + links: group.links.map(patchLink), + })), + })); + }, []); const recentUpdates = useMemo(() => loadRecentUpdates(), []); const registryUpdatedAt = useMemo(() => getRegistryUpdatedAt(), []); const allLinks = useMemo(() => flattenNavLinks(sections), [sections]); @@ -512,7 +540,7 @@ export default function OneosPrototypeNavApp() { const searchRef = useRef(null); const handleSelectLink = useCallback((link: NavLinkItem) => { - setSelectedLink(link); + setSelectedLink({ ...link, href: linkHref(link) }); }, []); const handleCloseDetail = useCallback(() => { diff --git a/src/prototypes/oneos-prototype-nav/nav-data.ts b/src/prototypes/oneos-prototype-nav/nav-data.ts index c9febf2..65a6a00 100644 --- a/src/prototypes/oneos-prototype-nav/nav-data.ts +++ b/src/prototypes/oneos-prototype-nav/nav-data.ts @@ -22,6 +22,31 @@ import { } from 'lucide-react'; const CURRENT_PROTOTYPE_ID = 'oneos-prototype-nav'; +const PUBLISHED_CLOUD_HOSTS = new Set(['prototype.lnoneos.com']); + +/** 对象存储静态发布站点(无 Make 本地 dev server 路由) */ +export function isPublishedCloudHost(): boolean { + if (typeof window === 'undefined') return false; + const { hostname, pathname } = window.location; + if (PUBLISHED_CLOUD_HOSTS.has(hostname)) return true; + // export-html 发布页:/{prototype}/index.html + return /\/index\.html$/u.test(pathname) && !pathname.startsWith('/prototypes/'); +} + +export function resolvePrototypeHref(prototypeId: string): string { + if (typeof window === 'undefined') return `/prototypes/${prototypeId}`; + if (isPublishedCloudHost()) { + return `${window.location.origin}/prototypes/${prototypeId}/index.html`; + } + return `/prototypes/${prototypeId}`; +} + +/** 卡片/弹窗打开时按当前环境解析链接,避免静态包内 href 写死 */ +export function linkHref(link: NavLinkItem): string { + if (link.external || /^https?:\/\//u.test(link.href)) return link.href; + if (link.href.includes('#page=')) return link.href; + return resolvePrototypeHref(link.prototypeId); +} export interface NavLinkItem { id: string; @@ -38,6 +63,8 @@ export interface PrototypeChangelogEntry { date: string; time: string; summary: string; + details?: string[]; + commit?: string; files: string[]; } diff --git a/src/prototypes/oneos-prototype-nav/nav-menu.json b/src/prototypes/oneos-prototype-nav/nav-menu.json index a5d7b84..ee0e8f0 100644 --- a/src/prototypes/oneos-prototype-nav/nav-menu.json +++ b/src/prototypes/oneos-prototype-nav/nav-menu.json @@ -1,5 +1,77 @@ { "prototypes": [ + { + "id": "item-prototypes-task-work-order", + "kind": "item", + "title": "task work order", + "itemKey": "prototypes/task-work-order" + }, + { + "id": "item-prototypes-lease-business-line-overview", + "kind": "item", + "title": "lease business line overview", + "itemKey": "prototypes/lease-business-line-overview" + }, + { + "id": "item-prototypes-oneos-web-business", + "kind": "item", + "title": "oneos web business", + "itemKey": "prototypes/oneos-web-business" + }, + { + "id": "item-prototypes-oneos-web-contract-template", + "kind": "item", + "title": "oneos web contract template", + "itemKey": "prototypes/oneos-web-contract-template" + }, + { + "id": "item-prototypes-oneos-web-data-analysis", + "kind": "item", + "title": "oneos web data analysis", + "itemKey": "prototypes/oneos-web-data-analysis" + }, + { + "id": "item-prototypes-oneos-web-finance", + "kind": "item", + "title": "oneos web finance", + "itemKey": "prototypes/oneos-web-finance" + }, + { + "id": "item-prototypes-oneos-web-h2-station", + "kind": "item", + "title": "oneos web h2 station", + "itemKey": "prototypes/oneos-web-h2-station" + }, + { + "id": "item-prototypes-oneos-web-help-center", + "kind": "item", + "title": "oneos web help center", + "itemKey": "prototypes/oneos-web-help-center" + }, + { + "id": "item-prototypes-oneos-web-lease-contract", + "kind": "item", + "title": "oneos web lease contract", + "itemKey": "prototypes/oneos-web-lease-contract" + }, + { + "id": "item-prototypes-oneos-web-ledger-data", + "kind": "item", + "title": "oneos web ledger data", + "itemKey": "prototypes/oneos-web-ledger-data" + }, + { + "id": "item-prototypes-oneos-web-ops", + "kind": "item", + "title": "oneos web ops", + "itemKey": "prototypes/oneos-web-ops" + }, + { + "id": "item-prototypes-oneos-web-procurement", + "kind": "item", + "title": "oneos web procurement", + "itemKey": "prototypes/oneos-web-procurement" + }, { "id": "item:prototypes:oneos-prototype-nav", "kind": "item", @@ -120,7 +192,7 @@ { "id": "item:prototypes:self-operated-business-ledger", "kind": "item", - "title": "物流业务台账", + "title": "物流业务明细", "itemKey": "prototypes/self-operated-business-ledger" }, { @@ -142,6 +214,12 @@ "kind": "folder", "title": "财务管理", "children": [ + { + "id": "item:prototypes:vehicle-pickup-receivable", + "kind": "item", + "title": "提车应收款", + "itemKey": "prototypes/vehicle-pickup-receivable" + }, { "id": "item:prototypes:payment-records", "kind": "item", diff --git a/src/prototypes/oneos-prototype-nav/prototype-registry.json b/src/prototypes/oneos-prototype-nav/prototype-registry.json index 10ba74f..798de55 100644 --- a/src/prototypes/oneos-prototype-nav/prototype-registry.json +++ b/src/prototypes/oneos-prototype-nav/prototype-registry.json @@ -1,25 +1,106 @@ { "version": 1, - "updatedAt": "2026-07-09T10:33:56.938Z", + "updatedAt": "2026-07-12T14:45:00.000Z", "prototypes": { "oneos-web-workbench": { "title": "工作台", - "version": "v1.0", - "revision": 0, - "lastUpdated": "2026-07-09T09:38:21.355Z", - "contentHash": "9ed67ef611b4007f6ee686b12844443d0d4019110b6c5fb8cf171482fe7a3fcd", + "version": "v1.8", + "revision": 8, + "lastUpdated": "2026-07-11T21:02:25.326Z", + "contentHash": "3d7954b2ad60057932b8a0b25e645a83ef0ff1b5fe3db37d1a25382810e29126", "trackedFiles": [ "index.tsx", "pages/01-工作台.jsx" ], - "changelog": [] + "changelog": [ + { + "version": "v1.8", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-工作台.jsx" + ] + }, + { + "version": "v1.7", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-工作台.jsx" + ] + }, + { + "version": "v1.6", + "date": "2026-07-11", + "time": "11:49", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-工作台.jsx" + ] + }, + { + "version": "v1.5", + "date": "2026-07-11", + "time": "11:49", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-工作台.jsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-09", + "time": "17:52", + "summary": "多业务台账/财务/工作台页面迭代与标注目录同步。", + "details": [], + "commit": "6ccbbef", + "files": [] + }, + { + "version": "v1.3", + "date": "2026-07-08", + "time": "11:44", + "summary": "新增车辆维修明细、还车应结款原型;车辆管理、收款记录、工作台、加氢站等同步对齐", + "details": [], + "commit": "6d14c58", + "files": [] + }, + { + "version": "v1.2", + "date": "2026-06-30", + "time": "16:54", + "summary": "页面逻辑与交互:index.tsx", + "details": [], + "commit": "0982837", + "files": [ + "index.tsx" + ] + }, + { + "version": "v1.1", + "date": "2026-06-30", + "time": "16:36", + "summary": "页面逻辑与交互:index.tsx", + "details": [], + "commit": "d71ebc5", + "files": [ + "index.tsx" + ] + } + ] }, "vehicle-management": { "title": "车辆资产", - "version": "v1.5", - "revision": 5, - "lastUpdated": "2026-07-09T09:39:46.947Z", - "contentHash": "3a73cb6460568cc5653f254cd401454f86021de2b555c07e48b0edc3550b6e93", + "version": "v1.27", + "revision": 27, + "lastUpdated": "2026-07-11T20:59:58.930Z", + "contentHash": "9b780aa62650c093877ad900ad113bf83841eb63416c5bb93fe931f6c444d9f8", "trackedFiles": [ ".spec/prototype-comments.json", ".spec/prototype-review.md", @@ -54,6 +135,7 @@ "components/KpiCards.tsx", "components/LocationMapModal.tsx", "components/MultiPlateFilterField.tsx", + "components/OnLeaseFleetSection.tsx", "components/OperateCityEditModal.tsx", "components/OperateCityField.tsx", "components/OperateCityPickerPanel.tsx", @@ -104,8 +186,288 @@ ], "changelog": [ { - "version": "v1.5", - "date": "2026-07-09", + "version": "v1.27", + "date": "2026-07-12", + "time": "04:59", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.26", + "date": "2026-07-12", + "time": "04:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.25", + "date": "2026-07-12", + "time": "00:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.24", + "date": "2026-07-12", + "time": "00:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.23", + "date": "2026-07-12", + "time": "00:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.22", + "date": "2026-07-11", + "time": "17:54", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.21", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.20", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.19", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.18", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.17", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.16", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.15", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.14", + "date": "2026-07-11", + "time": "17:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.13", + "date": "2026-07-11", "time": "17:39", "summary": "更新需求说明与标注", "files": [ @@ -124,8 +486,8 @@ ] }, { - "version": "v1.4", - "date": "2026-07-09", + "version": "v1.12", + "date": "2026-07-11", "time": "17:39", "summary": "更新需求说明与标注", "files": [ @@ -134,83 +496,242 @@ ".spec/requirements-prd-detail.md", ".spec/requirements-prd-list.md", ".spec/ui-review.md", - "annotation-source.json", "components/DateRangeCalendarPanel.tsx", "components/DateRangeFilterField.tsx", "components/DetailAccidentRecordsTab.tsx", "components/DetailAnnualReviewRecordsTab.tsx", "components/DetailBasicInfoTab.tsx", - "components/DetailExpireField.tsx" + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.11", + "date": "2026-07-11", + "time": "17:38", + "summary": "更新页面逻辑与交互", + "files": [ + "components/OnLeaseFleetSection.tsx" + ] + }, + { + "version": "v1.10", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.9", + "date": "2026-07-11", + "time": "16:49", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.8", + "date": "2026-07-11", + "time": "16:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.7", + "date": "2026-07-11", + "time": "16:41", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.6", + "date": "2026-07-11", + "time": "16:41", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.5", + "date": "2026-07-10", + "time": "23:10", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd-detail.md", + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/DateRangeCalendarPanel.tsx", + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailBasicInfoTab.tsx", + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-08", + "time": "11:44", + "summary": "页面逻辑与交互:DateRangeFilterField.tsx、DetailAccidentRecordsTab.tsx、DetailAnnualReviewRecordsTab.tsx、DetailFaultRecordsTab.tsx 等", + "details": [ + "样式与布局:style.css" + ], + "commit": "6d14c58", + "files": [ + "components/DateRangeFilterField.tsx", + "components/DetailAccidentRecordsTab.tsx", + "components/DetailAnnualReviewRecordsTab.tsx", + "components/DetailFaultRecordsTab.tsx", + "components/DetailLeaseRecordsTab.tsx", + "components/DetailMovementRecordsTab.tsx", + "components/DetailTransferRecordsTab.tsx", + "components/DetailViolationRecordsTab.tsx", + "components/FilterPickerField.tsx", + "components/MultiPlateFilterField.tsx", + "components/TablePagination.tsx", + "style.css" ] }, { "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-06", + "time": "17:18", + "summary": "页面逻辑与交互:TablePagination.tsx", + "details": [ + "样式与布局:style.css" + ], + "commit": "d0d7458", "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/requirements-prd-detail.md", - ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "annotation-source.json", - "components/DateRangeCalendarPanel.tsx", - "components/DateRangeFilterField.tsx", - "components/DetailAccidentRecordsTab.tsx", - "components/DetailAnnualReviewRecordsTab.tsx", - "components/DetailBasicInfoTab.tsx", - "components/DetailExpireField.tsx" + "components/TablePagination.tsx", + "style.css" ] }, { "version": "v1.2", - "date": "2026-07-09", - "time": "17:38", - "summary": "更新需求说明与标注", + "date": "2026-06-30", + "time": "15:27", + "summary": "页面逻辑与交互:DetailBasicInfoTab.tsx、DetailInlineEditField.tsx、DetailOperateCityInlineEdit.tsx、DetailOpsManagerInlineEdit.tsx 等", + "details": [ + "样式与布局:style.css", + "需求说明与 PRD:prototype-review.md、requirements-prd-list.md", + "演示数据与配置:china-operate-regions.json" + ], + "commit": "af29b26", "files": [ - ".spec/prototype-comments.json", ".spec/prototype-review.md", - ".spec/requirements-prd-detail.md", ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "annotation-source.json", - "components/DateRangeCalendarPanel.tsx", - "components/DateRangeFilterField.tsx", - "components/DetailAccidentRecordsTab.tsx", - "components/DetailAnnualReviewRecordsTab.tsx", "components/DetailBasicInfoTab.tsx", - "components/DetailExpireField.tsx" + "components/DetailInlineEditField.tsx", + "components/DetailOperateCityInlineEdit.tsx", + "components/DetailOpsManagerInlineEdit.tsx", + "components/FilterPanel.tsx", + "components/ImportModal.tsx", + "components/KpiCards.tsx", + "components/LocationMapModal.tsx", + "components/OperateCityEditModal.tsx", + "components/OperateCityField.tsx" ] }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:38", - "summary": "更新需求说明与标注", + "date": "2026-06-26", + "time": "15:36", + "summary": "页面逻辑与交互:DateRangeCalendarPanel.tsx、DateRangeFilterField.tsx、DetailAccidentRecordsTab.tsx、DetailAnnualReviewRecordsTab.tsx 等", + "details": [ + "样式与布局:style.css", + "需求说明与 PRD:requirements-prd-detail.md、requirements-prd-list.md、ui-review.md", + "演示数据与配置:accident-records.json、annual-review-records.json、fault-records.json、insurance-purchases.json、lease-records.json、movement-records.json、transfer-records.json、vehicle-insurance-ledger.json、vehicles.json、violation-records.json" + ], + "commit": "7ef8852", "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", ".spec/requirements-prd-detail.md", ".spec/requirements-prd-list.md", ".spec/ui-review.md", - "annotation-source.json", "components/DateRangeCalendarPanel.tsx", "components/DateRangeFilterField.tsx", "components/DetailAccidentRecordsTab.tsx", "components/DetailAnnualReviewRecordsTab.tsx", "components/DetailBasicInfoTab.tsx", - "components/DetailExpireField.tsx" + "components/DetailExpireField.tsx", + "components/DetailFaultRecordsTab.tsx", + "components/DetailInlineEditField.tsx", + "components/DetailInsuranceRecordsTab.tsx" ] } ] }, "customer-management": { "title": "客户管理", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.948Z", - "contentHash": "b39c6bd3494aa8b653ee8007b5c46eee3732e9f229a67142f6b032dec8f3e452", + "version": "v1.5", + "revision": 5, + "lastUpdated": "2026-07-11T16:16:51.651Z", + "contentHash": "d1b57a768182b120855143480ed45e1b0c3be9ef18d73fe928538b1059f0daa2", "trackedFiles": [ ".spec/requirements-prd-create.md", ".spec/requirements-prd-list.md", @@ -240,9 +761,9 @@ ], "changelog": [ { - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.5", + "date": "2026-07-12", + "time": "00:16", "summary": "更新需求说明与标注", "files": [ ".spec/requirements-prd-create.md", @@ -260,15 +781,14 @@ ] }, { - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.4", + "date": "2026-07-11", + "time": "16:47", "summary": "更新需求说明与标注", "files": [ ".spec/requirements-prd-create.md", ".spec/requirements-prd-list.md", ".spec/ui-review.md", - "annotation-source.json", "components/BatchRiskTagModal.tsx", "components/CustomerTable.tsx", "components/FilterPanel.tsx", @@ -276,27 +796,75 @@ "components/MultiSelectField.tsx", "components/RiskLabelTags.tsx", "components/RiskTagDrawer.tsx", - "components/RiskTagForm.tsx" + "components/RiskTagForm.tsx", + "components/StatusBadge.tsx" + ] + }, + { + "version": "v1.3", + "date": "2026-07-08", + "time": "11:44", + "summary": "页面逻辑与交互:FilterPanel.tsx、MultiSelectField.tsx、index.tsx", + "details": [], + "commit": "6d14c58", + "files": [ + "components/FilterPanel.tsx", + "components/MultiSelectField.tsx", + "index.tsx" + ] + }, + { + "version": "v1.2", + "date": "2026-07-01", + "time": "17:48", + "summary": "Add supplier-management prototype and enhance customer-management risk tags.", + "details": [ + "页面逻辑与交互:BatchRiskTagModal.tsx、CustomerTable.tsx、RiskLabelTags.tsx、RiskTagDrawer.tsx 等", + "样式与布局:index.css", + "需求说明与 PRD:requirements-prd-list.md、ui-review.md", + "演示数据与配置:risk-tag-operation-logs.json" + ], + "commit": "24a9fab", + "files": [ + ".spec/requirements-prd-list.md", + ".spec/ui-review.md", + "components/BatchRiskTagModal.tsx", + "components/CustomerTable.tsx", + "components/RiskLabelTags.tsx", + "components/RiskTagDrawer.tsx", + "components/RiskTagForm.tsx", + "data/risk-tag-operation-logs.json", + "index.tsx", + "pages/CreatePage.tsx", + "styles/index.css", + "types.ts" ] }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-06-30", + "time": "15:27", + "summary": "Add vehicle-h2-fee-ledger, customer-management, lease and self-operated ledgers, annotation sources, agent skills, and vite annotation runtime support. Update vehicle management, contract templates, and lease contract flows.", + "details": [ + "页面逻辑与交互:CustomerTable.tsx、FilterPanel.tsx、LabelTags.tsx、MultiSelectField.tsx 等", + "样式与布局:index.css", + "需求说明与 PRD:requirements-prd-create.md、requirements-prd-list.md", + "演示数据与配置:customers.json" + ], + "commit": "af29b26", "files": [ ".spec/requirements-prd-create.md", ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "annotation-source.json", - "components/BatchRiskTagModal.tsx", "components/CustomerTable.tsx", "components/FilterPanel.tsx", "components/LabelTags.tsx", "components/MultiSelectField.tsx", - "components/RiskLabelTags.tsx", - "components/RiskTagDrawer.tsx", - "components/RiskTagForm.tsx" + "components/StatusBadge.tsx", + "data/customers.json", + "index.tsx", + "pages/CreatePage.tsx", + "styles/index.css", + "types.ts" ] } ] @@ -305,8 +873,8 @@ "title": "供应商管理", "version": "v1.3", "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.948Z", - "contentHash": "8b1c73f0b2a7ab38a99e0d00dd2cd219a8d6b865c80e1861055d9e8ab8d3896d", + "lastUpdated": "2026-07-11T16:17:07.633Z", + "contentHash": "1ed87c36f3bb872a07efd3eb46a7bfd871b00e08c71f7758db8819af6e0318e7", "trackedFiles": [ "components/CoopStatusBadge.tsx", "components/FilterPanel.tsx", @@ -324,8 +892,8 @@ "changelog": [ { "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "date": "2026-07-12", + "time": "00:17", "summary": "更新页面样式", "files": [ "components/CoopStatusBadge.tsx", @@ -344,31 +912,30 @@ }, { "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-08", + "time": "11:44", + "summary": "页面逻辑与交互:index.tsx", + "details": [ + "样式与布局:index.css" + ], + "commit": "6d14c58", "files": [ - "annotation-source.json", - "components/CoopStatusBadge.tsx", - "components/FilterPanel.tsx", - "components/KpiCards.tsx", - "components/SupplierTable.tsx", - "data/suppliers.json", "index.tsx", - "pages/CreatePage.tsx", - "styles/index.css", - "types.ts", - "utils/address.ts", - "utils/constants.ts" + "styles/index.css" ] }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-01", + "time": "17:48", + "summary": "Add supplier-management prototype and enhance customer-management risk tags.", + "details": [ + "页面逻辑与交互:CoopStatusBadge.tsx、FilterPanel.tsx、KpiCards.tsx、SupplierTable.tsx 等", + "样式与布局:index.css", + "演示数据与配置:suppliers.json" + ], + "commit": "24a9fab", "files": [ - "annotation-source.json", "components/CoopStatusBadge.tsx", "components/FilterPanel.tsx", "components/KpiCards.tsx", @@ -379,19 +946,22 @@ "styles/index.css", "types.ts", "utils/address.ts", - "utils/constants.ts" + "utils/constants.ts", + "utils/filters.ts" ] } ] }, "lease-contract-management": { "title": "租赁合同", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.949Z", - "contentHash": "1aba6022e99a21d35920e1809d87bee7e0b161e4b83870dc579c1fd6d12e2146", + "version": "v1.787", + "revision": 787, + "lastUpdated": "2026-07-12T13:45:58.363Z", + "contentHash": "384ec56941b0af20408dcfb531f0eaaff12a95b52f1b5f352162486ae653edc0", "trackedFiles": [ ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", ".spec/requirements-prd-create.md", ".spec/requirements-prd-list.md", "contract-word-preview.js", @@ -399,11 +969,15 @@ "lease-business-org-data.js", "lease-contract-approval-rules.js", "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", "lease-contract-form-validation.js", "lease-contract-list-data.js", "lease-contract-preview-build.js", "lease-contract-risk-detect.js", + "lease-contract-signing.js", "lease-contract-vehicle-clause.js", + "lease-contract-view-data.js", + "lease-contract-view-sections.js", "lease-customer-credentials.js", "lease-order-vars.js", "LeaseContractCreate.jsx", @@ -411,18 +985,21 @@ "LeaseContractFleetSummary.jsx", "LeaseContractManagement.jsx", "LeaseContractPreviewPanel.jsx", + "LeaseContractView.jsx", "styles/index.css", "styles/lease-contract-create.css", "styles/lease-contract.css" ], "changelog": [ { - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.787", + "date": "2026-07-12", + "time": "21:45", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", ".spec/requirements-prd-create.md", ".spec/requirements-prd-list.md", "contract-word-preview.js", @@ -430,47 +1007,575 @@ "lease-business-org-data.js", "lease-contract-approval-rules.js", "lease-contract-edit-bridge.js", - "lease-contract-form-validation.js", - "lease-contract-list-data.js", - "lease-contract-preview-build.js", - "lease-contract-risk-detect.js" + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" ] }, { - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.786", + "date": "2026-07-12", + "time": "21:12", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", ".spec/requirements-prd-create.md", ".spec/requirements-prd-list.md", - "annotation-source.json", "contract-word-preview.js", "index.tsx", "lease-business-org-data.js", "lease-contract-approval-rules.js", "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.785", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.784", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.783", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.782", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.781", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.780", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.779", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.778", + "date": "2026-07-12", + "time": "21:12", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.777", + "date": "2026-07-12", + "time": "21:11", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.776", + "date": "2026-07-12", + "time": "21:11", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.775", + "date": "2026-07-12", + "time": "21:11", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js" + ] + }, + { + "version": "v1.774", + "date": "2026-07-12", + "time": "20:49", + "summary": "更新需求说明与标注", + "files": [ + ".spec/requirements-annotation-context.md", + ".spec/requirements-flow-operations.md" + ] + }, + { + "version": "v1.773", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", "lease-contract-form-validation.js", "lease-contract-list-data.js", "lease-contract-preview-build.js" ] }, { - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.772", + "date": "2026-07-12", + "time": "20:42", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", ".spec/requirements-prd-create.md", ".spec/requirements-prd-list.md", - "annotation-source.json", "contract-word-preview.js", "index.tsx", "lease-business-org-data.js", "lease-contract-approval-rules.js", "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.771", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.770", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.769", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.768", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.767", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.766", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.765", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.764", + "date": "2026-07-12", + "time": "20:42", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.763", + "date": "2026-07-12", + "time": "20:41", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.762", + "date": "2026-07-12", + "time": "20:37", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.761", + "date": "2026-07-12", + "time": "20:37", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.760", + "date": "2026-07-12", + "time": "20:37", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.759", + "date": "2026-07-12", + "time": "20:37", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", + "lease-contract-form-validation.js", + "lease-contract-list-data.js", + "lease-contract-preview-build.js" + ] + }, + { + "version": "v1.758", + "date": "2026-07-12", + "time": "20:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd-create.md", + ".spec/requirements-prd-list.md", + "contract-word-preview.js", + "index.tsx", + "lease-business-org-data.js", + "lease-contract-approval-rules.js", + "lease-contract-edit-bridge.js", + "lease-contract-flow-bridge.js", "lease-contract-form-validation.js", "lease-contract-list-data.js", "lease-contract-preview-build.js" @@ -480,10 +1585,10 @@ }, "insurance-procurement": { "title": "保险采购", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.949Z", - "contentHash": "63947f2a1d82a6925294537837fc1725d8289633c4f845f1dc33418eedcd6341", + "version": "v1.7", + "revision": 7, + "lastUpdated": "2026-07-11T21:03:06.031Z", + "contentHash": "0394aa51c3f0da383c8f1afb99537ac5c7d9f04941e488f87cb30fcd2607f872", "trackedFiles": [ ".spec/prototype-comments.json", "index.tsx", @@ -492,9 +1597,9 @@ ], "changelog": [ { - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.7", + "date": "2026-07-12", + "time": "05:03", "summary": "更新页面样式", "files": [ ".spec/prototype-comments.json", @@ -504,39 +1609,88 @@ ] }, { - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "version": "v1.6", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面样式", "files": [ ".spec/prototype-comments.json", - "annotation-source.json", "index.tsx", "InsuranceProcurement.jsx", "styles/index.css" ] }, { - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "version": "v1.5", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面样式", "files": [ ".spec/prototype-comments.json", - "annotation-source.json", "index.tsx", "InsuranceProcurement.jsx", "styles/index.css" ] + }, + { + "version": "v1.4", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "index.tsx", + "InsuranceProcurement.jsx", + "styles/index.css" + ] + }, + { + "version": "v1.3", + "date": "2026-07-01", + "time": "16:28", + "summary": "页面逻辑与交互:InsuranceProcurement.jsx、index.tsx", + "details": [ + "样式与布局:index.css" + ], + "commit": "b9dee3a", + "files": [ + "InsuranceProcurement.jsx", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.2", + "date": "2026-06-30", + "time": "15:27", + "summary": "页面逻辑与交互:InsuranceProcurement.jsx、index.tsx", + "details": [], + "commit": "af29b26", + "files": [ + "InsuranceProcurement.jsx", + "index.tsx" + ] + }, + { + "version": "v1.1", + "date": "2026-06-26", + "time": "15:36", + "summary": "页面逻辑与交互:InsuranceProcurement.jsx、index.tsx", + "details": [], + "commit": "7ef8852", + "files": [ + "InsuranceProcurement.jsx", + "index.tsx" + ] } ] }, "contract-template-management": { "title": "合同模板管理", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.950Z", - "contentHash": "3ac8d97832d281f5d671d3bf8c3a9020431ea740305821f30d9905fcf407dd43", + "version": "v1.19", + "revision": 19, + "lastUpdated": "2026-07-11T21:01:32.488Z", + "contentHash": "229d085b020d1a3286d362a604aece6ce84fa5e5509652172f1fae977030a0c7", "trackedFiles": [ ".spec/acp/conversations.json", ".spec/prototype-comments.json", @@ -566,9 +1720,9 @@ ], "changelog": [ { - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.19", + "date": "2026-07-12", + "time": "05:01", "summary": "更新需求说明与标注", "files": [ ".spec/acp/conversations.json", @@ -586,43 +1740,338 @@ ] }, { - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.18", + "date": "2026-07-12", + "time": "05:01", "summary": "更新需求说明与标注", "files": [ ".spec/acp/conversations.json", ".spec/prototype-comments.json", ".spec/prototype-review.md", ".spec/ui-review.md", - "annotation-source.json", "contract-template-catalog.js", "contract-template-docx-fidelity.js", "contract-template-section-filter.js", "contract-template-section-headings.js", "contract-template-section-html.js", "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.17", + "date": "2026-07-12", + "time": "04:22", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.16", + "date": "2026-07-12", + "time": "03:15", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.15", + "date": "2026-07-12", + "time": "00:26", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.14", + "date": "2026-07-12", + "time": "00:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.13", + "date": "2026-07-12", + "time": "00:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.12", + "date": "2026-07-11", + "time": "19:03", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.11", + "date": "2026-07-11", + "time": "17:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.10", + "date": "2026-07-11", + "time": "16:47", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.9", + "date": "2026-07-11", + "time": "16:47", + "summary": "更新需求说明与标注", + "files": [ + ".spec/acp/conversations.json", + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/ui-review.md", + "contract-template-catalog.js", + "contract-template-docx-fidelity.js", + "contract-template-section-filter.js", + "contract-template-section-headings.js", + "contract-template-section-html.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js" + ] + }, + { + "version": "v1.8", + "date": "2026-07-09", + "time": "17:52", + "summary": "页面逻辑与交互:contract-template-store.js", + "details": [], + "commit": "6ccbbef", + "files": [ "contract-template-store.js" ] }, { - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "version": "v1.7", + "date": "2026-07-08", + "time": "11:44", + "summary": "合同模板:Word 导入与 docx 预览,模板编辑增强", + "details": [ + "租赁台账增强", + "合同模板 Word 导入", + "统一全局分页与筛选规范。", + "页面逻辑与交互:ContractTemplate.jsx、contract-template-docx-fidelity.js、contract-template-section-headings.js、contract-template-seed.js 等", + "样式与布局:contract-template.css", + "需求说明与 PRD:prototype-review.md" + ], + "commit": "6d14c58", + "files": [ + ".spec/prototype-review.md", + "ContractTemplate.jsx", + "contract-template-docx-fidelity.js", + "contract-template-section-headings.js", + "contract-template-seed.js", + "contract-template-store.js", + "contract-template-types.js", + "contract-template-word-import.js", + "styles/contract-template.css" + ] + }, + { + "version": "v1.6", + "date": "2026-07-06", + "time": "17:18", + "summary": "页面逻辑与交互:contract-template-section-filter.js、contract-template-vars.js", + "details": [], + "commit": "d0d7458", + "files": [ + "contract-template-section-filter.js", + "contract-template-vars.js" + ] + }, + { + "version": "v1.5", + "date": "2026-07-01", + "time": "16:28", + "summary": "页面逻辑与交互:index.tsx", + "details": [], + "commit": "b9dee3a", + "files": [ + "index.tsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-01", + "time": "09:11", + "summary": "合同模板管理:模板数据层、章节编辑与品牌车型选择器", + "details": [ + "合同模板重构与租赁合同列表/表单增强", + "页面逻辑与交互:ContractTemplate.jsx、VehicleBrandModelPicker.jsx、contract-template-catalog.js、contract-template-section-filter.js 等", + "样式与布局:contract-template.css", + "需求说明与 PRD:prototype-review.md、ui-review.md" + ], + "commit": "2e0a3d7", "files": [ - ".spec/acp/conversations.json", - ".spec/prototype-comments.json", ".spec/prototype-review.md", ".spec/ui-review.md", - "annotation-source.json", + "ContractTemplate.jsx", + "VehicleBrandModelPicker.jsx", "contract-template-catalog.js", - "contract-template-docx-fidelity.js", "contract-template-section-filter.js", - "contract-template-section-headings.js", "contract-template-section-html.js", "contract-template-seed.js", - "contract-template-store.js" + "contract-template-store.js", + "contract-template-types.js", + "index.tsx", + "styles/contract-template.css" + ] + }, + { + "version": "v1.3", + "date": "2026-06-30", + "time": "15:27", + "summary": "页面逻辑与交互:ContractTemplate.jsx、index.tsx、trial-agreement-18t-document.js、trial-agreement-document.js", + "details": [ + "样式与布局:contract-template.css" + ], + "commit": "af29b26", + "files": [ + "ContractTemplate.jsx", + "index.tsx", + "styles/contract-template.css", + "trial-agreement-18t-document.js", + "trial-agreement-document.js" + ] + }, + { + "version": "v1.2", + "date": "2026-06-26", + "time": "17:56", + "summary": "页面逻辑与交互:contract-template-vars.js", + "details": [], + "commit": "00ca184", + "files": [ + "contract-template-vars.js" + ] + }, + { + "version": "v1.1", + "date": "2026-06-26", + "time": "15:36", + "summary": "页面逻辑与交互:ContractTemplate.jsx、contract-template-vars.js、index.tsx、v266-lease-document.js", + "details": [ + "样式与布局:contract-template.css、index.css、oneos-app.css、oneos-tokens.css" + ], + "commit": "7ef8852", + "files": [ + "ContractTemplate.jsx", + "contract-template-vars.js", + "index.tsx", + "styles/contract-template.css", + "styles/index.css", + "styles/oneos-app.css", + "styles/oneos-tokens.css", + "v266-lease-document.js" ] } ] @@ -631,7 +2080,7 @@ "title": "站点信息", "version": "v1.3", "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.951Z", + "lastUpdated": "2026-07-09T09:52:00.000Z", "contentHash": "f77326526aa62f234abdaa573a67c752f71e0874b1930daf3fb27baf0478258d", "trackedFiles": [ ".spec/prototype-comments.json", @@ -647,50 +2096,45 @@ { "version": "v1.3", "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "time": "17:52", + "summary": "需求说明与 PRD:requirements-prd-statement.md、requirements-prd.md", + "details": [], + "commit": "6ccbbef", "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/requirements-prd-kpi-balance-alert.md", - ".spec/requirements-prd-list.md", ".spec/requirements-prd-statement.md", - ".spec/requirements-prd.md", - ".spec/requirements.md", - "index.tsx" + ".spec/requirements-prd.md" ] }, { "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-08", + "time": "11:44", + "summary": "需求说明与 PRD:requirements-prd-kpi-balance-alert.md、requirements-prd-list.md、requirements-prd.md、requirements.md", + "details": [], + "commit": "6d14c58", "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", ".spec/requirements-prd-kpi-balance-alert.md", ".spec/requirements-prd-list.md", - ".spec/requirements-prd-statement.md", ".spec/requirements-prd.md", - ".spec/requirements.md", - "annotation-source.json", - "index.tsx" + ".spec/requirements.md" ] }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-02", + "time": "03:35", + "summary": "加氢站:新增站点信息独立原型,订单/记录页与共享样式调整", + "details": [ + "页面逻辑与交互:index.tsx", + "需求说明与 PRD:prototype-review.md、requirements-prd-list.md、requirements-prd-statement.md、requirements-prd.md、requirements.md" + ], + "commit": "f5e3126", "files": [ - ".spec/prototype-comments.json", ".spec/prototype-review.md", - ".spec/requirements-prd-kpi-balance-alert.md", ".spec/requirements-prd-list.md", ".spec/requirements-prd-statement.md", ".spec/requirements-prd.md", ".spec/requirements.md", - "annotation-source.json", "index.tsx" ] } @@ -698,24 +2142,354 @@ }, "business-dept-ledger": { "title": "业务部台账", - "version": "v1.0", - "revision": 0, - "lastUpdated": "2026-07-09T09:38:21.380Z", - "contentHash": "37131082dea24dac50dec3c9cd4064fa6adaf93a95a9e1bcf548de91532e77cc", + "version": "v1.27", + "revision": 27, + "lastUpdated": "2026-07-09T15:43:55.730Z", + "contentHash": "5264ccedcb133f7d49eafcf572a3f384ae819e4fcc59f89014913c14e9ccaed3", "trackedFiles": [ ".spec/prototype-comments.json", "data/excel-2026-seed.json", "index.tsx", "styles/index.css" ], - "changelog": [] + "changelog": [ + { + "version": "v1.27", + "date": "2026-07-09", + "time": "23:43", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.26", + "date": "2026-07-09", + "time": "23:42", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.25", + "date": "2026-07-09", + "time": "23:41", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.24", + "date": "2026-07-09", + "time": "23:29", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.23", + "date": "2026-07-09", + "time": "23:29", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.22", + "date": "2026-07-09", + "time": "23:26", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.21", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.20", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.19", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.18", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.17", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.16", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.15", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.14", + "date": "2026-07-09", + "time": "22:59", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.13", + "date": "2026-07-09", + "time": "22:59", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.12", + "date": "2026-07-09", + "time": "22:59", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.11", + "date": "2026-07-09", + "time": "22:58", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.10", + "date": "2026-07-09", + "time": "22:58", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.9", + "date": "2026-07-09", + "time": "22:58", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.8", + "date": "2026-07-09", + "time": "22:54", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.7", + "date": "2026-07-09", + "time": "22:45", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.6", + "date": "2026-07-09", + "time": "22:45", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.5", + "date": "2026-07-09", + "time": "22:44", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.4", + "date": "2026-07-09", + "time": "22:44", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.3", + "date": "2026-07-09", + "time": "22:44", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.2", + "date": "2026-07-09", + "time": "17:52", + "summary": "页面逻辑与交互:index.tsx", + "details": [ + "样式与布局:index.css", + "演示数据与配置:excel-2026-seed.json" + ], + "commit": "6ccbbef", + "files": [ + "data/excel-2026-seed.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.1", + "date": "2026-07-06", + "time": "17:18", + "summary": "新增业务部台账、收款记录、业务条线说明及 ledger-shared / vm-shared 公共壳层", + "details": [ + "页面逻辑与交互:index.tsx" + ], + "commit": "d0d7458", + "files": [ + "index.tsx" + ] + } + ] }, "lease-business-ledger": { "title": "租赁业务台账", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.952Z", - "contentHash": "d8e83176b5f3bf175a95da7c8665e1714830470887b45021da675d8ca92602d5", + "version": "v1.8", + "revision": 8, + "lastUpdated": "2026-07-11T16:20:27.717Z", + "contentHash": "773015ea35fa4f62c2b330e5bb7dcb099f5fac660a61c8831ea03bf00edc8d2f", "trackedFiles": [ ".spec/prototype-comments.json", ".spec/requirements-prd.md", @@ -750,9 +2524,9 @@ ], "changelog": [ { - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.8", + "date": "2026-07-12", + "time": "00:20", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", @@ -770,14 +2544,13 @@ ] }, { - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.7", + "date": "2026-07-12", + "time": "00:17", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", ".spec/requirements-prd.md", - "annotation-source.json", "columnHeaderTips.ts", "components/AccidentLinkModal.tsx", "components/ConfirmMarkModal.tsx", @@ -786,66 +2559,808 @@ "components/LeaseLedgerImportModal.tsx", "components/LeaseLedgerTable.tsx", "components/LedgerDetailPopover.tsx", - "components/LedgerKpiRow.tsx" + "components/LedgerKpiRow.tsx", + "components/LedgerLineItemsCell.tsx" + ] + }, + { + "version": "v1.6", + "date": "2026-07-12", + "time": "00:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/AccidentLinkModal.tsx", + "components/ConfirmMarkModal.tsx", + "components/DeleteConfirmModal.tsx", + "components/FilterPanel.tsx", + "components/LeaseLedgerImportModal.tsx", + "components/LeaseLedgerTable.tsx", + "components/LedgerDetailPopover.tsx", + "components/LedgerKpiRow.tsx", + "components/LedgerLineItemsCell.tsx" + ] + }, + { + "version": "v1.5", + "date": "2026-07-09", + "time": "17:52", + "summary": "页面逻辑与交互:index.tsx", + "details": [], + "commit": "6ccbbef", + "files": [ + "index.tsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-08", + "time": "11:44", + "summary": "租赁业务台账:运维费汇总、明细钻取弹层、其他收入/减免批量导入", + "details": [ + "页面逻辑与交互:columnHeaderTips.ts、AccidentLinkModal.tsx、FilterPanel.tsx、LeaseLedgerImportModal.tsx 等", + "样式与布局:index.css", + "演示数据与配置:maintenance-ledger-records.json、return-settlement-fees.json、rows.json" + ], + "commit": "6d14c58", + "files": [ + "columnHeaderTips.ts", + "components/AccidentLinkModal.tsx", + "components/FilterPanel.tsx", + "components/LeaseLedgerImportModal.tsx", + "components/LeaseLedgerTable.tsx", + "components/LedgerDetailPopover.tsx", + "components/LedgerLineItemsCell.tsx", + "components/MaintenanceLinkModal.tsx", + "components/ReceiptLinkModal.tsx", + "components/tableColumns.ts", + "data/maintenance-ledger-records.json", + "data/return-settlement-fees.json" + ] + }, + { + "version": "v1.3", + "date": "2026-07-06", + "time": "17:18", + "summary": "租赁业务台账:字段计算、宽表交互、标注 PRD 与表头说明修复", + "details": [ + "页面逻辑与交互:columnHeaderTips.ts、DeleteConfirmModal.tsx、FilterPanel.tsx、LeaseLedgerTable.tsx 等", + "样式与布局:index.css", + "需求说明与 PRD:requirements-prd.md", + "演示数据与配置:hydrogen-fees.json、maintenance-orders.json、receipts.json、rows.json" + ], + "commit": "d0d7458", + "files": [ + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DeleteConfirmModal.tsx", + "components/FilterPanel.tsx", + "components/LeaseLedgerTable.tsx", + "components/LedgerDetailPopover.tsx", + "components/LedgerLineItemsCell.tsx", + "components/OperationHistoryModal.tsx", + "components/ReceiptLinkModal.tsx", + "components/tableColumns.ts", + "data/hydrogen-fees.json", + "data/maintenance-orders.json" + ] + }, + { + "version": "v1.2", + "date": "2026-07-02", + "time": "03:35", + "summary": "租赁业务台账:空值留白、排序/列宽拖拽、操作列与交互优化", + "details": [ + "页面逻辑与交互:DeleteConfirmModal.tsx、FilterPanel.tsx、LeaseLedgerTable.tsx、tableColumns.ts 等", + "样式与布局:index.css" + ], + "commit": "f5e3126", + "files": [ + "components/DeleteConfirmModal.tsx", + "components/FilterPanel.tsx", + "components/LeaseLedgerTable.tsx", + "components/tableColumns.ts", + "index.tsx", + "styles/index.css", + "utils/ledger.ts" ] }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-06-30", + "time": "15:27", + "summary": "页面逻辑与交互:AccidentLinkModal.tsx、ConfirmMarkModal.tsx、FilterPanel.tsx、LeaseLedgerTable.tsx 等", + "details": [ + "样式与布局:index.css", + "需求说明与 PRD:requirements-prd.md", + "演示数据与配置:accident-records.json、bi-mileage.json、hydrogen-fees.json、maintenance-orders.json、receipts.json、rows.json" + ], + "commit": "af29b26", "files": [ - ".spec/prototype-comments.json", ".spec/requirements-prd.md", - "annotation-source.json", - "columnHeaderTips.ts", "components/AccidentLinkModal.tsx", "components/ConfirmMarkModal.tsx", - "components/DeleteConfirmModal.tsx", "components/FilterPanel.tsx", - "components/LeaseLedgerImportModal.tsx", "components/LeaseLedgerTable.tsx", - "components/LedgerDetailPopover.tsx", - "components/LedgerKpiRow.tsx" + "components/LedgerKpiRow.tsx", + "components/MaintenanceLinkModal.tsx", + "components/ReceiptLinkModal.tsx", + "components/tableColumns.ts", + "data/accident-records.json", + "data/bi-mileage.json", + "data/hydrogen-fees.json" ] } ] }, "lease-business-detail": { "title": "租赁业务明细", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.953Z", - "contentHash": "8d1ffcb686f2737ee637a320c34602bdb53d7f576f4a7dc1b57d56501c6f3d7d", + "version": "v1.108", + "revision": 108, + "lastUpdated": "2026-07-10T09:49:25.059Z", + "contentHash": "0f7be74931eda02bc0e3ccaeb47ac65130053fdfc2ea68d334380b8e2c30dcce", "trackedFiles": [ ".spec/prototype-comments.json", ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", "components/DetailImportModal.tsx", "components/DetailKpiRow.tsx", "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx", "components/FilterPanel.tsx", "components/tableColumns.ts", + "components/TableHeaderTip.tsx", + "data/change-log-seed.json", + "data/hydrogen-refuel-records.json", "data/rows.json", "index.tsx", "styles/index.css", "types.ts", "utils/batch-io.ts", "utils/calc-detail.ts", - "utils/detail.ts" + "utils/change-log.ts", + "utils/detail.ts", + "utils/edit-fields.ts", + "utils/field-checks.ts", + "utils/hydrogen-fee.ts", + "utils/payment-status.ts", + "utils/role.ts", + "utils/system-ref.ts" ], "changelog": [ { - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.108", + "date": "2026-07-10", + "time": "17:49", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", "components/DetailImportModal.tsx", "components/DetailKpiRow.tsx", "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx", + "components/FilterPanel.tsx" + ] + }, + { + "version": "v1.107", + "date": "2026-07-10", + "time": "17:49", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx", + "components/FilterPanel.tsx" + ] + }, + { + "version": "v1.106", + "date": "2026-07-10", + "time": "17:49", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx", + "components/FilterPanel.tsx" + ] + }, + { + "version": "v1.105", + "date": "2026-07-10", + "time": "17:49", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx", + "components/FilterPanel.tsx" + ] + }, + { + "version": "v1.104", + "date": "2026-07-10", + "time": "12:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx", + "components/FilterPanel.tsx" + ] + }, + { + "version": "v1.103", + "date": "2026-07-10", + "time": "12:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.102", + "date": "2026-07-10", + "time": "12:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.101", + "date": "2026-07-10", + "time": "12:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.100", + "date": "2026-07-10", + "time": "12:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.99", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.98", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.97", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.96", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.95", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.94", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.93", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.92", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.91", + "date": "2026-07-10", + "time": "12:20", + "summary": "更新 payment-status.ts", + "files": [ + "utils/payment-status.ts" + ] + }, + { + "version": "v1.90", + "date": "2026-07-10", + "time": "12:13", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.89", + "date": "2026-07-10", + "time": "12:13", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.88", + "date": "2026-07-10", + "time": "12:13", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.87", + "date": "2026-07-10", + "time": "12:13", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.86", + "date": "2026-07-10", + "time": "12:13", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.85", + "date": "2026-07-10", + "time": "11:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.84", + "date": "2026-07-10", + "time": "11:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.83", + "date": "2026-07-10", + "time": "11:22", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.82", + "date": "2026-07-10", + "time": "11:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.81", + "date": "2026-07-10", + "time": "11:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.80", + "date": "2026-07-10", + "time": "11:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + }, + { + "version": "v1.79", + "date": "2026-07-10", + "time": "11:21", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/prototype-review.md", + ".spec/requirements-prd.md", + "columnHeaderTips.ts", + "components/DetailChangeLogModal.tsx", + "components/DetailConfirmModal.tsx", + "components/DetailDeleteConfirmModal.tsx", + "components/DetailEditModal.tsx", + "components/DetailImportModal.tsx", + "components/DetailKpiRow.tsx", + "components/DetailTable.tsx", + "components/FieldCheckIcon.tsx" + ] + } + ] + }, + "self-operated-business-ledger": { + "title": "物流业务明细", + "version": "v1.72", + "revision": 72, + "lastUpdated": "2026-07-11T16:20:46.376Z", + "contentHash": "d4e29b38f9458eeb0d10dd3e97668af63ead0084891129cf1085af48f2ef55bc", + "trackedFiles": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts", + "utils/calc-row.ts", + "utils/ledger.ts", + "utils/vehicle-ref.ts" + ], + "changelog": [ + { + "version": "v1.72", + "date": "2026-07-12", + "time": "00:20", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", "components/tableColumns.ts", "data/rows.json", "index.tsx", @@ -855,130 +3370,582 @@ ] }, { - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.71", + "date": "2026-07-12", + "time": "00:19", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", - ".spec/prototype-review.md", - "annotation-source.json", - "components/DetailImportModal.tsx", - "components/DetailKpiRow.tsx", - "components/DetailTable.tsx", + ".spec/requirements-prd.md", "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", "components/tableColumns.ts", "data/rows.json", "index.tsx", "styles/index.css", - "types.ts" + "types.ts", + "utils/batch-io.ts" ] }, { - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.70", + "date": "2026-07-12", + "time": "00:16", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", - ".spec/prototype-review.md", - "annotation-source.json", - "components/DetailImportModal.tsx", - "components/DetailKpiRow.tsx", - "components/DetailTable.tsx", + ".spec/requirements-prd.md", "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", "components/tableColumns.ts", "data/rows.json", "index.tsx", "styles/index.css", - "types.ts" - ] - } - ] - }, - "self-operated-business-ledger": { - "title": "物流业务台账", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.953Z", - "contentHash": "5a281e005c0f35dfc29c71a482cc8c7a3e51eed59820e71b5af81ef3aad039bd", - "trackedFiles": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "components/FilterPanel.tsx", - "components/LedgerKpiRow.tsx", - "components/SelfLedgerTable.tsx", - "components/tableColumns.ts", - "data/rows.json", - "index.tsx", - "types.ts", - "utils/ledger.ts" - ], - "changelog": [ - { - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "components/FilterPanel.tsx", - "components/LedgerKpiRow.tsx", - "components/SelfLedgerTable.tsx", - "components/tableColumns.ts", - "data/rows.json", - "index.tsx", "types.ts", - "utils/ledger.ts" + "utils/batch-io.ts" ] }, { - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.69", + "date": "2026-07-10", + "time": "15:20", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", ".spec/requirements-prd.md", - "annotation-source.json", "components/FilterPanel.tsx", "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", "components/SelfLedgerTable.tsx", "components/tableColumns.ts", "data/rows.json", "index.tsx", + "styles/index.css", "types.ts", - "utils/ledger.ts" + "utils/batch-io.ts" ] }, { - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", + "version": "v1.68", + "date": "2026-07-10", + "time": "15:20", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", ".spec/requirements-prd.md", - "annotation-source.json", "components/FilterPanel.tsx", "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", "components/SelfLedgerTable.tsx", "components/tableColumns.ts", "data/rows.json", "index.tsx", + "styles/index.css", "types.ts", - "utils/ledger.ts" + "utils/batch-io.ts" + ] + }, + { + "version": "v1.67", + "date": "2026-07-10", + "time": "15:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.66", + "date": "2026-07-10", + "time": "15:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.65", + "date": "2026-07-10", + "time": "15:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.64", + "date": "2026-07-10", + "time": "15:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.63", + "date": "2026-07-10", + "time": "15:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.62", + "date": "2026-07-10", + "time": "15:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.61", + "date": "2026-07-10", + "time": "15:19", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.60", + "date": "2026-07-10", + "time": "14:54", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.59", + "date": "2026-07-10", + "time": "14:54", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.58", + "date": "2026-07-10", + "time": "14:54", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.57", + "date": "2026-07-10", + "time": "14:54", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.56", + "date": "2026-07-10", + "time": "14:54", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.55", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.54", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.53", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.52", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.51", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.50", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.49", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.48", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.47", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.46", + "date": "2026-07-10", + "time": "14:53", + "summary": "更新 vehicle-ref.ts", + "files": [ + "utils/vehicle-ref.ts" + ] + }, + { + "version": "v1.45", + "date": "2026-07-10", + "time": "14:51", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.44", + "date": "2026-07-10", + "time": "14:51", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" + ] + }, + { + "version": "v1.43", + "date": "2026-07-10", + "time": "14:50", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "components/FilterPanel.tsx", + "components/LedgerKpiRow.tsx", + "components/SelfImportModal.tsx", + "components/SelfLedgerTable.tsx", + "components/tableColumns.ts", + "data/rows.json", + "index.tsx", + "styles/index.css", + "types.ts", + "utils/batch-io.ts" ] } ] }, "vehicle-h2-fee-ledger": { "title": "车辆氢费明细", - "version": "v1.5", - "revision": 5, - "lastUpdated": "2026-07-09T10:01:49.887Z", - "contentHash": "ffe27c79a960ac86f3ecfe0ebe9d976c0a51a30c51fe2773439297e02b86dee3", + "version": "v1.7", + "revision": 7, + "lastUpdated": "2026-07-11T21:04:17.350Z", + "contentHash": "ee0d8d6dfc26c9116c41f5928dcd8c7c6b0cde7909859862024ed7a99f6e1985", "trackedFiles": [ ".spec/PRD.md", ".spec/prototype-comments.json", @@ -987,10 +3954,36 @@ "index.tsx" ], "changelog": [ + { + "version": "v1.7", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "globals.ts", + "H2LedgerPage.jsx", + "index.tsx" + ] + }, + { + "version": "v1.6", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "globals.ts", + "H2LedgerPage.jsx", + "index.tsx" + ] + }, { "version": "v1.5", - "date": "2026-07-09", - "time": "18:01", + "date": "2026-07-12", + "time": "05:04", "summary": "更新需求说明与标注", "files": [ ".spec/PRD.md", @@ -1003,55 +3996,53 @@ { "version": "v1.4", "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/PRD.md", - ".spec/prototype-comments.json", - "globals.ts", - "H2LedgerPage.jsx", - "index.tsx" - ] + "time": "18:34", + "summary": "车辆氢费明细标注目录同步至 v1.5。", + "details": [], + "commit": "824512a", + "files": [] }, { "version": "v1.3", "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "time": "17:52", + "summary": "车辆氢费明细成本单价校验增强", + "details": [ + "页面逻辑与交互:H2LedgerPage.jsx", + "需求说明与 PRD:PRD.md" + ], + "commit": "6ccbbef", "files": [ ".spec/PRD.md", - ".spec/prototype-comments.json", - "annotation-source.json", - "globals.ts", - "H2LedgerPage.jsx", - "index.tsx" + "H2LedgerPage.jsx" ] }, { "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-02", + "time": "03:35", + "summary": "车辆氢费明细对接 h2VehicleLedgerBridge;移除 vehicle-management-clay", + "details": [ + "页面逻辑与交互:H2LedgerPage.jsx、globals.ts" + ], + "commit": "f5e3126", "files": [ - ".spec/PRD.md", - ".spec/prototype-comments.json", - "annotation-source.json", - "globals.ts", "H2LedgerPage.jsx", - "index.tsx" + "globals.ts" ] }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:38", - "summary": "成本单价校验与固定/阶梯标签", + "date": "2026-06-30", + "time": "15:27", + "summary": "Add vehicle-h2-fee-ledger, customer-management, lease and self-operated ledgers, annotation sources, agent skills, and vite annotation runtime support. Update vehicle management, contract templates, and lease contract flows.", + "details": [ + "页面逻辑与交互:H2LedgerPage.jsx、globals.ts、index.tsx" + ], + "commit": "af29b26", "files": [ - ".spec/PRD.md", - ".spec/prototype-comments.json", - "annotation-source.json", - "globals.ts", "H2LedgerPage.jsx", + "globals.ts", "index.tsx" ] } @@ -1059,10 +4050,10 @@ }, "vehicle-maintenance-ledger": { "title": "维修保养台账", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.953Z", - "contentHash": "855c07b0cb6318bb8fe6c02da1846f6c797961c105a52870ad45f5cb2648618d", + "version": "v1.5", + "revision": 5, + "lastUpdated": "2026-07-11T21:03:55.548Z", + "contentHash": "d5e11ebaf8e2df28936bdd8e9a21f43af079a0f8db562d983c1144f43bfcd404", "trackedFiles": [ ".spec/prototype-comments.json", ".spec/requirements-prd.md", @@ -1072,10 +4063,38 @@ "styles/index.css" ], "changelog": [ + { + "version": "v1.5", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "globals.ts", + "index.tsx", + "MaintenanceLedgerPage.jsx", + "styles/index.css" + ] + }, + { + "version": "v1.4", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新需求说明与标注", + "files": [ + ".spec/prototype-comments.json", + ".spec/requirements-prd.md", + "globals.ts", + "index.tsx", + "MaintenanceLedgerPage.jsx", + "styles/index.css" + ] + }, { "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "date": "2026-07-12", + "time": "05:03", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", @@ -1088,13 +4107,12 @@ }, { "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "date": "2026-07-12", + "time": "00:17", "summary": "更新需求说明与标注", "files": [ ".spec/prototype-comments.json", ".spec/requirements-prd.md", - "annotation-source.json", "globals.ts", "index.tsx", "MaintenanceLedgerPage.jsx", @@ -1103,16 +4121,19 @@ }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-08", + "time": "11:44", + "summary": "页面逻辑与交互:MaintenanceLedgerPage.jsx、globals.ts、index.tsx", + "details": [ + "样式与布局:index.css", + "需求说明与 PRD:requirements-prd.md" + ], + "commit": "6d14c58", "files": [ - ".spec/prototype-comments.json", ".spec/requirements-prd.md", - "annotation-source.json", + "MaintenanceLedgerPage.jsx", "globals.ts", "index.tsx", - "MaintenanceLedgerPage.jsx", "styles/index.css" ] } @@ -1120,10 +4141,10 @@ }, "payment-records": { "title": "收款记录", - "version": "v1.3", - "revision": 3, - "lastUpdated": "2026-07-09T09:39:46.954Z", - "contentHash": "18cece26e84129ceae37b929a6a9891a55fef52c039cfcc39377342ba73087ec", + "version": "v1.7", + "revision": 7, + "lastUpdated": "2026-07-11T21:00:51.384Z", + "contentHash": "fb2974cbf5518761021be442e2e9bd949eb32fc947dd2ddd8572a153af452150", "trackedFiles": [ ".spec/prototype-comments.json", "components/AddPaymentModal.tsx", @@ -1141,10 +4162,90 @@ "utils/payment.ts" ], "changelog": [ + { + "version": "v1.7", + "date": "2026-07-12", + "time": "05:00", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "components/AddPaymentModal.tsx", + "components/BillLinkModal.tsx", + "components/FilterPanel.tsx", + "components/LinkedBillsNestedTable.tsx", + "components/PaymentKpiDetailFilterBar.tsx", + "components/PaymentKpiDetailModal.tsx", + "components/PaymentRecordsTable.tsx", + "data/bills.json", + "data/records.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.6", + "date": "2026-07-12", + "time": "00:19", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "components/AddPaymentModal.tsx", + "components/BillLinkModal.tsx", + "components/FilterPanel.tsx", + "components/LinkedBillsNestedTable.tsx", + "components/PaymentKpiDetailFilterBar.tsx", + "components/PaymentKpiDetailModal.tsx", + "components/PaymentRecordsTable.tsx", + "data/bills.json", + "data/records.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.5", + "date": "2026-07-12", + "time": "00:19", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "components/AddPaymentModal.tsx", + "components/BillLinkModal.tsx", + "components/FilterPanel.tsx", + "components/LinkedBillsNestedTable.tsx", + "components/PaymentKpiDetailFilterBar.tsx", + "components/PaymentKpiDetailModal.tsx", + "components/PaymentRecordsTable.tsx", + "data/bills.json", + "data/records.json", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.4", + "date": "2026-07-11", + "time": "17:17", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "components/AddPaymentModal.tsx", + "components/BillLinkModal.tsx", + "components/FilterPanel.tsx", + "components/LinkedBillsNestedTable.tsx", + "components/PaymentKpiDetailFilterBar.tsx", + "components/PaymentKpiDetailModal.tsx", + "components/PaymentRecordsTable.tsx", + "data/bills.json", + "data/records.json", + "index.tsx", + "styles/index.css" + ] + }, { "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "date": "2026-07-11", + "time": "17:17", "summary": "更新页面样式", "files": [ ".spec/prototype-comments.json", @@ -1163,32 +4264,40 @@ }, { "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-08", + "time": "11:44", + "summary": "新增车辆维修明细、还车应结款原型;车辆管理、收款记录、工作台、加氢站等同步对齐", + "details": [ + "页面逻辑与交互:AddPaymentModal.tsx、BillLinkModal.tsx、FilterPanel.tsx、PaymentKpiDetailModal.tsx 等", + "样式与布局:index.css", + "演示数据与配置:records.json" + ], + "commit": "6d14c58", "files": [ - ".spec/prototype-comments.json", - "annotation-source.json", "components/AddPaymentModal.tsx", "components/BillLinkModal.tsx", "components/FilterPanel.tsx", - "components/LinkedBillsNestedTable.tsx", - "components/PaymentKpiDetailFilterBar.tsx", "components/PaymentKpiDetailModal.tsx", "components/PaymentRecordsTable.tsx", - "data/bills.json", "data/records.json", - "index.tsx" + "index.tsx", + "styles/index.css", + "types.ts", + "utils/payment.ts" ] }, { "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", + "date": "2026-07-06", + "time": "17:18", + "summary": "新增业务部台账、收款记录、业务条线说明及 ledger-shared / vm-shared 公共壳层", + "details": [ + "页面逻辑与交互:AddPaymentModal.tsx、BillLinkModal.tsx、FilterPanel.tsx、LinkedBillsNestedTable.tsx 等", + "样式与布局:index.css", + "演示数据与配置:bills.json、records.json" + ], + "commit": "d0d7458", "files": [ - ".spec/prototype-comments.json", - "annotation-source.json", "components/AddPaymentModal.tsx", "components/BillLinkModal.tsx", "components/FilterPanel.tsx", @@ -1198,16 +4307,18 @@ "components/PaymentRecordsTable.tsx", "data/bills.json", "data/records.json", - "index.tsx" + "index.tsx", + "styles/index.css", + "types.ts" ] } ] }, "vehicle-return-settlement": { "title": "还车应结款", - "version": "v1.0", - "revision": 0, - "lastUpdated": "2026-07-09T09:38:21.390Z", + "version": "v1.2", + "revision": 2, + "lastUpdated": "2026-07-09T09:52:00.000Z", "contentHash": "76326ecfbf6eb1d0c27018862e6ae071efb56d674ad6e763146b5b2a0d09c86a", "trackedFiles": [ ".spec/prototype-comments.json", @@ -1216,14 +4327,44 @@ "VehicleReturnSettlementApp.jsx", "VehicleReturnSettlementHandle.jsx" ], - "changelog": [] + "changelog": [ + { + "version": "v1.2", + "date": "2026-07-09", + "time": "17:52", + "summary": "页面逻辑与交互:VehicleReturnSettlementApp.jsx、VehicleReturnSettlementHandle.jsx、index.tsx", + "details": [ + "样式与布局:index.css" + ], + "commit": "6ccbbef", + "files": [ + "VehicleReturnSettlementApp.jsx", + "VehicleReturnSettlementHandle.jsx", + "index.tsx", + "styles/index.css" + ] + }, + { + "version": "v1.1", + "date": "2026-07-08", + "time": "11:44", + "summary": "新增车辆维修明细、还车应结款原型;车辆管理、收款记录、工作台、加氢站等同步对齐", + "details": [ + "页面逻辑与交互:index.tsx" + ], + "commit": "6d14c58", + "files": [ + "index.tsx" + ] + } + ] }, "vehicle-return-settlement-v2": { "title": "还车应结款-新", - "version": "v1.0", - "revision": 0, - "lastUpdated": "2026-07-09T09:38:21.391Z", - "contentHash": "0eb68b25c6ff479c6203ec37064611e0090fbbd73dbc5d80a6236903138bd4b8", + "version": "v1.4", + "revision": 4, + "lastUpdated": "2026-07-12T08:06:00.871Z", + "contentHash": "a581b2271b6687a81a82ae72e0f6e8523991596cad86c30080f79354b65b9a63", "trackedFiles": [ ".spec/prototype-comments.json", "index.tsx", @@ -1233,14 +4374,78 @@ "VehicleReturnSettlementV2List.jsx", "vrsV2Data.js" ], - "changelog": [] + "changelog": [ + { + "version": "v1.4", + "date": "2026-07-12", + "time": "16:06", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "index.tsx", + "styles/index.css", + "VehicleReturnSettlementV2App.jsx", + "VehicleReturnSettlementV2Detail.jsx", + "VehicleReturnSettlementV2List.jsx", + "vrsV2Data.js" + ] + }, + { + "version": "v1.3", + "date": "2026-07-12", + "time": "00:20", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "index.tsx", + "styles/index.css", + "VehicleReturnSettlementV2App.jsx", + "VehicleReturnSettlementV2Detail.jsx", + "VehicleReturnSettlementV2List.jsx", + "vrsV2Data.js" + ] + }, + { + "version": "v1.2", + "date": "2026-07-11", + "time": "16:41", + "summary": "更新页面样式", + "files": [ + ".spec/prototype-comments.json", + "index.tsx", + "styles/index.css", + "VehicleReturnSettlementV2App.jsx", + "VehicleReturnSettlementV2Detail.jsx", + "VehicleReturnSettlementV2List.jsx", + "vrsV2Data.js" + ] + }, + { + "version": "v1.1", + "date": "2026-07-09", + "time": "17:52", + "summary": "页面逻辑与交互:VehicleReturnSettlementV2App.jsx、VehicleReturnSettlementV2Detail.jsx、VehicleReturnSettlementV2List.jsx、index.tsx 等", + "details": [ + "样式与布局:index.css" + ], + "commit": "6ccbbef", + "files": [ + "VehicleReturnSettlementV2App.jsx", + "VehicleReturnSettlementV2Detail.jsx", + "VehicleReturnSettlementV2List.jsx", + "index.tsx", + "styles/index.css", + "vrsV2Data.js" + ] + } + ] }, "oneos-prototype-nav": { "title": "原型导航", - "version": "v1.1", - "revision": 1, - "lastUpdated": "2026-07-09T10:33:56.745Z", - "contentHash": "564fe84bcf20fddeb53e953a70dc3eb8fe07806d767fecb8c1597c6d1ebd4b21", + "version": "v1.3", + "revision": 3, + "lastUpdated": "2026-07-12T14:45:00.000Z", + "contentHash": "b15c348c456ad6c648f89b54f97120f759be5d01d5d1a144daf4a599d8afc1b0", "trackedFiles": [ ".spec/prototype-comments.json", "index.tsx", @@ -1251,28 +4456,3024 @@ ], "changelog": [ { - "version": "v1.1", - "date": "2026-07-09", - "time": "18:31", - "summary": "VoltAgent 视觉改版、密码门禁与侧栏按业务线切换、批注体验优化", + "version": "v1.3", + "date": "2026-07-12", + "time": "22:45", + "summary": "统一标注壳与操作规范,租赁明细/合同/提车应收款增强,新增任务工单,同步合包页面", + "details": [ + "【平台】全项目接入 PrototypeAnnotationHost,PRD 迁入右侧标注目录;列表统一 OperationActions 与工具栏图标规范", + "【租赁业务明细】新增收款状态列、47 列宽表与 23 项汇总;氢费钻取、字段交叉校验、变更记录、批量导入导出与主管解锁", + "【租赁合同】KPI/状态收敛;新增查看页、线上/线下签署、交付变更、附加费用拆分、风控条款检测;与提车应收联动", + "【提车应收款】独立原型上线:自动生成/手动办理、15 日前后分档计费、与合同双向 bridge;finance 合包仅保留租赁账单", + "【任务工单】新增业管发起端:合同/独立双入口、六类任务类型、KPI 视角与督办催办(执行端后续)", + "【物流业务明细】原物流业务台账更名;列对齐 Excel、系统车型列、批量导入与自动计算", + "【车辆管理】在租车队 KPI 迁入;车型分布弹窗;详情 Tab 与列表样式优化", + "【客户/模板/保险】OperationActions 统一;模板新增先付后用/先用后付;业务部台账表格视觉增强", + "【合包】business/ops/finance/ledger-data/lease-contract/h2-station 等移除面包屑并标准化操作区;workbench 提车入口改跳独立原型", + "【下线】移除 oneos-web-login、requirements、vehicle-asset 合包;侧栏新增任务工单与提车应收款" + ], "files": [ + ".spec/prototype-comments.json", "index.tsx", - "style.css", "nav-data.ts", - ".spec/prototype-comments.json" + "style.css", + "xll-nav-menu.json", + "xll-nav-source.json" ] }, { - "version": "v1.0", + "version": "v1.2", + "date": "2026-07-09", + "time": "18:34", + "summary": "VoltAgent 视觉改版", + "details": [ + "密码门禁与侧栏按业务线切换", + "页面逻辑与交互:index.tsx、nav-data.ts", + "样式与布局:style.css" + ], + "commit": "824512a", + "files": [ + "index.tsx", + "nav-data.ts", + "style.css" + ] + }, + { + "version": "v1.1", "date": "2026-07-09", "time": "17:52", "summary": "原型导航与版本追踪上线", + "details": [ + "页面逻辑与交互:index.tsx、nav-data.ts", + "样式与布局:style.css", + "演示数据与配置:xll-nav-menu.json、xll-nav-source.json" + ], + "commit": "6ccbbef", "files": [ "index.tsx", - "style.css", "nav-data.ts", - "nav-menu.json", - "prototype-registry.json" + "style.css", + "xll-nav-menu.json", + "xll-nav-source.json" + ] + } + ] + }, + "oneos-web-data-analysis": { + "title": "oneos web data analysis", + "version": "v1.36", + "revision": 36, + "lastUpdated": "2026-07-11T21:02:03.958Z", + "contentHash": "5c6df8bcc6e82ed8de7e02a3c7dba3ef7f67ccb3b65e006107169eba5e4ad228", + "trackedFiles": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ], + "changelog": [ + { + "version": "v1.36", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.35", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.34", + "date": "2026-07-12", + "time": "00:20", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.33", + "date": "2026-07-12", + "time": "00:20", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.32", + "date": "2026-07-11", + "time": "16:57", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.31", + "date": "2026-07-09", + "time": "23:47", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.30", + "date": "2026-07-09", + "time": "23:47", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.29", + "date": "2026-07-09", + "time": "23:43", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.28", + "date": "2026-07-09", + "time": "23:26", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.27", + "date": "2026-07-09", + "time": "23:24", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.26", + "date": "2026-07-09", + "time": "23:24", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.25", + "date": "2026-07-09", + "time": "23:24", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.24", + "date": "2026-07-09", + "time": "23:23", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.23", + "date": "2026-07-09", + "time": "23:23", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.22", + "date": "2026-07-09", + "time": "23:23", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.21", + "date": "2026-07-09", + "time": "23:23", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.20", + "date": "2026-07-09", + "time": "23:23", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.19", + "date": "2026-07-09", + "time": "23:22", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.18", + "date": "2026-07-09", + "time": "23:22", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.17", + "date": "2026-07-09", + "time": "23:22", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.16", + "date": "2026-07-09", + "time": "23:22", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.15", + "date": "2026-07-09", + "time": "23:22", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.14", + "date": "2026-07-09", + "time": "23:14", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.13", + "date": "2026-07-09", + "time": "23:12", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.12", + "date": "2026-07-09", + "time": "23:12", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.11", + "date": "2026-07-09", + "time": "23:12", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.10", + "date": "2026-07-09", + "time": "23:12", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.9", + "date": "2026-07-09", + "time": "22:59", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.8", + "date": "2026-07-09", + "time": "22:58", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + }, + { + "version": "v1.7", + "date": "2026-07-09", + "time": "22:58", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-客户服务部业务统计报表.jsx", + "pages/02-物流业务台账.jsx", + "pages/03-物流业务月度统计.jsx", + "pages/04-业务部汇总台账.jsx", + "pages/05-业务部台账.jsx", + "pages/06-业务部业绩明细.jsx", + "pages/07-业务台账.jsx", + "pages/08-租赁车辆收入明细.jsx", + "pages/09-租赁客户氢费台账.jsx" + ] + } + ] + }, + "task-work-order": { + "title": "task work order", + "version": "v1.18", + "revision": 18, + "lastUpdated": "2026-07-11T16:24:15.965Z", + "contentHash": "15f17a2d566dbe11036326fbc3727d43f23bdf8a67e3400e68052f1f0eda57e0", + "trackedFiles": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ], + "changelog": [ + { + "version": "v1.18", + "date": "2026-07-12", + "time": "00:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.17", + "date": "2026-07-12", + "time": "00:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.16", + "date": "2026-07-11", + "time": "11:03", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.15", + "date": "2026-07-11", + "time": "10:31", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.14", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.13", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.12", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.11", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.10", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.9", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.8", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.7", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "components/task-work-order-kpi.jsx", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.6", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新页面逻辑与交互", + "files": [ + "components/task-work-order-kpi.jsx" + ] + }, + { + "version": "v1.5", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-11", + "time": "10:30", + "summary": "更新需求说明与标注", + "files": [ + ".spec/2026-07-10-requirements.md", + "data/task-work-order-seed.js", + "index.tsx", + "styles/index.css", + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.3", + "date": "2026-07-10", + "time": "23:10", + "summary": "更新页面逻辑与交互", + "files": [ + "TaskWorkOrderPage.jsx" + ] + }, + { + "version": "v1.2", + "date": "2026-07-10", + "time": "23:09", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx" + ] + }, + { + "version": "v1.1", + "date": "2026-07-10", + "time": "23:09", + "summary": "更新页面样式", + "files": [ + "styles/index.css" + ] + } + ] + }, + "vehicle-pickup-receivable": { + "title": "提车应收款", + "version": "v1.227", + "revision": 227, + "lastUpdated": "2026-07-12T14:34:09.897Z", + "contentHash": "cf81a91cd3cba609253f27abc6094c2c8ab63b9ab9542cec13aace0fe5db5a3e", + "trackedFiles": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ], + "changelog": [ + { + "version": "v1.227", + "date": "2026-07-12", + "time": "22:34", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.226", + "date": "2026-07-12", + "time": "22:34", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.225", + "date": "2026-07-12", + "time": "22:34", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.224", + "date": "2026-07-12", + "time": "22:33", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.223", + "date": "2026-07-12", + "time": "22:33", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.222", + "date": "2026-07-12", + "time": "22:33", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.221", + "date": "2026-07-12", + "time": "22:33", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.220", + "date": "2026-07-12", + "time": "22:28", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.219", + "date": "2026-07-12", + "time": "22:28", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.218", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.217", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.216", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.215", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.214", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.213", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.212", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.211", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.210", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.209", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.208", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.207", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.206", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.205", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.204", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.203", + "date": "2026-07-12", + "time": "22:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.202", + "date": "2026-07-12", + "time": "22:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.201", + "date": "2026-07-12", + "time": "22:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.200", + "date": "2026-07-12", + "time": "22:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.199", + "date": "2026-07-12", + "time": "22:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "version": "v1.198", + "date": "2026-07-12", + "time": "22:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + } + ] + }, + "oneos-web-finance": { + "title": "oneos web finance", + "version": "v1.66", + "revision": 66, + "lastUpdated": "2026-07-12T13:30:10.471Z", + "contentHash": "fbfde8aca3da5329961b52f23fb2f304ce1c83c7d01c9801390201f7c22e8466", + "trackedFiles": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ], + "changelog": [ + { + "version": "v1.66", + "date": "2026-07-12", + "time": "21:30", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.65", + "date": "2026-07-12", + "time": "21:30", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.64", + "date": "2026-07-12", + "time": "21:30", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.63", + "date": "2026-07-12", + "time": "21:30", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.62", + "date": "2026-07-12", + "time": "21:29", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.61", + "date": "2026-07-12", + "time": "21:29", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.60", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.59", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.58", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.57", + "date": "2026-07-12", + "time": "00:18", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.56", + "date": "2026-07-11", + "time": "17:16", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.55", + "date": "2026-07-11", + "time": "17:16", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.54", + "date": "2026-07-11", + "time": "17:16", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.53", + "date": "2026-07-11", + "time": "17:16", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.52", + "date": "2026-07-11", + "time": "17:16", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.51", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.50", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.49", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.48", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.47", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.46", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.45", + "date": "2026-07-11", + "time": "17:15", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.44", + "date": "2026-07-11", + "time": "17:10", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.43", + "date": "2026-07-11", + "time": "17:09", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.42", + "date": "2026-07-11", + "time": "17:09", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.41", + "date": "2026-07-11", + "time": "17:09", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.40", + "date": "2026-07-11", + "time": "17:09", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.39", + "date": "2026-07-11", + "time": "17:09", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.38", + "date": "2026-07-11", + "time": "17:08", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + }, + { + "version": "v1.37", + "date": "2026-07-11", + "time": "17:08", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-还车应结款-查看.jsx", + "pages/02-还车应结款-费用明细.jsx", + "pages/03-还车应结款.jsx", + "pages/04-提车收款单-编辑.jsx", + "pages/05-提车应收款-查看.jsx", + "pages/06-提车应收款-开票信息.jsx", + "pages/07-提车应收款-审核.jsx", + "pages/08-提车应收款-提车收款单.jsx", + "pages/09-提车应收款.jsx", + "pages/10-租赁账单.jsx" + ] + } + ] + }, + "lease-business-line-overview": { + "title": "lease business line overview", + "version": "v1.0", + "revision": 0, + "lastUpdated": "2026-07-11T03:48:35.931Z", + "contentHash": "5301b410eb837abfdb8fd933ed4099e33938ff422c9fb0e9a78846b97e6283ca", + "trackedFiles": [ + ".spec/prototype-comments.json", + "data-foundations.ts", + "hooks.ts", + "index.tsx", + "lines.ts", + "style.css" + ], + "changelog": [] + }, + "oneos-web-contract-template": { + "title": "oneos web contract template", + "version": "v1.3", + "revision": 3, + "lastUpdated": "2026-07-11T21:02:22.370Z", + "contentHash": "a3330ed07315a1fb54c5bc063eee96b8e4fe9e9a692654ebbfc7e1be1f6c9aab", + "trackedFiles": [ + "index.tsx", + "pages/01-合同模板管理.jsx" + ], + "changelog": [ + { + "version": "v1.3", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-合同模板管理.jsx" + ] + }, + { + "version": "v1.2", + "date": "2026-07-11", + "time": "16:47", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-合同模板管理.jsx" + ] + }, + { + "version": "v1.1", + "date": "2026-07-11", + "time": "16:44", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-合同模板管理.jsx" + ] + } + ] + }, + "oneos-web-h2-station": { + "title": "oneos web h2 station", + "version": "v1.12", + "revision": 12, + "lastUpdated": "2026-07-11T21:02:00.181Z", + "contentHash": "ef1dc5bf2f93f80d38273976fa63752796cd9f39bddb1dced1cf779960d68ff3", + "trackedFiles": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ], + "changelog": [ + { + "version": "v1.12", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.11", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.10", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.9", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.8", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.7", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.6", + "date": "2026-07-12", + "time": "00:26", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.5", + "date": "2026-07-12", + "time": "00:23", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.4", + "date": "2026-07-12", + "time": "00:20", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.3", + "date": "2026-07-12", + "time": "00:20", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.2", + "date": "2026-07-11", + "time": "16:45", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + }, + { + "version": "v1.1", + "date": "2026-07-11", + "time": "16:44", + "summary": "更新页面样式", + "files": [ + "index.tsx", + "pages/01-加氢订单.jsx", + "pages/02-加氢记录.jsx", + "pages/03-站点信息.jsx", + "styles/h2-station-vm-filter.css" + ] + } + ] + }, + "oneos-web-procurement": { + "title": "oneos web procurement", + "version": "v1.2", + "revision": 2, + "lastUpdated": "2026-07-11T21:03:08.589Z", + "contentHash": "f66bafe345eda150da4047f5ef28064454d87dccdd68b01a62d58f95bc656960", + "trackedFiles": [ + "index.tsx", + "pages/01-三方退租车管理.jsx", + "pages/02-三方退租申请-新增.jsx" + ], + "changelog": [ + { + "version": "v1.2", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-三方退租车管理.jsx", + "pages/02-三方退租申请-新增.jsx" + ] + }, + { + "version": "v1.1", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-三方退租车管理.jsx", + "pages/02-三方退租申请-新增.jsx" + ] + } + ] + }, + "oneos-web-ops": { + "title": "oneos web ops", + "version": "v1.14", + "revision": 14, + "lastUpdated": "2026-07-11T21:02:30.579Z", + "contentHash": "a58a00246936f10fd91012d54ac6b7031fcb04a1d8f432ab301ffd6fbc8b9e74", + "trackedFiles": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx", + "pages/12-调拨管理-新增.jsx", + "pages/13-调拨管理-业务提交-编辑.jsx", + "pages/14-调拨管理-运维(调拨方)-编辑.jsx", + "pages/15-调拨管理-运维(接收)-编辑.jsx", + "pages/16-调拨管理.jsx", + "pages/17-故障管理-编辑.jsx", + "pages/18-故障管理.jsx", + "pages/19-还车管理-查看.jsx", + "pages/20-还车管理-还车.jsx", + "pages/21-还车管理.jsx", + "pages/22-后装设备.jsx", + "pages/23-交车管理-编辑抽屉.jsx", + "pages/24-交车管理-交车单-编辑.jsx", + "pages/25-交车管理-交车单-查看.jsx", + "pages/26-交车管理-交车单.jsx", + "pages/27-交车管理.jsx", + "pages/28-年审管理-办理.jsx", + "pages/29-年审管理-查看.jsx", + "pages/30-年审管理.jsx", + "pages/31-上牌管理.jsx", + "pages/32-替换车管理-编辑.jsx", + "pages/33-替换车管理-查看.jsx", + "pages/34-替换车管理-新增.jsx", + "pages/35-替换车管理.jsx", + "pages/36-新增故障.jsx", + "pages/37-新增后装设备.jsx", + "pages/38-异动管理-编辑.jsx", + "pages/39-异动管理-查看.jsx", + "pages/40-异动管理-结束异动.jsx", + "pages/41-异动管理-新增.jsx", + "pages/42-异动管理.jsx", + "pages/43-证照管理-编辑.jsx", + "pages/44-证照管理.jsx", + "pages/45-停车场管理.jsx", + "pages/46-型号参数-编辑.jsx", + "pages/47-型号参数-查看.jsx", + "pages/48-型号参数-新增.jsx", + "pages/49-型号参数.jsx" + ], + "changelog": [ + { + "version": "v1.14", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.13", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.12", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.11", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.10", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.9", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.8", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.7", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.6", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.5", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.3", + "date": "2026-07-11", + "time": "16:57", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.2", + "date": "2026-07-11", + "time": "16:57", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + }, + { + "version": "v1.1", + "date": "2026-07-11", + "time": "16:57", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-备件库存.jsx", + "pages/02-仓库管理.jsx", + "pages/03-备车-编辑.jsx", + "pages/04-备车-查看.jsx", + "pages/05-备车-新增.jsx", + "pages/06-备车管理.jsx", + "pages/07-查看故障.jsx", + "pages/08-调拨管理-查看.jsx", + "pages/09-调拨管理-调拨信息.jsx", + "pages/10-调拨管理-接收信息-第三方运输.jsx", + "pages/11-调拨管理-接收信息-司机运输.jsx" + ] + } + ] + }, + "oneos-web-lease-contract": { + "title": "oneos web lease contract", + "version": "v1.1", + "revision": 1, + "lastUpdated": "2026-07-11T21:01:24.219Z", + "contentHash": "2a9d52b860d437019cf3f34650249c0afc238f46aaa672b4a90c9ad138ef03ec", + "trackedFiles": [ + "index.tsx", + "pages/01-车辆租赁合同-变更为三方合同.jsx", + "pages/02-车辆租赁合同-查看.jsx", + "pages/03-车辆租赁合同-附加费用.jsx", + "pages/04-车辆租赁合同-新增.jsx", + "pages/05-车辆租赁合同-新增车辆.jsx", + "pages/06-车辆租赁合同-续签合同.jsx", + "pages/07-车辆租赁合同-转正式合同.jsx", + "pages/08-车辆租赁合同.jsx" + ], + "changelog": [ + { + "version": "v1.1", + "date": "2026-07-12", + "time": "05:01", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-车辆租赁合同-变更为三方合同.jsx", + "pages/02-车辆租赁合同-查看.jsx", + "pages/03-车辆租赁合同-附加费用.jsx", + "pages/04-车辆租赁合同-新增.jsx", + "pages/05-车辆租赁合同-新增车辆.jsx", + "pages/06-车辆租赁合同-续签合同.jsx", + "pages/07-车辆租赁合同-转正式合同.jsx", + "pages/08-车辆租赁合同.jsx" + ] + } + ] + }, + "oneos-web-business": { + "title": "oneos web business", + "version": "v1.10", + "revision": 10, + "lastUpdated": "2026-07-11T21:03:38.838Z", + "contentHash": "81527424b7c28091f51aeaa1c530e0517eca0025fa5a79711a907e2f52edb127", + "trackedFiles": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx", + "pages/12-客户管理-新增.jsx", + "pages/13-客户管理.jsx", + "pages/14-新增交车任务.jsx", + "pages/15-租赁账单-查看.jsx", + "pages/16-租赁账单-收费明细.jsx", + "pages/17-租赁账单.jsx", + "pages/18-ETC管理.jsx" + ], + "changelog": [ + { + "version": "v1.10", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.9", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.8", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.7", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.6", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.5", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-12", + "time": "05:03", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.3", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.2", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + }, + { + "version": "v1.1", + "date": "2026-07-12", + "time": "05:02", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险采购.jsx", + "pages/02-编辑交车任务.jsx", + "pages/03-查看交车任务.jsx", + "pages/04-车辆成本维护.jsx", + "pages/05-供应商管理-编辑.jsx", + "pages/06-供应商管理-查看.jsx", + "pages/07-供应商管理-新增.jsx", + "pages/08-供应商管理.jsx", + "pages/09-交车任务.jsx", + "pages/10-客户管理-编辑.jsx", + "pages/11-客户管理-查看.jsx" + ] + } + ] + }, + "oneos-web-ledger-data": { + "title": "oneos web ledger data", + "version": "v1.6", + "revision": 6, + "lastUpdated": "2026-07-11T21:04:46.084Z", + "contentHash": "68be711a6da9e583a1ac610fac01a778716c02028a62b1fcf4a164cc22a33172", + "trackedFiles": [ + "index.tsx", + "pages/01-保险分摊明细.jsx", + "pages/02-车辆氢费明细.jsx", + "pages/03-车辆维修明细.jsx", + "pages/04-氢费采购端汇总报表.jsx", + "pages/05-租赁业务台账.jsx" + ], + "changelog": [ + { + "version": "v1.6", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险分摊明细.jsx", + "pages/02-车辆氢费明细.jsx", + "pages/03-车辆维修明细.jsx", + "pages/04-氢费采购端汇总报表.jsx", + "pages/05-租赁业务台账.jsx" + ] + }, + { + "version": "v1.5", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险分摊明细.jsx", + "pages/02-车辆氢费明细.jsx", + "pages/03-车辆维修明细.jsx", + "pages/04-氢费采购端汇总报表.jsx", + "pages/05-租赁业务台账.jsx" + ] + }, + { + "version": "v1.4", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险分摊明细.jsx", + "pages/02-车辆氢费明细.jsx", + "pages/03-车辆维修明细.jsx", + "pages/04-氢费采购端汇总报表.jsx", + "pages/05-租赁业务台账.jsx" + ] + }, + { + "version": "v1.3", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险分摊明细.jsx", + "pages/02-车辆氢费明细.jsx", + "pages/03-车辆维修明细.jsx", + "pages/04-氢费采购端汇总报表.jsx", + "pages/05-租赁业务台账.jsx" + ] + }, + { + "version": "v1.2", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险分摊明细.jsx", + "pages/02-车辆氢费明细.jsx", + "pages/03-车辆维修明细.jsx", + "pages/04-氢费采购端汇总报表.jsx", + "pages/05-租赁业务台账.jsx" + ] + }, + { + "version": "v1.1", + "date": "2026-07-12", + "time": "05:04", + "summary": "更新页面逻辑与交互", + "files": [ + "index.tsx", + "pages/01-保险分摊明细.jsx", + "pages/02-车辆氢费明细.jsx", + "pages/03-车辆维修明细.jsx", + "pages/04-氢费采购端汇总报表.jsx", + "pages/05-租赁业务台账.jsx" + ] + } + ] + }, + "ledger-shared": { + "title": "ledger-shared", + "version": "v1.1", + "revision": 1, + "lastUpdated": "2026-07-12T08:08:00.906Z", + "contentHash": "cfa97e916eb8480e2d3c62d5b7a785ae02638dfdd9ace32883a56aa66982b531", + "trackedFiles": [ + "DESIGN.md", + "styles/ledger-page.css" + ], + "changelog": [ + { + "version": "v1.1", + "date": "2026-07-12", + "time": "16:08", + "summary": "更新页面样式", + "files": [ + "DESIGN.md", + "styles/ledger-page.css" + ] + } + ] + }, + "vm-shared": { + "title": "vm-shared", + "version": "v1.2", + "revision": 2, + "lastUpdated": "2026-07-12T08:05:34.337Z", + "contentHash": "4a8d78c8ffaa1ffbf48164c0a77651919681b3e89b7b5ed700a2acf9c7f00e91", + "trackedFiles": [ + "DESIGN.md" + ], + "changelog": [ + { + "version": "v1.2", + "date": "2026-07-12", + "time": "16:05", + "summary": "更新 DESIGN.md", + "files": [ + "DESIGN.md" + ] + }, + { + "version": "v1.1", + "date": "2026-07-12", + "time": "16:05", + "summary": "更新 DESIGN.md", + "files": [ + "DESIGN.md" ] } ] @@ -1282,795 +7483,770 @@ { "prototypeId": "oneos-prototype-nav", "title": "原型导航", - "version": "v1.1", - "date": "2026-07-09", - "time": "18:31", - "summary": "VoltAgent 视觉改版、密码门禁与侧栏按业务线切换、批注体验优化", + "version": "v1.3", + "date": "2026-07-12", + "time": "22:45", + "summary": "统一标注壳与操作规范,租赁明细/合同/提车应收款增强,新增任务工单,同步合包页面", + "details": [ + "【平台】全项目接入 PrototypeAnnotationHost,PRD 迁入右侧标注目录;列表统一 OperationActions 与工具栏图标规范", + "【租赁业务明细】新增收款状态列、47 列宽表与 23 项汇总;氢费钻取、字段交叉校验、变更记录、批量导入导出与主管解锁", + "【租赁合同】KPI/状态收敛;新增查看页、线上/线下签署、交付变更、附加费用拆分、风控条款检测;与提车应收联动", + "【提车应收款】独立原型上线:自动生成/手动办理、15 日前后分档计费、与合同双向 bridge;finance 合包仅保留租赁账单", + "【任务工单】新增业管发起端:合同/独立双入口、六类任务类型、KPI 视角与督办催办(执行端后续)", + "【物流业务明细】原物流业务台账更名;列对齐 Excel、系统车型列、批量导入与自动计算", + "【车辆管理】在租车队 KPI 迁入;车型分布弹窗;详情 Tab 与列表样式优化", + "【客户/模板/保险】OperationActions 统一;模板新增先付后用/先用后付;业务部台账表格视觉增强", + "【合包】business/ops/finance/ledger-data/lease-contract/h2-station 等移除面包屑并标准化操作区;workbench 提车入口改跳独立原型", + "【下线】移除 oneos-web-login、requirements、vehicle-asset 合包;侧栏新增任务工单与提车应收款" + ], "files": [ + ".spec/prototype-comments.json", "index.tsx", - "style.css", "nav-data.ts", - ".spec/prototype-comments.json" + "style.css", + "xll-nav-menu.json", + "xll-nav-source.json" ] }, { - "prototypeId": "vehicle-h2-fee-ledger", - "title": "车辆氢费明细", - "version": "v1.5", - "date": "2026-07-09", - "time": "18:01", + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.227", + "date": "2026-07-12", + "time": "22:34", "summary": "更新需求说明与标注", "files": [ ".spec/PRD.md", ".spec/prototype-comments.json", - "globals.ts", - "H2LedgerPage.jsx", - "index.tsx" - ] - }, - { - "prototypeId": "payment-records", - "title": "收款记录", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新页面样式", - "files": [ - ".spec/prototype-comments.json", - "components/AddPaymentModal.tsx", - "components/BillLinkModal.tsx", - "components/FilterPanel.tsx", - "components/LinkedBillsNestedTable.tsx", - "components/PaymentKpiDetailFilterBar.tsx", - "components/PaymentKpiDetailModal.tsx", - "components/PaymentRecordsTable.tsx", - "data/bills.json", - "data/records.json", + "data/pickup-receivable-list-data.js", "index.tsx", - "styles/index.css" + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" ] }, { - "prototypeId": "vehicle-maintenance-ledger", - "title": "维修保养台账", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "globals.ts", - "index.tsx", - "MaintenanceLedgerPage.jsx", - "styles/index.css" - ] - }, - { - "prototypeId": "vehicle-h2-fee-ledger", - "title": "车辆氢费明细", - "version": "v1.4", - "date": "2026-07-09", - "time": "17:39", + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.226", + "date": "2026-07-12", + "time": "22:34", "summary": "更新需求说明与标注", "files": [ ".spec/PRD.md", ".spec/prototype-comments.json", - "globals.ts", - "H2LedgerPage.jsx", - "index.tsx" - ] - }, - { - "prototypeId": "self-operated-business-ledger", - "title": "物流业务台账", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "components/FilterPanel.tsx", - "components/LedgerKpiRow.tsx", - "components/SelfLedgerTable.tsx", - "components/tableColumns.ts", - "data/rows.json", - "index.tsx", - "types.ts", - "utils/ledger.ts" - ] - }, - { - "prototypeId": "lease-business-detail", - "title": "租赁业务明细", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - "components/DetailImportModal.tsx", - "components/DetailKpiRow.tsx", - "components/DetailTable.tsx", - "components/FilterPanel.tsx", - "components/tableColumns.ts", - "data/rows.json", + "data/pickup-receivable-list-data.js", "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", "styles/index.css", - "types.ts", - "utils/batch-io.ts" + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" ] }, { - "prototypeId": "lease-business-ledger", - "title": "租赁业务台账", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "columnHeaderTips.ts", - "components/AccidentLinkModal.tsx", - "components/ConfirmMarkModal.tsx", - "components/DeleteConfirmModal.tsx", - "components/FilterPanel.tsx", - "components/LeaseLedgerImportModal.tsx", - "components/LeaseLedgerTable.tsx", - "components/LedgerDetailPopover.tsx", - "components/LedgerKpiRow.tsx", - "components/LedgerLineItemsCell.tsx" - ] - }, - { - "prototypeId": "oneos-web-h2-station-site", - "title": "站点信息", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/requirements-prd-kpi-balance-alert.md", - ".spec/requirements-prd-list.md", - ".spec/requirements-prd-statement.md", - ".spec/requirements-prd.md", - ".spec/requirements.md", - "index.tsx" - ] - }, - { - "prototypeId": "contract-template-management", - "title": "合同模板管理", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/acp/conversations.json", - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/ui-review.md", - "contract-template-catalog.js", - "contract-template-docx-fidelity.js", - "contract-template-section-filter.js", - "contract-template-section-headings.js", - "contract-template-section-html.js", - "contract-template-seed.js", - "contract-template-store.js", - "contract-template-types.js" - ] - }, - { - "prototypeId": "insurance-procurement", - "title": "保险采购", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新页面样式", - "files": [ - ".spec/prototype-comments.json", - "index.tsx", - "InsuranceProcurement.jsx", - "styles/index.css" - ] - }, - { - "prototypeId": "lease-contract-management", - "title": "租赁合同", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd-create.md", - ".spec/requirements-prd-list.md", - "contract-word-preview.js", - "index.tsx", - "lease-business-org-data.js", - "lease-contract-approval-rules.js", - "lease-contract-edit-bridge.js", - "lease-contract-form-validation.js", - "lease-contract-list-data.js", - "lease-contract-preview-build.js", - "lease-contract-risk-detect.js" - ] - }, - { - "prototypeId": "supplier-management", - "title": "供应商管理", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新页面样式", - "files": [ - "components/CoopStatusBadge.tsx", - "components/FilterPanel.tsx", - "components/KpiCards.tsx", - "components/SupplierTable.tsx", - "data/suppliers.json", - "index.tsx", - "pages/CreatePage.tsx", - "styles/index.css", - "types.ts", - "utils/address.ts", - "utils/constants.ts", - "utils/filters.ts" - ] - }, - { - "prototypeId": "customer-management", - "title": "客户管理", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/requirements-prd-create.md", - ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "components/BatchRiskTagModal.tsx", - "components/CustomerTable.tsx", - "components/FilterPanel.tsx", - "components/LabelTags.tsx", - "components/MultiSelectField.tsx", - "components/RiskLabelTags.tsx", - "components/RiskTagDrawer.tsx", - "components/RiskTagForm.tsx", - "components/StatusBadge.tsx" - ] - }, - { - "prototypeId": "vehicle-management", - "title": "车辆资产", - "version": "v1.5", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/requirements-prd-detail.md", - ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "components/DateRangeCalendarPanel.tsx", - "components/DateRangeFilterField.tsx", - "components/DetailAccidentRecordsTab.tsx", - "components/DetailAnnualReviewRecordsTab.tsx", - "components/DetailBasicInfoTab.tsx", - "components/DetailExpireField.tsx", - "components/DetailFaultRecordsTab.tsx" - ] - }, - { - "prototypeId": "payment-records", - "title": "收款记录", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - "annotation-source.json", - "components/AddPaymentModal.tsx", - "components/BillLinkModal.tsx", - "components/FilterPanel.tsx", - "components/LinkedBillsNestedTable.tsx", - "components/PaymentKpiDetailFilterBar.tsx", - "components/PaymentKpiDetailModal.tsx", - "components/PaymentRecordsTable.tsx", - "data/bills.json", - "data/records.json", - "index.tsx" - ] - }, - { - "prototypeId": "vehicle-maintenance-ledger", - "title": "维修保养台账", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "annotation-source.json", - "globals.ts", - "index.tsx", - "MaintenanceLedgerPage.jsx", - "styles/index.css" - ] - }, - { - "prototypeId": "vehicle-h2-fee-ledger", - "title": "车辆氢费明细", - "version": "v1.3", - "date": "2026-07-09", - "time": "17:39", + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.225", + "date": "2026-07-12", + "time": "22:34", "summary": "更新需求说明与标注", "files": [ ".spec/PRD.md", ".spec/prototype-comments.json", - "annotation-source.json", - "globals.ts", - "H2LedgerPage.jsx", - "index.tsx" - ] - }, - { - "prototypeId": "self-operated-business-ledger", - "title": "物流业务台账", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "annotation-source.json", - "components/FilterPanel.tsx", - "components/LedgerKpiRow.tsx", - "components/SelfLedgerTable.tsx", - "components/tableColumns.ts", - "data/rows.json", - "index.tsx", - "types.ts", - "utils/ledger.ts" - ] - }, - { - "prototypeId": "lease-business-detail", - "title": "租赁业务明细", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - "annotation-source.json", - "components/DetailImportModal.tsx", - "components/DetailKpiRow.tsx", - "components/DetailTable.tsx", - "components/FilterPanel.tsx", - "components/tableColumns.ts", - "data/rows.json", + "data/pickup-receivable-list-data.js", "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", "styles/index.css", - "types.ts" + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" ] }, { - "prototypeId": "lease-business-ledger", - "title": "租赁业务台账", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "annotation-source.json", - "columnHeaderTips.ts", - "components/AccidentLinkModal.tsx", - "components/ConfirmMarkModal.tsx", - "components/DeleteConfirmModal.tsx", - "components/FilterPanel.tsx", - "components/LeaseLedgerImportModal.tsx", - "components/LeaseLedgerTable.tsx", - "components/LedgerDetailPopover.tsx", - "components/LedgerKpiRow.tsx" - ] - }, - { - "prototypeId": "oneos-web-h2-station-site", - "title": "站点信息", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/requirements-prd-kpi-balance-alert.md", - ".spec/requirements-prd-list.md", - ".spec/requirements-prd-statement.md", - ".spec/requirements-prd.md", - ".spec/requirements.md", - "annotation-source.json", - "index.tsx" - ] - }, - { - "prototypeId": "contract-template-management", - "title": "合同模板管理", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/acp/conversations.json", - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/ui-review.md", - "annotation-source.json", - "contract-template-catalog.js", - "contract-template-docx-fidelity.js", - "contract-template-section-filter.js", - "contract-template-section-headings.js", - "contract-template-section-html.js", - "contract-template-seed.js", - "contract-template-store.js" - ] - }, - { - "prototypeId": "insurance-procurement", - "title": "保险采购", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - "annotation-source.json", - "index.tsx", - "InsuranceProcurement.jsx", - "styles/index.css" - ] - }, - { - "prototypeId": "lease-contract-management", - "title": "租赁合同", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd-create.md", - ".spec/requirements-prd-list.md", - "annotation-source.json", - "contract-word-preview.js", - "index.tsx", - "lease-business-org-data.js", - "lease-contract-approval-rules.js", - "lease-contract-edit-bridge.js", - "lease-contract-form-validation.js", - "lease-contract-list-data.js", - "lease-contract-preview-build.js" - ] - }, - { - "prototypeId": "supplier-management", - "title": "供应商管理", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - "annotation-source.json", - "components/CoopStatusBadge.tsx", - "components/FilterPanel.tsx", - "components/KpiCards.tsx", - "components/SupplierTable.tsx", - "data/suppliers.json", - "index.tsx", - "pages/CreatePage.tsx", - "styles/index.css", - "types.ts", - "utils/address.ts", - "utils/constants.ts" - ] - }, - { - "prototypeId": "customer-management", - "title": "客户管理", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/requirements-prd-create.md", - ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "annotation-source.json", - "components/BatchRiskTagModal.tsx", - "components/CustomerTable.tsx", - "components/FilterPanel.tsx", - "components/LabelTags.tsx", - "components/MultiSelectField.tsx", - "components/RiskLabelTags.tsx", - "components/RiskTagDrawer.tsx", - "components/RiskTagForm.tsx" - ] - }, - { - "prototypeId": "vehicle-management", - "title": "车辆资产", - "version": "v1.4", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/requirements-prd-detail.md", - ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "annotation-source.json", - "components/DateRangeCalendarPanel.tsx", - "components/DateRangeFilterField.tsx", - "components/DetailAccidentRecordsTab.tsx", - "components/DetailAnnualReviewRecordsTab.tsx", - "components/DetailBasicInfoTab.tsx", - "components/DetailExpireField.tsx" - ] - }, - { - "prototypeId": "payment-records", - "title": "收款记录", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - "annotation-source.json", - "components/AddPaymentModal.tsx", - "components/BillLinkModal.tsx", - "components/FilterPanel.tsx", - "components/LinkedBillsNestedTable.tsx", - "components/PaymentKpiDetailFilterBar.tsx", - "components/PaymentKpiDetailModal.tsx", - "components/PaymentRecordsTable.tsx", - "data/bills.json", - "data/records.json", - "index.tsx" - ] - }, - { - "prototypeId": "vehicle-maintenance-ledger", - "title": "维修保养台账", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "annotation-source.json", - "globals.ts", - "index.tsx", - "MaintenanceLedgerPage.jsx", - "styles/index.css" - ] - }, - { - "prototypeId": "vehicle-h2-fee-ledger", - "title": "车辆氢费明细", - "version": "v1.2", - "date": "2026-07-09", - "time": "17:39", + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.224", + "date": "2026-07-12", + "time": "22:33", "summary": "更新需求说明与标注", "files": [ ".spec/PRD.md", ".spec/prototype-comments.json", - "annotation-source.json", - "globals.ts", - "H2LedgerPage.jsx", - "index.tsx" - ] - }, - { - "prototypeId": "self-operated-business-ledger", - "title": "物流业务台账", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "annotation-source.json", - "components/FilterPanel.tsx", - "components/LedgerKpiRow.tsx", - "components/SelfLedgerTable.tsx", - "components/tableColumns.ts", - "data/rows.json", - "index.tsx", - "types.ts", - "utils/ledger.ts" - ] - }, - { - "prototypeId": "lease-business-detail", - "title": "租赁业务明细", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - "annotation-source.json", - "components/DetailImportModal.tsx", - "components/DetailKpiRow.tsx", - "components/DetailTable.tsx", - "components/FilterPanel.tsx", - "components/tableColumns.ts", - "data/rows.json", + "data/pickup-receivable-list-data.js", "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", "styles/index.css", - "types.ts" + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" ] }, { - "prototypeId": "lease-business-ledger", - "title": "租赁业务台账", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.223", + "date": "2026-07-12", + "time": "22:33", "summary": "更新需求说明与标注", "files": [ + ".spec/PRD.md", ".spec/prototype-comments.json", - ".spec/requirements-prd.md", - "annotation-source.json", - "columnHeaderTips.ts", - "components/AccidentLinkModal.tsx", - "components/ConfirmMarkModal.tsx", - "components/DeleteConfirmModal.tsx", - "components/FilterPanel.tsx", - "components/LeaseLedgerImportModal.tsx", - "components/LeaseLedgerTable.tsx", - "components/LedgerDetailPopover.tsx", - "components/LedgerKpiRow.tsx" - ] - }, - { - "prototypeId": "oneos-web-h2-station-site", - "title": "站点信息", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/requirements-prd-kpi-balance-alert.md", - ".spec/requirements-prd-list.md", - ".spec/requirements-prd-statement.md", - ".spec/requirements-prd.md", - ".spec/requirements.md", - "annotation-source.json", - "index.tsx" - ] - }, - { - "prototypeId": "contract-template-management", - "title": "合同模板管理", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/acp/conversations.json", - ".spec/prototype-comments.json", - ".spec/prototype-review.md", - ".spec/ui-review.md", - "annotation-source.json", - "contract-template-catalog.js", - "contract-template-docx-fidelity.js", - "contract-template-section-filter.js", - "contract-template-section-headings.js", - "contract-template-section-html.js", - "contract-template-seed.js", - "contract-template-store.js" - ] - }, - { - "prototypeId": "insurance-procurement", - "title": "保险采购", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - "annotation-source.json", + "data/pickup-receivable-list-data.js", "index.tsx", - "InsuranceProcurement.jsx", - "styles/index.css" - ] - }, - { - "prototypeId": "lease-contract-management", - "title": "租赁合同", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - ".spec/prototype-comments.json", - ".spec/requirements-prd-create.md", - ".spec/requirements-prd-list.md", - "annotation-source.json", - "contract-word-preview.js", - "index.tsx", - "lease-business-org-data.js", - "lease-contract-approval-rules.js", - "lease-contract-edit-bridge.js", - "lease-contract-form-validation.js", - "lease-contract-list-data.js", - "lease-contract-preview-build.js" - ] - }, - { - "prototypeId": "supplier-management", - "title": "供应商管理", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", - "summary": "更新需求说明与标注", - "files": [ - "annotation-source.json", - "components/CoopStatusBadge.tsx", - "components/FilterPanel.tsx", - "components/KpiCards.tsx", - "components/SupplierTable.tsx", - "data/suppliers.json", - "index.tsx", - "pages/CreatePage.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", "styles/index.css", - "types.ts", - "utils/address.ts", - "utils/constants.ts" + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" ] }, { - "prototypeId": "customer-management", - "title": "客户管理", - "version": "v1.1", - "date": "2026-07-09", - "time": "17:39", + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.222", + "date": "2026-07-12", + "time": "22:33", "summary": "更新需求说明与标注", "files": [ - ".spec/requirements-prd-create.md", - ".spec/requirements-prd-list.md", - ".spec/ui-review.md", - "annotation-source.json", - "components/BatchRiskTagModal.tsx", - "components/CustomerTable.tsx", - "components/FilterPanel.tsx", - "components/LabelTags.tsx", - "components/MultiSelectField.tsx", - "components/RiskLabelTags.tsx", - "components/RiskTagDrawer.tsx", - "components/RiskTagForm.tsx" + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.221", + "date": "2026-07-12", + "time": "22:33", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.220", + "date": "2026-07-12", + "time": "22:28", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.219", + "date": "2026-07-12", + "time": "22:28", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.218", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.217", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.216", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.215", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.214", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.213", + "date": "2026-07-12", + "time": "22:25", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.212", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.211", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.210", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.209", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.208", + "date": "2026-07-12", + "time": "22:24", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.207", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.206", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.205", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.204", + "date": "2026-07-12", + "time": "22:18", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.203", + "date": "2026-07-12", + "time": "22:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.202", + "date": "2026-07-12", + "time": "22:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.201", + "date": "2026-07-12", + "time": "22:17", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.200", + "date": "2026-07-12", + "time": "22:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.199", + "date": "2026-07-12", + "time": "22:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.198", + "date": "2026-07-12", + "time": "22:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.197", + "date": "2026-07-12", + "time": "22:16", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.196", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.195", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.194", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.193", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.192", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.191", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.190", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" + ] + }, + { + "prototypeId": "vehicle-pickup-receivable", + "title": "提车应收款", + "version": "v1.189", + "date": "2026-07-12", + "time": "22:14", + "summary": "更新需求说明与标注", + "files": [ + ".spec/PRD.md", + ".spec/prototype-comments.json", + "data/pickup-receivable-list-data.js", + "index.tsx", + "pickup-receivable-bridge.js", + "pickup-receivable-calc.js", + "styles/index.css", + "VehiclePickupReceivableApp.jsx", + "VehiclePickupReceivableList.jsx" ] } ] diff --git a/src/prototypes/oneos-prototype-nav/style.css b/src/prototypes/oneos-prototype-nav/style.css index b4af957..ab0ad0b 100644 --- a/src/prototypes/oneos-prototype-nav/style.css +++ b/src/prototypes/oneos-prototype-nav/style.css @@ -852,6 +852,14 @@ line-height: 1.45; } +.opn-updates-detail { + display: block; + margin-top: 2px; + color: var(--opn-ink-tertiary); + font-size: 12px; + line-height: 1.4; +} + .opn-updates-side { display: flex; flex-direction: column; @@ -1184,13 +1192,25 @@ } .opn-changelog-summary { - margin: 0; - color: var(--opn-ink-muted); + margin: 0 0 8px; + color: var(--opn-ink); font-size: 14px; font-weight: 500; line-height: 1.5; } +.opn-changelog-details { + margin: 0 0 10px; + padding-left: 18px; + color: var(--opn-ink-muted); + font-size: 13px; + line-height: 1.55; +} + +.opn-changelog-details li + li { + margin-top: 4px; +} + .opn-changelog-files { margin-top: 10px; color: var(--opn-ink-subtle); diff --git a/src/prototypes/oneos-prototype-nav/xll-nav-menu.json b/src/prototypes/oneos-prototype-nav/xll-nav-menu.json index 27753ee..f161771 100644 --- a/src/prototypes/oneos-prototype-nav/xll-nav-menu.json +++ b/src/prototypes/oneos-prototype-nav/xll-nav-menu.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-07-09T09:42:05.769Z", + "updatedAt": "2026-07-12T14:43:43.194Z", "title": "小羚羚", "sectionId": "folder-prototypes-xll-miniapp", "description": "氢能车辆运营移动端原型;菜单与小羚羚「小程序」项目目录同步。", diff --git a/src/prototypes/oneos-web-business/pages/01-保险采购.jsx b/src/prototypes/oneos-web-business/pages/01-保险采购.jsx index dc34610..cabd2c9 100644 --- a/src/prototypes/oneos-web-business/pages/01-保险采购.jsx +++ b/src/prototypes/oneos-web-business/pages/01-保险采购.jsx @@ -3,6 +3,7 @@ // 模块:① 比价单(选车报价 → 保存 → 按最晚付费日临期/超期提醒 → 勾选提交采购审批) // ② 保单管理(一车一档台账,OCR/导入/逐条录入,与比价单不关联) // 与车辆管理「保险状态」联动:交强险 + 商业险均存在且在有效期内为正常,否则异常(禁止交车) +import { OperationActions } from '../../../common/OperationActions'; const { useState, useMemo, useCallback, useRef } = React; const moment = window.moment || window.dayjs; @@ -4499,37 +4500,20 @@ const Component = function () { { title: '操作', key: 'action', - width: 188, + width: 168, fixed: 'right', render: (_, record) => ( -
- - - - { - domEvent.stopPropagation(); - handlePolicyMoreMenuClick(record, key); - }, - }} - > - - e.stopPropagation()} - > - {ICONS.more} - - - -
+ openVehicleInsHistoryEdit(record) }} + more={[ + { key: 'preview', label: '预览', onClick: () => handleInsuranceRecordPreview(record) }, + { key: 'download', label: '下载', onClick: () => handleInsuranceRecordDownload(record) }, + ...getPolicyMoreMenuItems(record).map((item) => ({ + ...item, + onClick: item.key ? () => handlePolicyMoreMenuClick(record, item.key) : undefined, + })), + ]} + /> ), }, ]; @@ -6002,33 +5986,42 @@ const Component = function () { { title: '操作', key: 'action', - width: 96, + width: 168, fixed: 'right', onHeaderCell: () => ({ className: 'lc-compare-th-edit' }), render: (_, row) => ( -
- { - setCopyPopoverRowId(open ? row.id : null); - if (!open) setCopyCountDraft(1); - }} - content={( -
-
复制条数
- setCopyCountDraft(v || 1)} style={{ width: '100%' }} /> -
- - -
+ { + setCopyPopoverRowId(open ? row.id : null); + if (!open) setCopyCountDraft(1); + }} + content={( +
+
复制条数
+ setCopyCountDraft(v || 1)} style={{ width: '100%' }} /> +
+ +
- )} - > - - - -
+
+ )} + > + { + setCopyPopoverRowId(row.id); + setCopyCountDraft(1); + }, + }, + { key: 'delete', label: '删除', danger: true, onClick: () => handleDeleteCompareRow(row.id) }, + ]} + /> +
), }, ]; @@ -6564,18 +6557,14 @@ const Component = function () { { title: '操作', key: 'action', - width: 64, + width: 168, + fixed: 'right', onHeaderCell: listColumnHeaderCell, render: (record) => ( - - ), + openVehicleInsuranceMgmt(record) }]} + /> + ) }, ]; @@ -7747,17 +7736,13 @@ const Component = function () { { title: '操作', key: 'action', - width: 120, + width: 168, fixed: 'right', render: (_, record) => ( - - - - + openCompareEditor(record) }} + more={[{ key: 'delete', label: '删除', danger: true, onClick: () => handleDeleteCompareSheet(record) }]} + /> ), }, ]} @@ -8197,31 +8182,30 @@ const Component = function () { { title: '操作', key: 'action', - width: 120, + width: 168, fixed: 'right', render: (_, record) => { const recognizing = isPolicyRecognTaskRecognizing(record); const noSuccess = !(record.recognSuccessCount > 0); const disabled = recognizing || noSuccess; - const btn = ( - + const action = ( + openPolicyRecognTaskRecord(record), + }]} + /> ); if (recognizing) { return ( - {btn} + {action} ); } - return btn; + return action; }, }, ]} diff --git a/src/prototypes/oneos-web-business/pages/02-编辑交车任务.jsx b/src/prototypes/oneos-web-business/pages/02-编辑交车任务.jsx index eb51dbe..b1ac67e 100644 --- a/src/prototypes/oneos-web-business/pages/02-编辑交车任务.jsx +++ b/src/prototypes/oneos-web-business/pages/02-编辑交车任务.jsx @@ -311,14 +311,7 @@ const Component = function() { } return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, - React.createElement('span', null, '业务管理'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', null, '交车任务'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { style: { color: '#1890ff' } }, '编辑交车任务')), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { reqModalOpen[1](true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { reqModalOpen[1](true); } }, '查看需求说明')), React.createElement('div', { style: styles.card }, React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')), React.createElement('div', { style: styles.cardBody }, diff --git a/src/prototypes/oneos-web-business/pages/03-查看交车任务.jsx b/src/prototypes/oneos-web-business/pages/03-查看交车任务.jsx index 86e06a5..1701409 100644 --- a/src/prototypes/oneos-web-business/pages/03-查看交车任务.jsx +++ b/src/prototypes/oneos-web-business/pages/03-查看交车任务.jsx @@ -347,14 +347,7 @@ const Component = function () { '5.页面底部为「返回」按钮。'; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, - React.createElement('span', null, '业务管理'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', null, '交车任务'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { style: { color: '#1890ff' } }, '查看交车任务')), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqModalOpen[1](true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqModalOpen[1](true); } }, '查看需求说明')), React.createElement('div', { style: styles.card }, React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')), React.createElement('div', { style: styles.cardBody }, diff --git a/src/prototypes/oneos-web-business/pages/04-车辆成本维护.jsx b/src/prototypes/oneos-web-business/pages/04-车辆成本维护.jsx index 551036e..764ed7a 100644 --- a/src/prototypes/oneos-web-business/pages/04-车辆成本维护.jsx +++ b/src/prototypes/oneos-web-business/pages/04-车辆成本维护.jsx @@ -1,11 +1,11 @@ // 【重要】必须使用 const Component 作为组件变量名 // 业务管理 - 车辆成本维护(2026年3月12日版本) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -129,24 +129,24 @@ const Component = function () { { title: '操作', key: 'action', - width: 140, + width: 168, render: function (_, record) { var isEditing = editingKey === record.id; if (isEditing) { - return React.createElement(Space, { size: 'small' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { saveRow(record.id); } }, '保存'), - React.createElement(Button, { type: 'link', size: 'small', onClick: cancelEdit }, '取消') - ); + return React.createElement(OperationActions, { + more: [ + { key: 'save', label: '保存', onClick: function () { saveRow(record.id); } }, + { key: 'cancel', label: '取消', onClick: cancelEdit } + ] + }); } - return React.createElement(Button, { - type: 'link', - size: 'small', - onClick: function () { + return React.createElement(OperationActions, { + edit: { onClick: function () { setEditingKey(record.id); setEditRental(record.rentalCostPerDay != null ? String(record.rentalCostPerDay) : ''); setEditSelfOperated(record.selfOperatedCostPerDay != null ? String(record.selfOperatedCostPerDay) : ''); - } - }, '编辑'); + } } + }); } } ]; @@ -154,14 +154,7 @@ const Component = function () { var requirementContent = '车辆成本维护(2026年3月12日版本)\n一个「数字化资产ONEOS运管平台」中的「车辆成本维护」模块\n#面包屑:业务管理-车辆成本维护;\n\n页面为1个卡片:车辆成本列表。列表默认显示当前系统中所有品牌和型号记录,如型号参数表有新增,则该处同步新增一条数据;\n1.品牌:显示车辆品牌;\n2.型号:显示车辆型号(如18吨双飞翼,而不是公告型号);\n3.租赁车辆日成本:显示格式为:xx.xx元,支持两位小数;\n4.自营车辆日成本:显示格式为:xx.xx元,支持两位小数;\n5.操作:编辑;'; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '车辆成本维护' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '车辆成本列表', style: cardStyle }, React.createElement(Table, { diff --git a/src/prototypes/oneos-web-business/pages/05-供应商管理-编辑.jsx b/src/prototypes/oneos-web-business/pages/05-供应商管理-编辑.jsx index 80cc4fc..7880006 100644 --- a/src/prototypes/oneos-web-business/pages/05-供应商管理-编辑.jsx +++ b/src/prototypes/oneos-web-business/pages/05-供应商管理-编辑.jsx @@ -4,7 +4,6 @@ const Component = function () { var useState = React.useState; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -257,16 +256,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '供应商管理' }, - { title: '编辑', className: 'breadcrumb-last' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('供应商基本信息'), style: cardStyle }, basicFields), + React.createElement(Card, { title: CardTitleWithBar('供应商基本信息'), style: cardStyle }, basicFields), React.createElement(Card, { title: CardTitleWithBar('供应商开票信息'), style: cardStyle }, invoiceFields), React.createElement('div', { style: { height: 60 } }), React.createElement('div', { style: footerStyle }, diff --git a/src/prototypes/oneos-web-business/pages/06-供应商管理-查看.jsx b/src/prototypes/oneos-web-business/pages/06-供应商管理-查看.jsx index c4eb638..10e19ca 100644 --- a/src/prototypes/oneos-web-business/pages/06-供应商管理-查看.jsx +++ b/src/prototypes/oneos-web-business/pages/06-供应商管理-查看.jsx @@ -3,7 +3,6 @@ const Component = function () { var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -170,16 +169,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '供应商管理' }, - { title: '查看', className: 'breadcrumb-last' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('供应商基本信息'), style: cardStyle }, basicFields), + React.createElement(Card, { title: CardTitleWithBar('供应商基本信息'), style: cardStyle }, basicFields), React.createElement(Card, { title: CardTitleWithBar('供应商开票信息'), style: cardStyle }, invoiceFields), React.createElement('div', { style: { height: 60 } }), React.createElement('div', { style: footerStyle }, diff --git a/src/prototypes/oneos-web-business/pages/07-供应商管理-新增.jsx b/src/prototypes/oneos-web-business/pages/07-供应商管理-新增.jsx index b992d4c..b676ce8 100644 --- a/src/prototypes/oneos-web-business/pages/07-供应商管理-新增.jsx +++ b/src/prototypes/oneos-web-business/pages/07-供应商管理-新增.jsx @@ -4,7 +4,6 @@ const Component = function () { var useState = React.useState; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -234,16 +233,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '供应商管理' }, - { title: '新增供应商', className: 'breadcrumb-last' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('供应商基本信息'), style: cardStyle }, basicFields), + React.createElement(Card, { title: CardTitleWithBar('供应商基本信息'), style: cardStyle }, basicFields), React.createElement(Card, { title: CardTitleWithBar('供应商开票信息'), style: cardStyle }, invoiceFields), React.createElement('div', { style: { height: 60 } }), React.createElement('div', { style: footerStyle }, diff --git a/src/prototypes/oneos-web-business/pages/08-供应商管理.jsx b/src/prototypes/oneos-web-business/pages/08-供应商管理.jsx index fd6bba8..7293db3 100644 --- a/src/prototypes/oneos-web-business/pages/08-供应商管理.jsx +++ b/src/prototypes/oneos-web-business/pages/08-供应商管理.jsx @@ -1,12 +1,12 @@ // 【重要】必须使用 const Component 作为组件变量名 // 业务管理 - 供应商管理(2026年3月12日版本) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -162,14 +162,14 @@ const Component = function () { { title: '操作', key: 'action', - width: 180, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement(Space, { size: 'small' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goEdit(record); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { openDeleteConfirm(record); } }, '删除') - ); + return React.createElement(OperationActions, { + view: { onClick: function () { goView(record); } }, + edit: { onClick: function () { goEdit(record); } }, + more: [{ key: 'delete', label: '删除', danger: true, onClick: function () { openDeleteConfirm(record); } }] + }); } } ]; @@ -253,14 +253,7 @@ const Component = function () { var requirementContent = '供应商管理(2026年3月12日版本)\n一个「数字化资产ONEOS运管平台」中的「供应商管理」模块\n#面包屑:业务管理-供应商管理;\n\n页面分为2个卡片;\n1.筛选:默认显示首行,可通过展开、收起功能进行展开/收起的切换,右侧为重置、查询按钮;\n#支持供应商编码、合作状态、类型、区域、城市、创建人等筛选方式;\n1.1.供应商编码:选择器,支持输入框内输入内容模糊搜索,下拉显示对应选项;\n1.2.合作状态:选择器,支持多选,选项为已合作、终止合作、洽谈中、合约过期;\n1.3.类型:选择器,支持多选,选项为:备件供应商、保险公司、加氢站、充电站、维修站、救援车队、整车厂、其他;\n1.4.区域:选择器,支持多选,选项为:华北、华东、华南、华中、东北、西南、西北;\n1.4.城市:地区级联选择器,支持2级选择,格式为省-市;\n1.5.创建人:选择器,选项为所有创建用户姓名;\n\n2.供应商列表:\n#左上方为供应商名称输入框(输入供应商名称直接联动),右上角为新增、导出、导入;\n2.1.供应商编码:显示供应商编码;\n2.2.合作状态:显示合作状态,选项为已合作、终止合作、洽谈中、合约过期;\n2.3.供应商名称:显示供应商名称;\n2.4.类型:备件供应商、保险公司、加氢站、充电站、维修站、救援车队、整车厂、其他;\n2.4.区域:显示供应商区域,由选择城市后自动匹配;\n2.5.城市:显示供应商城市,格式为:省-市;\n2.6.信用代码/身份证:显示供应商信用代码或身份证,取决于新增时输入的是什么;\n2.7.联系人:显示供应商联系人;\n2.8.联系人手机:显示供应商联系人手机;\n2.9.联系人座机:显示供应商联系人座机,无则显示为-;\n2.10.供应商地址:显示供应商地址信息;\n2.11.电子邮箱:显示供应商电子邮箱;\n2.12.备注:显示供应商备注信息;\n2.13.创建人:显示供应商创建人用户姓名;\n2.14.创建时间:显示供应商创建时间;\n2.15.操作:查看、编辑、删除;\n 2.16.1.查看:跳转供应商管理-查看页;\n 2.16.2.编辑:跳转供应商管理-编辑页;\n 2.16.3.删除:二次确认,点击确认后删除,已删除供应商无法在各类合同或账单选择时选择,但历史数据中该供应商信息依然显示;'; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '供应商管理' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '筛选', style: cardStyle }, React.createElement('div', { diff --git a/src/prototypes/oneos-web-business/pages/09-交车任务.jsx b/src/prototypes/oneos-web-business/pages/09-交车任务.jsx index 190771d..1cf6b1f 100644 --- a/src/prototypes/oneos-web-business/pages/09-交车任务.jsx +++ b/src/prototypes/oneos-web-business/pages/09-交车任务.jsx @@ -1,12 +1,12 @@ // 【重要】必须使用 const Component 作为组件变量名 // 数字化资产ONEOS运管平台 - 业务管理 - 交车任务 +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Select = antd.Select; @@ -395,14 +395,14 @@ const Component = function () { { title: '操作', key: 'action', - width: 200, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement(Space, { size: 'small' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goEdit(record); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { toggleEnable(record); } }, record.enabled ? '挂起' : '激活') - ); + return React.createElement(OperationActions, { + view: { onClick: function () { goView(record); } }, + edit: { onClick: function () { goEdit(record); } }, + more: [{ key: 'toggle', label: record.enabled ? '挂起' : '激活', onClick: function () { toggleEnable(record); } }] + }); } } ]; @@ -472,9 +472,7 @@ const Component = function () { "4.列表右下方为分页功能,支持单页显示条目选择;"; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '业务管理' }, { title: '交车任务' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqSpecOpen[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqSpecOpen[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '筛选', style: cardStyle }, React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, @@ -630,7 +628,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Select = antd.Select; @@ -1026,10 +1023,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '业务管理' }, { title: '交车任务' }] }) - ), - React.createElement(Card, { title: '筛选', style: cardStyle }, + React.createElement(Card, { title: '筛选', style: cardStyle }, React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, React.createElement('div', { style: filterItemStyle }, React.createElement('div', { style: filterLabelStyle }, '交车任务编码'), @@ -1123,7 +1117,6 @@ const Component = function() { var useState = React.useState; var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Select = antd.Select; var Button = antd.Button; var Table = antd.Table; @@ -1598,14 +1591,7 @@ const Component = function() { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '交车任务' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { _reqSpecOpen[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { _reqSpecOpen[1](true); } }, '查看需求说明') ), React.createElement(Card, { style: { marginBottom: 16 } }, React.createElement('div', { diff --git a/src/prototypes/oneos-web-business/pages/10-客户管理-编辑.jsx b/src/prototypes/oneos-web-business/pages/10-客户管理-编辑.jsx index 39cdbb2..1c3eea2 100644 --- a/src/prototypes/oneos-web-business/pages/10-客户管理-编辑.jsx +++ b/src/prototypes/oneos-web-business/pages/10-客户管理-编辑.jsx @@ -4,7 +4,6 @@ const Component = function () { var useState = React.useState; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -252,16 +251,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '客户管理' }, - { title: '编辑', className: 'breadcrumb-last' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('客户基本信息'), style: cardStyle }, basicFields), + React.createElement(Card, { title: CardTitleWithBar('客户基本信息'), style: cardStyle }, basicFields), React.createElement(Card, { title: CardTitleWithBar('客户开票信息'), style: cardStyle }, invoiceFields), React.createElement('div', { style: { height: 60 } }), React.createElement('div', { style: footerStyle }, diff --git a/src/prototypes/oneos-web-business/pages/11-客户管理-查看.jsx b/src/prototypes/oneos-web-business/pages/11-客户管理-查看.jsx index d6ac3c3..48bbb3f 100644 --- a/src/prototypes/oneos-web-business/pages/11-客户管理-查看.jsx +++ b/src/prototypes/oneos-web-business/pages/11-客户管理-查看.jsx @@ -3,7 +3,6 @@ const Component = function () { var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -146,16 +145,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '客户管理' }, - { title: '查看', className: 'breadcrumb-last' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('客户基本信息'), style: cardStyle }, basicFields), + React.createElement(Card, { title: CardTitleWithBar('客户基本信息'), style: cardStyle }, basicFields), React.createElement(Card, { title: CardTitleWithBar('客户开票信息'), style: cardStyle }, invoiceFields), React.createElement('div', { style: { height: 60 } }), React.createElement('div', { style: footerStyle }, diff --git a/src/prototypes/oneos-web-business/pages/12-客户管理-新增.jsx b/src/prototypes/oneos-web-business/pages/12-客户管理-新增.jsx index 8a3748d..9fc7130 100644 --- a/src/prototypes/oneos-web-business/pages/12-客户管理-新增.jsx +++ b/src/prototypes/oneos-web-business/pages/12-客户管理-新增.jsx @@ -4,7 +4,6 @@ const Component = function () { var useState = React.useState; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -229,16 +228,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '客户管理' }, - { title: '新增客户', className: 'breadcrumb-last' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('客户基本信息'), style: cardStyle }, basicFields), + React.createElement(Card, { title: CardTitleWithBar('客户基本信息'), style: cardStyle }, basicFields), React.createElement(Card, { title: CardTitleWithBar('客户开票信息'), style: cardStyle }, invoiceFields), React.createElement('div', { style: { height: 60 } }), React.createElement('div', { style: footerStyle }, diff --git a/src/prototypes/oneos-web-business/pages/13-客户管理.jsx b/src/prototypes/oneos-web-business/pages/13-客户管理.jsx index 21e4780..aabf054 100644 --- a/src/prototypes/oneos-web-business/pages/13-客户管理.jsx +++ b/src/prototypes/oneos-web-business/pages/13-客户管理.jsx @@ -1,12 +1,12 @@ // 【重要】必须使用 const Component 作为组件变量名 // 业务管理 - 客户管理(2026年3月12日版本) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -160,14 +160,14 @@ const Component = function () { { title: '操作', key: 'action', - width: 180, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement(Space, { size: 'small' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goEdit(record); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { openDeleteConfirm(record); } }, '删除') - ); + return React.createElement(OperationActions, { + view: { onClick: function () { goView(record); } }, + edit: { onClick: function () { goEdit(record); } }, + more: [{ key: 'delete', label: '删除', danger: true, onClick: function () { openDeleteConfirm(record); } }] + }); } } ]; @@ -250,14 +250,7 @@ const Component = function () { var requirementContent = '客户管理(2026年3月12日版本)\n一个「数字化资产ONEOS运管平台」中的「客户管理」模块\n#面包屑:业务管理-客户管理;\n\n页面分为2个卡片;\n1.筛选:默认显示首行,可通过展开、收起功能进行展开/收起的切换,右侧为重置、查询按钮;\n#支持客户编码、合作状态、区域、城市、业务经理、创建人等筛选方式;\n1.1.客户编码:选择器,支持输入框内输入内容模糊搜索,下拉显示对应选项;\n1.2.合作状态:选择器,支持多选,选项为已合作、终止合作、洽谈中、合约过期;\n1.3.区域:选择器,支持多选,选项为:华北、华东、华南、华中、东北、西南、西北;\n1.4.城市:地区级联选择器,支持2级选择,格式为省-市;\n1.5.业务经理:选择器,选项为客户表中所有业务经理;\n1.6.创建人:选择器,选项为所有创建用户姓名;\n\n2.客户列表:\n#左上方为客户名称输入框(输入客户名称直接联动),右上角为新增、导出、导入;\n2.1.客户编码:显示客户编码;\n2.2.合作状态:显示合作状态,选项为已合作、终止合作、洽谈中、合约过期;\n2.3.客户名称:显示客户名称;\n2.4.区域:显示客户区域,客户区域由选择城市后自动匹配;\n2.5.城市:显示客户城市,格式为:省-市;\n2.6.信用代码/身份证:显示客户信用代码或身份证,取决于新增时输入的是什么;\n2.7.联系人:显示客户联系人;\n2.8.联系人手机:显示客户联系人手机;\n2.9.联系人座机:显示客户联系人座机,无则显示为-;\n2.10.业务经理:显示客户对应业务经理姓名,可能会存在多个业务经理维护一个客户;\n2.11.客户地址:显示客户地址信息;\n2.12.电子邮箱:显示客户电子邮箱;\n2.13.备注:显示客户备注信息;\n2.14.创建人:显示客户创建人用户姓名;\n2.15.创建时间:显示客户创建时间;\n2.16.操作:查看、编辑、删除;\n 2.16.1.查看:跳转客户管理-查看页;\n 2.16.2.编辑:跳转客户管理-编辑页;\n 2.16.3.删除:二次确认,点击确认后删除,已删除客户无法再被新合同选择客户时选择,但历史数据中该客户信息依然显示;'; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '客户管理' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '筛选', style: cardStyle }, React.createElement('div', { diff --git a/src/prototypes/oneos-web-business/pages/14-新增交车任务.jsx b/src/prototypes/oneos-web-business/pages/14-新增交车任务.jsx index 15dfde9..b4e88d8 100644 --- a/src/prototypes/oneos-web-business/pages/14-新增交车任务.jsx +++ b/src/prototypes/oneos-web-business/pages/14-新增交车任务.jsx @@ -312,14 +312,7 @@ const Component = function() { var reqSpecText = '新增交车任务\n一个「数字化资产ONEOS运管平台」中的「新增交车任务」模块\n1.面包屑:\n1.1.业务管理-交车任务-新增交车任务\n\n2.表单:\n2.1.选择项目名称:必选项,选择器,默认提示文本:请选择或输入项目名称,支持从输入框内输入内容进行模糊搜索,对应自营合同、租赁合同-「项目名称」字段;;\n2.2.合同编码:输入框(禁用状态),根据所选项目名称自动反写合同编码;\n2.3.客户名称:输入框(禁用状态),根据所选项目名称自动反写客户名称;\n2.4.交车区域:输入框(禁用状态),根据所选项目名称自动反写交车区域。提交时根据交车区域,为对应区域运维人员分配对应交车任务;\n2.5.交车地点:输入框(禁用状态),根据所选项目名称自动反写交车地点;\n2.6.预计交车日期:必填项,日期选择器,支持某天或某个时间段两种模式,格式为YYYY-MM-DD或YYYY-MM-DD至YYYY-MM-DD,结束日期不能早于开始日期,并且结束日期不能早于当前日期;\n 2.6.1.如勾选不需要还车,则预计交车日期隐藏,不需要设置;\n2.7.开始计费日期:必填项,日期选择器,支持单日选择,格式为YYYY-MM-DD;\n2.8.已交车辆是否需要还车:勾选框,内容为不需要还车,默认不勾选,不勾选时,无法在车辆列表勾选已交未还的车辆,勾选后,已交未还的车辆也可以勾选,但不会生成新交车任务,只是按照开始计费日期生成账单;\n 2.8.1.勾选时,车辆列表只显示已交未还的车辆清单,不能添加新车(避免已交未还部分车辆开始计费日期计算不精确);\n 2.8.2.不勾选时,车辆列表显示已交未还的车辆清单及续签合同新增车辆,但是已交未还部分的车辆不可勾选;\n2.9.下方为列表,列表拉取该车辆租赁合同对应所有车辆信息(该合同下已提交过交车任务的车辆不可选,已完成交车的车辆不显示在列表中),列表字段为:全选/多选、序号、品牌、型号、车牌号、车辆识别代码、车辆月租金、服务费、保证金、备注;\n 2.9.1.序号:与租赁合同车辆序号对应;\n 2.9.2.全选/多选:支持全选、多选模式,选择对应车辆后,点击提交自动生成被选中车辆交车任务,需要至少选择1辆,才能进行提交,该合同下已提交过交车任务和已交车的车辆不可选,多选框为禁用状态,悬浮时提示:该车辆已完成交车/该车辆已创建交车任务;\n 2.9.3.品牌:输入框(禁用状态),根据所选项目名称自动反写品牌;\n 2.9.4.型号:输入框(禁用状态),根据所选项目名称自动反写型号;\n 2.9.5.车牌号:输入框(禁用状态),根据所选项目名称自动反写车牌号,车牌号可能为空,为空时显示为-;\n 2.9.6.车辆识别代码:输入框(禁用状态),根据所选项目名称自动反写车辆识别代码;\n 2.9.7.车辆月租金:输入框(禁用状态),根据所选项目名称自动反写车辆月租金,后缀为元;\n 2.9.8.服务费:输入框(禁用状态),根据所选项目名称自动反写服务费,后缀为元;\n 2.9.9.保证金:输入框(禁用状态),根据所选项目名称自动反写保证金,后缀为元;\n 2.9.10.备注:输入框(禁用状态),根据所选项目名称自动反写备注信息,备注为空时显示为-;\n2.10.页面底部为提交、取消;'; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, - React.createElement('span', null, '业务管理'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', null, '交车任务'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { style: { color: '#1890ff' } }, '新增交车任务')), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { reqModalOpen[1](true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { reqModalOpen[1](true); } }, '查看需求说明')), React.createElement('div', { style: styles.card }, React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')), React.createElement('div', { style: styles.cardBody }, diff --git a/src/prototypes/oneos-web-business/pages/15-租赁账单-查看.jsx b/src/prototypes/oneos-web-business/pages/15-租赁账单-查看.jsx index c233cb6..78e56c4 100644 --- a/src/prototypes/oneos-web-business/pages/15-租赁账单-查看.jsx +++ b/src/prototypes/oneos-web-business/pages/15-租赁账单-查看.jsx @@ -6,7 +6,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Popover = antd.Popover; @@ -161,15 +160,7 @@ const Component = function () { var requirementContent = '租赁账单-查看(2026年3月11日版本)\n一个「数字化资产ONEOS运管平台」中的「租赁账单」「查看」模块\n#面包屑:业务管理-租赁账单-收费明细;\n1.账单信息:\n#显示该账单对应合同及账单信息,显示以下字段:\n 1.1.合同编码:显示该账单对应合同编码;\n 1.2.合同类型:显示该账单对应合同类型,包括正式合同、试用合同;\n 1.3.项目名称:显示该账单对应合同中项目名称;\n 1.4.客户名称:显示该账单对应合同中客户名称;\n 1.5.交车任务编码:显示交车任务对应编码;\n 1.6.账单编码:显示该账单编码,账单编码规则为:[合同编码][账单编号]组成,主要用于后期与用友YS系统打通时获取财务收款及发票相关数据;\n 前缀为合同编码,后缀为账单编号,规则为:ZD+4位编号,为该合同下第x份账单,例如:ZD0001为该合同下第1份账单,依次类推;\n 例如:JXZL20260216YW101235AZD0001即为JXZL20260216YW101235A合同下第1份账单;\n 1.7.账单期数:显示该账单期数;\n 1.8.账单开始日期:显示账单开始日期,根据租赁合同及交车任务开始计费日期按规则生成,格式为:YYYY-MM-DD;\n 1.9.账单结束日期:显示账单结束日期,根据租赁合同及交车任务开始计费日期按规则生成,格式为:YYYY-MM-DD;\n2.账单明细:\n#顶部显示应收款总额、实收款总额;\n 2.1.应收款总额:「应收车辆月租金总和」+「应收车辆保证金总和」+「应收服务费总和」+「氢费预付款应收金额」\n 2.2.实收款总额:「实收车辆月租金总和」+「应收车辆保证金总和」+「实收服务费总和」+「氢费预付款实收金额」-「减免费用总和」「服务费各项减免费用总和」\n\n#显示该账单对应车辆列表及相关费用信息,显示以下字段;\n 2.3.序号:与租赁合同及交车任务中该车辆序号一致;\n 2.4.品牌:与租赁合同及交车任务中该车辆品牌一致;\n 2.5.型号:与租赁合同及交车任务中该车辆型号一致;\n 2.6.车牌号:与租赁合同及交车任务中该车辆车牌号一致;\n 2.7.应收车辆月租金:与租赁合同中该车辆车辆月租金一致,格式为:xx.xx元;\n 2.8.实收车辆月租金:与收费明细中实收车辆月租金一致,格式为:xx.xx元;\n 2.9.车辆租金备注:显示收费明细中已填写备注信息;\n 2.10.减免金额:显示收费明细中已填写减免金额;\n 2.11.减免金额备注:显示收费明细中减免金额备注信息;\n 2.12.减免证明:显示已上传减免证明文件,已上传文件一行显示一条,支持点击预览,格式支持各类图片、doc、docx、pdf等格式;\n 2.13.应收车辆保证金:首期与合同中车辆保证金一致,第二期开始为0.00,格式为:xx.xx元;\n 2.14.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注;\n 2.14.1.服务项目:显示租赁合同中所有服务项目名称;\n 2.14.2.应收费用:显示租赁合同中所有服务项目对应费用,格式为:xx.xx元;\n 2.14.3.实收费用:显示收费明细中填写的实收费用金额,精确至2位小数,后缀为元,默认与应收费用一致;\n 2.14.4.减免费用:显示收费明细中填写的减免费用金额,精确至2位小数,后缀为元;\n 2.14.4.备注:显示收费明细中填写的备注信息;\n 2.15.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元;\n 2.16.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元;\n#列表下方为氢费预付款情况,首期账单显示,第二期账单开始无此区域内容显示;\n 2.17.氢费预付款应收金额:显示合同中氢费预付款金额;\n 2.18.氢费预付款实收金额:显示收费明细中填写的氢费预付款实收金额,格式为:xx.xx元;\n 2.19.减免金额:显示收费明细中填写的减免金额,格式为:xx.xx元;\n 2.20.减免金额备注:显示收费明细中填写的减免金额备注信息;\n\n3.下方为返回按钮\n 3.1.返回:点击返回按钮,返回租赁账单列表页面;\n'; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '租赁账单' }, - { title: '查看' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '账单信息', style: cardStyle }, React.createElement('div', { style: formRowStyle }, diff --git a/src/prototypes/oneos-web-business/pages/16-租赁账单-收费明细.jsx b/src/prototypes/oneos-web-business/pages/16-租赁账单-收费明细.jsx index 374bc27..3b1082c 100644 --- a/src/prototypes/oneos-web-business/pages/16-租赁账单-收费明细.jsx +++ b/src/prototypes/oneos-web-business/pages/16-租赁账单-收费明细.jsx @@ -7,7 +7,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Button = antd.Button; @@ -212,15 +211,7 @@ const Component = function () { var requirementContent = '租赁账单-收费明细(2026年3月11日版本)\n一个「数字化资产ONEOS运管平台」中的「租赁账单」「收费明细」模块\n#面包屑:业务管理-租赁账单-收费明细;\n1.账单信息:\n#显示该账单对应合同及账单信息,显示以下字段:\n 1.1.合同编码:显示该账单对应合同编码;\n 1.2.合同类型:显示该账单对应合同类型,包括正式合同、试用合同;\n 1.3.项目名称:显示该账单对应合同中项目名称;\n 1.4.客户名称:显示该账单对应合同中客户名称;\n 1.5.交车任务编码:显示交车任务对应编码;\n 1.6.账单编码:显示该账单编码,账单编码规则为:[合同编码][账单编号]组成,主要用于后期与用友YS系统打通时获取财务收款及发票相关数据;\n 前缀为合同编码,后缀为账单编号,规则为:ZD+4位编号,为该合同下第x份账单,例如:ZD0001为该合同下第1份账单,依次类推;\n 例如:JXZL20260216YW101235AZD0001即为JXZL20260216YW101235A合同下第1份账单;;\n 1.7.账单期数:显示该账单期数;\n 1.8.账单开始日期:显示账单开始日期,根据租赁合同及交车任务开始计费日期按规则生成,格式为:YYYY-MM-DD;\n 1.9.账单结束日期:显示账单结束日期,根据租赁合同及交车任务开始计费日期按规则生成,格式为:YYYY-MM-DD;\n2.账单明细:\n#顶部显示应收款总额、实收款总额;\n 2.1.应收款总额:「应收车辆月租金总和」+「应收车辆保证金总和」+「应收服务费总和」+「氢费预付款应收金额」\n 2.2.实收款总额:「实收车辆月租金总和」+「应收车辆保证金总和」+「实收服务费总和」+「氢费预付款实收金额」-「减免费用总和」「服务费各项减免费用总和」\n 2.3.开票金额:「实收车辆月租金总和」+「实收服务费总和」-「减免费用总和」「服务费各项减免费用总和」\n\n#显示该账单对应车辆列表及相关费用信息,显示以下字段;\n 2.3.序号:与租赁合同及交车任务中该车辆序号一致;\n 2.4.品牌:与租赁合同及交车任务中该车辆品牌一致;\n 2.5.型号:与租赁合同及交车任务中该车辆型号一致;\n 2.6.车牌号:与租赁合同及交车任务中该车辆车牌号一致;\n 2.7.应收车辆月租金:与租赁合同中该车辆车辆月租金一致,格式为:xx.xx元;\n 2.8.实收车辆月租金:输入框,2位小数,后缀为元,默认为与应收车辆月租金一致;\n 2.9.车辆租金备注:输入框,支持自定义输入;\n 2.10.减免金额:输入框,2位小数,后缀为元,默认为0.00元;\n 2.11.减免金额备注:输入框,支持自定义输入;\n 2.12.减免证明:附件上传按钮,点击支持多文件上传,已上传文件一行显示一条,支持点击预览,格式支持各类图片、doc、docx、pdf等格式;\n 2.13.应收车辆保证金:首期与合同中车辆保证金一致,第二期开始为0.00,格式为:xx.xx元;\n 2.14.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注;\n 2.14.1.服务项目:显示租赁合同中所有服务项目名称;\n 2.14.2.应收费用:显示租赁合同中所有服务项目对应费用,格式为:xx.xx元;\n 2.14.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元,默认与应收费用一致;\n 2.14.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元;\n 2.14.4.备注:选填项,输入框,由业务员自行输入备注信息;\n 2.15.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元;\n 2.16.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元;\n#列表下方为氢费预付款情况,首期账单显示,第二期账单开始无此区域内容显示;\n 2.17.氢费预付款应收金额:显示合同中氢费预付款金额;\n 2.18.氢费预付款实收金额:输入框,2位小数,后缀为元,默认与氢费预付款应收金额一致;\n 2.19.减免金额:输入框,2位小数,后缀为元;\n 2.20.减免金额备注:输入框,支持自定义输入;\n\n3.下方为提交审核、保存、取消按钮\n 3.1.提交审核:点击提交审核按钮,进入审核流程;\n 3.2.保存:点击保存按钮,保存当前表单已填数据,不做校验;\n'; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between' } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '租赁账单' }, - { title: '收费明细' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { marginBottom: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between' } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '账单信息', style: cardStyle }, React.createElement('div', { style: formRowStyle }, diff --git a/src/prototypes/oneos-web-business/pages/17-租赁账单.jsx b/src/prototypes/oneos-web-business/pages/17-租赁账单.jsx index adf6b1c..b64764f 100644 --- a/src/prototypes/oneos-web-business/pages/17-租赁账单.jsx +++ b/src/prototypes/oneos-web-business/pages/17-租赁账单.jsx @@ -1,12 +1,12 @@ // 【重要】必须使用 const Component 作为组件变量名 // 租赁账单(2026年3月10日版本) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -232,10 +232,14 @@ const Component = function () { function renderSubActions(record, parentRecord) { var status = record && record.status; var isSubmitted = status === '已提交'; - return React.createElement(Space, { size: 'small' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record, parentRecord); } }, '查看'), - isSubmitted ? null : React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goChargeDetail(record, parentRecord); } }, '收费明细') - ); + var more = []; + if (!isSubmitted) { + more.push({ key: 'chargeDetail', label: '收费明细', onClick: function () { goChargeDetail(record, parentRecord); } }); + } + return React.createElement(OperationActions, { + view: { onClick: function () { goView(record, parentRecord); } }, + more: more + }); } // 子表提车数量气泡:列表显示 品牌、型号、车牌号 @@ -310,7 +314,7 @@ const Component = function () { { title: '操作', key: 'action', - width: 160, + width: 168, fixed: 'right', render: function (_, record, rowIndex, extra) { var parentRecord = (extra && extra._parentRecord) || record._parentRecord; @@ -355,14 +359,7 @@ const Component = function () { var requirementContent = '租赁账单(2026年3月10日版本)\n一个「数字化资产ONEOS运管平台」中的「租赁账单」模块\n#面包屑:业务管理-租赁账单;\n1.筛选:\n#支持合同编码/项目名称/客户名称/业务部门/业务负责人等筛选方式;\n1.1.合同编码:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.2.项目名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.3.客户名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.4.业务部门:选择器,支持选择所有业务部门;\n1.5.业务负责人:选择器,支持选择所有业务负责人;\n1.6.交车任务编码:输入框,支持模糊搜索;\n1.7.右侧为重置、查询按钮;\n\n2.租赁账单列表:\n列表展示方式为:嵌套子表格,分为主表和子表\n2.1.主表:显示以下字段:合同编码、合同类型、项目名称、客户名称、合同生效日期、业务部门、业务负责人;\n 2.1.1.合同编码:显示车辆租赁合同编码;\n 2.1.2.合同类型:显示该租赁合同对应合同类型,显示该合同为试用合同还是正式合同;\n 2.1.3.项目名称:显示该租赁合同对应项目名称;\n 2.1.4.客户名称:显示该租赁合同对应客户名称;\n 2.1.5.合同生效日期:显示该租赁合同生效日期,格式为:YYYY-MM-DD;\n 2.1.6.交车任务编码:显示交车任务编码;\n 2.1.7.业务部门:显示该租赁合同对应业务部门名称;\n 2.1.8.业务负责人:显示该租赁合同对应业务负责人;\n 2.1.9.主表右下方为分页符,支持分页和选择单页显示数据条数;\n\n2.2.子表:显示以下字段:账单编号、账单期数、账单开始日期、账单结束日期、应收款总额、实收款总额、减免总金额、实际到账金额、是否已开票、开票金额、操作;\n 2.2.1.账单编号:[合同编码][账单编号]组成,主要用于后期与用友YS系统打通时获取财务收款及发票相关数据;\n 前缀为合同编码,后缀为账单编号,规则为:ZD+4位编号,为该合同下第x份账单,例如:ZD0001为该合同下第1份账单,依次类推;\n 例如:JXZL20260216YW101235AZD0001即为JXZL20260216YW101235A合同下第1份账单;\n 2.2.2.账单期数:显示该笔账单对应期数;\n 2.2.3.处理状态:已提交、待提交、已结清;\n 2.2.3.1.已提交:业务人员已通过点击收费明细,对收费项进行过维护并经过二次确认后提交;\n 2.2.3.2.待提交:业务人员未完成收费明细填报;\n 2.2.3.3.已结清:(等对接财务系统后,到账金额等于实收金额总额后算作已结清);\n 2.2.3.账单开始日期:显示该笔账单开始日期,格式为:YYYY-MM-DD;\n 2.2.4.账单结束日期:显示该笔账单结束日期,格式为:YYYY-MM-DD;\n 2.2.5.提车数量:显示提车数量,格式为:xx辆,点击弹出气泡卡片,卡片内为列表,显示:品牌、型号、车牌号;\n 2.2.6.应收款总额:显示该笔账单应收款总额,格式为:xx.xx元,计算方式:「所有车辆月租金总和」+「所有车辆服务费总和」;\n 2.2.7.实收款总额:显示该笔账单实收款总额,格式为:xx.xx元,计算方式:「所有车辆月租金总和」+「所有车辆服务费总和」-「减免总金额」;\n 2.2.8.减免总金额:显示该笔账单减免总金额,格式为:xx.xx元,计算方式:「所有减免金额总和」;\n 2.2.9.车辆成本:根据型号成本表中对应型号车辆成本,租赁订单包含车辆型号*实际账单天数计算得出;\n 2.2.10.氢费成本:显示格式为:xx.xx元,点击变为输入框,后缀为元,支持2位小数,失焦后保存;\n 2.2.11.其他成本:显示格式为:xx.xx元,点击变为输入框,后缀为元,支持2位小数,失焦后保存;\n\n 2.2.12.操作:查看、收费明细;\n 2.2.12.1.查看:点击跳转租赁账单-查看页;\n 2.2.12.2.收费明细:提交后收费明细将不可修改;\n\n'; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '租赁账单' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '筛选', style: cardStyle }, React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, diff --git a/src/prototypes/oneos-web-business/pages/18-ETC管理.jsx b/src/prototypes/oneos-web-business/pages/18-ETC管理.jsx index f4a53c1..a423664 100644 --- a/src/prototypes/oneos-web-business/pages/18-ETC管理.jsx +++ b/src/prototypes/oneos-web-business/pages/18-ETC管理.jsx @@ -1,13 +1,13 @@ // 【重要】必须使用 const Component 作为组件变量名 // 业务管理 - ETC管理(2026年3月10日版本) // 模块:充值明细 / ETC账单明细 / ETC余额管理 +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Tabs = antd.Tabs; @@ -95,12 +95,12 @@ const Component = function () { { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160 }, { title: '附件', dataIndex: 'attachment', key: 'attachment', width: 90, render: function (v) { return v ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('预览:' + v); } }, '预览') : '—'; } }, { title: '更新时间', dataIndex: 'updateTime', key: 'updateTime', width: 160 }, - { title: '操作', key: 'action', width: 160, fixed: 'right', render: function (_, record) { - return React.createElement(React.Fragment, null, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('查看 ' + record.rechargeNo); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('编辑 ' + record.rechargeNo); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { deleteConfirmRecord[1](record); deleteConfirmVisible[1](true); } }, '删除') - ); + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, record) { + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('查看 ' + record.rechargeNo); } }, + edit: { onClick: function () { message.info('编辑 ' + record.rechargeNo); } }, + more: [{ key: 'delete', label: '删除', danger: true, onClick: function () { deleteConfirmRecord[1](record); deleteConfirmVisible[1](true); } }] + }); } } ]; @@ -152,12 +152,12 @@ const Component = function () { { title: '是否已开票', dataIndex: 'isInvoiced', key: 'isInvoiced', width: 96 }, { title: '开票金额', dataIndex: 'invoiceAmount', key: 'invoiceAmount', width: 96, align: 'right', render: function (v) { return (v != null ? Number(v).toFixed(2) : '0.00') + ' 元'; } }, { title: '备注', dataIndex: 'remark', key: 'remark', width: 100, ellipsis: true }, - { title: '操作', key: 'action', width: 160, fixed: 'right', render: function (_, record) { - return React.createElement(React.Fragment, null, - React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { message.info('删除 ' + record.id); } }, '删除'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('编辑 ' + record.id); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('查看 ' + record.id); } }, '查看') - ); + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, record) { + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('查看 ' + record.id); } }, + edit: { onClick: function () { message.info('编辑 ' + record.id); } }, + more: [{ key: 'delete', label: '删除', danger: true, onClick: function () { message.info('删除 ' + record.id); } }] + }); } } ]; @@ -198,14 +198,18 @@ const Component = function () { { title: '客户充值余额', dataIndex: 'rechargeTotal', key: 'rechargeTotal', width: 120, align: 'right', render: function (v) { return v != null ? (Number(v).toFixed(2) + ' 元') : '—'; } }, { title: '客户消耗ETC费用', dataIndex: 'consumeTotal', key: 'consumeTotal', width: 130, align: 'right', render: function (v) { return v != null ? (Number(v).toFixed(2) + ' 元') : '—'; } }, { title: '最近汇款日期', dataIndex: 'lastRemitDate', key: 'lastRemitDate', width: 118 }, - { title: '操作', key: 'action', width: 180, fixed: 'right', render: function (_, record) { + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, record) { if (record.type === 'customer') { - return React.createElement(React.Fragment, null, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { thresholdModalRecord[1](record); thresholdModalVisible[1](true); } }, '阈值设置'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转账单明细:' + record.customerName); } }, '账单明细') - ); + return React.createElement(OperationActions, { + more: [ + { key: 'threshold', label: '阈值设置', onClick: function () { thresholdModalRecord[1](record); thresholdModalVisible[1](true); } }, + { key: 'billDetail', label: '账单明细', onClick: function () { message.info('跳转账单明细:' + record.customerName); } } + ] + }); } - return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('项目账单明细:' + record.projectName); } }, '账单明细'); + return React.createElement(OperationActions, { + more: [{ key: 'billDetail', label: '账单明细', onClick: function () { message.info('项目账单明细:' + record.projectName); } }] + }); } } ]; @@ -216,15 +220,7 @@ const Component = function () { var formItemStyle = { marginBottom: 12 }; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: 'ETC管理' } - ] - }) - ), - React.createElement(Card, { style: cardStyle }, + React.createElement(Card, { style: cardStyle }, React.createElement(Tabs, { activeKey: activeTab[0], onChange: setActiveTab, diff --git a/src/prototypes/oneos-web-contract-template/index.tsx b/src/prototypes/oneos-web-contract-template/index.tsx index 17b356c..68783a9 100644 --- a/src/prototypes/oneos-web-contract-template/index.tsx +++ b/src/prototypes/oneos-web-contract-template/index.tsx @@ -4,6 +4,7 @@ */ import '../../common/oneosWebLegacy/legacyGlobals'; import React from 'react'; +import '../vehicle-management/style.css'; import Page1 from './pages/01-合同模板管理.jsx'; import { OneosWebLegacyShell } from '../../common/oneosWebLegacy/OneosWebLegacyShell'; diff --git a/src/prototypes/oneos-web-contract-template/pages/01-合同模板管理.jsx b/src/prototypes/oneos-web-contract-template/pages/01-合同模板管理.jsx index 5a0dff8..3c6762b 100644 --- a/src/prototypes/oneos-web-contract-template/pages/01-合同模板管理.jsx +++ b/src/prototypes/oneos-web-contract-template/pages/01-合同模板管理.jsx @@ -88,6 +88,8 @@ if (typeof window !== 'undefined') { // ONE-OS 合同模板管理(法务人员维护 Word 版合同基础模板) // 设计规范:docs/ONE-OS-DESIGN-SPEC.md · 样式:web端/styles/contract-template.css +import { OperationActions } from '../../../common/OperationActions'; + const Component = function () { var ONEOS_ANT_TABLE_GLOBAL_FIX = [ '.ant-table-container .ant-table-header { margin-bottom: 0 !important; }', @@ -786,8 +788,6 @@ const Component = function () { '.ct-preview-lessor-bar__label{font-size:12px;color:#64748B;white-space:nowrap}' + '.ct-editor-page{display:flex;flex-direction:column;min-height:calc(100vh - 48px)}' + '.ct-editor-page__topbar{margin-bottom:12px}' + - '.ct-editor-page__back-btn.ant-btn{padding:4px 12px;height:32px;font-size:13px;color:#475569;border-color:#E2E8F0;background:#fff}' + - '.ct-editor-page__back-btn.ant-btn:hover{color:#059669;border-color:#bbf7d0;background:#ecfdf5}' + '.ct-editor-page__header{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:16px;padding:12px 16px;background:#fff;border:1px solid #E2E8F0;border-radius:12px;flex-wrap:wrap}' + '.ct-panel-head__actions{display:flex;align-items:center;gap:8px;flex-shrink:0}' + '.ct-editor-page__title{margin:0;font-size:18px;font-weight:700;color:#0F172A;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' + @@ -2129,24 +2129,24 @@ const Component = function () { { title: '更新人', dataIndex: 'updater', key: 'updater', width: 110 }, { title: '备注', dataIndex: 'remark', key: 'remark', ellipsis: true }, { - title: '操作', key: 'action', width: 240, fixed: 'right', + title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, record) { - return React.createElement(Space, { size: 4, wrap: true }, - React.createElement(Button, { type: 'link', size: 'small', className: 'oneos-action-btn ct-action-btn', onClick: function () { openEdit(record); } }, '编辑'), - record.status === 'published' && !record.isDefault - ? React.createElement(Button, { type: 'link', size: 'small', className: 'oneos-action-btn ct-action-btn', onClick: function () { confirmSetDefault(record); } }, '设为标准模板') - : null, - record.isDefault - ? React.createElement(Tooltip, { title: '客服草拟合同时使用此标准模板' }, - React.createElement(Button, { type: 'link', size: 'small', className: 'oneos-action-btn ct-action-btn', disabled: true }, '当前标准模板') - ) - : null, - record.isDefault - ? React.createElement(Tooltip, { title: '标准模板不可删除' }, - React.createElement(Button, { type: 'link', size: 'small', className: 'oneos-action-btn ct-action-btn', disabled: true, danger: true }, '删除') - ) - : React.createElement(Button, { type: 'link', size: 'small', className: 'oneos-action-btn ct-action-btn', danger: true, onClick: function () { confirmDelete(record); } }, '删除') - ); + var more = []; + if (record.status === 'published' && !record.isDefault) { + more.push({ key: 'setDefault', label: '设为标准模板', onClick: function () { confirmSetDefault(record); } }); + } + if (record.isDefault) { + more.push({ key: 'currentDefault', label: '当前标准模板', disabled: true }); + } + if (record.isDefault) { + more.push({ key: 'deleteDisabled', label: '删除', disabled: true, danger: true }); + } else { + more.push({ key: 'delete', label: '删除', danger: true, onClick: function () { confirmDelete(record); } }); + } + return React.createElement(OperationActions, { + edit: { onClick: function () { openEdit(record); } }, + more: more + }); } } ]; @@ -2281,7 +2281,7 @@ const Component = function () { var hasContent = !!(formDocumentHtmlState[0] || '').trim(); return React.createElement('div', { className: 'ct-editor-page' }, React.createElement('div', { className: 'ct-editor-page__topbar' }, - React.createElement(Button, { className: 'ct-editor-page__back-btn', onClick: backToList }, '← 返回列表') + React.createElement(Button, { className: 'vm-btn vm-btn-back', onClick: backToList }, '返回列表') ), React.createElement('div', { className: 'ct-editor-page__header' }, React.createElement('h2', { className: 'ct-editor-page__title' }, editorTitle), diff --git a/src/prototypes/oneos-web-data-analysis/pages/01-客户服务部业务统计报表.jsx b/src/prototypes/oneos-web-data-analysis/pages/01-客户服务部业务统计报表.jsx index ef64252..c3ea1bf 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/01-客户服务部业务统计报表.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/01-客户服务部业务统计报表.jsx @@ -2,6 +2,8 @@ // 数据分析 - 客户服务部业务统计报表(依据业务台账模版数据统计方案) // 经营总览 KPI + 租赁/物流/能源/成本账户/未收预警;支持汇总→明细钻取(原型演示数据) +import { OperationActions } from '../../../common/OperationActions'; + const Component = function () { var useState = React.useState; var useMemo = React.useMemo; @@ -9,7 +11,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -398,10 +399,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 88, + width: 168, fixed: 'right', render: function (_, r) { - return React.createElement('button', { type: 'button', style: drillLinkStyle, onClick: function () { openDrill('lease', r); } }, '明细'); + return React.createElement(OperationActions, { + view: { label: '明细', onClick: function () { openDrill('lease', r); } } + }); } } ]; @@ -419,10 +422,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 88, + width: 168, fixed: 'right', render: function (_, r) { - return React.createElement('button', { type: 'button', style: drillLinkStyle, onClick: function () { openDrill('logistics', r); } }, '明细'); + return React.createElement(OperationActions, { + view: { label: '明细', onClick: function () { openDrill('logistics', r); } } + }); } } ]; @@ -610,15 +615,7 @@ const Component = function () { React.createElement('style', null, tableStyle), React.createElement( 'div', - { style: layoutStyle }, - React.createElement(Breadcrumb, { - style: { marginBottom: 12 }, - items: [ - { title: '数据分析' }, - { title: '客户服务部业务统计' } - ] - }), - React.createElement( + { style: layoutStyle }, React.createElement( Card, { bordered: false, style: { marginBottom: 16 } }, React.createElement('div', { style: { fontSize: 18, fontWeight: 600, marginBottom: 16, color: 'rgba(0,0,0,0.88)' } }, pageTitle), diff --git a/src/prototypes/oneos-web-data-analysis/pages/02-物流业务台账.jsx b/src/prototypes/oneos-web-data-analysis/pages/02-物流业务台账.jsx index 9a393cc..5fe335f 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/02-物流业务台账.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/02-物流业务台账.jsx @@ -1279,6 +1279,8 @@ function llEmptyForm(user) { }; } +import { OperationActions, splitOperationActions } from '../../../common/OperationActions'; + const Component = function () { var useState = React.useState; var useMemo = React.useMemo; @@ -2087,60 +2089,39 @@ const Component = function () { cols.splice(3, 0, { title: '维护人', dataIndex: 'maintainerName', key: 'maintainerName', width: 88, render: function (v) { return plain(v); } }); } cols.push({ - title: '操作', key: 'action', width: 56, align: 'center', fixed: 'right', + title: '操作', key: 'action', width: 168, align: 'center', fixed: 'right', render: function (_, record) { var canEdit = llCanMutate(record, currentUser, isSupervisor); if (llIsDraftRow(record) && canEdit) { - return React.createElement('div', { style: { display: 'inline-flex', gap: 8 } }, - React.createElement(Button, { type: 'link', size: 'small', className: 'lc-action-btn', onClick: function () { handleSaveDraft(record); } }, '保存'), - React.createElement(Button, { type: 'link', size: 'small', className: 'lc-action-btn lc-action-btn-danger', onClick: function () { handleCancelDraft(record); } }, '取消') - ); + return React.createElement(OperationActions, { + more: [ + { key: 'save', label: '保存', onClick: function () { handleSaveDraft(record); } }, + { key: 'cancel', label: '取消', danger: true, onClick: function () { handleCancelDraft(record); } } + ] + }); } if (!canEdit) return null; - function renderIconAction(title, iconNode, onClick, extraClass, disabled) { - var cls = 'll-action-icon-btn' + (extraClass ? ' ' + extraClass : '') + (disabled ? ' is-disabled' : ''); - return React.createElement(Tooltip, { title: title }, - React.createElement('span', { - className: cls, - role: 'button', - tabIndex: disabled ? -1 : 0, - 'aria-label': title, - onClick: disabled || !onClick ? undefined : function (e) { e.stopPropagation(); } - }, iconNode) - ); - } + var menuItems = [ + { key: 'delete', label: '删除', danger: true, onClick: function () { + Modal.confirm({ + title: '确认删除该条台账?', + okText: '删除', + cancelText: '取消', + okButtonProps: { danger: true }, + onOk: function () { handleDelete(record); } + }); + } }, + { key: 'changelog', label: '变更日志', onClick: function () { openChangeLogModal(record); } } + ]; + var split = splitOperationActions([ + { key: 'edit', label: '编辑', onClick: function () { openEdit(record); } } + ].concat(menuItems)); - function renderMoreIcon() { - return React.createElement('svg', { viewBox: '0 0 16 16', width: 16, height: 16, fill: 'currentColor', 'aria-hidden': true }, - React.createElement('circle', { cx: 8, cy: 3, r: 1.5 }), - React.createElement('circle', { cx: 8, cy: 8, r: 1.5 }), - React.createElement('circle', { cx: 8, cy: 13, r: 1.5 }) - ); - } - - function buildMoreMenuItems() { - return [ - { key: 'edit', label: '编辑', onClick: function () { openEdit(record); } }, - { key: 'delete', label: '删除', danger: true, onClick: function () { - Modal.confirm({ - title: '确认删除该条台账?', - okText: '删除', - cancelText: '取消', - okButtonProps: { danger: true }, - onOk: function () { handleDelete(record); } - }); - } }, - { type: 'divider', key: 'divider-log' }, - { key: 'changelog', label: '变更日志', onClick: function () { openChangeLogModal(record); } } - ]; - } - - return React.createElement(Dropdown, { - trigger: ['click'], - placement: 'bottomRight', - menu: { items: buildMoreMenuItems() } - }, renderIconAction('更多', renderMoreIcon(), null, 'll-row-more-btn')); + return React.createElement(OperationActions, { + edit: split.edit, + more: split.more + }); } }); return cols; diff --git a/src/prototypes/oneos-web-data-analysis/pages/03-物流业务月度统计.jsx b/src/prototypes/oneos-web-data-analysis/pages/03-物流业务月度统计.jsx index 02e0212..23140fb 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/03-物流业务月度统计.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/03-物流业务月度统计.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -824,12 +823,7 @@ const Component = function () { ]; return React.createElement(App, null, - React.createElement('div', { style: layoutStyle }, - React.createElement(Breadcrumb, { - style: { marginBottom: 16 }, - items: [{ title: '数据分析' }, { title: '物流业务月度统计' }] - }), - React.createElement(Card, null, + React.createElement('div', { style: layoutStyle }, React.createElement(Card, null, React.createElement('style', null, tabsBarStyle), React.createElement(Tabs, { className: 'logistics-monthly-stat-tabs', diff --git a/src/prototypes/oneos-web-data-analysis/pages/04-业务部汇总台账.jsx b/src/prototypes/oneos-web-data-analysis/pages/04-业务部汇总台账.jsx index b4796d9..a180bbb 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/04-业务部汇总台账.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/04-业务部汇总台账.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -451,12 +450,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('style', null, ledgerTableStyle), - React.createElement('div', { style: layoutStyle }, - React.createElement(Breadcrumb, { style: { marginBottom: 12 }, items: [ - { title: '数据分析' }, - { title: '业务部汇总台账' } - ] }), - React.createElement(Card, { style: { marginBottom: 16 } }, + React.createElement('div', { style: layoutStyle }, React.createElement(Card, { style: { marginBottom: 16 } }, React.createElement(Row, { gutter: [16, 16], align: 'bottom' }, React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 6 }, React.createElement('div', { style: filterItemStyle }, diff --git a/src/prototypes/oneos-web-data-analysis/pages/05-业务部台账.jsx b/src/prototypes/oneos-web-data-analysis/pages/05-业务部台账.jsx index a0ae8c6..b7a2bd7 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/05-业务部台账.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/05-业务部台账.jsx @@ -54,20 +54,34 @@ const Component = function () { var METRIC_SUFFIX_LABEL = { Perf: '业绩', Cost: '成本', Profit: '盈亏' }; - var BDL_COL_WIDTH = 100; - var BDL_MONTH_COL_WIDTH = 64; - var BDL_SUMMARY_COL_WIDTH = 116; + var BDL_COL_WIDTH = 128; + var BDL_MONTH_COL_WIDTH = 72; + var BDL_SUMMARY_COL_WIDTH = 148; var BDL_LEDGER_SCROLL_X = BDL_MONTH_COL_WIDTH + SECTOR_COLUMNS.length * 3 * BDL_COL_WIDTH + 3 * BDL_SUMMARY_COL_WIDTH; - var BDL_CUSTOMER_SCROLL_X = 88 + 220 + SECTOR_COLUMNS.length * BDL_COL_WIDTH + BDL_SUMMARY_COL_WIDTH + 48; + var BDL_CUSTOMER_SCROLL_X = 96 + 240 + SECTOR_COLUMNS.length * BDL_COL_WIDTH + BDL_SUMMARY_COL_WIDTH + 48; + var BDL_TABLE_SCROLL_Y = 'max(calc(100vh - var(--bdl-list-chrome-offset, 420px)), 280px)'; var ONEOS_ANT_TABLE_GLOBAL_FIX = [ '.ant-table-container .ant-table-header { margin-bottom: 0 !important; }', '.ant-table-container .ant-table-body { margin-top: 0 !important; }', '.ant-table-container .ant-table-body > table, .ant-table-content table { margin-top: 0 !important; }', '.ant-table-tbody > tr.ant-table-measure-row, .ant-table-tbody > tr.ant-table-measure-row > td, .ant-table-tbody > tr.ant-table-measure-row > th { display: none !important; height: 0 !important; max-height: 0 !important; min-height: 0 !important; padding: 0 !important; margin: 0 !important; border: none !important; line-height: 0 !important; font-size: 0 !important; overflow: hidden !important; visibility: hidden !important; pointer-events: none !important; }', - '.vm-list-table .ant-table-thead th,.vm-list-table .ant-table-tbody td{white-space:nowrap;}' + '.vm-list-table .ant-table-thead th,.vm-list-table .ant-table-tbody td{white-space:nowrap;}', + '.bdl-list-table .ant-table-cell-scrollbar{width:0!important;min-width:0!important;max-width:0!important;padding:0!important;border:none!important;visibility:hidden;}' ]; + var CUSTOMER_SEED_NAMES = [ + '安徽驰远供应链管理有限公司', + '嘉兴港区韵达快递有限公司', + '无锡双庙运输有限公司', + '新疆佳淇信息科技有限公司', + '嘉兴益顺冷链物流有限公司', + '新疆中铁黑豹物流有限公司', + '嘉兴必出彩供应链有限公司', + '上海朔宇物流有限公司' + ]; + + var SearchIcon = function () { return React.createElement('svg', { viewBox: '0 0 48 48', width: 14, height: 14, fill: 'none', stroke: 'currentColor', strokeWidth: 4 }, React.createElement('path', { d: 'M33.072 33.071c6.248-6.248 6.248-16.379 0-22.627-6.249-6.249-16.38-6.249-22.628 0-6.248 6.248-6.248 16.379 0 22.627 6.248 6.248 16.38 6.248 22.628 0Zm0 0 8.485 8.485' })); }; var ResetIcon = function () { return React.createElement('svg', { viewBox: '0 0 48 48', width: 14, height: 14, fill: 'none', stroke: 'currentColor', strokeWidth: 4 }, React.createElement('path', { d: 'M38.837 18C36.463 12.136 30.715 8 24 8 15.163 8 8 15.163 8 24s7.163 16 16 16c7.455 0 13.72-5.1 15.496-12M40 8v10H30' })); }; var DownloadIcon = function () { return React.createElement('svg', { viewBox: '0 0 48 48', width: 14, height: 14, fill: 'none', stroke: 'currentColor', strokeWidth: 4 }, React.createElement('path', { d: 'm33.072 22.071-9.07 9.071-9.072-9.07M24 5v26m16 4v6H8v-6' })); }; @@ -108,29 +122,27 @@ const Component = function () { function renderFilterActions(onReset, onQuery) { return React.createElement('div', { className: 'vm-filter-actions ldb-filter-actions' }, - React.createElement('button', { + React.createElement('button', { 'data-vm-icon': 'rotate-ccw', type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: onReset - }, '重置'), + }, '重置'), React.createElement('button', { + 'data-vm-icon': 'search', type: 'button', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: onQuery - }, - React.createElement(SearchIcon), - '查询' - ) + }, '查询') ); } function renderDrillToolbar(onBack, title, subtitle) { return React.createElement('div', { className: 'vm-table-toolbar bdl-drill-toolbar ldb-table-toolbar' }, - React.createElement('button', { + React.createElement('button', { 'data-vm-icon': 'x', type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: onBack - }, '返回上一步'), + }, '返回上一步'), React.createElement('div', { className: 'bdl-drill-heading' }, React.createElement('h3', { className: 'bdl-drill-title' }, title), subtitle ? React.createElement('p', { className: 'bdl-drill-subtitle' }, subtitle) : null @@ -141,11 +153,73 @@ const Component = function () { function renderDrillToolbarInline(onBack, title) { return React.createElement('div', { className: 'vm-table-toolbar bdl-drill-toolbar bdl-drill-toolbar--inline ldb-table-toolbar' }, - React.createElement('button', { + React.createElement('button', { 'data-vm-icon': 'x', type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: onBack - }, '返回上一步'), + }, '返回上一步'), + React.createElement('h3', { className: 'bdl-drill-title' }, title) + ); + } + + function renderFilterField(label, control) { + return React.createElement('label', { className: 'vm-filter-field' }, + React.createElement('span', { className: 'vm-filter-label' }, label), + control + ); + } + + function renderProfitCell(v) { + if (v === null || v === undefined || v === '') return ''; + var x = Number(v); + if (isNaN(x)) return ''; + var text = fmtLedgerCell(v); + if (x < 0) return React.createElement('span', { className: 'bdl-amount-neg' }, text); + return text; + } + + function renderMetricCell(v, isProfit) { + return isProfit ? renderProfitCell(v) : fmtLedgerCell(v); + } + + function renderFilterActions(onReset, onQuery) { + return React.createElement('div', { className: 'vm-filter-actions ldb-filter-actions' }, + React.createElement('button', { 'data-vm-icon': 'rotate-ccw', + type: 'button', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', + onClick: onReset + }, '重置'), + React.createElement('button', { + 'data-vm-icon': 'search', + type: 'button', + className: 'vm-btn vm-btn-primary ldb-toolbar-btn', + onClick: onQuery + }, '查询') + ); + } + + function renderDrillToolbar(onBack, title, subtitle) { + return React.createElement('div', { className: 'vm-table-toolbar bdl-drill-toolbar ldb-table-toolbar' }, + React.createElement('button', { 'data-vm-icon': 'x', + type: 'button', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', + onClick: onBack + }, '返回上一步'), + React.createElement('div', { className: 'bdl-drill-heading' }, + React.createElement('h3', { className: 'bdl-drill-title' }, title), + subtitle ? React.createElement('p', { className: 'bdl-drill-subtitle' }, subtitle) : null + ), + React.createElement('div', { className: 'vm-table-actions bdl-drill-toolbar-spacer', 'aria-hidden': true }) + ); + } + + function renderDrillToolbarInline(onBack, title) { + return React.createElement('div', { className: 'vm-table-toolbar bdl-drill-toolbar bdl-drill-toolbar--inline ldb-table-toolbar' }, + React.createElement('button', { 'data-vm-icon': 'x', + type: 'button', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', + onClick: onBack + }, '返回上一步'), React.createElement('h3', { className: 'bdl-drill-title' }, title) ); } @@ -1051,6 +1125,19 @@ const Component = function () { var custByMonthFilterApplied = custByMonthFilterAppliedState[0]; var setCustByMonthFilterApplied = custByMonthFilterAppliedState[1]; + /** 汇总维度:按月份(默认) / 按客户 */ + var summaryDimensionState = useState('month'); + var summaryDimension = summaryDimensionState[0]; + var setSummaryDimension = summaryDimensionState[1]; + + var custByMonthFilterDraftState = useState({ customerName: undefined }); + var custByMonthFilterDraft = custByMonthFilterDraftState[0]; + var setCustByMonthFilterDraft = custByMonthFilterDraftState[1]; + + var custByMonthFilterAppliedState = useState({ customerName: undefined }); + var custByMonthFilterApplied = custByMonthFilterAppliedState[0]; + var setCustByMonthFilterApplied = custByMonthFilterAppliedState[1]; + var viewState = useState('main'); var view = viewState[0]; var setView = viewState[1]; @@ -1586,10 +1673,12 @@ const Component = function () { key: sec.key + 'Perf', width: BDL_COL_WIDTH, align: 'right', + className: 'col-amount', render: function (v, r) { if (sec.key === 'lease' && numOrZero(v) !== 0) { return React.createElement('button', { - type: 'button', + 'data-vm-icon': 'rotate-ccw', + type: 'button', className: 'biz-standbook-perf-link', onClick: function () { openLeaseDrill(r, sec.key + 'Perf', v); } }, fmtLedgerCell(v)); @@ -1640,38 +1729,41 @@ const Component = function () { }); }); - cols.push( - { - title: '业绩合计', - dataIndex: 'rowTotalPerf', - key: 'rowTotalPerf', - width: BDL_SUMMARY_COL_WIDTH, - align: 'right', - fixed: 'right', - className: 'col-summary', - render: function (v) { return fmtLedgerCell(v); } - }, - { - title: '成本合计', - dataIndex: 'rowTotalCost', - key: 'rowTotalCost', - width: BDL_SUMMARY_COL_WIDTH, - align: 'right', - fixed: 'right', - className: 'col-summary', - render: function (v) { return fmtLedgerCell(v); } - }, - { - title: '盈亏合计', - dataIndex: 'rowTotalProfit', - key: 'rowTotalProfit', - width: BDL_SUMMARY_COL_WIDTH, - align: 'right', - fixed: 'right', - className: 'col-summary', - render: function (v) { return renderProfitCell(v); } - } - ); + cols.push({ + title: '合计', + key: 'grp-row-total', + align: 'center', + fixed: 'right', + children: [ + { + title: '业绩合计', + dataIndex: 'rowTotalPerf', + key: 'rowTotalPerf', + width: BDL_SUMMARY_COL_WIDTH, + align: 'right', + className: 'col-summary', + render: function (v) { return fmtLedgerCell(v); } + }, + { + title: '成本合计', + dataIndex: 'rowTotalCost', + key: 'rowTotalCost', + width: BDL_SUMMARY_COL_WIDTH, + align: 'right', + className: 'col-summary', + render: function (v) { return fmtLedgerCell(v); } + }, + { + title: '盈亏合计', + dataIndex: 'rowTotalProfit', + key: 'rowTotalProfit', + width: BDL_SUMMARY_COL_WIDTH, + align: 'right', + className: 'col-summary', + render: function (v) { return renderProfitCell(v); } + } + ] + }); return cols; }, [openLeaseDrill, openHydrogenDrill]); @@ -1685,7 +1777,7 @@ const Component = function () { dataIndex: 'monthLabel', key: 'monthLabel', fixed: 'left', - width: 88, + width: 96, align: 'center', onCell: function (record) { return { rowSpan: record.monthSpan }; @@ -1709,6 +1801,7 @@ const Component = function () { key: 'cust-' + sec.key, width: BDL_COL_WIDTH, align: 'right', + className: 'col-amount', render: function (v) { return renderMetricCell(v, isProfit); } }); }); @@ -1813,6 +1906,7 @@ const Component = function () { ), React.createElement('div', { className: 'vm-filter-actions ldb-filter-actions' }, React.createElement('button', { + 'data-vm-icon': 'rotate-ccw', type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: function () { @@ -1821,28 +1915,14 @@ const Component = function () { } }, '重置'), React.createElement('button', { + 'data-vm-icon': 'search', type: 'button', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: function () { handleQuery(); if (isCustomerDim) handleCustByMonthQuery(); } - }, - React.createElement('svg', { - xmlns: 'http://www.w3.org/2000/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: 8 }), - React.createElement('path', { d: 'm21 21-4.3-4.3' }) - ), - '查询' - ) + }, '查询') ) ), React.createElement('section', { className: 'vm-table-section' }, @@ -1859,17 +1939,17 @@ const Component = function () { ) : null ), React.createElement('div', { className: 'vm-table-actions' }, - React.createElement('button', { + React.createElement('button', { 'data-vm-icon': 'download', type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: handleExport - }, '导出') + }, '导出') ) ), React.createElement('div', { className: 'vm-table-card', 'data-annotation-id': 'bdl-list-table' }, React.createElement('div', { className: 'vm-table-wrap' }, isCustomerDim ? React.createElement(Table, { - className: 'vm-list-table bdl-list-table biz-standbook-table', + className: 'vm-list-table bdl-list-table bdl-list-table--grouped biz-standbook-table', size: 'small', bordered: true, tableLayout: 'fixed', @@ -1880,7 +1960,7 @@ const Component = function () { rowClassName: function () { return 'bdl-row-month'; }, scroll: { x: BDL_CUSTOMER_SCROLL_X, - y: 'max(calc(100vh - var(--bdl-list-chrome-offset, 420px)), 280px)' + y: BDL_TABLE_SCROLL_Y }, locale: { emptyText: '当前筛选条件下暂无客户汇总数据' }, summary: custRows.length ? function () { @@ -1903,7 +1983,7 @@ const Component = function () { ); } : undefined }) : React.createElement(Table, { - className: 'vm-list-table bdl-list-table biz-standbook-table', + className: 'vm-list-table bdl-list-table bdl-list-table--grouped bdl-list-table--nested biz-standbook-table', size: 'small', bordered: true, tableLayout: 'fixed', @@ -1914,7 +1994,7 @@ const Component = function () { rowClassName: rowClassName, scroll: { x: BDL_LEDGER_SCROLL_X, - y: 'max(calc(100vh - var(--bdl-list-chrome-offset, 420px)), 280px)' + y: BDL_TABLE_SCROLL_Y } }) ) diff --git a/src/prototypes/oneos-web-data-analysis/pages/06-业务部业绩明细.jsx b/src/prototypes/oneos-web-data-analysis/pages/06-业务部业绩明细.jsx index 9ae4170..5c2fa7c 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/06-业务部业绩明细.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/06-业务部业绩明细.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -793,12 +792,7 @@ const Component = function () { ]; return React.createElement(App, null, - React.createElement('div', { style: layoutStyle }, - React.createElement(Breadcrumb, { - style: { marginBottom: 16 }, - items: [{ title: '数据分析' }, { title: '业务部业绩' }] - }), - React.createElement(Card, null, + React.createElement('div', { style: layoutStyle }, React.createElement(Card, null, React.createElement('style', null, tabsBarStyle), React.createElement(Tabs, { className: 'biz-dept-perf-tabs', diff --git a/src/prototypes/oneos-web-data-analysis/pages/07-业务台账.jsx b/src/prototypes/oneos-web-data-analysis/pages/07-业务台账.jsx index e51d1cb..cedac61 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/07-业务台账.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/07-业务台账.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -819,19 +818,9 @@ const Component = function () { ); }; - var breadcrumbItems = [{ title: '数据分析' }, { title: '业务台账' }]; - if (view === 'sales' || view === 'project') { - breadcrumbItems.push({ title: showSelfLeaseDrill ? '业务员汇总' : '业务员业绩' }); - } - if (view === 'project') { - breadcrumbItems.push({ title: '项目明细' }); - } - return React.createElement(App, null, React.createElement('style', null, ledgerTableStyle), - React.createElement('div', { style: layoutStyle }, - React.createElement(Breadcrumb, { style: { marginBottom: 14 }, items: breadcrumbItems }), - view === 'main' ? renderMainView() : null, + React.createElement('div', { style: layoutStyle }, view === 'main' ? renderMainView() : null, view === 'sales' ? renderSalesView() : null, view === 'project' ? renderProjectView() : null ) diff --git a/src/prototypes/oneos-web-data-analysis/pages/08-租赁车辆收入明细.jsx b/src/prototypes/oneos-web-data-analysis/pages/08-租赁车辆收入明细.jsx index ca8d68f..b8adcf9 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/08-租赁车辆收入明细.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/08-租赁车辆收入明细.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -782,12 +781,7 @@ const Component = function () { ]; return React.createElement(App, null, - React.createElement('div', { style: layoutStyle }, - React.createElement(Breadcrumb, { - style: { marginBottom: 16 }, - items: [{ title: '数据分析' }, { title: '租赁车辆收入明细' }] - }), - React.createElement(Card, null, + React.createElement('div', { style: layoutStyle }, React.createElement(Card, null, React.createElement('style', null, tabsBarStyle), React.createElement(Tabs, { className: 'lease-income-tabs', diff --git a/src/prototypes/oneos-web-data-analysis/pages/09-租赁客户氢费台账.jsx b/src/prototypes/oneos-web-data-analysis/pages/09-租赁客户氢费台账.jsx index 40ce883..b20ad32 100644 --- a/src/prototypes/oneos-web-data-analysis/pages/09-租赁客户氢费台账.jsx +++ b/src/prototypes/oneos-web-data-analysis/pages/09-租赁客户氢费台账.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -675,12 +674,7 @@ const Component = function () { ]; return React.createElement(App, null, - React.createElement('div', { style: layoutStyle }, - React.createElement(Breadcrumb, { - style: { marginBottom: 16 }, - items: [{ title: '数据分析' }, { title: '租赁客户氢费台账' }] - }), - React.createElement(Card, null, + React.createElement('div', { style: layoutStyle }, React.createElement(Card, null, React.createElement('style', null, tabsBarStyle), React.createElement(Tabs, { className: 'lease-h2-ledger-tabs', diff --git a/src/prototypes/oneos-web-finance/index.tsx b/src/prototypes/oneos-web-finance/index.tsx index f3cbebe..a96ee49 100644 --- a/src/prototypes/oneos-web-finance/index.tsx +++ b/src/prototypes/oneos-web-finance/index.tsx @@ -1,26 +1,14 @@ /** - * @name 财务管理 - * 自 ONE-OS web端 原稿复刻(7 个页面) + * @name 财务管理(合包) + * 提车应收款已独立为 vehicle-pickup-receivable;本合包保留租赁账单等页面 */ import '../../common/oneosWebLegacy/legacyGlobals'; import React from 'react'; -import Page1 from './pages/04-提车收款单-编辑.jsx'; -import Page2 from './pages/05-提车应收款-查看.jsx'; -import Page3 from './pages/06-提车应收款-开票信息.jsx'; -import Page4 from './pages/07-提车应收款-审核.jsx'; -import Page5 from './pages/08-提车应收款-提车收款单.jsx'; -import Page6 from './pages/09-提车应收款.jsx'; -import Page7 from './pages/10-租赁账单.jsx'; +import Page1 from './pages/10-租赁账单.jsx'; import { OneosWebLegacyShell } from '../../common/oneosWebLegacy/OneosWebLegacyShell'; const pages = [ - { id: 'u8d22u52a1u7ba1u7406-u63d0u8f66u6536u6b3eu5355-u', title: '提车收款单-编辑', component: Page1 }, - { id: 'u8d22u52a1u7ba1u7406-u63d0u8f66u5e94u6536u6b3e-u', title: '提车应收款-查看', component: Page2 }, - { id: 'u8d22u52a1u7ba1u7406-u63d0u8f66u5e94u6536u6b3e-u-6', title: '提车应收款-开票信息', component: Page3 }, - { id: 'u8d22u52a1u7ba1u7406-u63d0u8f66u5e94u6536u6b3e-u-7', title: '提车应收款-审核', component: Page4 }, - { id: 'u8d22u52a1u7ba1u7406-u63d0u8f66u5e94u6536u6b3e-u-8', title: '提车应收款-提车收款单', component: Page5 }, - { id: 'u8d22u52a1u7ba1u7406-u63d0u8f66u5e94u6536u6b3e', title: '提车应收款', component: Page6 }, - { id: 'u8d22u52a1u7ba1u7406-u79dfu8d41u8d26u5355', title: '租赁账单', component: Page7 }, + { id: 'u8d22u52a1u7ba1u7406-u79dfu8d41u8d26u5355', title: '租赁账单', component: Page1 }, ]; export default function OneosWebFinance() { @@ -28,7 +16,7 @@ export default function OneosWebFinance() { ); } diff --git a/src/prototypes/oneos-web-finance/pages/01-还车应结款-查看.jsx b/src/prototypes/oneos-web-finance/pages/01-还车应结款-查看.jsx index db0edec..1b8b6e9 100644 --- a/src/prototypes/oneos-web-finance/pages/01-还车应结款-查看.jsx +++ b/src/prototypes/oneos-web-finance/pages/01-还车应结款-查看.jsx @@ -5,7 +5,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -431,10 +430,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '财务管理' }, { title: '还车应结款' }, { title: '查看' }] }) - ), - + React.createElement(Card, { title: '还车车辆明细', style: cardStyle }, React.createElement(Table, { rowKey: 'key', columns: vehicleColumns, dataSource: vehicleDetail, pagination: false, bordered: true, size: 'middle', scroll: { x: 980 } }) ), diff --git a/src/prototypes/oneos-web-finance/pages/02-还车应结款-费用明细.jsx b/src/prototypes/oneos-web-finance/pages/02-还车应结款-费用明细.jsx index fda7ab1..a582977 100644 --- a/src/prototypes/oneos-web-finance/pages/02-还车应结款-费用明细.jsx +++ b/src/prototypes/oneos-web-finance/pages/02-还车应结款-费用明细.jsx @@ -9,7 +9,6 @@ const Component = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -1237,9 +1236,7 @@ var requirementDocContent = useMemo(function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '财务管理' }, { title: '还车应结款' }] }), - React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '还车车辆明细', style: cardStyle }, diff --git a/src/prototypes/oneos-web-finance/pages/03-还车应结款.jsx b/src/prototypes/oneos-web-finance/pages/03-还车应结款.jsx index d61dba7..6a43a7e 100644 --- a/src/prototypes/oneos-web-finance/pages/03-还车应结款.jsx +++ b/src/prototypes/oneos-web-finance/pages/03-还车应结款.jsx @@ -1,5 +1,6 @@ import { StatusTag } from '../../vehicle-management/components/StatusTag'; import { TablePagination, DEFAULT_PAGE_SIZE } from '../../../common/TablePagination'; +import { OperationActions } from '../../../common/OperationActions'; var ONEOS_ANT_TABLE_GLOBAL_FIX = [ '.ant-table-container .ant-table-header { margin-bottom: 0 !important; }', @@ -892,19 +893,27 @@ var requirementDocContent = useMemo(function () { { title: '操作', key: 'action', - width: 200, + width: 168, fixed: 'right', render: function (_, r) { var st = String(r && r.approvalStatus); var showFeeDetail = !(st === '待审批' || st === '审批中' || st === '审批完成'); var showRevoke = (st === '待审批' || st === '审批中'); var showGenerateBill = String(r && r.approvalStatus) === '待审批'; - return React.createElement('span', { className: 'vrs-action-links' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleView(r); } }, '查看'), - showGenerateBill ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleGenerateBill(r); } }, '生成账单') : null, - showFeeDetail ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleFeeDetail(r); } }, '去处理') : null, - showRevoke ? React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { handleRevoke(r); } }, '撤回') : null - ); + var more = []; + if (showGenerateBill) { + more.push({ key: 'generateBill', label: '生成账单', onClick: function () { handleGenerateBill(r); } }); + } + if (showFeeDetail) { + more.push({ key: 'feeDetail', label: '去处理', onClick: function () { handleFeeDetail(r); } }); + } + if (showRevoke) { + more.push({ key: 'revoke', label: '撤回', danger: true, onClick: function () { handleRevoke(r); } }); + } + return React.createElement(OperationActions, { + view: { onClick: function () { handleView(r); } }, + more: more + }); } } ]; @@ -1011,12 +1020,14 @@ return React.createElement(App, null, ? React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-link ldb-filter-toggle', + 'data-vm-icon': filterExpanded ? 'chevron-up' : 'filter', onClick: function () { setFilterExpanded(!filterExpanded); } }, filterExpanded ? '收起' : '更多筛选') : null, - React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: resetFilters }, '重置'), - React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: handleQuery }, - SearchIconSvg(), + React.createElement('button', { 'data-vm-icon': 'rotate-ccw', type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: resetFilters }, '重置'), + React.createElement('button', { + 'data-vm-icon': 'search', + type: 'button', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: handleQuery }, '查询' ) ) @@ -1026,10 +1037,10 @@ return React.createElement(App, null, React.createElement('div', { className: 'vm-table-actions' }, React.createElement('button', { type: 'button', - className: 'vm-btn vm-btn-ghost', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', + 'data-vm-icon': 'download', onClick: function () { openBillModal('export', (filteredData && filteredData[0]) || null); } }, - ExportIconSvg(), '导出' ) ) @@ -1067,7 +1078,8 @@ return React.createElement(App, null, open: billModalOpenState[0], onCancel: function () { billModalOpenState[1](false); }, footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { type: 'primary', onClick: printBill }, '打印'), + React.createElement(Button, { type: 'primary', onClick: printBill + }, '打印'), React.createElement(Button, { onClick: function () { billModalOpenState[1](false); } }, '返回') ), title: (billModalModeState[0] === 'generate' ? '生成账单' : '导出账单'), @@ -1140,7 +1152,6 @@ const ComponentFeeDetail = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -2195,9 +2206,7 @@ var requirementDocContent = useMemo(function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '财务管理' }, { title: '还车应结款' }] }), - React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '还车车辆明细', style: cardStyle }, diff --git a/src/prototypes/oneos-web-finance/pages/04-提车收款单-编辑.jsx b/src/prototypes/oneos-web-finance/pages/04-提车收款单-编辑.jsx index ceeb2ba..cafb700 100644 --- a/src/prototypes/oneos-web-finance/pages/04-提车收款单-编辑.jsx +++ b/src/prototypes/oneos-web-finance/pages/04-提车收款单-编辑.jsx @@ -9,7 +9,6 @@ const Component = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Button = antd.Button; @@ -253,15 +252,7 @@ const Component = function () { var activeServiceIndex = servicePopoverRowIndex[0]; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '财务管理' }, - { title: '提车应收款' }, - { title: '提车收款单-编辑' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') ), React.createElement(Card, { title: '项目信息', diff --git a/src/prototypes/oneos-web-finance/pages/05-提车应收款-查看.jsx b/src/prototypes/oneos-web-finance/pages/05-提车应收款-查看.jsx index 9d80088..c324761 100644 --- a/src/prototypes/oneos-web-finance/pages/05-提车应收款-查看.jsx +++ b/src/prototypes/oneos-web-finance/pages/05-提车应收款-查看.jsx @@ -6,7 +6,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Popover = antd.Popover; @@ -152,16 +151,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '财务管理' }, - { title: '提车应收款' }, - { title: '提车收款单' } - ] - }) - ), - React.createElement(Card, { title: '项目信息', style: cardStyle }, + React.createElement(Card, { title: '项目信息', style: cardStyle }, React.createElement('div', { style: formRowStyle }, React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '提车收款单编码'), React.createElement('div', { style: valueStyle }, projectInfo.collectCode || '—')), React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '合同编码'), React.createElement('div', { style: valueStyle }, projectInfo.contractCode || '—')), diff --git a/src/prototypes/oneos-web-finance/pages/06-提车应收款-开票信息.jsx b/src/prototypes/oneos-web-finance/pages/06-提车应收款-开票信息.jsx index b7ea105..d4cd5db 100644 --- a/src/prototypes/oneos-web-finance/pages/06-提车应收款-开票信息.jsx +++ b/src/prototypes/oneos-web-finance/pages/06-提车应收款-开票信息.jsx @@ -9,7 +9,6 @@ const Component = function () { var useEffect = React.useEffect; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Button = antd.Button; @@ -255,15 +254,7 @@ const Component = function () { var thBase = { padding: '10px 12px', border: '1px solid #f0f0f0', whiteSpace: 'nowrap' }; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '财务管理' }, - { title: '提车应收款' }, - { title: '开票信息' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '项目信息', diff --git a/src/prototypes/oneos-web-finance/pages/07-提车应收款-审核.jsx b/src/prototypes/oneos-web-finance/pages/07-提车应收款-审核.jsx index 6dbee66..88ad38b 100644 --- a/src/prototypes/oneos-web-finance/pages/07-提车应收款-审核.jsx +++ b/src/prototypes/oneos-web-finance/pages/07-提车应收款-审核.jsx @@ -6,7 +6,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Modal = antd.Modal; @@ -166,15 +165,7 @@ const Component = function () { }; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '财务管理' }, - { title: '提车应收款' }, - { title: '审批' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') ), React.createElement(Card, { title: '项目信息', diff --git a/src/prototypes/oneos-web-finance/pages/08-提车应收款-提车收款单.jsx b/src/prototypes/oneos-web-finance/pages/08-提车应收款-提车收款单.jsx index 78dc359..3da0ffc 100644 --- a/src/prototypes/oneos-web-finance/pages/08-提车应收款-提车收款单.jsx +++ b/src/prototypes/oneos-web-finance/pages/08-提车应收款-提车收款单.jsx @@ -9,7 +9,6 @@ const Component = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Button = antd.Button; @@ -19,10 +18,20 @@ const Component = function () { var Tooltip = antd.Tooltip; var Modal = antd.Modal; var message = antd.message; + var DatePicker = antd.DatePicker; + var Alert = antd.Alert; var Option = Select.Option; + var dayjs = window.dayjs; + + var fmtMoney = window.__oneosFormatMoney || function (v) { + var n = parseFloat(v); + if (!Number.isFinite(n)) n = 0; + return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + }; + function fmtYuan(v) { + return fmtMoney(v) + ' 元'; + } - var requirementModalVisible = useState(false); - var setRequirementModalVisible = requirementModalVisible[1]; var edited = useState(false); var setEdited = edited[1]; var submitConfirmVisible = useState(false); @@ -36,8 +45,32 @@ const Component = function () { var servicePopoverRowIndex = useState(null); var setServicePopoverRowIndex = servicePopoverRowIndex[1]; - // 模拟:项目信息(实际由路由/接口拉取) + var handleCtx = typeof window !== 'undefined' ? window.__pickupHandleContext : null; + var pickupCalc = typeof window !== 'undefined' ? window.__pickupCalc : null; + + var pickupDateState = useState(function () { + return (handleCtx && handleCtx.pickupDate) ? handleCtx.pickupDate : '2026-03-12'; + }); + var pickupDate = pickupDateState[0]; + var setPickupDate = pickupDateState[1]; + + // 项目信息:优先读取办理上下文(来自提车应收款列表) var projectInfo = useMemo(function () { + if (handleCtx) { + return { + collectCode: handleCtx.collectCode || '', + contractCode: handleCtx.contractCode || '', + contractType: handleCtx.contractType || '', + projectName: handleCtx.projectName || '', + customerName: handleCtx.customerName || '', + paymentMethod: handleCtx.paymentMethod || '预付', + paymentCycle: handleCtx.paymentCycle || '6个月', + contractStart: handleCtx.contractStart || '', + contractEnd: handleCtx.contractEnd || '', + businessDept: handleCtx.businessDept || '', + businessPerson: handleCtx.businessPerson || '' + }; + } return { collectCode: 'HT-ZL-2025-001TC0001', contractCode: 'HT-ZL-2025-001', @@ -51,11 +84,15 @@ const Component = function () { businessDept: '业务1部', businessPerson: '张经理' }; - }, []); + }, [handleCtx]); - // 模拟:车辆应收款明细(租赁合同中所有车辆),付款周期 6 个月 => 应收月租金 = 月租金*6 - var paymentCycleMonths = 6; - var vehicleRows = useState([ + var paymentCycleMonths = handleCtx && handleCtx.paymentCycleMonths + ? handleCtx.paymentCycleMonths + : 6; + + var defaultVehicles = handleCtx && handleCtx.vehicles && handleCtx.vehicles.length + ? handleCtx.vehicles + : [ { key: 'v1', index: 1, brand: '东风', model: 'DFH1180', plateNo: '浙A12345', monthRent: 5000, receivableRent: 30000, actualRent: '30000.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 200, actual: '200.00', discount: '0.00', remark: '' }, { name: '保险上浮', receivable: 500, actual: '500.00', discount: '0.00', remark: '' }], receivableService: 700, actualService: '700.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, { key: 'v2', index: 2, brand: '福田', model: 'BJ1180', plateNo: '浙A23456', monthRent: 4500, receivableRent: 27000, actualRent: '27000.00', rentRemark: '', receivableDeposit: 8000, serviceItems: [{ name: '保养费用', receivable: 300, actual: '300.00', discount: '0.00', remark: '' }], receivableService: 300, actualService: '300.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, { key: 'v3', index: 3, brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567', monthRent: 5200, receivableRent: 31200, actualRent: '31200.00', rentRemark: '', receivableDeposit: 10000, serviceItems: [{ name: '代处理费用', receivable: 180, actual: '180.00', discount: '0.00', remark: '' }, { name: '上牌服务', receivable: 400, actual: '400.00', discount: '0.00', remark: '' }], receivableService: 580, actualService: '580.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: true, receivableCompleted: true }, @@ -76,12 +113,36 @@ const Component = function () { { key: 'v18', index: 18, brand: '江淮', model: '格尔发K5', plateNo: '浙B56789', monthRent: 4400, receivableRent: 26400, actualRent: '26400.00', rentRemark: '', receivableDeposit: 8800, serviceItems: [{ name: '保养费用', receivable: 270, actual: '270.00', discount: '0.00', remark: '' }], receivableService: 270, actualService: '270.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false }, { key: 'v19', index: 19, brand: '东风', model: 'DFH1250', plateNo: '浙B67890', monthRent: 5800, receivableRent: 34800, actualRent: '34800.00', rentRemark: '', receivableDeposit: 11600, serviceItems: [{ name: '代处理费用', receivable: 240, actual: '240.00', discount: '0.00', remark: '' }], receivableService: 240, actualService: '240.00', discountAmount: '200.00', discountRemark: '客户协商', discountProof: [], selected: false }, { key: 'v20', index: 20, brand: '福田', model: '欧曼EST', plateNo: '浙B78901', monthRent: 4650, receivableRent: 27900, actualRent: '27900.00', rentRemark: '', receivableDeposit: 9300, serviceItems: [{ name: '上牌服务', receivable: 390, actual: '390.00', discount: '0.00', remark: '' }, { name: '保养费用', receivable: 290, actual: '290.00', discount: '0.00', remark: '' }], receivableService: 680, actualService: '680.00', discountAmount: '0.00', discountRemark: '', discountProof: [], selected: false } - ]); + ]; + + var vehicleRows = useState(defaultVehicles); var vehicles = vehicleRows[0]; var setVehicles = vehicleRows[1]; + function recalcVehiclesByPickupDate(nextDate, prevVehicles) { + if (!pickupCalc || !pickupCalc.applyPickupDateToVehicles) return prevVehicles; + return pickupCalc.applyPickupDateToVehicles(prevVehicles, nextDate, paymentCycleMonths); + } + + useEffect(function () { + if (!pickupDate) return; + setVehicles(function (prev) { + return recalcVehiclesByPickupDate(pickupDate, prev); + }); + }, [pickupDate, paymentCycleMonths]); + + var batchSummary = useMemo(function () { + if (!pickupCalc || !pickupCalc.buildBatchPickupScheduleSummary) return null; + return pickupCalc.buildBatchPickupScheduleSummary(vehicles, pickupDate, paymentCycleMonths); + }, [vehicles, pickupDate, paymentCycleMonths]); + + var pickupRuleText = useMemo(function () { + if (!pickupCalc || !pickupCalc.describePickupRule || !batchSummary) return ''; + return pickupCalc.describePickupRule(batchSummary.rule); + }, [batchSummary]); + // 氢费预付款(非预付款模式时不显示) - var hasHydrogenPrepay = true; + var hasHydrogenPrepay = !handleCtx || handleCtx.paymentMethod !== '后付'; var hydrogenRow = useState({ receivable: '3580.00', actual: '3580.00', @@ -174,14 +235,18 @@ const Component = function () { var hydrogenActual = hasHydrogenPrepay ? (parseFloat(hydrogen.actual) || 0) : 0; var receivableTotal = useMemo(function () { - var base = parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService); + var base = batchSummary ? batchSummary.totalReceivable : ( + parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService) + ); return (base + hydrogenReceivable).toFixed(2); - }, [totals, hydrogenReceivable]); + }, [totals, hydrogenReceivable, batchSummary]); var actualTotal = useMemo(function () { - var base = parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal); + var base = batchSummary ? batchSummary.beforePickup.amount : ( + parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal) + ); return (base + hydrogenActual).toFixed(2); - }, [totals, hydrogenActual]); + }, [totals, hydrogenActual, batchSummary]); var invoiceTotal = useMemo(function () { var base = parseFloat(totals.actualRent) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal); @@ -189,15 +254,26 @@ const Component = function () { }, [totals, hydrogenActual]); var receivablePopoverContent = useMemo(function () { - var rows = [ - React.createElement('tr', { key: 'rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableRent + ' 元')), - React.createElement('tr', { key: 'deposit' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), - React.createElement('tr', { key: 'service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableService + ' 元')) - ]; - if (hasHydrogenPrepay) { - rows.push(React.createElement('tr', { key: 'hydrogen' }, React.createElement('td', { style: { padding: '6px 8px' } }, '氢费预充值应收金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (hydrogen.receivable || '0.00') + ' 元'))); + var rows = []; + if (batchSummary) { + rows.push(React.createElement('tr', { key: 'before-deposit' }, React.createElement('td', { style: { padding: '6px 8px' } }, '提车前2日内 · 保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(batchSummary.beforePickup.deposit)))); + rows.push(React.createElement('tr', { key: 'before-rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '提车前2日内 · 租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(batchSummary.beforePickup.rent)))); + rows.push(React.createElement('tr', { key: 'before-service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '提车前2日内 · 服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(batchSummary.beforePickup.service)))); + if (batchSummary.month25.amount > 0) { + rows.push(React.createElement('tr', { key: 'm25-rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '当月25日 · 下周期租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(batchSummary.month25.rent)))); + rows.push(React.createElement('tr', { key: 'm25-service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '当月25日 · 下周期服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(batchSummary.month25.service)))); + } + } else { + rows = [ + React.createElement('tr', { key: 'rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(totals.receivableRent))), + React.createElement('tr', { key: 'deposit' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(totals.receivableDeposit))), + React.createElement('tr', { key: 'service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(totals.receivableService))) + ]; } - return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, + if (hasHydrogenPrepay) { + rows.push(React.createElement('tr', { key: 'hydrogen' }, React.createElement('td', { style: { padding: '6px 8px' } }, '氢费预充值应收金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(hydrogen.receivable)))); + } + return React.createElement('div', { style: { padding: 8, minWidth: 260 } }, React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, React.createElement('thead', null, React.createElement('tr', null, @@ -208,17 +284,17 @@ const Component = function () { React.createElement('tbody', null, rows) ) ); - }, [totals, hasHydrogenPrepay, hydrogen.receivable]); + }, [totals, hasHydrogenPrepay, hydrogen.receivable, batchSummary]); var actualPopoverContent = useMemo(function () { var rows = [ - React.createElement('tr', { key: 'rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualRent + ' 元')), - React.createElement('tr', { key: 'deposit' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')), - React.createElement('tr', { key: 'service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualService + ' 元')), - React.createElement('tr', { key: 'discount' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.discountTotal + ' 元')) + React.createElement('tr', { key: 'rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(totals.actualRent))), + React.createElement('tr', { key: 'deposit' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(totals.receivableDeposit))), + React.createElement('tr', { key: 'service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(totals.actualService))), + React.createElement('tr', { key: 'discount' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(totals.discountTotal))) ]; if (hasHydrogenPrepay) { - rows.push(React.createElement('tr', { key: 'hydrogen' }, React.createElement('td', { style: { padding: '6px 8px' } }, '氢费预充值实收金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (hydrogen.actual || '0.00') + ' 元'))); + rows.push(React.createElement('tr', { key: 'hydrogen' }, React.createElement('td', { style: { padding: '6px 8px' } }, '氢费预充值实收金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' }, className: 'tabular-nums' }, fmtYuan(hydrogen.actual)))); } return React.createElement('div', { style: { padding: 8, minWidth: 220 } }, React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } }, @@ -234,17 +310,65 @@ const Component = function () { }, [totals, hasHydrogenPrepay, hydrogen.actual]); var handleSubmit = useCallback(function () { + if (!pickupDate) { + message.warning('请先设置提车日期'); + return; + } + var selected = vehicles.filter(function (v) { return v.selected && !v.receivableCompleted; }); + if (!selected.length) { + message.warning('请至少勾选一辆本次提车车辆'); + return; + } setSubmitConfirmVisible(true); - }, []); + }, [pickupDate, vehicles]); var handleSubmitConfirm = useCallback(function () { - message.success('提车应收款已提交审核'); + if (!pickupDate) { + message.warning('请先设置提车日期'); + return; + } + var selected = vehicles.filter(function (v) { return v.selected && !v.receivableCompleted; }); + if (!selected.length) { + message.warning('请至少勾选一辆本次提车车辆'); + return; + } + var deliveryVehicles = selected.map(function (v) { + return { brand: v.brand, model: v.model, plateNo: v.plateNo, seq: v.index }; + }); + var payload = { + pickupDate: pickupDate, + receivableSchedule: batchSummary, + deliveryVehicles: deliveryVehicles, + receivableRent: batchSummary + ? (batchSummary.beforePickup.rent || 0) + (batchSummary.month25.rent || 0) + : parseFloat(totals.receivableRent) || 0, + receivableDeposit: batchSummary + ? (batchSummary.beforePickup.deposit || 0) + : parseFloat(totals.receivableDeposit) || 0, + receivableService: batchSummary + ? (batchSummary.beforePickup.service || 0) + (batchSummary.month25.service || 0) + : parseFloat(totals.receivableService) || 0, + receivedRent: parseFloat(totals.actualRent) || 0, + receivedDeposit: parseFloat(totals.receivableDeposit) || 0, + receivedService: parseFloat(totals.actualService) || 0, + receivableTotal: parseFloat(receivableTotal) || 0, + actualTotal: parseFloat(actualTotal) || 0, + discountTotal: parseFloat(totals.discountTotal) || 0, + arrivalAmount: 0, + financeReceived: 0, + }; + if (window.__pickupHandleSubmit) window.__pickupHandleSubmit(payload); + var ccMsg = window.__pickupBuildSubmitCcMessage + ? window.__pickupBuildSubmitCcMessage() + : '已快速生成提车应收款单据,审批中心已抄送「财务」'; + message.success(ccMsg); setSubmitConfirmVisible(false); if (window.__receivableSubmit) window.__receivableSubmit(); - }, []); + }, [vehicles, pickupDate, batchSummary, receivableTotal, actualTotal, totals.discountTotal]); var handleSave = useCallback(function () { message.success('保存成功'); + setEdited(false); if (window.__receivableBack) window.__receivableBack(); else message.info('跳转至提车应收款列表页(原型)'); }, []); @@ -261,19 +385,43 @@ const Component = function () { if (window.__receivableBack) window.__receivableBack(); else message.info('返回提车应收款列表(原型)'); }, []); - var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' }; - var cardStyle = { marginBottom: 16 }; var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; var requiredStar = { color: '#ff4d4f', marginRight: 2 }; - var formRowStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', marginBottom: 16 }; var formItemStyle = { marginBottom: 12 }; - var highlightStyle = { color: '#1890ff', fontWeight: 600, cursor: 'pointer' }; - var valueStyle = { color: 'rgba(0,0,0,0.85)', fontSize: 14, lineHeight: '22px', minHeight: 22 }; var thBase = { padding: '10px 12px', border: '1px solid #f0f0f0', whiteSpace: 'nowrap' }; - var requirementContent = '提车应收款-提车收款单(2026年3月10日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」「提车收款单」模块\n#面包屑:财务管理-提车应收款-提车收款单;\n\n页面分为3个卡片,业务流程请参考流程图;\n1.项目信息:\n#显示项目详细信息,包括:\n1.1.合同编码:显示该租赁合同对应合同编码;\n1.2.合同类型:显示该租赁合同对应合同类型;\n1.3.项目名称:显示该租赁合同对应项目名称;\n1.4.客户名称:显示该租赁合同对应客户名称;\n1.5.付款方式:显示该租赁合同对应付款方式,分为:预付、后付两种,根据实际合同反写;\n1.6.付款周期:显示该租赁合同对应付款周期,格式为:x个月;\n1.7.合同生效时间:显示该租赁合同对应合同生效时间;\n1.8.合同结束时间:显示该租赁合同对应合同结束时间;\n1.9.业务部门:显示该租赁合同对应业务部门名称;\n1.10.业务负责人:显示该租赁合同对应业务负责人;\n\n2.提车应收款信息:\n#上方显示应收款总额、实收款总额;\n2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」+「氢费预充值应收金额」;\n 点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;\n 2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项;\n 2.1.2.金额:显示该项目对应应收金额;\n2.2.实收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆实收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆实收服务费总和」+「氢费预充值实收金额」-「选中车辆减免金额总和」;\n 点击实收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;\n 2.2.1.项目:包括总计实收车辆月租金、总计应收车辆保证金、总计实收服务费、总计减免金额四项;\n 2.2.2.金额:显示该项目对应实收金额;\n2.3.应开票总额:格式为:xx.xx元,计算方式为:「选中车辆实收车辆月租金总和」+「选中车辆实收服务费总和」-「选中车辆减免金额总和」;\n\n#中间为车辆应收款明细,以列表展示租赁合同中所有车辆明细,包括以下字段:\n2.1.全选/多选:多选按钮组,支持表头点击全选,默认为取消勾选(整个车辆清单);\n 选择代表本次提车应收款只收取该部分车辆费用,可以编辑以下相应字段,不选择则代表本次提车应收款不收取该部分车辆费用,不能编辑车辆相应字段;\n 提车应收款支持分批次进行收款,如果该车辆之前已提交提车应收款流程,则多选框不能勾选,悬浮多选框时提示:该车辆已完成提车应收款/该车辆已提交提车收款单;\n2.2.序号:根据租赁合同中车辆对应序号顺序展示;\n2.3.品牌:显示租赁合同中车辆对应品牌;\n2.4.型号:显示租赁合同中车辆对应型号;\n2.5.车牌号:显示租赁合同中车辆对应车牌号,如果车牌号为空则显示为-(后续如果交车成功,则车牌号会反显在该处);\n2.6.应收车辆月租金:根据租赁合同付款周期自动计算,例如:付款周期为6个月,则这里显示金额为:车辆月租金*6;\n2.7.实收车辆月租金:必填项,输入框,默认反写实收车辆月租金,支持修改,由业务员自行输入车辆月租金金额,精确至2位小数,后缀为元;\n2.8.车辆租金备注:选填项,输入框,由业务员自行输入;\n2.9.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元;\n2.10.减免金额备注:选填项,输入框,由业务员自行输入备注信息;\n2.11.应收车辆保证金:显示租赁合同中当前车辆保证金金额;\n2.12.服务费项目:点击管理,弹出气泡卡片,气泡卡片标题为服务费项目,下方为列表,显示服务项目、应收费用、实收费用、备注;\n 2.12.1.服务项目:显示租赁合同中所有服务项目名称;\n 2.12.2.应收费用:显示租赁合同中所有服务项目对应费用;\n 2.12.3.实收费用:必填项,输入框,默认反写应收费用,支持修改,由业务员自行输入实收费用金额,精确至2位小数,后缀为元;\n 2.12.4.减免费用:选填项,输入框,默认为:0.00,精确至2位小数,后缀为元;\n 2.12.4.备注:选填项,输入框,由业务员自行输入备注信息;\n2.13.应收服务费:显示当前车辆所有服务费应收费用总和,格式为:xx.xx元;\n2.14.实收服务费:显示当前车辆所有服务费实收费用总和,格式为:xx.xx元;\n2.15.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为0.00,支持2位小数,后缀为元;\n2.16.减免金额备注:选填项,输入框,由业务员自行输入备注信息;\n2.17.减免证明:选填项,附件上传按钮,点击后上传本地文件,支持:jpg、png、pdf等格式,支持多附件上传;\n\n列表不支持分页功能;\n#下方为氢费预付款情况,以列表展示租赁合同中所有氢费预付款明细,包括以下字段,如租赁合同氢费非预付款模式,则不显示该部分内容:\n2.19.氢费预付款应收金额:显示租赁合同中氢费预付款金额,格式为:xx.xx元,支持2位小数;\n2.20.氢费预付款实收金额:必填项,输入框,默认反写氢费预付款应收金额,支持修改,由业务员自行输入金额,格式为:xx.xx元,支持2位小数;\n2.21.减免金额:选填项,输入框,由业务员自行输入减免金额,默认为:0.00元,格式为:xx.xx元,支持2位小数;\n2.22.减免金额备注:选填项,输入框,由业务员自行输入减免金额备注信息;\n#最底部为开票方式、开票备注;\n2.23.开票方式:选择器,选项包括:先开票后付款、先付款后开票,默认为先开票后付款;\n2.24.开票备注:必填项,文本域,默认提示文本为:请输入开票项目、税率以及其他备注信息,财务将以此进行开票;\n\n3.底部为提交审核,保存,取消;\n3.1.提交审核:点击提交审核,进行二次确认,文案为:请仔细核对提车首付款实收金额,点击确认则进入工作流;\n3.2.保存:点击保存,提示保存成功,跳转至提车应收款列表页,同时该条数据审核状态为:待提交:\n3.3.取消:点击取消,进行二次确认,文案为:取消将会丢失所有已填写数据,是否确认,点击确认则跳转至提车应收款列表页;'; + function renderReadonlyField(label, value) { + return React.createElement('div', { className: 'vpr-collect-field' }, + React.createElement('div', { className: 'vpr-collect-field__label' }, label), + React.createElement('div', { className: 'vpr-collect-field__value' }, value || '-') + ); + } + + function vehicleRowClassName(row) { + if (row.receivableCompleted) return 'vpr-collect-row vpr-collect-row--done'; + if (row.selected) return 'vpr-collect-row vpr-collect-row--selected'; + return 'vpr-collect-row vpr-collect-row--idle'; + } + + function handleVehicleRowClick(row, e) { + if (row.receivableCompleted) return; + var target = e.target; + if (!target) return; + var tag = (target.tagName || '').toUpperCase(); + if (tag === 'INPUT' || tag === 'BUTTON' || tag === 'TEXTAREA' || tag === 'SELECT' || tag === 'A' || tag === 'LABEL') return; + if (target.closest && target.closest('.ant-popover, .ant-btn, .ant-input, .ant-select')) return; + toggleVehicleSelected(row.key); + } + + function handleKpiCardKeyDown(e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.currentTarget.click(); + } + } var selectableVehicles = vehicles.filter(function (v) { return !v.receivableCompleted; }); + var selectedCount = vehicles.filter(function (v) { return v.selected && !v.receivableCompleted; }).length; var allSelected = selectableVehicles.length > 0 && selectableVehicles.every(function (v) { return v.selected; }); var indeterminate = selectableVehicles.some(function (v) { return v.selected; }) && !allSelected; var headerCheckRef = useRef(null); @@ -281,88 +429,153 @@ const Component = function () { if (headerCheckRef.current) headerCheckRef.current.indeterminate = indeterminate; }, [indeterminate]); - return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '财务管理' }, - { title: '提车应收款' }, - { title: '提车收款单' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') + return React.createElement('div', { className: 'vm-page vpr-collect-page', 'data-annotation-id': 'vpr-collect' }, + React.createElement('header', { className: 'vpr-collect-topbar', 'data-annotation-id': 'vpr-collect-topbar' }, + React.createElement('div', { className: 'vpr-collect-topbar__left' }, + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-back', + onClick: handleCancel, + }, '返回'), + React.createElement('div', { className: 'vpr-collect-topbar__intro' }, + React.createElement('h1', { className: 'vpr-collect-topbar__title' }, '办理提车收款单'), + React.createElement('p', { className: 'vpr-collect-topbar__meta' }, + projectInfo.contractCode || '-', + ' · ', + projectInfo.projectName || '-', + edited[0] + ? React.createElement('span', { className: 'vpr-collect-draft-badge' }, '未保存') + : null + ) + ) + ) ), React.createElement(Card, { + className: 'vpr-collect-card', + 'data-annotation-id': 'vpr-collect-project', title: '项目信息', - style: cardStyle, - extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setProjectCardExpanded(!projectCardExpanded[0]); } }, projectCardExpanded[0] ? '收起' : '展开') + extra: React.createElement(Button, { + type: 'link', + size: 'small', + className: 'vpr-collect-card-toggle', + onClick: function () { setProjectCardExpanded(!projectCardExpanded[0]); }, + }, projectCardExpanded[0] ? '收起' : '展开') }, - projectCardExpanded[0] ? React.createElement('div', { style: formRowStyle }, - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '提车收款单编码'), - React.createElement('div', { style: valueStyle }, projectInfo.collectCode || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '合同编码'), - React.createElement('div', { style: valueStyle }, projectInfo.contractCode || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '合同类型'), - React.createElement('div', { style: valueStyle }, projectInfo.contractType || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '项目名称'), - React.createElement('div', { style: valueStyle }, projectInfo.projectName || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '客户名称'), - React.createElement('div', { style: valueStyle }, projectInfo.customerName || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '付款方式'), - React.createElement('div', { style: valueStyle }, projectInfo.paymentMethod || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '付款周期'), - React.createElement('div', { style: valueStyle }, projectInfo.paymentCycle || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '合同生效时间'), - React.createElement('div', { style: valueStyle }, projectInfo.contractStart || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '合同结束时间'), - React.createElement('div', { style: valueStyle }, projectInfo.contractEnd || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '业务部门'), - React.createElement('div', { style: valueStyle }, projectInfo.businessDept || '-') - ), - React.createElement('div', { style: formItemStyle }, - React.createElement('div', { style: labelStyle }, '业务负责人'), - React.createElement('div', { style: valueStyle }, projectInfo.businessPerson || '-') - ) + projectCardExpanded[0] ? React.createElement('div', { className: 'vpr-collect-info-grid' }, + renderReadonlyField('提车收款单编码', projectInfo.collectCode), + renderReadonlyField('合同编码', projectInfo.contractCode), + renderReadonlyField('合同类型', projectInfo.contractType), + renderReadonlyField('项目名称', projectInfo.projectName), + renderReadonlyField('客户名称', projectInfo.customerName), + renderReadonlyField('付款方式', projectInfo.paymentMethod), + renderReadonlyField('付款周期', projectInfo.paymentCycle), + renderReadonlyField('合同生效时间', projectInfo.contractStart), + renderReadonlyField('合同结束时间', projectInfo.contractEnd), + renderReadonlyField('业务部门', projectInfo.businessDept), + renderReadonlyField('业务负责人', projectInfo.businessPerson) ) : null ), React.createElement(Card, { + className: 'vpr-collect-card', + 'data-annotation-id': 'vpr-collect-form', title: '提车应收款信息', - style: cardStyle, - extra: React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setReceivableCardExpanded(!receivableCardExpanded[0]); } }, receivableCardExpanded[0] ? '收起' : '展开') + extra: React.createElement(Button, { + type: 'link', + size: 'small', + className: 'vpr-collect-card-toggle', + onClick: function () { setReceivableCardExpanded(!receivableCardExpanded[0]); }, + }, receivableCardExpanded[0] ? '收起' : '展开') }, receivableCardExpanded[0] ? React.createElement(React.Fragment, null, - React.createElement('div', { style: { marginBottom: 16, display: 'flex', gap: 24, alignItems: 'center', flexWrap: 'wrap' } }, - React.createElement(Popover, { content: receivablePopoverContent, title: '应收款明细', trigger: 'click' }, - React.createElement('span', { style: { cursor: 'pointer' } }, '应收款总额:', React.createElement('span', { style: highlightStyle }, receivableTotal, ' 元')) + React.createElement(Alert, { + type: 'info', + showIcon: true, + style: { marginBottom: 16 }, + message: '提车前打款 · 收款记录导入', + description: '客户于提车前完成打款;财务人员在「收款记录」功能导入到账后,系统反写实收金额。应收金额按提车日期与合同付款规则自动计算。' + }), + React.createElement('div', { className: 'vpr-collect-form-grid' }, + React.createElement('div', { className: 'vpr-collect-form-item' }, + React.createElement('div', { style: labelStyle }, React.createElement('span', { style: requiredStar }, '*'), '提车日期'), + dayjs && DatePicker + ? React.createElement(DatePicker, { + value: dayjs(pickupDate), + style: { width: '100%' }, + format: 'YYYY-MM-DD', + onChange: function (_d, dateString) { + setEdited(true); + setPickupDate(dateString || ''); + }, + }) + : React.createElement(Input, { + value: pickupDate, + placeholder: 'YYYY-MM-DD', + onChange: function (e) { + setEdited(true); + setPickupDate(e.target.value); + }, + }) ), - React.createElement(Popover, { content: actualPopoverContent, title: '实收款明细', trigger: 'click' }, - React.createElement('span', { style: { cursor: 'pointer' } }, '实收款总额:', React.createElement('span', { style: highlightStyle }, actualTotal, ' 元')) - ), - React.createElement('span', null, '应开票总额:', React.createElement('span', { style: highlightStyle }, invoiceTotal, ' 元')) + React.createElement('div', { className: 'vpr-collect-form-item' }, + React.createElement('div', { style: labelStyle }, '计费规则'), + React.createElement('div', { className: 'vpr-collect-field__value', style: { color: 'var(--ln-muted)' } }, pickupRuleText || '请选择提车日期后自动生成') + ) ), - React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16, overflowY: 'visible' } }, - React.createElement('table', { style: { width: '100%', minWidth: 1600, borderCollapse: 'collapse', fontSize: 13, tableLayout: 'fixed' } }, + batchSummary ? React.createElement('div', { className: 'vpr-collect-batch-panel' }, + React.createElement('div', { className: 'vpr-collect-batch-panel__title' }, '应收付款节点(选中 ' + batchSummary.vehicleCount + ' 辆)'), + React.createElement('div', null, '提车前2日内应付:', React.createElement('span', { className: 'vpr-collect-batch-panel__amount tabular-nums' }, fmtYuan(batchSummary.beforePickup.amount)), '(保证金 ', fmtMoney(batchSummary.beforePickup.deposit), ' + 租金 ', fmtMoney(batchSummary.beforePickup.rent), ' + 服务费 ', fmtMoney(batchSummary.beforePickup.service), ')'), + batchSummary.month25.amount > 0 + ? React.createElement('div', { style: { marginTop: 6 } }, '当月25日应付:', React.createElement('span', { className: 'vpr-collect-batch-panel__amount tabular-nums' }, fmtYuan(batchSummary.month25.amount)), '(下周期租金 ', fmtMoney(batchSummary.month25.rent), ' + 服务费 ', fmtMoney(batchSummary.month25.service), ')') + : null, + React.createElement('div', { style: { marginTop: 6, color: 'var(--ln-muted-soft)' } }, '当月剩余天数租金/服务费按(月租金+月服务费)/30×实际租赁天数,四舍五入取整到元') + ) : null, + React.createElement('div', { className: 'vpr-collect-kpi-row', 'data-annotation-id': 'vpr-collect-kpi' }, + React.createElement(Popover, { content: receivablePopoverContent, title: '应收款明细', trigger: 'click' }, + React.createElement('div', { + className: 'vpr-collect-kpi-card vpr-collect-kpi-card--clickable', + role: 'button', + tabIndex: 0, + onKeyDown: handleKpiCardKeyDown, + }, + React.createElement('div', { className: 'vpr-collect-kpi-card__label' }, '应收款总额'), + React.createElement('div', { className: 'vpr-collect-kpi-card__value tabular-nums' }, fmtYuan(receivableTotal)), + React.createElement('div', { className: 'vpr-collect-kpi-card__hint' }, '点击查看明细') + ) + ), + React.createElement(Popover, { content: actualPopoverContent, title: '提车前应付明细', trigger: 'click' }, + React.createElement('div', { + className: 'vpr-collect-kpi-card vpr-collect-kpi-card--clickable', + role: 'button', + tabIndex: 0, + onKeyDown: handleKpiCardKeyDown, + }, + React.createElement('div', { className: 'vpr-collect-kpi-card__label' }, '提车前应付'), + React.createElement('div', { className: 'vpr-collect-kpi-card__value tabular-nums' }, fmtYuan(actualTotal)), + React.createElement('div', { className: 'vpr-collect-kpi-card__hint' }, '点击查看明细') + ) + ), + React.createElement('div', { className: 'vpr-collect-kpi-card' }, + React.createElement('div', { className: 'vpr-collect-kpi-card__label' }, '应开票总额'), + React.createElement('div', { className: 'vpr-collect-kpi-card__value tabular-nums' }, fmtYuan(invoiceTotal)), + React.createElement('div', { className: 'vpr-collect-kpi-card__hint' }, '按实收租金与服务费计算') + ) + ), + React.createElement('div', { className: 'vpr-collect-table-toolbar' + (selectedCount === 0 ? ' vpr-collect-table-toolbar--warn' : '') }, + React.createElement('div', { className: 'vpr-collect-table-toolbar__left' }, + React.createElement('span', { className: 'vpr-collect-table-toolbar__count' }, '已选 ', selectedCount, ' / ', selectableVehicles.length, ' 辆可办理'), + React.createElement('span', { className: 'vpr-collect-table-toolbar__hint' }, + selectedCount === 0 ? '请勾选本次提车车辆后再填写实收金额' : '点击行或勾选复选框选择车辆;已完成收款的车辆不可再次选择' + ) + ), + selectableVehicles.length ? React.createElement('div', { className: 'vpr-collect-table-toolbar__actions' }, + React.createElement(Button, { size: 'small', disabled: allSelected, onClick: function () { toggleAllSelected(true); } }, '全选'), + React.createElement(Button, { size: 'small', disabled: selectedCount === 0, onClick: function () { toggleAllSelected(false); } }, '取消全选') + ) : null + ), + React.createElement('div', { className: 'vpr-collect-table-wrap', 'data-annotation-id': 'vpr-collect-table' }, + React.createElement('table', { className: 'vpr-collect-table' }, React.createElement('thead', null, - React.createElement('tr', { style: { backgroundColor: '#fafafa' } }, + React.createElement('tr', null, React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 48 }) }, React.createElement('input', { type: 'checkbox', checked: allSelected, ref: headerCheckRef, onChange: function (e) { toggleAllSelected(e.target.checked); } }) ), @@ -370,7 +583,7 @@ const Component = function () { React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 80 }) }, '品牌'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 90 }) }, '型号'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 100 }) }, '车牌号'), - React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 120 }) }, '应收车辆月租金'), + React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'right', width: 120 }) }, '应收租金(提车前)'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, React.createElement('span', null, React.createElement('span', { style: requiredStar }, '*'), '实收车辆月租金')), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '车辆租金备注'), React.createElement('th', { style: Object.assign({}, thBase, { textAlign: 'left', width: 130 }) }, '减免金额'), @@ -439,26 +652,35 @@ const Component = function () { ) ) : React.createElement('input', { type: 'checkbox', checked: row.selected, onChange: function () { toggleVehicleSelected(row.key); } }); - return React.createElement('tr', { key: row.key }, - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, checkboxCell), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.index), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.brand), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.model), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, row.plateNo || '-'), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableRent || 0) + ' 元'), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + return React.createElement('tr', { + key: row.key, + className: vehicleRowClassName(row), + onClick: function (e) { handleVehicleRowClick(row, e); }, + }, + React.createElement('td', { onClick: function (e) { e.stopPropagation(); } }, checkboxCell), + React.createElement('td', null, row.index), + React.createElement('td', null, + row.brand, + row.receivableCompleted + ? React.createElement('span', { className: 'vpr-collect-row-badge' }, '已完成') + : null + ), + React.createElement('td', null, row.model), + React.createElement('td', null, row.plateNo || '-'), + React.createElement('td', { className: 'vpr-collect-td--num tabular-nums' }, fmtYuan(row.receivableRent)), + React.createElement('td', { onClick: function (e) { e.stopPropagation(); } }, React.createElement(Input, { size: 'small', value: row.actualRent, disabled: disabled, suffix: '元', onChange: function (e) { updateVehicle(row.key, 'actualRent', e.target.value); } }) ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement('td', { onClick: function (e) { e.stopPropagation(); } }, React.createElement(Input, { size: 'small', value: row.rentRemark, disabled: disabled, onChange: function (e) { updateVehicle(row.key, 'rentRemark', e.target.value); } }) ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement('td', { onClick: function (e) { e.stopPropagation(); } }, React.createElement(Input, { size: 'small', value: row.discountAmount, disabled: disabled, suffix: '元', onChange: function (e) { updateVehicle(row.key, 'discountAmount', e.target.value); } }) ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement('td', { onClick: function (e) { e.stopPropagation(); } }, React.createElement(Input, { size: 'small', value: row.discountRemark, disabled: disabled, onChange: function (e) { updateVehicle(row.key, 'discountRemark', e.target.value); } }) ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', width: 130 } }, + React.createElement('td', { onClick: function (e) { e.stopPropagation(); } }, disabled ? '-' : React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 4 } }, proofList.map(function (p, pidx) { return React.createElement('div', { key: pidx, style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 12 } }, @@ -472,59 +694,75 @@ const Component = function () { ) ) ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableDeposit || 0) + ' 元'), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0' } }, + React.createElement('td', { className: 'vpr-collect-td--num tabular-nums' }, fmtYuan(row.receivableDeposit)), + React.createElement('td', { onClick: function (e) { e.stopPropagation(); } }, React.createElement(Popover, { content: servicePopover, title: null, trigger: 'click' }, React.createElement(Button, { type: 'link', size: 'small', disabled: disabled }, '管理') ) ), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right' } }, (row.receivableService || 0) + ' 元'), - React.createElement('td', { style: { padding: '8px 12px', border: '1px solid #f0f0f0', textAlign: 'right', width: 130 } }, (row.actualService || '0.00') + ' 元') + React.createElement('td', { className: 'vpr-collect-td--num tabular-nums' }, fmtYuan(row.receivableService)), + React.createElement('td', { className: 'vpr-collect-td--num tabular-nums' }, fmtYuan(row.actualService)) ); }) ) ) ), hasHydrogenPrepay ? React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement('div', { style: { fontSize: 14, fontWeight: 500, marginBottom: 8 } }, '氢费预付款情况'), - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, maxWidth: 800 } }, - React.createElement('div', { style: formItemStyle }, + React.createElement('div', { className: 'vpr-collect-section-title' }, '氢费预付款情况'), + React.createElement('div', { className: 'vpr-collect-form-grid vpr-collect-form-grid--4' }, + React.createElement('div', { className: 'vpr-collect-form-item' }, React.createElement('div', { style: labelStyle }, '氢费预付款应收金额'), - React.createElement(Input, { value: hydrogen.receivable + ' 元', disabled: true }) + React.createElement(Input, { value: fmtYuan(hydrogen.receivable), disabled: true }) ), - React.createElement('div', { style: formItemStyle }, + React.createElement('div', { className: 'vpr-collect-form-item' }, React.createElement('div', { style: labelStyle }, React.createElement('span', { style: requiredStar }, '*'), '氢费预付款实收金额'), - React.createElement(Input, { value: hydrogen.actual, suffix: '元', onChange: function (e) { setHydrogen(function (p) { return Object.assign({}, p, { actual: e.target.value }); }); } }) + React.createElement(Input, { value: hydrogen.actual, suffix: '元', onChange: function (e) { setEdited(true); setHydrogen(function (p) { return Object.assign({}, p, { actual: e.target.value }); }); } }) ), - React.createElement('div', { style: formItemStyle }, + React.createElement('div', { className: 'vpr-collect-form-item' }, React.createElement('div', { style: labelStyle }, '减免金额'), - React.createElement(Input, { value: hydrogen.discount, suffix: '元', onChange: function (e) { setHydrogen(function (p) { return Object.assign({}, p, { discount: e.target.value }); }); } }) + React.createElement(Input, { value: hydrogen.discount, suffix: '元', onChange: function (e) { setEdited(true); setHydrogen(function (p) { return Object.assign({}, p, { discount: e.target.value }); }); } }) ), - React.createElement('div', { style: formItemStyle }, + React.createElement('div', { className: 'vpr-collect-form-item' }, React.createElement('div', { style: labelStyle }, '减免金额备注'), - React.createElement(Input, { value: hydrogen.discountRemark, onChange: function (e) { setHydrogen(function (p) { return Object.assign({}, p, { discountRemark: e.target.value }); }); } }) + React.createElement(Input, { value: hydrogen.discountRemark, onChange: function (e) { setEdited(true); setHydrogen(function (p) { return Object.assign({}, p, { discountRemark: e.target.value }); }); } }) ) ) ) : null, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px 24px', marginTop: 16 } }, - React.createElement('div', { style: formItemStyle }, + React.createElement('div', { className: 'vpr-collect-form-grid vpr-collect-form-grid--2', style: { marginTop: 16 } }, + React.createElement('div', { className: 'vpr-collect-form-item' }, React.createElement('div', { style: labelStyle }, '开票方式'), - React.createElement(Select, { style: { width: '100%' }, value: invoiceMethod[0], onChange: function (v) { setInvoiceMethod(v); }, placeholder: '请选择' }, + React.createElement(Select, { style: { width: '100%' }, value: invoiceMethod[0], onChange: function (v) { setEdited(true); setInvoiceMethod(v); }, placeholder: '请选择' }, React.createElement(Option, { value: '先开票后付款' }, '先开票后付款'), React.createElement(Option, { value: '先付款后开票' }, '先付款后开票') ) ), - React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) }, + React.createElement('div', { className: 'vpr-collect-form-item vpr-collect-form-item--full' }, React.createElement('div', { style: labelStyle }, React.createElement('span', { style: requiredStar }, '*'), '开票备注'), React.createElement(Input.TextArea, { value: invoiceRemark[0], onChange: function (e) { setInvoiceRemark(e.target.value); setEdited(true); }, rows: 3, placeholder: '请输入开票项目、税率以及其他备注信息,财务将以此进行开票', style: { width: '100%' } }) ) ) ) : null ), - React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 24 } }, - React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交审核'), - React.createElement(Button, { onClick: handleSave }, '保存'), - React.createElement(Button, { onClick: handleCancel }, '取消') + React.createElement('footer', { className: 'vpr-collect-footer', 'data-annotation-id': 'vpr-collect-footer' }, + React.createElement('div', { className: 'vpr-collect-footer__summary' + (selectedCount === 0 ? ' vpr-collect-footer__summary--warn' : '') }, + selectedCount === 0 ? '请先勾选提车车辆' : React.createElement(React.Fragment, null, + '提车前应付', + React.createElement('strong', { className: 'tabular-nums' }, fmtYuan(actualTotal)), + ' · 已选 ', + selectedCount, + ' 辆' + ) + ), + React.createElement('div', { className: 'vpr-collect-footer__actions' }, + React.createElement('button', { 'data-vm-icon': 'x', type: 'button', className: 'vm-btn vm-btn-ghost', onClick: handleCancel }, '取消'), + React.createElement('button', { 'data-vm-icon': 'save', type: 'button', className: 'vm-btn vm-btn-secondary', onClick: handleSave }, '保存'), + React.createElement('button', { 'data-vm-icon': 'send', + type: 'button', + className: 'vm-btn vm-btn-primary', + disabled: selectedCount === 0, + onClick: handleSubmit, + }, '提交审核') + ) ), React.createElement(Modal, { title: '确认', @@ -533,7 +771,7 @@ const Component = function () { onOk: handleSubmitConfirm, okText: '确认', cancelText: '取消' - }, React.createElement('div', null, '请仔细核对提车首付款实收金额,点击确认则进入工作流')), + }, React.createElement('div', null, '请仔细核对本次提车车辆与应收金额,确认后将快速生成提车应收款单据并抄送财务')), React.createElement(Modal, { title: '确认取消', open: cancelConfirmVisible[0], @@ -541,15 +779,7 @@ const Component = function () { onOk: handleCancelConfirm, okText: '确认', cancelText: '取消' - }, React.createElement('div', null, '取消将会丢失所有已填写数据,是否确认?')), - React.createElement(Modal, { - title: '需求说明', - open: requirementModalVisible[0], - onCancel: function () { setRequirementModalVisible(false); }, - width: 720, - footer: React.createElement(Button, { onClick: function () { setRequirementModalVisible(false); } }, '关闭'), - bodyStyle: { maxHeight: '70vh', overflow: 'auto' } - }, React.createElement('div', { style: { padding: '8px 0', whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent)) + }, React.createElement('div', null, '取消将会丢失所有已填写数据,是否确认?')) ); }; diff --git a/src/prototypes/oneos-web-finance/pages/09-提车应收款.jsx b/src/prototypes/oneos-web-finance/pages/09-提车应收款.jsx index c12cd8f..7541015 100644 --- a/src/prototypes/oneos-web-finance/pages/09-提车应收款.jsx +++ b/src/prototypes/oneos-web-finance/pages/09-提车应收款.jsx @@ -1,12 +1,12 @@ // 【重要】必须使用 const Component 作为组件变量名 // 财务管理 - 提车应收款(2026年3月5日版本) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -153,7 +153,8 @@ const Component = function () { else message.info('跳转至提车应收款-收款(原型)'); } function goView(record) { - message.info('查看提车收款单详情(原型)'); + if (window.__receivableToView) window.__receivableToView(record); + else message.info('查看提车收款单详情(原型)'); } function goEdit(record) { if (window.__receivableToEdit) window.__receivableToEdit(record); @@ -178,12 +179,13 @@ const Component = function () { { title: '操作', key: 'action', - width: 100, + width: 168, fixed: 'right', render: function (_, record) { - // 该合同所有车辆都已完成提车应收款时,不显示“提车收款单” if (record && record.allVehiclesReceivableCompleted) return '—'; - return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goCollect(record); } }, '提车收款单'); + return React.createElement(OperationActions, { + more: [{ key: 'collect', label: '提车收款单', onClick: function () { goCollect(record); } }] + }); } } ]; @@ -267,7 +269,7 @@ const Component = function () { { title: '操作', key: 'action', - width: 180, + width: 168, fixed: 'right', render: function (_, record) { var auditStatus = record && record.auditStatus; @@ -280,11 +282,15 @@ const Component = function () { deleteConfirmParentRecord[1](parentRecord); deleteConfirmVisible[1](true); } - return React.createElement(Space, { size: 'small' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record); } }, '查看'), - canEdit ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goEdit(record); } }, '编辑') : null, - (isPending || isRejected) ? React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: handleDelete }, '删除') : null - ); + var more = []; + if (isPending || isRejected) { + more.push({ key: 'delete', label: '删除', danger: true, onClick: handleDelete }); + } + return React.createElement(OperationActions, { + view: { onClick: function () { goView(record); } }, + edit: canEdit ? { onClick: function () { goEdit(record); } } : undefined, + more: more + }); } } ]; @@ -329,14 +335,7 @@ const Component = function () { var requirementContent = '提车应收款(2026年3月10日版本)\n一个「数字化资产ONEOS运管平台」中的「财务管理」「提车应收款」模块\n#面包屑:财务管理-提车应收款;\n\n页面分为2个卡片;\n1.筛选:\n#支持合同编码/项目名称/客户名称/业务部门/业务负责人等筛选方式;\n1.1.合同编码:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.2.项目名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.3.客户名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.4.业务部门:选择器,支持选择所有业务部门;\n1.5.业务负责人:选择器,支持选择所有业务负责人;\n\n2.列表:\n#列表展示方式为:嵌套子表格,分为主表和子表,可点击主表展开子表,一个合同编码可以展开多个提车应收款明细;\n2.1.主表显示字段为:合同编码、合同类型、项目名称、客户名称、合同生效日期、业务部门、业务负责人、操作;\n2.1.1.合同编码:显示该提车应收款租赁合同对应合同编码;\n2.1.2.合同类型:显示该租赁合同对应合同类型,显示该合同为试用合同还是正式合同;\n2.1.3.项目名称:显示该租赁合同对应项目名称;\n2.1.4.客户名称:显示该租赁合同对应客户名称;\n2.1.5.合同生效日期:显示该租赁合同生效日期,格式为:YYYY-MM-DD;\n2.1.6.业务部门:显示该租赁合同对应业务部门名称;\n2.1.7.业务负责人:显示该租赁合同对应业务负责人;\n2.1.8.操作:提车收款单;\n 2.1.8.1.提车收款单:点击提车收款单,跳转提车收款单页进行子表创建,该合同所有车辆都已完成提车应收款后,该按钮不显示;\n主表右下角为分页符,支持选择单页显示多少条数据;\n\n2.2.子表显示字段为:序号、审批状态、创建时间、提车数量、应收款总额、实收款总额、减免总金额、实际到账金额、是否已开票、已开票金额、操作;\n2.2.1.序号:按照提车首付款收费单据提交时间从最早开始显示第1条,按照顺序1.2.3....;\n2.2.2.审批状态:审批通过、待审批、审批中、已驳回;\n 2.2.2.1.审批通过:该审批通过最终节点审批,显示为审批通过;\n 2.2.2.2.待审批:审批提车收款单仅保存,但未提交审批;\n 2.2.2.3.审批中:已发起审批流程,但未完成最终节点审批;\n 2.2.2.4.审批驳回:发起审批流程,但任意节点被驳回。被驳回的提车收款单支持重新修改后提交审批;\n2.2.3.创建时间:显示提车收款单创建时间,格式为:YYYY-MM-DD HH:MM;\n2.2.4.创建人:显示已提车收款单创建人姓名;\n2.2.5.提车数量:显示该提车收费单对应提车数量,点击提车数量弹出气泡卡片,卡片中列表显示品牌、型号、车牌号;\n2.2.6.应收款金额:按照该提车收费单据应收款金额,格式为:xx.xx元;\n2.2.7.实收款金额:按照该提车收费单据实收款总额,格式为:xx.xx元;\n2.2.8.减免金额:按照该提车收费单减免总金额,显示当前子表对应减免总额,格式为:xx.xx元;\n2.2.9.实际到账金额:显示该提车收费单财务填写的实际到账金额,格式为:xx.xx元;\n2.2.10.是否已开票:显示财务是否完成开票,已开票显示为:已开票,未开票显示为:未开票,部分开票显示为:部分开票;\n2.2.11.已开票金额:显示财务已开票金额,格式为:xx.xx元;\n2.2.12.总计:显示应收款总额、实收款总额、减免总金额、实际到账金额、已开票金额等所有子表求和数据;\n2.2.13.操作:查看、编辑、删除;\n 2.2.12.1.查看:点击查看后,跳转提车应收款-查看页面,审批通过、待审批、审批中、审批驳回均可点击查看来查看提车收款单详情;\n 2.2.12.2.编辑:点击编辑后,跳转提车应收款-编辑页面,待审批、审批驳回均可进行编辑操作;\n 2.2.12.2.删除:待审批、审批驳回状态子表支持删除功能,点击弹出二次确认,点击确认后删除该记录;\n '; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '财务管理' }, - { title: '提车应收款' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '筛选', style: cardStyle }, React.createElement('div', { diff --git a/src/prototypes/oneos-web-finance/pages/10-租赁账单.jsx b/src/prototypes/oneos-web-finance/pages/10-租赁账单.jsx index 7d8a668..b8804e1 100644 --- a/src/prototypes/oneos-web-finance/pages/10-租赁账单.jsx +++ b/src/prototypes/oneos-web-finance/pages/10-租赁账单.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -439,9 +438,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('style', null, ledgerTableStyle), React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12, gap: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '财务管理' }, { title: '租赁账单' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0, flexShrink: 0, fontSize: 14 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12, gap: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0, flexShrink: 0, fontSize: 14 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { style: filterCardStyle, bodyStyle: { paddingBottom: 4 } }, diff --git a/src/prototypes/oneos-web-h2-station-site/annotation-source.json b/src/prototypes/oneos-web-h2-station-site/annotation-source.json index c1d7c21..ff5ba65 100644 --- a/src/prototypes/oneos-web-h2-station-site/annotation-source.json +++ b/src/prototypes/oneos-web-h2-station-site/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "oneos-web-h2-station-site", "pageId": "site-info", - "updatedAt": 1783589987167, + "updatedAt": 1783800844968, "nodes": [ { "id": "h2-site-filter", @@ -173,36 +173,6 @@ "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "h2-site-doc-root", diff --git a/src/prototypes/oneos-web-h2-station-site/index.tsx b/src/prototypes/oneos-web-h2-station-site/index.tsx index 223418c..466d463 100644 --- a/src/prototypes/oneos-web-h2-station-site/index.tsx +++ b/src/prototypes/oneos-web-h2-station-site/index.tsx @@ -12,10 +12,10 @@ import '../vehicle-management/style.css'; import '../lease-contract-management/styles/lease-contract.css'; import '../oneos-web-h2-station/styles/h2-station-vm-filter.css'; import { - AnnotationViewer, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import { clearHostPrototypeRouteInfo } from '../../common/useHashPage'; import SiteInfoPage from '../oneos-web-h2-station/pages/03-站点信息.jsx'; import annotationSourceDocument from './annotation-source.json'; @@ -76,7 +76,7 @@ export default function OneosWebH2StationSite() { return ( - diff --git a/src/prototypes/oneos-web-h2-station/index.tsx b/src/prototypes/oneos-web-h2-station/index.tsx index fc2140c..b715d3b 100644 --- a/src/prototypes/oneos-web-h2-station/index.tsx +++ b/src/prototypes/oneos-web-h2-station/index.tsx @@ -5,6 +5,7 @@ import '../../common/oneosWebLegacy/legacyGlobals'; import '../../common/h2VehicleLedgerBridge.js'; import React from 'react'; +import '../vehicle-management/style.css'; import Page1 from './pages/01-加氢订单.jsx'; import Page2 from './pages/02-加氢记录.jsx'; import { OneosWebLegacyShell } from '../../common/oneosWebLegacy/OneosWebLegacyShell'; diff --git a/src/prototypes/oneos-web-h2-station/pages/01-加氢订单.jsx b/src/prototypes/oneos-web-h2-station/pages/01-加氢订单.jsx index 859ce86..ceca633 100644 --- a/src/prototypes/oneos-web-h2-station/pages/01-加氢订单.jsx +++ b/src/prototypes/oneos-web-h2-station/pages/01-加氢订单.jsx @@ -6,7 +6,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -126,14 +125,7 @@ const Component = function () { var requirementContent = '加氢订单:一个「数字化资产ONEOS运管平台」中的「加氢站管理-加氢订单」模块。\n\n#1.面包屑:加氢站管理-加氢订单\n\n#2.筛选:支持加氢站、加氢时间、车牌号等筛选;\n2.1.加氢站:选择器,选择加氢站;\n2.2.加氢时间:日期时间范围选择器,支持开始时间-结束时间;\n2.3.车牌号:输入框,支持模糊匹配;\n右侧为查询、重置按钮。\n\n#3.列表:加氢订单列表,展示序号、加氢站、加氢时间、车牌号、加氢量(kg)、金额(元);支持分页与导出。'; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '加氢站管理' }, - { title: '加氢订单' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '筛选', style: cardStyle }, React.createElement('div', { diff --git a/src/prototypes/oneos-web-h2-station/pages/02-加氢记录.jsx b/src/prototypes/oneos-web-h2-station/pages/02-加氢记录.jsx index 6a2d871..f9aaa1e 100644 --- a/src/prototypes/oneos-web-h2-station/pages/02-加氢记录.jsx +++ b/src/prototypes/oneos-web-h2-station/pages/02-加氢记录.jsx @@ -1,6 +1,8 @@ // 【重要】必须使用 const Component 作为组件变量名 // 加氢站管理 - 加氢记录(列表 + 筛选 + 预约加氢弹窗 + 卡片式新增/编辑 + 导出) +import { OperationActions } from '../../../common/OperationActions'; + function hrSvgIcon(paths, size) { return React.createElement('svg', { viewBox: '0 0 24 24', @@ -41,8 +43,6 @@ var HR_PAGE_STYLE = ONEOS_ANT_TABLE_GLOBAL_FIX.concat([ '.h2-station-page--create .h2-create-shell { width: 100%; max-width: none; margin: 0; padding: 24px 24px 0; box-sizing: border-box; }', '.h2-station-page--create .h2-create-topbar { display: flex; align-items: center; min-height: 40px; margin-bottom: 16px; position: relative; }', '.h2-station-page--create .h2-create-topbar-title { position: absolute; left: 50%; transform: translateX(-50%); margin: 0; font-size: 18px; font-weight: 700; color: #0f172a; line-height: 1.35; pointer-events: none; white-space: nowrap; }', - '.h2-station-page--create .h2-create-back-btn { display: inline-flex !important; align-items: center; gap: 6px; height: 32px; padding: 0 12px !important; border-radius: 8px !important; font-weight: 500; color: #475569 !important; border: 1px solid #e2e8f0 !important; background: #fff !important; }', - '.h2-station-page--create .h2-create-back-btn:hover { color: #059669 !important; border-color: #10b981 !important; background: #f0fdf4 !important; }', '.h2-station-page .h2-card-title-bar--step::before { display: none !important; }', '.h2-station-page .h2-card-step-badge { width: 28px; height: 28px; border-radius: 50%; background: linear-gradient(145deg, #10b981 0%, #059669 100%); color: #fff; font-size: 14px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; box-shadow: 0 2px 8px rgba(16, 185, 129, 0.32); }', '.h2-station-page .h2-card-title-text { font-size: 15px; font-weight: 700; color: #0f172a; }', @@ -472,7 +472,6 @@ const Component = function () { var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -987,23 +986,13 @@ const Component = function () { { title: '操作', key: 'actions', - width: 140, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement('div', { className: 'h2-row-actions' }, - React.createElement(Button, { - type: 'link', - size: 'small', - className: 'lc-action-btn', - onClick: function () { openEdit(record); } - }, '编辑'), - React.createElement(Button, { - type: 'link', - size: 'small', - className: 'lc-action-btn lc-action-btn-danger', - onClick: function () { setDeleteModal({ open: true, record: record }); } - }, '删除') - ); + return React.createElement(OperationActions, { + edit: { onClick: function () { openEdit(record); } }, + more: [{ key: 'del', label: '删除', danger: true, onClick: function () { setDeleteModal({ open: true, record: record }); } }] + }); } } ]; @@ -1079,7 +1068,7 @@ const Component = function () { { title: '操作', key: 'actions', - width: 200, + width: 168, fixed: 'right', render: function (_, record) { if (record.status !== 'pending') { @@ -1090,20 +1079,12 @@ const Component = function () { ) : React.createElement('span', { style: { color: '#94a3b8', fontSize: 12 } }, '已处理'); } - return React.createElement('div', { className: 'h2-row-actions' }, - React.createElement(Button, { - type: 'link', - size: 'small', - className: 'lc-action-btn', - onClick: function () { handleAcceptAppointment(record); } - }, '接受预约'), - React.createElement(Button, { - type: 'link', - size: 'small', - className: 'lc-action-btn lc-action-btn-danger', - onClick: function () { openRejectAppointment(record); } - }, '拒绝预约') - ); + return React.createElement(OperationActions, { + more: [ + { key: 'accept', label: '接受预约', onClick: function () { handleAcceptAppointment(record); } }, + { key: 'reject', label: '拒绝预约', danger: true, onClick: function () { openRejectAppointment(record); } } + ] + }); } } ]; @@ -1376,13 +1357,11 @@ const Component = function () { var batchCreateViewNode = subView === 'form' && formMode === 'create' ? React.createElement('div', { className: 'h2-create-shell' }, React.createElement('div', { className: 'h2-create-topbar' }, - React.createElement(Button, { - className: 'h2-create-back-btn', - type: 'default', - icon: HR_ICONS.back, + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-back', onClick: handleFormPageBack, disabled: formSubmitting, - style: { borderRadius: 8 } }, '返回'), React.createElement('h1', { className: 'h2-create-topbar-title' }, formPageTitle) ), @@ -1447,13 +1426,11 @@ const Component = function () { var editFormViewNode = subView === 'form' && formMode === 'edit' ? React.createElement('div', { className: 'h2-create-shell' }, React.createElement('div', { className: 'h2-create-topbar' }, - React.createElement(Button, { - className: 'h2-create-back-btn', - type: 'default', - icon: HR_ICONS.back, + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-back', onClick: handleFormPageBack, disabled: formSubmitting, - style: { borderRadius: 8 } }, '返回'), React.createElement('h1', { className: 'h2-create-topbar-title' }, formPageTitle) ), @@ -1602,10 +1579,7 @@ const Component = function () { batchCreateViewNode, editFormViewNode, subView === 'list' ? React.createElement('div', { style: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' } }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '加氢站管理' }, { title: '加氢记录' }] }) - ), - + React.createElement(Card, { className: 'lc-filter-card', title: '筛选条件', bordered: false }, React.createElement('div', { className: 'lc-filter-grid' }, renderFilterField('加氢时间', React.createElement(RangePicker, { diff --git a/src/prototypes/oneos-web-h2-station/pages/03-站点信息.jsx b/src/prototypes/oneos-web-h2-station/pages/03-站点信息.jsx index 646a23e..67ec115 100644 --- a/src/prototypes/oneos-web-h2-station/pages/03-站点信息.jsx +++ b/src/prototypes/oneos-web-h2-station/pages/03-站点信息.jsx @@ -2,6 +2,7 @@ // 加氢站管理 - 站点信息(列表 + KPI 分类 + 独立新建/编辑页 + 使用说明书) import { TablePagination, COMPACT_PAGE_SIZE_OPTIONS } from '../../../common/TablePagination'; +import { OperationActions } from '../../../common/OperationActions'; var H2_REGION_CASCADER_OPTIONS = [ { value: '浙江省', label: '浙江省', children: [{ value: '嘉兴市', label: '嘉兴市' }, { value: '杭州市', label: '杭州市' }, { value: '宁波市', label: '宁波市' }] }, @@ -650,9 +651,6 @@ var H2_PAGE_STYLE = ONEOS_ANT_TABLE_GLOBAL_FIX.concat([ '.h2-station-page--create .h2-create-form--visual .h2-create-radio-group { min-height: 40px; }', '.h2-station-page--create .h2-create-form--visual .ant-form-item-control-input { max-width: 100%; }', '.h2-station-page--create .h2-create-back-only, .h2-station-page--edit .h2-create-back-only { display: flex; align-items: center; margin-bottom: 16px; }', - '.h2-station-page--create .h2-create-back-btn, .h2-station-page--edit .h2-create-back-btn { display: inline-flex !important; align-items: center; gap: 6px; height: 32px; padding: 0 12px !important; border-radius: 8px !important; font-weight: 500; color: #475569 !important; border: 1px solid #e2e8f0 !important; background: #fff !important; }', - '.h2-station-page--create .h2-create-back-btn:hover, .h2-station-page--edit .h2-create-back-btn:hover { color: #059669 !important; border-color: #10b981 !important; background: #f0fdf4 !important; }', - '.h2-station-page--create .h2-create-back-btn:focus-visible, .h2-station-page--edit .h2-create-back-btn:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }', '.h2-station-page .h2-create-card.ant-card { border-radius: 16px !important; border: 1px solid #e2e8f0 !important; box-shadow: 0 4px 24px -6px rgba(15, 23, 42, 0.06) !important; margin-bottom: 0 !important; transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease; }', '.h2-station-page--create .h2-create-card.ant-card:hover { box-shadow: 0 4px 24px -6px rgba(15, 23, 42, 0.06) !important; transform: none; }', '.h2-station-page .h2-create-card > .ant-card-head { border-bottom: 1px solid #f1f5f9 !important; min-height: auto; padding: 16px 22px !important; background: linear-gradient(180deg, #fafbfc 0%, #fff 100%); }', @@ -4918,12 +4916,16 @@ var H2_PRIMARY_BTN_STYLE = { function renderH2GhostButton(label, onClick, options) { options = options || {}; - return React.createElement('button', { + var iconByLabel = { '取消': 'x', '关闭': 'x', '重置': 'rotate-ccw' }; + var icon = options.icon || iconByLabel[label]; + var btnProps = { type: 'button', className: 'vm-btn vm-btn-ghost' + (options.className ? ' ' + options.className : ''), onClick: onClick, disabled: !!options.disabled - }, label); + }; + if (icon) btnProps['data-vm-icon'] = icon; + return React.createElement('button', btnProps, label); } function h2ResolveRecordSupplierMode(record) { @@ -5451,12 +5453,11 @@ function h2BuildStationCreateView(ctx) { return React.createElement(React.Fragment, null, React.createElement('div', { className: 'h2-create-shell' }, React.createElement('div', { className: 'h2-create-back-only' }, - React.createElement(Button, { - className: 'h2-create-back-btn', - type: 'default', - icon: H2_ICONS.back, + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-back', onClick: ctx.onCancel, - disabled: ctx.submitting + disabled: ctx.submitting, }, '返回') ), @@ -7358,7 +7359,6 @@ const Component = function () { var getRowMoreMenuItems = useCallback(function (record) { return [ - { key: 'edit', label: '编辑', onClick: function () { openEdit(record); } }, { key: 'business', label: '营业状态', onClick: function () { openBusinessSetting(record); } }, { key: 'price', label: '价格配置', onClick: function () { openPriceConfig(record); } }, { key: 'balanceAlert', label: '余额提醒设置', onClick: function () { openBalanceAlertSetting(record); } }, @@ -7367,7 +7367,7 @@ const Component = function () { { type: 'divider' }, { key: 'delete', label: '删除', danger: true, onClick: function () { setDeleteModal({ open: true, record: record }); } } ]; - }, [openEdit, openBusinessSetting, openPriceConfig, openBalanceAlertSetting, openStatementModal, openStatementHistoryModal]); + }, [openBusinessSetting, openPriceConfig, openBalanceAlertSetting, openStatementModal, openStatementHistoryModal]); var closeImportModal = useCallback(function () { setImportModalOpen(false); @@ -8320,14 +8320,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 96, + width: 168, fixed: 'right', render: function (_, row) { - return React.createElement(Button, { - type: 'link', - size: 'small', - onClick: function () { openStatementDetailModal(row); } - }, '查看明细'); + return React.createElement(OperationActions, { + view: { label: '查看明细', onClick: function () { openStatementDetailModal(row); } } + }); } } ]; @@ -8558,14 +8556,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 72, + width: 168, fixed: 'right', render: function (_, cfg) { - return React.createElement(Button, { - type: 'link', - size: 'small', - onClick: function () { loadPriceModalFromConfig(cfg); } - }, '修改'); + return React.createElement(OperationActions, { + edit: { label: '修改', onClick: function () { loadPriceModalFromConfig(cfg); } } + }); } } ]; @@ -8735,31 +8731,14 @@ const Component = function () { { title: '操作', key: 'action', - width: 108, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement('div', { className: 'h2-row-actions' }, - React.createElement('button', { - type: 'button', - className: 'vm-link', - onClick: function () { openView(record); } - }, '查看'), - React.createElement(Dropdown, { - trigger: ['click'], - placement: 'bottomRight', - menu: { items: getRowMoreMenuItems(record) } - }, - React.createElement(Tooltip, { title: '更多' }, - React.createElement('span', { - className: 'h2-action-more-btn', - role: 'button', - tabIndex: 0, - 'aria-label': '更多操作', - onClick: function (e) { e.stopPropagation(); } - }, h2MoreIcon()) - ) - ) - ); + return React.createElement(OperationActions, { + view: { onClick: function () { openView(record); } }, + edit: { onClick: function () { openEdit(record); } }, + more: getRowMoreMenuItems(record).filter(function (item) { return !item.type; }) + }); } } ]; @@ -9489,25 +9468,13 @@ const Component = function () { })) ), React.createElement('div', { className: 'vm-filter-actions' }, - React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-ghost', onClick: handleListFilterReset }, '重置'), - React.createElement('button', { type: 'button', className: 'vm-btn vm-btn-primary', onClick: handleListFilterQuery }, - React.createElement('svg', { - xmlns: 'http://www.w3.org/2000/svg', - width: 16, - height: 16, - viewBox: '0 0 24 24', - fill: 'none', - stroke: 'currentColor', - strokeWidth: 2, - strokeLinecap: 'round', - strokeLinejoin: 'round', - 'aria-hidden': true - }, - React.createElement('circle', { cx: 11, cy: 11, r: 8 }), - React.createElement('path', { d: 'm21 21-4.3-4.3' }) - ), - '查询' - ) + React.createElement('button', { 'data-vm-icon': 'rotate-ccw', type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: handleListFilterReset }, '重置'), + React.createElement('button', { + 'data-vm-icon': 'search', + type: 'button', + className: 'vm-btn vm-btn-primary ldb-toolbar-btn', + onClick: handleListFilterQuery + }, '查询') ) ), @@ -9547,25 +9514,29 @@ const Component = function () { React.createElement('div', { className: 'vm-table-actions' }, React.createElement('button', { type: 'button', - className: 'vm-btn vm-btn-ghost', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', + 'data-vm-icon': 'settings', onClick: openGlobalBalanceAlertSetting, 'aria-label': '全站余额提醒设置' - }, '全站余额提醒设置'), + }, '全站余额提醒设置'), React.createElement('button', { - type: 'button', - className: 'vm-btn vm-btn-ghost', + 'data-vm-icon': 'import', + type: 'button', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: function () { setImportModalOpen(true); }, 'aria-label': '批量导入加氢站点' - }, '批量导入'), + }, '批量导入'), React.createElement('button', { + 'data-vm-icon': 'plus', type: 'button', - className: 'vm-btn vm-btn-ghost', + className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: function () { openRechargeModal(); }, 'aria-label': '发起充值单' }, '发起充值单'), React.createElement('button', { + 'data-vm-icon': 'plus', type: 'button', - className: 'vm-btn vm-btn-primary', + className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: openCreate, 'aria-label': '新建加氢站点' }, '新建站点') @@ -9974,11 +9945,11 @@ const Component = function () { open: alertStationModal.open, onCancel: closeAlertStationModal, footer: React.createElement(Space, null, - React.createElement('button', { + React.createElement('button', { 'data-vm-icon': 'x', type: 'button', className: 'vm-btn vm-btn-ghost', onClick: closeAlertStationModal - }, '关闭'), + }, '关闭'), React.createElement(Button, { type: 'primary', disabled: !(alertStationModal.stations || []).length, diff --git a/src/prototypes/oneos-web-lease-contract/pages/01-车辆租赁合同-变更为三方合同.jsx b/src/prototypes/oneos-web-lease-contract/pages/01-车辆租赁合同-变更为三方合同.jsx index b35c134..b61378f 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/01-车辆租赁合同-变更为三方合同.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/01-车辆租赁合同-变更为三方合同.jsx @@ -420,7 +420,7 @@ const Component = function() { ) : null; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '变更为三方合同')), React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明'))), React.createElement('div', { style: styles.anchorWrap }, React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), diff --git a/src/prototypes/oneos-web-lease-contract/pages/02-车辆租赁合同-查看.jsx b/src/prototypes/oneos-web-lease-contract/pages/02-车辆租赁合同-查看.jsx index fa1b1a1..4fcd882 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/02-车辆租赁合同-查看.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/02-车辆租赁合同-查看.jsx @@ -528,7 +528,7 @@ const Component = function() { return React.createElement('div', { style: styles.page }, servicePopoverContentCustom, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '查看合同')), React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明'))), React.createElement('div', { style: styles.anchorWrap }, React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), diff --git a/src/prototypes/oneos-web-lease-contract/pages/03-车辆租赁合同-附加费用.jsx b/src/prototypes/oneos-web-lease-contract/pages/03-车辆租赁合同-附加费用.jsx index 5e9310c..ca423b8 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/03-车辆租赁合同-附加费用.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/03-车辆租赁合同-附加费用.jsx @@ -330,7 +330,7 @@ const Component = function() { }; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '附加费用'))), + React.createElement('div', { style: styles.anchorWrap }, React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), diff --git a/src/prototypes/oneos-web-lease-contract/pages/04-车辆租赁合同-新增.jsx b/src/prototypes/oneos-web-lease-contract/pages/04-车辆租赁合同-新增.jsx index 66100cb..d346d57 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/04-车辆租赁合同-新增.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/04-车辆租赁合同-新增.jsx @@ -788,7 +788,7 @@ const Component = function() { return React.createElement('div', { style: styles.page }, acroCss, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb, className: 'acro-text-base' }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1677ff' } }, '新增租赁合同')), React.createElement('button', { type: 'button', 'aria-label': '打开需求说明', style: { color: '#1677ff', cursor: 'pointer', border: 'none', background: 'none', padding: '8px 4px', minHeight: 40, borderRadius: 6 }, className: 'acro-text-base', onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement('button', { type: 'button', 'aria-label': '打开需求说明', style: { color: '#1677ff', cursor: 'pointer', border: 'none', background: 'none', padding: '8px 4px', minHeight: 40, borderRadius: 6 }, className: 'acro-text-base', onClick: function() { setReqSpecOpen(true); } }, '查看需求说明'))), React.createElement('div', { style: styles.anchorWrap, role: 'navigation', 'aria-label': '页面区块锚点导航' }, React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), React.createElement('button', { type: 'button', style: styles.anchorItem, 'aria-label': '跳转到客户基本信息', onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), diff --git a/src/prototypes/oneos-web-lease-contract/pages/05-车辆租赁合同-新增车辆.jsx b/src/prototypes/oneos-web-lease-contract/pages/05-车辆租赁合同-新增车辆.jsx index 1b80454..e221b1f 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/05-车辆租赁合同-新增车辆.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/05-车辆租赁合同-新增车辆.jsx @@ -563,10 +563,8 @@ const Component = function() { var requirementContent = '车辆租赁合同-新增(2026年3月3日版本)\n「数字化资产ONE-OS运管平台」中的「车辆租赁合同」-「新增租赁合同」模块,点击车辆租赁合同右上角「新增」进行创建;\n1.面包屑:\n#业务管理-车辆租赁合同-新增合同\n\n2.客户基本信息卡片:\n#用于从客户列表中选择客户,并将该合同绑定到业务部门及业务负责人(绑定业务部门/业务负责人主要为了后期从部门/业务负责人维度进行数据统计);\n2.1.客户名称:必选项,选择器,支持从输入框内输入内容进行模糊搜索,从客户信息列表中选择对应客户(只显示已通过审核的客户);\n2.2.客户统一信用代码:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「信用代码」字段;\n2.3.客户地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户地址」字段;\n2.4.客户联系人:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户联系人」字段;\n2.5.客户电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电话」字段;\n2.6.客户电子邮箱:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「客户电子邮箱」字段;\n2.7.企业名称:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业名称」字段;\n2.8.企业电话:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「企业电话」字段;\n2.9.邮寄地址:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「邮寄地址」字段;\n2.10.开户银行:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「开户银行」字段;\n2.11.银行账号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「银行账号」字段;\n2.12.纳税人识别号:输入框(禁用状态),根据所选客户自动从客户信息列表中拉取对应客户-「纳税人识别号」字段;\n2.13.业务部门:必选项,选择器,从部门表中选择该租赁合同对应部门;\n2.14.业务负责人:必选项,选择器,从已选业务部门下拉取对应业务负责人,未选择业务部门时,业务负责人字段不可选;\n\n3.合同基本信息卡片:\n#用于定义租赁合同基本情况和付款方式;\n3.1.项目名称:必填项,输入框,用于定义该合同项目名称,默认提示信息"请输入项目名称";\n3.2.合同类型:必选项,选择器,合同类型分为「正式合同」「试用合同」;\n3.3.生效日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天,默认为点击新增日期;\n3.4.付款方式:必选项,付款方式分为「预付」「后付」两种;\n 3.4.1.如果选择预付,以对应「付款方式」和「付款周期」规则,在每一期新账单生成就列入业务待办(工作台功能),同时以消息通知对应用户;\n 3.4.2.如果选择后付,以对应「付款方式」和「付款周期」规则,在每一期新账单生成时,将前一期账单列入业务待办(工作台功能),同时以消息通知对应用户;\n3.5.主要车型:输入框(禁用状态),根据租赁订单信息中所有所选车型,自动反写入输入框并以标签形式显示,支持多车型显示,标签显示:型号名称;\n3.6.结束日期:必选项,日期选择器,格式为YYYY-MM-DD,精确至天;\n 3.6.1.合同结束日期前30天将以消息提醒方式提醒(消息中心、工作台);\n 3.6.2.到达合同结束日期时,租赁账单将会立刻停止计算,作为最后一期账单;\n 3.6.3.续签合同/转正式合同将重新生成账单,不会对旧合同账单做任何继承处理;\n3.7.付款周期:必选项,选择器,支持1个月-12个月 12种付款周期,账单将以此周期和账单计算方式规则,从交车任务形成的交车单进行完整交车后,定时自动生成账单;\n3.8.签约公司:必选项,选择器,从组织机构表中获取所有根组织机构(如嘉兴羚牛、上海羚牛、广东羚牛等),默认显示新增用户当前机构,可手动修改,后期需要考虑从签约公司维度统计合同相关数据;\n3.9.交车区域:必选项,地区选择器,支持省-市2级,选择区域后,该任务生成交车任务时会自动推送至该区域负责运维人员;\n3.10.交车地点:必填项,输入框,支持自定义输入交车地点;\n3.11.合同审批类型:必填项,选择器,分为「标准合同审批」「非标准合同审批」;\n3.12.合同原件:必填项,按钮,按钮文字为:上传附件,支持多个附件上传(doc/docx/pdf格式);\n3.13.备注:文本域,支持自定义输入备注信息;\n\n4.被授权人信息卡片:\n#用于定义租赁合同相关被授权人相关信息,被授权人在交车单完成时,需要选择被授权人,并通过被授权人手机短信,在E签宝进行签字确认;\n4.1.被授权人:必填项,输入框,用于输入被授权人信息;\n4.2.被授权人联系电话:必填项,输入框,用于输入被授权人联系电话,该电话后续需要接收E签宝签字链接;\n4.3.被授权人身份证:必填项,输入框,用于输入被授权人身份证信息;\n4.4.支持通过新增/删除一行的方式,创建或管理多个授权人,后续交车单完成时可从多个授权人中选择接收授权人;\n\n5.租赁订单信息卡片:\n#用于定义租赁合同对应车辆明细费用、氢费明细费用等相关信息;\n5.1.上方为租赁车辆总计数据,包括租赁车辆数、租金及服务费合计、保证金总额、氢气预付款金额等相关信息;\n 5.1.1.租赁车辆数:显示下方租赁订单信息包含多少辆车;\n 5.1.2.租金及服务费合计:显示下方租赁订单信息中车辆租金总额及服务费总额;\n 5.1.3.保证金总额:显示下方租赁订单信息中车辆保证金总额;\n 5.1.4.氢气预付款金额:显示氢气预付款金额,如客户选择自行承担氢费或羚牛承担氢费,则该处为0不计入氢气预付款金额;\n5.2.下方为列表,显示序号、品牌、型号、车牌号、车辆识别代码、车辆月租金(元)、服务费项目、服务费、保证金、备注,默认且至少显示一行空数据;\n 5.2.1.序号:自动按照条数生成,规则为1、2、3....以此类推;\n 5.2.2.品牌:必选项,选择器,从型号参数库中「品牌」字段拉取所有品牌;\n 5.2.3.型号:必选项,选择器,与品牌存在级联关系,未选择品牌则无法选择型号;\n 5.2.4.车牌号:选填项,选择器(支持从输入框输入车牌号关键字下拉匹配),可通过选择车牌号对品牌、型号进行反写;\n 5.2.5.车辆识别代码:输入框(禁用),显示该车辆对应车辆识别代码,根据所选车牌号从车辆表直接拉取进行反写;\n 5.2.6.车辆月租金:输入框,支持两位小数,用于输入该车辆月租金金额,输入框后缀为元,如还车时账单周期不满1个月,则按照:(车辆月租金/30)* 实际天数进行计算;\n 5.2.7.服务费项目:点击管理按钮弹出卡片,卡片标题为:服务项目,下方列表显示服务项目、费用、生效时间、操作;\n 5.2.7.1.服务项目:必选项,选择器(支持输入框输入服务项目关键字进行下拉匹配),选项包含:代处理费用、罚款、违章处理违约金、未参加安全培训、车辆出险、年检年审违约、停车费、设备损坏金(包含易损件)、清洗费、上门收车人工费、上门收车送车行驶费、上门收车基础服务费、保险上浮、保养费用、补办驾驶证、补办牌照、补办营运证、补办加氢证、借用备用钥匙、补配钥匙、租金、氢气费-客、退还车氢量差、能源费补缴、能源费退款、送车上门人工费、送车上门送车行驶费、送车上门基础服务费、保证金、氢气预付费、维修费用、ETC-客、ETC卡缺损费、ETC设备缺损费、电费-客、未结算保养费、未结算维修费、车损费、工具损坏或丢失费、证件费、广告损坏费、送车服务费、接车服务费、补办行驶证、超赔险、轮胎磨损费、无忧包、轮胎保、养护保、尾板;\n 5.2.7.2.费用:必填项,输入框,支持2位小数,输入框后缀为元;\n 5.2.7.3.生效时间:必选项,日期选择器,格式为YYYY-MM-DD;\n 5.2.7.4.操作:删除,点击删除直接删除该行数据;\n 5.2.7.5.新增一行数据:点击添加一行服务项目;\n #在提车应收款中,车辆的交车日期和服务费生效日期可能会存在不同的情况,所以服务费需要单独以生效时间进行计算,例如:\n 交车后车辆从1月1日开始计费,付款周期为2个月,则提车应收款租金会计算到3月1日,但是服务费生效时间为1月30日,此情况下需要以3月1日-1月30日,计算出服务费具体收费天数为30天,然后根据:(服务费费用/30)* 服务费收费天数30)进行计算;\n 5.2.8.服务费:自动根据添加的所有服务费项目计算总额,支持2位小数,格式为:xx.xx元;\n 5.2.9.保证金:必填项,输入框,支持2位小数,后缀为元,用于填写车辆需要支付的保证金金额,保证金为提车应收款一次性支付,还车时需要计入退还费用中;\n 5.2.10.备注:选填项,输入框,用于备注车辆复杂情况;\n 5.2.11.操作:删除,点击删除删除该行数据;\n 5.2.12.添加一行:点击后列表新增一行,用于填写一条新的车辆租金费用信息;\n5.3.氢费承担方:必选项,选择器,选项为:「我方」、「客户」,默认选择「客户」;\n 5.3.1.选择「我方」:不显示付款方式、氢气预付款;\n 5.3.2.选择「客户」:付款方式字段默认为预付,可手动修改;\n5.4.付款方式:必选项,选择器,选项为:「预付」、「月付款」、「自行结算」,默认为:「预付」;\n 5.4.1.预付:选择「预付」,需要填写:「氢气预付款」,氢气预付款指合同签署时客户就需预先付出的氢费款项,该部分款项会自动计入提车应收款中氢气预付款金额;\n 5.4.2.月付款:选择「月付款」,提车应收款中不进行收费,而是由业务人员按照实际情况,通过氢费账单功能生成对应氢费账单,单独与客户进行结算;\n 5.4.3.自行结算:选择「自行结算」,指合同签署后,所有氢气费用由客户自行承担;\n5.5.氢气预付款:选择「客户」「预付」时显示,必填项,输入框,支持2位小数,氢气预付款金额会计算入该合同交车应收款中,并计入5.1.4.氢气预付款金额中;\n5.6.退还车氢气单价:必填项,输入框,支持2位小数,后缀为元,不管氢费承担方和付款方式选择任何选项都需要进行维护,该金额主要用于与客户约定还车时,与交车时氢气差值以此费用进行自动计算和结算;\n\n6.其他费用信息卡片:\n#用于选择对应租赁费用模板,选择后展示证照补办费用、违约金费用、易损件费用、其他费用等信息,租赁费用模板管理功能位于「车辆租赁合同」列表左上角;\n6.1.选择费用模板:必选项,从「租赁费用模板」中拉取,选择后自动将该费用模板所有环节费用显示在合同中;\n 6.1.1.证照补办费用:单独标题显示,内容为列表,列表中包括项目、收费标准、服务费,选择费用模板后自动反显;\n 6.1.2.违约金费用:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;\n 6.1.3.易损件信息:单独标题显示,内容为列表,列表中包含类别、损坏部位、配件、数量、费用明细,选择费用模板后自动反显;\n 6.1.4.其他费用信息:单独标题显示,内容为列表,列表中包含项目、收费标准、服务费,选择费用模板后自动反显;\n\n7.账单计算方式卡片:\n#必选项,填充按钮组,默认为按自然月结算,可手动修改,用于定义租赁合同的账单计算方式,分为「按自然月结算」、「按付款周期天数结算」两种方式;\n7.1.按付款周期天数结算:账单按照合同基本信息卡片中每隔付款周期*30天形成一期账单;\n 例如付款周期为2个月,则交车任务交车成功时,提车应收款从交车任务配置的开始计费时间开始,根据60天收取车辆租金,此后每隔60天生成一期租赁账单;\n7.2.按自然月结算:账单按照第一个月计费开始日期到当月最后一天为第一期,之后按照付款周期所选月份间隔,从开始月份第一天到间隔月份最后一天的自然月方式形成一期账单;\n 例如付款周期为2个月,则交车任务交车成功时,提车应收款车辆租金需要收取≥2个月租金作为标准流程,<2个月租金作为非标流程;\n 租赁账单首期从交车任务配置的开始计费时间开始,如付款周期为2个月,则首期账单结束时间为第二个月最后一天,付款周期为3个月,则首期账单结束时间为第三个月最后一天,此后每一期按照实际自然月开始-结束形成账单,例如付款周期为2个月,则第二期账单从2月1日-3月31日,以此类推;\n\n8.最下方为提交并审核、保存、取消三个按钮;\n8.1.点击提交并审核,toast提示:租赁合同已提交审核。同时该租赁合同进入租赁合同审核列表中;\n8.2.点击保存,会存储租赁订单已填写内容,不做必填项校验,同时显示在租赁合同列表中,该条数据只能保存人自己查看并编辑,其他人无法操作;\n8.3.点击取消,如当前页面有已编辑内容时,点击取消会进行二次提示,内容为:取消将会丢失所有已填写内容,是否确认?点击确认返回车辆租赁合同列表页;\n\n所有卡片支持收起/展开功能,通过点击卡片右侧收起/展开实现;并增加锚点功能,锚点固定于页面右上角,点击锚点对应卡片名称,页面自动跳转至该卡片所在区域;'; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '新增车辆')), React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明') - ), + )), React.createElement('div', { style: styles.anchorWrap }, React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), React.createElement('button', { type: 'button', style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), diff --git a/src/prototypes/oneos-web-lease-contract/pages/06-车辆租赁合同-续签合同.jsx b/src/prototypes/oneos-web-lease-contract/pages/06-车辆租赁合同-续签合同.jsx index 77d9198..86df5f4 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/06-车辆租赁合同-续签合同.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/06-车辆租赁合同-续签合同.jsx @@ -766,7 +766,7 @@ const Component = function() { ) : null; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '续签合同')), React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明'))), React.createElement('div', { style: styles.anchorWrap }, React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), diff --git a/src/prototypes/oneos-web-lease-contract/pages/07-车辆租赁合同-转正式合同.jsx b/src/prototypes/oneos-web-lease-contract/pages/07-车辆租赁合同-转正式合同.jsx index eab7881..b4ce6cd 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/07-车辆租赁合同-转正式合同.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/07-车辆租赁合同-转正式合同.jsx @@ -838,7 +838,7 @@ const Component = function() { var reqSpecModalContent = reqSpecOpen ? React.createElement('div', { style: styles.modalMask, onClick: function(e) { if (e.target === e.currentTarget) setReqSpecOpen(false); } }, React.createElement('div', { style: Object.assign({}, styles.modalBox, { maxWidth: 720 }), onClick: function(e) { e.stopPropagation(); } }, React.createElement('div', { style: styles.modalHeader }, '需求说明'), React.createElement('div', { style: Object.assign({}, styles.modalBody, { maxHeight: '70vh', padding: '20px 24px' }) }, React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent)), React.createElement('div', { style: { padding: '12px 20px', borderTop: '1px solid #f0f0f0', textAlign: 'right' } }, React.createElement(Button, { onClick: function() { setReqSpecOpen(false); } }, '关闭')))) : null; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, React.createElement('div', { style: styles.breadcrumb }, React.createElement('span', null, '业务管理'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', null, '车辆租赁合同'), React.createElement('span', { style: styles.breadcrumbSep }, ' / '), React.createElement('span', { style: { color: '#1890ff' } }, '转正式合同')), React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明')), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement('span', { style: { color: '#1890ff', cursor: 'pointer', fontSize: 14 }, onClick: function() { setReqSpecOpen(true); } }, '查看需求说明'))), React.createElement('div', { style: styles.anchorWrap }, React.createElement('div', { style: { marginBottom: 8, fontWeight: 600, fontSize: 14 } }, '锚点导航'), React.createElement('button', { style: styles.anchorItem, onClick: function() { scrollToCard('card-customer'); } }, '客户基本信息'), diff --git a/src/prototypes/oneos-web-lease-contract/pages/08-车辆租赁合同.jsx b/src/prototypes/oneos-web-lease-contract/pages/08-车辆租赁合同.jsx index 7960ea0..2958655 100644 --- a/src/prototypes/oneos-web-lease-contract/pages/08-车辆租赁合同.jsx +++ b/src/prototypes/oneos-web-lease-contract/pages/08-车辆租赁合同.jsx @@ -1,12 +1,13 @@ // 【重要】必须使用 const Component 作为组件变量名 // 业务管理 - 车辆租赁合同(列表页,租赁合同管理) +import { OperationActions } from '../../../common/OperationActions'; + const Component = function() { var useState = React.useState; var useMemo = React.useMemo; var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Select = antd.Select; var Input = antd.Input; var Button = antd.Button; @@ -865,22 +866,10 @@ const Component = function() { function getOperationButtons(record) { var menuItems = getMoreMenuItems(record); - var moreDropdown = menuItems.length > 0 - ? React.createElement( - Dropdown, - { - menu: { items: menuItems }, - trigger: ['click'] - }, - React.createElement(Button, { type: 'link', size: 'small' }, '更多') - ) - : null; - return React.createElement( - Space, - { size: 4, wrap: true }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function() { message.info('查看(跳转车辆租赁合同-查看)'); } }, '查看'), - moreDropdown - ); + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('查看(跳转车辆租赁合同-查看)'); } }, + more: menuItems, + }); } var columns = [ @@ -1001,7 +990,7 @@ const Component = function() { { title: '更新人', dataIndex: 'updater', key: 'updater', width: 90 }, { title: '最后更新时间', dataIndex: 'updateTime', key: 'updateTime', width: 150 }, { title: '备注', dataIndex: 'remark', key: 'remark', width: 100, ellipsis: true }, - { title: '操作', key: 'action', width: 140, fixed: 'right', render: function(_, record) { return getOperationButtons(record); } } + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function(_, record) { return getOperationButtons(record); } } ]; var filterItems = [ @@ -1151,14 +1140,7 @@ const Component = function() { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '业务管理' }, - { title: '车辆租赁合同' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { _requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { _requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { style: { marginBottom: 16 } }, React.createElement('div', { diff --git a/src/prototypes/oneos-web-ledger-data/pages/01-保险分摊明细.jsx b/src/prototypes/oneos-web-ledger-data/pages/01-保险分摊明细.jsx index 77d23be..c080411 100644 --- a/src/prototypes/oneos-web-ledger-data/pages/01-保险分摊明细.jsx +++ b/src/prototypes/oneos-web-ledger-data/pages/01-保险分摊明细.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -386,12 +385,7 @@ const Component = function () { React.createElement('style', null, ledgerTableStyle), React.createElement( 'div', - { style: layoutStyle }, - React.createElement(Breadcrumb, { - style: { marginBottom: 12 }, - items: [{ title: '台账数据' }, { title: '保险分摊明细' }] - }), - React.createElement( + { style: layoutStyle }, React.createElement( Card, { style: filterCardStyle, bodyStyle: { paddingBottom: 4 } }, React.createElement( diff --git a/src/prototypes/oneos-web-ledger-data/pages/02-车辆氢费明细.jsx b/src/prototypes/oneos-web-ledger-data/pages/02-车辆氢费明细.jsx index d2deb64..eb2ae68 100644 --- a/src/prototypes/oneos-web-ledger-data/pages/02-车辆氢费明细.jsx +++ b/src/prototypes/oneos-web-ledger-data/pages/02-车辆氢费明细.jsx @@ -750,6 +750,8 @@ function renderH2RequirementDocPanel() { ); } +import { OperationActions, splitOperationActions } from '../../../common/OperationActions'; + const Component = function () { var useState = React.useState; var useMemo = React.useMemo; @@ -758,7 +760,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -3732,7 +3733,7 @@ const Component = function () { { title: '操作', key: 'action', - width: 56, + width: 168, align: 'center', fixed: 'right', render: function (_, record) { @@ -3942,15 +3943,14 @@ const Component = function () { function renderMoreAction() { var popOpen = copyPopoverKey === record.key; - var dropdownNode = React.createElement( - Dropdown, - { - trigger: ['click'], - placement: 'bottomRight', - menu: { items: buildMoreMenuItems() } - }, - renderIconAction('更多', renderMoreIcon(), null, 'h2-row-more-btn') - ); + var flatItems = buildMoreMenuItems().filter(function (item) { + return item && !item.type && item.key !== 'divider-actions'; + }); + var split = splitOperationActions(flatItems); + var actionsNode = React.createElement(OperationActions, { + edit: split.edit, + more: split.more + }); return React.createElement( Popover, { @@ -3964,7 +3964,7 @@ const Component = function () { } } }, - dropdownNode + actionsNode ); } @@ -4098,11 +4098,7 @@ const Component = function () { marginBottom: 12, gap: 16 } - }, - React.createElement(Breadcrumb, { - items: [{ title: '台账数据' }, { title: '车辆氢费明细' }] - }), - React.createElement( + }, React.createElement( Button, { type: 'link', diff --git a/src/prototypes/oneos-web-ledger-data/pages/03-车辆维修明细.jsx b/src/prototypes/oneos-web-ledger-data/pages/03-车辆维修明细.jsx index 2d35967..ccfae41 100644 --- a/src/prototypes/oneos-web-ledger-data/pages/03-车辆维修明细.jsx +++ b/src/prototypes/oneos-web-ledger-data/pages/03-车辆维修明细.jsx @@ -2,6 +2,8 @@ // 台账数据 - 车辆维修/保养明细(运维手动录入;提报人/类型多选筛选;确认提交后锁定,主管可编辑) // 原型:保存/确认提交、表格内联编辑;联调后对接登录用户、角色与车辆清单接口 +import { OperationActions } from '../../../common/OperationActions'; + const Component = function () { var useState = React.useState; var useMemo = React.useMemo; @@ -10,7 +12,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -1256,7 +1257,7 @@ const Component = function () { { title: '操作', key: 'action', - width: 128, + width: 168, align: 'center', fixed: 'right', render: function (_, record) { @@ -1329,29 +1330,69 @@ const Component = function () { } function renderMoreAction() { - return React.createElement( - Dropdown, - { - trigger: ['hover'], - placement: 'bottomRight', - menu: { - items: [ - { - key: 'changelog', - label: '变更日志', - onClick: function () { - openChangeLogModal(record); - } - } - ] - } - }, - renderIconAction('更多', renderMoreIcon(), null, 'maint-row-more-btn') - ); + return React.createElement(OperationActions, { + more: [{ + key: 'changelog', + label: '变更日志', + onClick: function () { openChangeLogModal(record); } + }] + }); } - function renderCopyButton() { - if (!allowCopy) return null; + function renderEditDeletePair(onEditClick, opts) { + opts = opts || {}; + var showCopy = opts.showCopy !== false && allowCopy; + var allowEdit = opts.allowEdit !== false; + var allowDelete = + opts.allowDelete !== false && canDeleteRow(record, ref.isSupervisor); + var isEditing = ref.editingKeys.indexOf(record.key) >= 0; + var editTitle = isEditing ? '完成编辑' : '编辑'; + var more = []; + if (showCopy) { + more.push({ + key: 'copy', + label: '复制', + onClick: function () { + setCopyPopoverKey(record.key); + setCopyRowCount(1); + } + }); + } + if (allowDelete) { + more.push({ + key: 'delete', + label: '删除', + danger: true, + onClick: function () { + Modal.confirm({ + title: '确认删除该条维修/保养记录?', + okText: '删除', + okType: 'danger', + cancelText: '取消', + onOk: function () { + if (!allowDelete) { + if (record.submitStatus === 'submitted') { + message.info('已提交记录仅运维主管可删除'); + } + return; + } + ref.setAllRows(function (prev) { + return prev.filter(function (r) { return r.key !== record.key; }); + }); + ref.setEditingKeys(function (prev) { + return prev.filter(function (k) { return k !== record.key; }); + }); + message.success('已删除'); + } + }); + } + }); + } + more.push({ + key: 'changelog', + label: '变更日志', + onClick: function () { openChangeLogModal(record); } + }); var popOpen = copyPopoverKey === record.key; var popContent = React.createElement( 'div', @@ -1389,6 +1430,11 @@ const Component = function () { '确认复制' ) ); + var actionsNode = React.createElement(OperationActions, { + edit: allowEdit ? { label: editTitle, onClick: onEditClick } : undefined, + more: more + }); + if (!showCopy) return actionsNode; return React.createElement( Popover, { @@ -1405,7 +1451,7 @@ const Component = function () { } } }, - renderIconAction('复制', renderCopyIcon()) + actionsNode ); } @@ -1418,68 +1464,6 @@ const Component = function () { }); } - function renderEditDeletePair(onEditClick, opts) { - opts = opts || {}; - var showCopy = opts.showCopy !== false && allowCopy; - var allowEdit = opts.allowEdit !== false; - var allowDelete = - opts.allowDelete !== false && canDeleteRow(record, ref.isSupervisor); - var isEditing = ref.editingKeys.indexOf(record.key) >= 0; - var editTitle = opts.editDisabledTitle && !allowEdit - ? opts.editDisabledTitle - : isEditing - ? '完成编辑' - : '编辑'; - var deleteTitle = opts.deleteDisabledTitle && !allowDelete - ? opts.deleteDisabledTitle - : '删除'; - var deleteBtn = React.createElement( - Popconfirm, - { - title: '确认删除该条维修/保养记录?', - disabled: !allowDelete, - onConfirm: function () { - if (!allowDelete) { - if (record.submitStatus === 'submitted') { - message.info('已提交记录仅运维主管可删除'); - } - return; - } - ref.setAllRows(function (prev) { - return prev.filter(function (r) { return r.key !== record.key; }); - }); - ref.setEditingKeys(function (prev) { - return prev.filter(function (k) { return k !== record.key; }); - }); - message.success('已删除'); - } - }, - renderIconAction( - deleteTitle, - renderDeleteIcon(), - null, - 'maint-action-icon-danger', - !allowDelete - ) - ); - var nodes = [ - renderIconAction( - editTitle, - renderEditIcon(), - allowEdit ? onEditClick : null, - '', - !allowEdit - ) - ]; - if (showCopy) nodes.push(renderCopyButton()); - nodes.push(deleteBtn, renderMoreAction()); - return React.createElement( - 'div', - { style: { display: 'inline-flex', alignItems: 'center', gap: 2, flexWrap: 'nowrap' } }, - nodes - ); - } - if (submitted) { if (!canShowSubmittedActions(record, ref.isSupervisor)) { return renderMoreAction(); @@ -1509,25 +1493,7 @@ const Component = function () { if (ref.isSupervisor || !isOwn) return renderMoreAction(); - return React.createElement( - 'div', - { style: { display: 'inline-flex', alignItems: 'center', gap: 2, flexWrap: 'nowrap' } }, - renderCopyButton(), - React.createElement( - Popconfirm, - { - title: '确认删除该条维修/保养记录?', - onConfirm: function () { - ref.setAllRows(function (prev) { - return prev.filter(function (r) { return r.key !== record.key; }); - }); - message.success('已删除'); - } - }, - renderIconAction('删除', renderDeleteIcon(), null, 'maint-action-icon-danger') - ), - renderMoreAction() - ); + return renderEditDeletePair(function () {}, { allowEdit: false, showCopy: true }); } } ]; @@ -1624,12 +1590,7 @@ const Component = function () { React.createElement('style', null, ledgerTableStyle), React.createElement( 'div', - { style: layoutStyle }, - React.createElement(Breadcrumb, { - style: { marginBottom: 12 }, - items: [{ title: '台账数据' }, { title: '车辆维修/保养明细' }] - }), - React.createElement( + { style: layoutStyle }, React.createElement( Card, { style: filterCardStyle, bodyStyle: { paddingBottom: 4 } }, React.createElement( diff --git a/src/prototypes/oneos-web-ledger-data/pages/04-氢费采购端汇总报表.jsx b/src/prototypes/oneos-web-ledger-data/pages/04-氢费采购端汇总报表.jsx index da550f0..f52b105 100644 --- a/src/prototypes/oneos-web-ledger-data/pages/04-氢费采购端汇总报表.jsx +++ b/src/prototypes/oneos-web-ledger-data/pages/04-氢费采购端汇总报表.jsx @@ -332,7 +332,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -1977,11 +1976,7 @@ const Component = function () { marginBottom: 12, gap: 16 } - }, - React.createElement(Breadcrumb, { - items: [{ title: '台账数据' }, { title: '氢费(采购端)汇总报表' }] - }), - React.createElement( + }, React.createElement( Button, { type: 'link', diff --git a/src/prototypes/oneos-web-ledger-data/pages/05-租赁业务台账.jsx b/src/prototypes/oneos-web-ledger-data/pages/05-租赁业务台账.jsx index ecb6eb0..f8cc3e5 100644 --- a/src/prototypes/oneos-web-ledger-data/pages/05-租赁业务台账.jsx +++ b/src/prototypes/oneos-web-ledger-data/pages/05-租赁业务台账.jsx @@ -706,6 +706,8 @@ function openLeaseBillPdf(bill) { w.print(); } +import { OperationActions } from '../../../common/OperationActions'; + const Component = function () { var useState = React.useState; var useMemo = React.useMemo; @@ -2758,56 +2760,48 @@ const Component = function () { render: function (v) { return fmtMoney(v); } }, { - title: '操作', key: 'action', width: 220, fixed: 'right', align: 'center', + title: '操作', key: 'action', width: 168, fixed: 'right', align: 'center', render: function (_, bill) { - var actions = []; - if (bill.status === 'preview' || bill.status === 'rejected') { - actions.push(React.createElement(Button, { - key: 'confirm', type: 'link', size: 'small', - onClick: function () { openBillPreviewFromManage(bill); } - }, '继续确认')); - } + var more = []; if (bill.status !== 'approving' && bill.status !== 'cancelled' && bill.status !== 'approved') { - actions.push(React.createElement(Popconfirm, { + more.push({ key: 'cancel', - title: '取消账单将释放明细为已保存,是否继续?', - onConfirm: function () { handleCancelBill(bill); } - }, React.createElement(Button, { type: 'link', size: 'small', danger: true }, '取消账单'))); + label: '取消账单', + danger: true, + onClick: function () { + Modal.confirm({ + title: '取消账单将释放明细为已保存,是否继续?', + onOk: function () { handleCancelBill(bill); } + }); + } + }); } if (bill.status === 'approving') { - actions.push(React.createElement(Button, { - key: 'withdraw', type: 'link', size: 'small', - onClick: function () { handleWithdrawBill(bill); } - }, '撤回')); + more.push({ key: 'withdraw', label: '撤回', onClick: function () { handleWithdrawBill(bill); } }); } if (bill.status === 'approved') { - actions.push(React.createElement(Button, { - key: 'withdraw-approved', type: 'link', size: 'small', + more.push({ + key: 'withdraw-approved', + label: '撤回', onClick: function () { patchBillInLists(bill.id, { status: 'preview' }); patchRowsByBillId(bill.id, { detailStatus: 'bill_generated', submitStatus: 'draft' }); message.success('已撤回至待确认'); } - }, '撤回')); + }); } if (bill.status === 'approving') { if (bill.invoiceMode === 'first' && bill.invoiceStep < 1) { - actions.push(React.createElement(Button, { - key: 'fin1', type: 'link', size: 'small', - onClick: function () { handleFinanceApproveBill(bill, 1); } - }, '财务·上传发票')); + more.push({ key: 'fin1', label: '财务·上传发票', onClick: function () { handleFinanceApproveBill(bill, 1); } }); } else { - actions.push(React.createElement(Button, { - key: 'fin2', type: 'link', size: 'small', - onClick: function () { handleFinanceApproveBill(bill, 2); } - }, '财务·确认到账')); + more.push({ key: 'fin2', label: '财务·确认到账', onClick: function () { handleFinanceApproveBill(bill, 2); } }); } - actions.push(React.createElement(Button, { - key: 'reject', type: 'link', size: 'small', danger: true, - onClick: function () { handleFinanceRejectBill(bill); } - }, '驳回')); + more.push({ key: 'reject', label: '驳回', danger: true, onClick: function () { handleFinanceRejectBill(bill); } }); } - return React.createElement(Space, { size: 4, wrap: true }, actions); + var view = (bill.status === 'preview' || bill.status === 'rejected') + ? { label: '继续确认', onClick: function () { openBillPreviewFromManage(bill); } } + : undefined; + return React.createElement(OperationActions, { view: view, more: more }); } } ]; diff --git a/src/prototypes/oneos-web-login/index.tsx b/src/prototypes/oneos-web-login/index.tsx deleted file mode 100644 index f6e4b17..0000000 --- a/src/prototypes/oneos-web-login/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @name 登录 - * 自 ONE-OS web端 原稿复刻(1 个页面) - */ -import '../../common/oneosWebLegacy/legacyGlobals'; -import React from 'react'; -import Page1 from './pages/01-登录.jsx'; -import { OneosWebLegacyShell } from '../../common/oneosWebLegacy/OneosWebLegacyShell'; - -const pages = [ - { id: 'u767bu5f55', title: '登录', component: Page1 }, -]; - -export default function OneosWebLogin() { - return ( - - ); -} diff --git a/src/prototypes/oneos-web-login/pages/01-登录.jsx b/src/prototypes/oneos-web-login/pages/01-登录.jsx deleted file mode 100644 index 7d33e6e..0000000 --- a/src/prototypes/oneos-web-login/pages/01-登录.jsx +++ /dev/null @@ -1,457 +0,0 @@ -// 【重要】必须使用 const Component 作为组件变量名 -// 参考 Arco Design Pro 登录页布局:左侧品牌区 + 右侧登录表单 -const Component = function () { - var antd = window.antd; - var useState = React.useState; - var useRef = React.useRef; - var useEffect = React.useEffect; - - var _state = useState({ username: '', password: '', remember: true, agreeTerms: false, phone: '', smsCode: '' }); - var formData = _state[0]; - var setFormData = _state[1]; - - var _mode = useState('account'); - var loginMode = _mode[0]; - var setLoginMode = _mode[1]; - - var _countdown = useState(0); - var countdown = _countdown[0]; - var setCountdown = _countdown[1]; - - var _hasRequestedCode = useState(false); - var hasRequestedCode = _hasRequestedCode[0]; - var setHasRequestedCode = _hasRequestedCode[1]; - - var timerRef = useRef(null); - - useEffect(function () { - return function () { - if (timerRef.current) { - clearInterval(timerRef.current); - } - }; - }, []); - - var handleChange = function (field, value) { - var next = {}; - next[field] = value; - setFormData(function (prev) { - return Object.assign({}, prev, next); - }); - }; - - var handleGetCode = function () { - if (countdown > 0) return; - setHasRequestedCode(true); - setCountdown(60); - timerRef.current = setInterval(function () { - setCountdown(function (prev) { - if (prev <= 1) { - clearInterval(timerRef.current); - timerRef.current = null; - return 0; - } - return prev - 1; - }); - }, 1000); - }; - - var handleSubmit = function () { - if (!formData.agreeTerms) { - antd.message.warning('请阅读并同意《用户服务协议》和《隐私政策》'); - return; - } - if (loginMode === 'phone') { - if (!formData.phone) { - antd.message.warning('请输入手机号'); - return; - } - if (!formData.smsCode) { - antd.message.warning('请输入验证码'); - return; - } - } else { - if (!formData.username) { - antd.message.warning('请输入用户名'); - return; - } - if (!formData.password) { - antd.message.warning('请输入密码'); - return; - } - } - antd.message.success('登录成功(原型演示)'); - if (window.$axure && typeof window.$axure.navigateToUrl === 'function') { - window.$axure.navigateToUrl('工作台'); - } else { - window.location.hash = '工作台'; - } - }; - - var switchToPhone = function (e) { - if (e && e.preventDefault) e.preventDefault(); - setLoginMode('phone'); - }; - var switchToAccount = function (e) { - if (e && e.preventDefault) e.preventDefault(); - setLoginMode('account'); - }; - - var styles = { - wrap: { - display: 'flex', - minHeight: '100vh', - width: '100%', - fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', - backgroundColor: '#f5f5f5' - }, - left: { - flex: '1', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - padding: '48px', - background: 'linear-gradient(135deg, #1d2129 0%, #2d3748 50%, #1d2129 100%)', - color: '#fff' - }, - leftContent: { - maxWidth: '400px', - textAlign: 'center' - }, - title: { - fontSize: '28px', - fontWeight: '600', - marginBottom: '16px', - letterSpacing: '0.5px' - }, - slogan: { - fontSize: '17px', - fontWeight: '500', - opacity: 0.92, - lineHeight: 1.65, - marginBottom: '20px', - letterSpacing: '0.02em' - }, - desc: { - fontSize: '14px', - opacity: 0.72, - lineHeight: 1.6 - }, - right: { - width: '480px', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - padding: '48px', - backgroundColor: '#fff', - boxShadow: '-4px 0 24px rgba(0,0,0,0.06)' - }, - formCard: { - width: '100%', - maxWidth: '360px' - }, - formTitle: { - fontSize: '24px', - fontWeight: '600', - color: '#1d2129', - marginBottom: '8px', - textAlign: 'center' - }, - formSub: { - fontSize: '14px', - color: '#86909c', - marginBottom: '32px', - textAlign: 'center' - }, - formItem: { - marginBottom: '20px' - }, - label: { - display: 'block', - fontSize: '14px', - color: '#4e5969', - marginBottom: '8px' - }, - input: { - width: '100%', - height: '40px', - padding: '8px 12px', - fontSize: '14px', - border: '1px solid #e5e6eb', - borderRadius: '6px', - boxSizing: 'border-box', - outline: 'none' - }, - codeRow: { - display: 'flex', - gap: '8px', - alignItems: 'center' - }, - codeInput: { - flex: '1', - height: '40px', - padding: '8px 12px', - fontSize: '14px', - border: '1px solid #e5e6eb', - borderRadius: '6px', - boxSizing: 'border-box', - outline: 'none' - }, - getCodeBtn: { - flexShrink: 0, - height: '40px', - padding: '0 16px', - fontSize: '14px', - color: '#165dff', - backgroundColor: '#fff', - border: '1px solid #165dff', - borderRadius: '6px', - cursor: 'pointer' - }, - getCodeBtnDisabled: { - color: '#86909c', - borderColor: '#e5e6eb', - cursor: 'not-allowed' - }, - row: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: '24px' - }, - submitBtn: { - width: '100%', - height: '44px', - fontSize: '16px', - fontWeight: '500', - borderRadius: '6px' - }, - tabBar: { - display: 'flex', - marginBottom: '24px', - borderBottom: '1px solid #e5e6eb' - }, - tab: { - flex: 1, - textAlign: 'center', - padding: '12px 0', - fontSize: '15px', - color: '#86909c', - cursor: 'pointer', - borderBottom: '2px solid transparent', - marginBottom: '-1px' - }, - tabActive: { - color: '#165dff', - fontWeight: '500', - borderBottomColor: '#165dff' - } - }; - - return React.createElement( - React.Fragment, - null, - React.createElement('style', { - dangerouslySetInnerHTML: { - __html: '@media (max-width: 768px) { .login-right { width: 100% !important; min-width: 100% !important; } .login-left { min-height: 200px !important; } .login-wrap { flex-direction: column !important; } }' - } - }), - React.createElement( - 'div', - { style: styles.wrap, className: 'login-wrap' }, - React.createElement( - 'div', - { style: styles.left, className: 'login-left' }, - React.createElement( - 'div', - { style: styles.leftContent }, - React.createElement('h1', { style: styles.title }, '羚牛氢能'), - React.createElement('p', { style: styles.slogan }, '氢能资产数字化,业务运营一体化。'), - React.createElement('p', { style: styles.desc }, '数字化资产 · ONE-OS 运管平台') - ) - ), - React.createElement( - 'div', - { style: styles.right, className: 'login-right' }, - React.createElement( - 'div', - { style: styles.formCard }, - React.createElement('h2', { style: styles.formTitle }, '欢迎登录'), - React.createElement( - 'div', - { style: styles.tabBar }, - React.createElement( - 'div', - { - style: loginMode === 'account' ? Object.assign({}, styles.tab, styles.tabActive) : styles.tab, - onClick: switchToAccount, - role: 'button', - tabIndex: 0 - }, - '账号密码登录' - ), - React.createElement( - 'div', - { - style: loginMode === 'phone' ? Object.assign({}, styles.tab, styles.tabActive) : styles.tab, - onClick: switchToPhone, - role: 'button', - tabIndex: 0 - }, - '手机号登录' - ) - ), - React.createElement('p', { style: styles.formSub }, loginMode === 'phone' ? '请输入手机号与验证码' : '请输入您的账号与密码'), - loginMode === 'phone' - ? React.createElement( - React.Fragment, - null, - React.createElement( - 'div', - { style: styles.formItem }, - React.createElement('label', { style: styles.label }, '手机号'), - React.createElement('input', { - style: styles.input, - placeholder: '请输入手机号', - value: formData.phone, - onChange: function (e) { - handleChange('phone', e.target.value); - } - }) - ), - React.createElement( - 'div', - { style: styles.formItem }, - React.createElement('label', { style: styles.label }, '验证码'), - React.createElement( - 'div', - { style: styles.codeRow }, - React.createElement('input', { - style: styles.codeInput, - placeholder: '请输入验证码', - value: formData.smsCode, - onChange: function (e) { - handleChange('smsCode', e.target.value); - } - }), - React.createElement( - 'button', - { - type: 'button', - style: countdown > 0 ? Object.assign({}, styles.getCodeBtn, styles.getCodeBtnDisabled) : styles.getCodeBtn, - disabled: countdown > 0, - onClick: handleGetCode - }, - countdown > 0 ? countdown + '秒后重新获取' : (hasRequestedCode ? '再次获取' : '获取验证码') - ) - ) - ) - ) - : React.createElement( - React.Fragment, - null, - React.createElement( - 'div', - { style: styles.formItem }, - React.createElement('label', { style: styles.label }, '用户名'), - React.createElement('input', { - style: styles.input, - placeholder: '请输入用户名', - value: formData.username, - onChange: function (e) { - handleChange('username', e.target.value); - } - }) - ), - React.createElement( - 'div', - { style: styles.formItem }, - React.createElement('label', { style: styles.label }, '密码'), - React.createElement('input', { - style: styles.input, - type: 'password', - placeholder: '请输入密码', - value: formData.password, - onChange: function (e) { - handleChange('password', e.target.value); - } - }) - ) - ), - React.createElement( - 'div', - { style: styles.row }, - React.createElement( - 'label', - { style: { display: 'flex', alignItems: 'center', cursor: 'pointer', fontSize: '14px', color: '#4e5969' } }, - React.createElement('input', { - type: 'checkbox', - checked: formData.remember, - onChange: function (e) { - handleChange('remember', e.target.checked); - }, - style: { marginRight: '8px' } - }), - '记住我' - ) - ), - React.createElement( - 'label', - { - style: { - display: 'flex', - alignItems: 'flex-start', - cursor: 'pointer', - fontSize: '14px', - color: '#4e5969', - marginBottom: '20px', - lineHeight: 1.5 - } - }, - React.createElement('input', { - type: 'checkbox', - checked: formData.agreeTerms, - onChange: function (e) { - handleChange('agreeTerms', e.target.checked); - }, - style: { marginRight: '8px', marginTop: '3px', flexShrink: 0 } - }), - React.createElement( - 'span', - null, - '阅读并同意', - React.createElement('a', { href: '#', style: { color: '#165dff', marginLeft: '2px' }, onClick: function (e) { e.preventDefault(); } }, '《用户服务协议》'), - '和', - React.createElement('a', { href: '#', style: { color: '#165dff' }, onClick: function (e) { e.preventDefault(); } }, '《隐私政策》') - ) - ), - antd && antd.Button - ? React.createElement(antd.Button, { - type: 'primary', - block: true, - size: 'large', - style: styles.submitBtn, - onClick: handleSubmit - }, '登 录') - : React.createElement( - 'button', - { - style: Object.assign({}, styles.submitBtn, { - backgroundColor: '#165dff', - color: '#fff', - border: 'none', - cursor: 'pointer' - }), - onClick: handleSubmit - }, - '登 录' - ) - ) - ) - ) - ); -}; - -export default Component; diff --git a/src/prototypes/oneos-web-ops/pages/01-备件库存.jsx b/src/prototypes/oneos-web-ops/pages/01-备件库存.jsx index 9511bf2..d1cd3f0 100644 --- a/src/prototypes/oneos-web-ops/pages/01-备件库存.jsx +++ b/src/prototypes/oneos-web-ops/pages/01-备件库存.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 数字化资产 ONEOS 运管平台 - 运维管理 - 备件库 - 备件库存(原型) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -7,7 +8,6 @@ const Component = function () { var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -685,14 +685,16 @@ const Component = function () { { title: '操作', key: 'actions', - width: 220, + width: 168, fixed: 'right', render: function (_, row) { - return React.createElement(Space, { size: 'small', wrap: true }, - React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { openDetail(row); } }, '备件明细'), - React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { openInboundHist(row); } }, '入库记录'), - React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { openOutboundHist(row); } }, '出库记录') - ); + return React.createElement(OperationActions, { + more: [ + { key: 'detail', label: '备件明细', onClick: function () { openDetail(row); } }, + { key: 'inbound', label: '入库记录', onClick: function () { openInboundHist(row); } }, + { key: 'outbound', label: '出库记录', onClick: function () { openOutboundHist(row); } } + ] + }); } } ]; @@ -808,9 +810,7 @@ const Component = function () { }, [detailRow, spareCatalog]); return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '备件库' }, { title: '备件库存' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/02-仓库管理.jsx b/src/prototypes/oneos-web-ops/pages/02-仓库管理.jsx index 96e720e..93c65ea 100644 --- a/src/prototypes/oneos-web-ops/pages/02-仓库管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/02-仓库管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 运维管理 - 备件库 - 仓库管理(列表页原型) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -7,7 +8,6 @@ const Component = function () { var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Drawer = antd.Drawer; @@ -340,22 +340,20 @@ const Component = function () { { title: '操作', key: 'action', - width: 180, + width: 168, fixed: 'right', render: function (_, r) { - return React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleEdit(r); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { handleDelete(r); } }, '删除') - ); + return React.createElement(OperationActions, { + edit: { onClick: function () { handleEdit(r); } }, + more: [{ key: 'delete', label: '删除', danger: true, onClick: function () { handleDelete(r); } }] + }); } } ]; }, [serialStart, handleDelete]); return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '备件库' }, { title: '仓库管理' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/03-备车-编辑.jsx b/src/prototypes/oneos-web-ops/pages/03-备车-编辑.jsx index 8c1b903..33f3386 100644 --- a/src/prototypes/oneos-web-ops/pages/03-备车-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/03-备车-编辑.jsx @@ -377,21 +377,9 @@ const Component = function () { )); }; - var breadcrumbNodes = [ - React.createElement('span', { key: '1' }, '运维管理'), - React.createElement('span', { key: '2', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '3' }, '车辆业务'), - React.createElement('span', { key: '4', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '5' }, '备车管理'), - React.createElement('span', { key: '6', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '7', style: { color: '#1890ff' } }, '编辑') - ]; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, breadcrumbNodes) - ), - React.createElement('div', { style: styles.card }, + React.createElement('div', { style: styles.card }, React.createElement('div', { style: styles.cardBody }, React.createElement('div', { style: styles.formRow }, React.createElement(FormItem, { label: '车牌号', required: true }, @@ -546,7 +534,7 @@ const Component = function () { styles: { body: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', paddingBottom: 0 } }, footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, React.createElement(Button, { onClick: function () { drawerOpenState[1](false); } }, '返回'), - React.createElement(Button, { type: 'primary', onClick: function () { drawerOpenState[1](false); message.success('检查单已保存'); } }, '提交') + React.createElement(Button, { type: 'primary', onClick: function () { drawerOpenState[1](false); message.success('检查单已保存'); } }, '提交') ) }, React.createElement('div', { style: { flex: 1, minHeight: 0, overflow: 'auto' } }, diff --git a/src/prototypes/oneos-web-ops/pages/04-备车-查看.jsx b/src/prototypes/oneos-web-ops/pages/04-备车-查看.jsx index c650a20..00e48bc 100644 --- a/src/prototypes/oneos-web-ops/pages/04-备车-查看.jsx +++ b/src/prototypes/oneos-web-ops/pages/04-备车-查看.jsx @@ -6,7 +6,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Button = antd.Button; @@ -138,20 +137,9 @@ const Component = function () { var requirementDocContent = '新增备车\n\n1.面包屑:\n1.1.运维管理-车辆业务-备车管理-新增备车\n\n2.表单:\n2.1.车牌号:必选项,选择器,支持从输入框内输入内容进行模糊搜索,默认拉取「车牌管理」中所有「车牌号」;\n2.2.车辆类型:输入框禁用,选择车牌号后自动拉取该车牌号对应「车辆类型」;\n2.3.品牌:输入框禁用,选择车牌号后自动拉取该车牌号对应「品牌」;\n2.4.型号:输入框禁用,选择车牌号后自动拉取该车牌号对应「型号」\n2.5.车辆识别代码:输入框禁用,选择车牌号后自动拉取该车牌号「车辆识别代码」;\n2.6.车身广告:开关,选择车辆后拉取该车辆「后装设备」「车身广告」,如果该车辆有「车身广告」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「车身广告」中,安装时间以该条备车记录提交成功为准;\n2.7.广告照片:图片上传,最多支持上传4张图片,支持主流照片格式。上传后,上传按钮后方横排显示已上传图片缩略图,支持点击预览和删除;\n2.8.尾板:开关,选择车辆后拉取该车辆「后装设备」「尾板」,如果该车辆有「尾板」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「尾板」中,安装时间以该条备车记录提交成功为准;\n2.9.是否有挂:输入框,提示信息为:请输入挂车牌号,不输入为无挂;\n2.10.瑕疵:图片上传,最多支持上传4张图片,支持主流照片格式。上传后,上传按钮后方横排显示已上传图片缩略图,支持点击预览和删除;\n2.11.车辆检查:按钮,文字为备车检查单,点击右侧展开抽屉,抽屉内显示列表,字段为类别、检查项目、选择、备注;\n 2.11.1.类别:分为车灯、仪表盘、驾驶室、轮胎、液位检查、外观检查、车辆外观、其他、随车工具、随车证件、整车、燃料电池系统、冷机、制动系统;\n 2.11.2.检查项目:车灯类别对应(大灯、转向灯、小灯、示廓灯、刹车灯、倒车灯、牌照灯、防雾灯、室内灯)、仪表盘对应(氢系统指示、电控系统指示、数值清晰准确、故障报警灯)、驾驶室对应(点烟器、车窗升降、按键开关、雨刮器、内后视镜是否正常、内/外门把手、安全带、空调冷暖风、仪表盘、门锁功能、手刹、车钥匙功能是否正常、喇叭、音响功能、遮阳板、主副驾座椅、方向盘、内饰干净整洁)\n 2.11.3.检查情况:开关,在检查项目每项后方显示,默认为开,可手动进行关闭;\n 2.11.4.备注:输入框;\n2.12.下方为提交和保存;\n 2.12.1.提交:点击提交,toast提示:备车成功,同时保存该条数据并跳转至「备车管理」「已完成」;\n 2.12.2.保存:点击保存,toast提示:暂存成功,同时保存该条数据并跳转至「备车管理」「待提交」;'; - var breadcrumbNodes = [ - React.createElement('span', { key: '1' }, '运维管理'), - React.createElement('span', { key: '2', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '3' }, '车辆业务'), - React.createElement('span', { key: '4', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '5' }, '备车管理'), - React.createElement('span', { key: '6', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '7', style: { color: '#1890ff' } }, '查看备车') - ]; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, breadcrumbNodes), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') ), React.createElement('div', { style: styles.card }, React.createElement('div', { style: styles.cardBody }, diff --git a/src/prototypes/oneos-web-ops/pages/05-备车-新增.jsx b/src/prototypes/oneos-web-ops/pages/05-备车-新增.jsx index a90dba4..6915878 100644 --- a/src/prototypes/oneos-web-ops/pages/05-备车-新增.jsx +++ b/src/prototypes/oneos-web-ops/pages/05-备车-新增.jsx @@ -9,7 +9,6 @@ const Component = function () { var useEffect = React.useEffect; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Select = antd.Select; var Input = antd.Input; @@ -391,20 +390,8 @@ const Component = function () { var requirementDocContent = '新增备车\n一个「数字化资产ONEOS运管平台」中的「新增」模块\n1.面包屑:\n1.1.运维管理-车辆业务-备车管理-新增\n\n2.表单:\n2.1.车牌号:必选项,选择器,支持从输入框内输入内容进行模糊搜索,默认拉取「车牌管理」中所有「车牌号」;\n2.2.车辆类型:输入框禁用,选择车牌号后自动拉取该车牌号对应「车辆类型」;\n2.3.品牌:输入框禁用,选择车牌号后自动拉取该车牌号对应「品牌」;\n2.4.型号:输入框禁用,选择车牌号后自动拉取该车牌号对应「型号」\n2.5.车辆识别代码:输入框禁用,选择车牌号后自动拉取该车牌号「车辆识别代码」;\n2.6.车身广告及放大字:开关,选择车辆后拉取该车辆「后装设备」「车身广告」,如果该车辆有「车身广告」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「车身广告」中,安装时间以该条备车记录提交成功为准;\n2.7.广告照片:图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮;\n2.8.放大字照片:图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮;\n2.8.尾板:开关,选择车辆后拉取该车辆「后装设备」「尾板」,如果该车辆有「尾板」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「尾板」中,安装时间以该条备车记录提交成功为准;\n2.9.备胎照片:图片上传,最多支持上传1张图片,支持主流照片格式。上传时弹出卡片,提示正在识别中,请勿关闭页面,之后卡片左侧显示备胎照片,右侧输入框显示识别出的胎纹深度,后缀单位为mm,点击卡片中确认按钮,反写至备胎胎纹深度字段下;\n2.10.备胎胎纹深度:输入框,反写备胎照片OCR识别胎纹深度结果,支持修改;\n2.11.瑕疵:图片上传,最多支持上传4张图片,支持主流照片格式。上传后,上传按钮后方横排显示已上传图片缩略图,支持点击预览和删除;\n2.12.车辆检查:按钮,文字为备车检查单,点击右侧展开抽屉,抽屉内显示列表,字段为类别、检查项目、选择、备注;\n 2.11.1.类别:分为车灯、仪表盘、驾驶室、轮胎、液位检查、外观检查、车辆外观、其他、随车工具、随车证件、整车、燃料电池系统、冷机、制动系统;\n 2.11.2.检查项目:车灯类别对应(大灯、转向灯、小灯、示廓灯、刹车灯、倒车灯、牌照灯、防雾灯、室内灯)、仪表盘对应(氢系统指示、电控系统指示、数值清晰准确、故障报警灯)、驾驶室对应(点烟器、车窗升降、按键开关、雨刮器、内后视镜是否正常、内/外门把手、安全带、空调冷暖风、仪表盘、门锁功能、手刹、车钥匙功能是否正常、喇叭、音响功能、遮阳板、主副驾座椅、方向盘、内饰干净整洁)\n 2.11.3.检查情况:其他项为开关,在检查项目每项后方显示,默认为开,可手动进行关闭,轮胎为输入框,提示请输入胎纹深度;\n 2.11.4.备注:输入框;\n2.12.下方为提交和保存;\n 2.12.1.提交:点击提交,toast提示:备车成功,同时保存该条数据并跳转至「备车管理」「已完成」;\n 2.12.2.保存:点击保存,toast提示:暂存成功,同时保存该条数据并跳转至「备车管理」「待提交」;'; - var breadcrumbNodes = [ - React.createElement('span', { key: '1' }, '运维管理'), - React.createElement('span', { key: '2', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '3' }, '车辆业务'), - React.createElement('span', { key: '4', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '5' }, '备车管理'), - React.createElement('span', { key: '6', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '7', style: { color: '#1890ff' } }, '新增') - ]; - return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, breadcrumbNodes), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') ), React.createElement('div', { style: styles.card }, React.createElement('div', { style: styles.cardBody }, @@ -573,7 +560,7 @@ const Component = function () { styles: { body: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', paddingBottom: 0 } }, footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, React.createElement(Button, { onClick: function () { drawerOpenState[1](false); } }, '返回'), - React.createElement(Button, { type: 'primary', onClick: function () { drawerOpenState[1](false); message.success('检查单已保存'); } }, '提交') + React.createElement(Button, { type: 'primary', onClick: function () { drawerOpenState[1](false); message.success('检查单已保存'); } }, '提交') ) }, React.createElement('div', { style: { flex: 1, minHeight: 0, overflow: 'auto' } }, diff --git a/src/prototypes/oneos-web-ops/pages/06-备车管理.jsx b/src/prototypes/oneos-web-ops/pages/06-备车管理.jsx index 6d62661..5af4ec9 100644 --- a/src/prototypes/oneos-web-ops/pages/06-备车管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/06-备车管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 备车管理 - 车辆资产管理后台(按 antd 规范) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -7,7 +8,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var DatePicker = antd.DatePicker; var Select = antd.Select; @@ -388,8 +388,10 @@ const Component = function () { { title: '车身广告及放大字', dataIndex: 'bodyAd', key: 'bodyAd', width: 120, render: function (v) { return v === '有' || v === '无' ? v : '—'; } }, { title: '尾板', dataIndex: 'tailboard', key: 'tailboard', width: 70, render: function (v) { return v === '有' || v === '无' ? v : '—'; } }, { title: '备胎胎纹深度', key: 'spareTireTreadDepth', width: 110, render: function (_, r) { var d = r.spareTireTreadDepth; return d ? (String(d).replace(/mm$/, '') + 'mm') : '—'; } }, - { title: '操作', key: 'action', width: 80, fixed: 'right', render: function (_, r) { - return React.createElement(Button, { type: 'link', size: 'small', onClick: function () {} }, '查看'); + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, r) { + return React.createElement(OperationActions, { + view: { onClick: function () {} } + }); }} ]; } @@ -411,10 +413,10 @@ const Component = function () { { title: '车身广告及放大字', dataIndex: 'bodyAd', key: 'bodyAd', width: 120, render: function (v) { return v === '有' || v === '无' ? v : '—'; } }, { title: '尾板', dataIndex: 'tailboard', key: 'tailboard', width: 70, render: function (v) { return v === '有' || v === '无' ? v : '—'; } }, { title: '备胎胎纹深度', key: 'spareTireTreadDepth', width: 110, render: function (_, r) { var d = r.spareTireTreadDepth; return d ? (String(d).replace(/mm$/, '') + 'mm') : '—'; } }, - { title: '操作', key: 'action', width: 160, fixed: 'right', render: function (_, r) { - return React.createElement(Space, { size: 'small' }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () {} }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, r) { + return React.createElement(OperationActions, { + view: { onClick: function () {} }, + edit: { onClick: function () { setAddForm(function (prev) { var n = {}; for (var k in prev) n[k] = prev[k]; @@ -425,9 +427,9 @@ const Component = function () { }); setAddStep(1); setCurrentView('add'); - } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { setPendingDeleteId(r.id); } }, '删除') - ); + } }, + more: [{ key: 'delete', label: '删除', danger: true, onClick: function () { setPendingDeleteId(r.id); } }] + }); }} ]; } @@ -449,15 +451,8 @@ const Component = function () { // —————— 列表页 —————— function ListView() { - var breadcrumbItems = [ - { title: '运维管理' }, - { title: '车辆业务' }, - { title: '备车管理' } - ]; return React.createElement('div', { style: { padding: 24, background: '#f5f5f5', minHeight: '100vh' } }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: breadcrumbItems }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqDocOpenState[1](true); } }, '查看需求说明') ), React.createElement(Modal, { title: '需求说明', @@ -657,9 +652,7 @@ const Component = function () { { title: '备车管理' }, { title: '新增备车' } ]; - return React.createElement('div', { style: { padding: 24, background: '#f5f5f5', minHeight: '100vh' } }, - React.createElement(Breadcrumb, { items: addBreadcrumbItems, style: { marginBottom: 16 } }), - React.createElement(Card, null, + return React.createElement('div', { style: { padding: 24, background: '#f5f5f5', minHeight: '100vh' } }, React.createElement(Card, null, React.createElement(Steps, { current: step - 1, style: { marginBottom: 24 }, items: [ { title: '步骤1 车辆记录' }, { title: '步骤2 备车检查' } diff --git a/src/prototypes/oneos-web-ops/pages/07-查看故障.jsx b/src/prototypes/oneos-web-ops/pages/07-查看故障.jsx index 98afaa0..2388ec7 100644 --- a/src/prototypes/oneos-web-ops/pages/07-查看故障.jsx +++ b/src/prototypes/oneos-web-ops/pages/07-查看故障.jsx @@ -12,7 +12,6 @@ const Component = function () { var Form = antd.Form; var Row = antd.Row; var Col = antd.Col; - var Breadcrumb = antd.Breadcrumb; var Layout = antd.Layout; var message = antd.message; var Card = antd.Card; @@ -115,18 +114,7 @@ const Component = function () { .arco-theme-overrides .ant-form-item-label > label::after { display: none !important; content: "" !important; margin: 0 !important; } `), React.createElement('div', { className: 'arco-grouped-form-page' }, - React.createElement('div', { className: 'arco-grouped-form-page-content' }, - React.createElement(Breadcrumb, { - separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'), - items: [ - { title: '首页' }, - { title: '运维管理' }, - { title: '车辆业务' }, - { title: React.createElement('a', { onClick: function(e) { e.preventDefault(); handleBack(); } }, '故障管理') }, - { title: React.createElement('span', { style: { color: '#1d2129' } }, '查看故障单') } - ] - }), - + React.createElement('div', { className: 'arco-grouped-form-page-content' }, React.createElement(Form, { form: faultForm, layout: 'vertical', disabled: true }, // 车辆信息 React.createElement(Card, { title: '车辆信息', bordered: false }, diff --git a/src/prototypes/oneos-web-ops/pages/08-调拨管理-查看.jsx b/src/prototypes/oneos-web-ops/pages/08-调拨管理-查看.jsx index 8550c7d..e042ebc 100644 --- a/src/prototypes/oneos-web-ops/pages/08-调拨管理-查看.jsx +++ b/src/prototypes/oneos-web-ops/pages/08-调拨管理-查看.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var DatePicker = antd.DatePicker; @@ -141,9 +140,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '查看' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( diff --git a/src/prototypes/oneos-web-ops/pages/09-调拨管理-调拨信息.jsx b/src/prototypes/oneos-web-ops/pages/09-调拨管理-调拨信息.jsx index 5d0440d..11e5c0f 100644 --- a/src/prototypes/oneos-web-ops/pages/09-调拨管理-调拨信息.jsx +++ b/src/prototypes/oneos-web-ops/pages/09-调拨管理-调拨信息.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var Input = antd.Input; @@ -433,11 +432,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { - items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '调拨信息' }] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( diff --git a/src/prototypes/oneos-web-ops/pages/10-调拨管理-接收信息-第三方运输.jsx b/src/prototypes/oneos-web-ops/pages/10-调拨管理-接收信息-第三方运输.jsx index d791c5a..a98484d 100644 --- a/src/prototypes/oneos-web-ops/pages/10-调拨管理-接收信息-第三方运输.jsx +++ b/src/prototypes/oneos-web-ops/pages/10-调拨管理-接收信息-第三方运输.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var DatePicker = antd.DatePicker; @@ -252,11 +251,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { - items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '接收信息' }] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( diff --git a/src/prototypes/oneos-web-ops/pages/11-调拨管理-接收信息-司机运输.jsx b/src/prototypes/oneos-web-ops/pages/11-调拨管理-接收信息-司机运输.jsx index 84044a4..4fd7ab8 100644 --- a/src/prototypes/oneos-web-ops/pages/11-调拨管理-接收信息-司机运输.jsx +++ b/src/prototypes/oneos-web-ops/pages/11-调拨管理-接收信息-司机运输.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var DatePicker = antd.DatePicker; @@ -354,9 +353,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '接收信息' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( diff --git a/src/prototypes/oneos-web-ops/pages/12-调拨管理-新增.jsx b/src/prototypes/oneos-web-ops/pages/12-调拨管理-新增.jsx index e91d0f3..161849a 100644 --- a/src/prototypes/oneos-web-ops/pages/12-调拨管理-新增.jsx +++ b/src/prototypes/oneos-web-ops/pages/12-调拨管理-新增.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var Cascader = antd.Cascader; @@ -354,11 +353,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { - items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '新增' }] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( diff --git a/src/prototypes/oneos-web-ops/pages/13-调拨管理-业务提交-编辑.jsx b/src/prototypes/oneos-web-ops/pages/13-调拨管理-业务提交-编辑.jsx index 07017f2..d2493d4 100644 --- a/src/prototypes/oneos-web-ops/pages/13-调拨管理-业务提交-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/13-调拨管理-业务提交-编辑.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var Cascader = antd.Cascader; @@ -329,9 +328,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '编辑' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( 'div', diff --git a/src/prototypes/oneos-web-ops/pages/14-调拨管理-运维(调拨方)-编辑.jsx b/src/prototypes/oneos-web-ops/pages/14-调拨管理-运维(调拨方)-编辑.jsx index a7d47a8..944757b 100644 --- a/src/prototypes/oneos-web-ops/pages/14-调拨管理-运维(调拨方)-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/14-调拨管理-运维(调拨方)-编辑.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var Input = antd.Input; @@ -413,11 +412,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { - items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '运维(调拨方)-编辑' }] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( 'div', diff --git a/src/prototypes/oneos-web-ops/pages/15-调拨管理-运维(接收)-编辑.jsx b/src/prototypes/oneos-web-ops/pages/15-调拨管理-运维(接收)-编辑.jsx index dfca410..d21dbdd 100644 --- a/src/prototypes/oneos-web-ops/pages/15-调拨管理-运维(接收)-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/15-调拨管理-运维(接收)-编辑.jsx @@ -9,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Card = antd.Card; var DatePicker = antd.DatePicker; @@ -262,11 +261,7 @@ const Component = function () { flexWrap: 'wrap', gap: 8 } - }, - React.createElement(Breadcrumb, { - items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }, { title: '运维(接收)-编辑' }] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement( 'div', diff --git a/src/prototypes/oneos-web-ops/pages/16-调拨管理.jsx b/src/prototypes/oneos-web-ops/pages/16-调拨管理.jsx index 4d813da..75fc73e 100644 --- a/src/prototypes/oneos-web-ops/pages/16-调拨管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/16-调拨管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 运维管理 - 车辆业务 - 调拨管理(列表页) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -8,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -753,7 +753,7 @@ const Component = function () { { title: '操作', key: 'action', - width: 340, + width: 168, fixed: 'right', render: function (_, r) { var bizSt = String(r && r.bizApprovalStatus ? r.bizApprovalStatus : ''); @@ -763,14 +763,17 @@ const Component = function () { var rowId = r && r.id; var showTransferInfo = bizSt === '审批完成' && (!opsSt || opsSt === '待提交'); var showReceiveInfo = opsSt === '审批中' || opsSt === '审批完成'; - return React.createElement('span', { style: { display: 'inline-flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' } }, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转调拨管理-查看页(原型)ID:' + rowId); } }, '查看'), - showEdit ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转调拨管理-编辑页(原型)ID:' + rowId); } }, '编辑') : null, - showTransferInfo ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转调拨管理-调拨信息页(原型)ID:' + rowId); } }, '调拨信息') : null, - showReceiveInfo ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转调拨管理-接收信息页(原型)ID:' + rowId); } }, '接收信息') : null, - canRevoke ? React.createElement(Button, { - type: 'link', - size: 'small', + var more = []; + if (showTransferInfo) { + more.push({ key: 'transferInfo', label: '调拨信息', onClick: function () { message.info('跳转调拨管理-调拨信息页(原型)ID:' + rowId); } }); + } + if (showReceiveInfo) { + more.push({ key: 'receiveInfo', label: '接收信息', onClick: function () { message.info('跳转调拨管理-接收信息页(原型)ID:' + rowId); } }); + } + if (canRevoke) { + more.push({ + key: 'revoke', + label: '撤回', danger: true, onClick: function () { Modal.confirm({ @@ -789,8 +792,13 @@ const Component = function () { } }); } - }, '撤回') : null - ); + }); + } + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('跳转调拨管理-查看页(原型)ID:' + rowId); } }, + edit: showEdit ? { onClick: function () { message.info('跳转调拨管理-编辑页(原型)ID:' + rowId); } } : undefined, + more: more + }); } } ]; @@ -860,10 +868,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 100, + width: 168, fixed: 'right', render: function (_, r) { - return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转调拨管理-查看页(原型)ID:' + r.id); } }, '查看'); + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('跳转调拨管理-查看页(原型)ID:' + r.id); } } + }); } } ]; @@ -959,9 +969,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '调拨管理' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/17-故障管理-编辑.jsx b/src/prototypes/oneos-web-ops/pages/17-故障管理-编辑.jsx index 1fcb01c..9439077 100644 --- a/src/prototypes/oneos-web-ops/pages/17-故障管理-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/17-故障管理-编辑.jsx @@ -10,7 +10,6 @@ const Component = function () { var Form = antd.Form; var Row = antd.Row; var Col = antd.Col; - var Breadcrumb = antd.Breadcrumb; var Layout = antd.Layout; var message = antd.message; var Card = antd.Card; @@ -138,17 +137,6 @@ const Component = function () { `), React.createElement('div', { className: 'arco-grouped-form-page' }, React.createElement('div', { className: 'arco-grouped-form-page-content' }, - React.createElement(Breadcrumb, { - separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'), - items: [ - { title: '首页' }, - { title: '运维管理' }, - { title: '车辆业务' }, - { title: React.createElement('a', { onClick: function(e) { e.preventDefault(); handleBack(); } }, '故障管理') }, - { title: React.createElement('span', { style: { color: '#1d2129' } }, '编辑故障单') } - ] - }), - React.createElement(Form, { form: faultForm, layout: 'vertical' }, React.createElement(Card, { title: '车辆信息', bordered: false }, React.createElement(Row, { gutter: 24 }, diff --git a/src/prototypes/oneos-web-ops/pages/18-故障管理.jsx b/src/prototypes/oneos-web-ops/pages/18-故障管理.jsx index 488afaa..457a370 100644 --- a/src/prototypes/oneos-web-ops/pages/18-故障管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/18-故障管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // ONEOS-web - 运维管理 - 车辆业务 - 故障管理(列表与表单) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -16,7 +17,6 @@ const Component = function () { var Row = antd.Row; var Col = antd.Col; var Divider = antd.Divider; - var Breadcrumb = antd.Breadcrumb; var Layout = antd.Layout; var message = antd.message; var Tag = antd.Tag; @@ -132,15 +132,12 @@ const Component = function () { title: '操作', key: 'action', fixed: 'right', - width: listTab === 'history' ? 72 : 104, + width: 168, render: function (_, record) { - if (listTab === 'history') { - return React.createElement(Button, { type: 'link', size: 'small', style: { padding: '0 4px' }, onClick: function () { handleView(record); } }, '查看'); - } - return React.createElement(Space, { size: 0, split: React.createElement('span', { style: { color: '#e5e6eb' } }, '|') }, - React.createElement(Button, { type: 'link', size: 'small', style: { padding: '0 4px' }, onClick: function () { handleView(record); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', style: { padding: '0 4px' }, onClick: function () { handleEdit(record); } }, '编辑') - ); + return React.createElement(OperationActions, { + view: { onClick: function () { handleView(record); } }, + edit: listTab === 'history' ? undefined : { onClick: function () { handleEdit(record); } } + }); } } ]; @@ -355,17 +352,7 @@ const Component = function () { `), React.createElement('div', { style: { padding: '16px 20px 24px', display: 'flex', flexDirection: 'column', flex: 1 } }, React.createElement(Card, { className: 'customer-page-card', bordered: false }, - React.createElement('div', { className: 'customer-page-header-row', style: { marginBottom: 16, justifyContent: 'space-between', width: '100%' } }, - React.createElement(Breadcrumb, { - separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'), - items: [ - { title: React.createElement(ListIcon, { style: { display: 'inline-flex', alignItems: 'center', fontSize: 14, transform: 'translate(-2px, 1px)' } }) }, - { title: '运维管理' }, - { title: '车辆业务' }, - { title: React.createElement('span', { style: { color: '#1d2129', fontWeight: 700, fontSize: 16, lineHeight: '22px' } }, '故障管理') } - ] - }), - React.createElement(Button, { + React.createElement('div', { className: 'customer-page-header-row', style: { marginBottom: 16, justifyContent: 'space-between', width: '100%' } }, React.createElement(Button, { type: 'link', icon: React.createElement(FileTextIcon, null), style: { display: 'flex', alignItems: 'center', gap: 4, padding: '0 4px', color: '#165dff', fontWeight: 500 }, diff --git a/src/prototypes/oneos-web-ops/pages/19-还车管理-查看.jsx b/src/prototypes/oneos-web-ops/pages/19-还车管理-查看.jsx index 829213c..6d531da 100644 --- a/src/prototypes/oneos-web-ops/pages/19-还车管理-查看.jsx +++ b/src/prototypes/oneos-web-ops/pages/19-还车管理-查看.jsx @@ -9,7 +9,6 @@ const Component = function () { var useEffect = React.useEffect; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -402,9 +401,7 @@ const Component = function () { // ---------- render ---------- return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '还车管理' }, { title: '查看' }] }), - React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '合同信息', style: cardStyle }, diff --git a/src/prototypes/oneos-web-ops/pages/20-还车管理-还车.jsx b/src/prototypes/oneos-web-ops/pages/20-还车管理-还车.jsx index 8fae36c..929105a 100644 --- a/src/prototypes/oneos-web-ops/pages/20-还车管理-还车.jsx +++ b/src/prototypes/oneos-web-ops/pages/20-还车管理-还车.jsx @@ -9,7 +9,6 @@ const Component = function () { var useEffect = React.useEffect; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -558,9 +557,7 @@ const Component = function () { // ---------- render ---------- return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '还车管理' }] }), - React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '合同信息', style: cardStyle }, diff --git a/src/prototypes/oneos-web-ops/pages/21-还车管理.jsx b/src/prototypes/oneos-web-ops/pages/21-还车管理.jsx index a73f241..8474818 100644 --- a/src/prototypes/oneos-web-ops/pages/21-还车管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/21-还车管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 运维管理 - 车辆业务 - 还车管理 +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -7,7 +8,6 @@ const Component = function () { var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Select = antd.Select; var Button = antd.Button; @@ -227,14 +227,16 @@ const Component = function () { { title: '操作', key: 'action', - width: 160, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } }, - record.vehicleArrived - ? React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: handleReturnCar }, '还车') - : React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { handleVehicleArrived(record); } }, '车辆到达') - ); + return React.createElement(OperationActions, { + more: [{ + key: record.vehicleArrived ? 'return' : 'arrived', + label: record.vehicleArrived ? '还车' : '车辆到达', + onClick: record.vehicleArrived ? handleReturnCar : function () { handleVehicleArrived(record); } + }] + }); } } ]; @@ -257,10 +259,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 80, + width: 168, fixed: 'right', render: function () { - return React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: handleViewHistory }, '查看'); + return React.createElement(OperationActions, { + view: { onClick: handleViewHistory } + }); } } ]; @@ -401,15 +405,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '运维管理' }, - { title: '车辆业务' }, - { title: '还车管理' } - ] - }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明') ), React.createElement(Card, { style: { marginBottom: 16 } }, React.createElement('div', { diff --git a/src/prototypes/oneos-web-ops/pages/22-后装设备.jsx b/src/prototypes/oneos-web-ops/pages/22-后装设备.jsx index 1fd5eef..b9e9163 100644 --- a/src/prototypes/oneos-web-ops/pages/22-后装设备.jsx +++ b/src/prototypes/oneos-web-ops/pages/22-后装设备.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 后装设备 - 运维管理-车辆业务 +import { OperationActions } from '../../../common/OperationActions'; var ARCO_TOKEN = { primary: '#165DFF', primaryHover: '#4080FF', @@ -211,16 +212,7 @@ const Component = function () { return React.createElement( 'div', { style: styles.page }, - React.createElement( - 'div', - { style: styles.breadcrumb }, - React.createElement('div', { style: styles.breadcrumbLeft }, - React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'), - React.createElement('span', { style: { marginRight: '8px' } }, '/'), - React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'), - React.createElement('span', { style: { marginRight: '8px' } }, '/'), - React.createElement('span', { style: styles.breadcrumbCurrent }, '后装设备') - ), + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明') ), React.createElement( @@ -292,13 +284,14 @@ const Component = function () { { title: '操作', key: 'action', - width: 160, + width: 168, + fixed: 'right', render: function (_, row) { - return React.createElement(React.Fragment, null, - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-查看'); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-编辑'); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function () { setRemoveConfirmRecord(row); } }, '拆除') - ); + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-查看'); } }, + edit: { onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-编辑'); } }, + more: [{ key: 'remove', label: '拆除', danger: true, onClick: function () { setRemoveConfirmRecord(row); } }] + }); } } ], @@ -333,9 +326,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 80, + width: 168, + fixed: 'right', render: function (_, row) { - return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-查看'); } }, '查看'); + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('请查看运维管理-车辆业务-后装设备-查看'); } } + }); } } ], diff --git a/src/prototypes/oneos-web-ops/pages/24-交车管理-交车单-编辑.jsx b/src/prototypes/oneos-web-ops/pages/24-交车管理-交车单-编辑.jsx index c2c32d4..1b10d6e 100644 --- a/src/prototypes/oneos-web-ops/pages/24-交车管理-交车单-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/24-交车管理-交车单-编辑.jsx @@ -8,7 +8,6 @@ const Component = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -603,9 +602,7 @@ function handleSave() { // ---------- render ---------- return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '交车管理' }, { title: '交车单-编辑' }] }), - React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明') ), React.createElement(Card, { title: '交车明细', style: cardStyle }, diff --git a/src/prototypes/oneos-web-ops/pages/25-交车管理-交车单-查看.jsx b/src/prototypes/oneos-web-ops/pages/25-交车管理-交车单-查看.jsx index f456657..5fd5678 100644 --- a/src/prototypes/oneos-web-ops/pages/25-交车管理-交车单-查看.jsx +++ b/src/prototypes/oneos-web-ops/pages/25-交车管理-交车单-查看.jsx @@ -7,7 +7,6 @@ const Component = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Table = antd.Table; var Button = antd.Button; @@ -318,10 +317,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '交车管理' }, { title: '交车单-查看' }] }) - ), - + React.createElement(Card, { title: '交车明细', style: cardStyle }, React.createElement('div', null, React.createElement('div', { style: styles.formRow }, diff --git a/src/prototypes/oneos-web-ops/pages/26-交车管理-交车单.jsx b/src/prototypes/oneos-web-ops/pages/26-交车管理-交车单.jsx index 019784c..a6f76f4 100644 --- a/src/prototypes/oneos-web-ops/pages/26-交车管理-交车单.jsx +++ b/src/prototypes/oneos-web-ops/pages/26-交车管理-交车单.jsx @@ -7,7 +7,6 @@ const Component = function () { var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Input = antd.Input; var Button = antd.Button; var Select = antd.Select; @@ -192,17 +191,7 @@ const Component = function () { ]; return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, - React.createElement('div', { style: styles.breadcrumb }, - React.createElement('span', null, '运维管理'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', null, '车辆业务'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', null, '交车管理'), - React.createElement('span', { style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { style: { color: '#1890ff' } }, '交车单') - ), - React.createElement(Button, { type: 'link', onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement(Button, { type: 'link', onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement('div', { id: 'card-project' }, React.createElement(CardBlock, { id: 'card-project', title: '项目详情' }, React.createElement('div', { style: styles.formRow }, diff --git a/src/prototypes/oneos-web-ops/pages/27-交车管理.jsx b/src/prototypes/oneos-web-ops/pages/27-交车管理.jsx index cf91354..d2b7ead 100644 --- a/src/prototypes/oneos-web-ops/pages/27-交车管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/27-交车管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 车辆业务 - 交车管理(ONEOS运管平台,布局参照新增租赁合同) +import { OperationActions } from '../../../common/OperationActions'; var DV_KPI_STYLE = '' + '.dv-kpi-stats-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-bottom:16px;}' @@ -1844,7 +1845,6 @@ const Component = function () { var useEffect = React.useEffect; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var DatePicker = antd.DatePicker; var Select = antd.Select; @@ -2921,14 +2921,11 @@ const Component = function () { var listColumns = useMemo(function () { return sharedListColumns.concat([ { - title: '操作', key: 'action', width: 96, fixed: 'right', render: function (_, r) { - var nodes = [ - React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { openViewDrawer(r); } }, '查看') - ]; - if (canEditDeliveryRow(r)) { - nodes.push(React.createElement(Button, { key: 'edit', type: 'link', size: 'small', onClick: function () { openEditDrawer(r); } }, '编辑')); - } - return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'nowrap' } }, nodes); + title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, r) { + return React.createElement(OperationActions, { + view: { onClick: function () { openViewDrawer(r); } }, + edit: canEditDeliveryRow(r) ? { onClick: function () { openEditDrawer(r); } } : undefined + }); } } ]); @@ -3248,17 +3245,10 @@ const Component = function () { filterNodes.push(filterItems[fi]); } - var breadcrumbItems = [ - { title: '运维管理' }, - { title: '车辆业务' }, - { title: '交车管理' } - ]; return React.createElement('div', { style: styles.page }, React.createElement('style', null, DV_KPI_STYLE), - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: breadcrumbItems }), - React.createElement(Button, { + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement(Button, { type: 'default', style: { borderRadius: 8, border: '1px solid #cbd5e1', fontWeight: 600, color: '#475569' }, onClick: function () { setRequirementModalOpen(true); } diff --git a/src/prototypes/oneos-web-ops/pages/30-年审管理.jsx b/src/prototypes/oneos-web-ops/pages/30-年审管理.jsx index 859534c..178db32 100644 --- a/src/prototypes/oneos-web-ops/pages/30-年审管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/30-年审管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 运维管理 - 车辆业务 - 年审管理(Web 列表页,逻辑对齐 ONE-OS 小程序年审管理) +import { OperationActions } from '../../../common/OperationActions'; const { useState, useMemo, useEffect } = React; const moment = window.moment || window.dayjs; @@ -1089,12 +1090,12 @@ const Component = function AnnualReviewWebList() { title: '操作', key: 'action', fixed: 'right', - width: 88, + width: 168, render: (_, row) => ( -
goHandlePage(row)} role="button" tabIndex={0}> - 办理 - - ), + goHandlePage(row) }]} + /> + ), }, ]; @@ -1167,11 +1168,9 @@ const Component = function AnnualReviewWebList() { title: '操作', key: 'action', fixed: 'right', - width: 88, + width: 168, render: (_, row) => ( - goViewPage(row)} role="button" tabIndex={0}> - 查看 - + goViewPage(row) }} /> ), }, ]; diff --git a/src/prototypes/oneos-web-ops/pages/31-上牌管理.jsx b/src/prototypes/oneos-web-ops/pages/31-上牌管理.jsx index 2048ae9..066f454 100644 --- a/src/prototypes/oneos-web-ops/pages/31-上牌管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/31-上牌管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 车辆上牌管理 - 车辆资产管理后台模块 +import { OperationActions } from '../../../common/OperationActions'; var ARCO_TOKEN = { primary: '#165DFF', primaryHover: '#4080FF', @@ -602,23 +603,7 @@ const Component = function () { return React.createElement( 'div', { style: styles.page }, - React.createElement( - 'div', - { style: styles.breadcrumb }, - React.createElement( - 'div', - { style: styles.breadcrumbLeft }, - React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'), - React.createElement('span', { style: { marginRight: '8px' } }, '/'), - React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'), - React.createElement('span', { style: { marginRight: '8px' } }, '/'), - React.createElement('span', { style: styles.breadcrumbCurrent }, '上牌管理') - ), - React.createElement( - 'div', - { style: styles.breadcrumbRight }, - React.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明') - ) + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明') ), React.createElement( 'div', @@ -721,7 +706,11 @@ const Component = function () { { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 100 }, { title: '品牌', dataIndex: 'brand', key: 'brand', width: 100 }, { title: '型号', dataIndex: 'model', key: 'model', width: 120 }, - { title: '操作', key: 'action', width: 80, render: function (_, row) { return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setViewPhotoRecord(row); } }, '查看'); } } + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function (_, row) { + return React.createElement(OperationActions, { + view: { onClick: function () { setViewPhotoRecord(row); } } + }); + } } ], dataSource: paginatedList, pagination: { diff --git a/src/prototypes/oneos-web-ops/pages/32-替换车管理-编辑.jsx b/src/prototypes/oneos-web-ops/pages/32-替换车管理-编辑.jsx index 73e8428..0db6c28 100644 --- a/src/prototypes/oneos-web-ops/pages/32-替换车管理-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/32-替换车管理-编辑.jsx @@ -8,7 +8,6 @@ const Component = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Select = antd.Select; var Input = antd.Input; @@ -780,19 +779,7 @@ const Component = function () { 'div', { className: 'vr-add-page', style: { padding: '20px 24px 32px', minHeight: '100vh', background: 'linear-gradient(165deg,#eef4ff 0%,#f5f7fa 42%,#f0f2f5 100%)' } }, React.createElement('style', null, pageCss), - React.createElement( - 'header', - { className: 'vr-page-header' }, - React.createElement(Breadcrumb, { - items: [ - { title: '运维管理' }, - { title: '车辆业务' }, - { title: '替换车管理' }, - { title: '编辑' } - ] - }), - React.createElement( - Button, + React.createElement('header', { className: 'vr-page-header', style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0, flexShrink: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明' ) diff --git a/src/prototypes/oneos-web-ops/pages/33-替换车管理-查看.jsx b/src/prototypes/oneos-web-ops/pages/33-替换车管理-查看.jsx index 4d0c67d..c4c0dd5 100644 --- a/src/prototypes/oneos-web-ops/pages/33-替换车管理-查看.jsx +++ b/src/prototypes/oneos-web-ops/pages/33-替换车管理-查看.jsx @@ -5,7 +5,6 @@ const Component = function () { var useState = React.useState; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Button = antd.Button; @@ -301,19 +300,7 @@ const Component = function () { 'div', { className: 'vr-add-page', style: { padding: '20px 24px 32px', minHeight: '100vh', background: 'linear-gradient(165deg,#eef4ff 0%,#f5f7fa 42%,#f0f2f5 100%)' } }, React.createElement('style', null, pageCss), - React.createElement( - 'header', - { className: 'vr-page-header' }, - React.createElement(Breadcrumb, { - items: [ - { title: '运维管理' }, - { title: '车辆业务' }, - { title: '替换车管理' }, - { title: '查看' } - ] - }), - React.createElement( - Button, + React.createElement('header', { className: 'vr-page-header', style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0, flexShrink: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明' ) diff --git a/src/prototypes/oneos-web-ops/pages/34-替换车管理-新增.jsx b/src/prototypes/oneos-web-ops/pages/34-替换车管理-新增.jsx index 3d6ae76..f2fca2d 100644 --- a/src/prototypes/oneos-web-ops/pages/34-替换车管理-新增.jsx +++ b/src/prototypes/oneos-web-ops/pages/34-替换车管理-新增.jsx @@ -8,7 +8,6 @@ const Component = function () { var useRef = React.useRef; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Select = antd.Select; var Input = antd.Input; @@ -740,19 +739,7 @@ const Component = function () { 'div', { className: 'vr-add-page', style: { padding: '20px 24px 32px', minHeight: '100vh', background: 'linear-gradient(165deg,#eef4ff 0%,#f5f7fa 42%,#f0f2f5 100%)' } }, React.createElement('style', null, pageCss), - React.createElement( - 'header', - { className: 'vr-page-header' }, - React.createElement(Breadcrumb, { - items: [ - { title: '运维管理' }, - { title: '车辆业务' }, - { title: '替换车管理' }, - { title: '新增' } - ] - }), - React.createElement( - Button, + React.createElement('header', { className: 'vr-page-header', style: { display: 'flex', justifyContent: 'flex-end', marginBottom: 16 } }, React.createElement(Button, { type: 'link', style: { padding: 0, flexShrink: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明' ) diff --git a/src/prototypes/oneos-web-ops/pages/35-替换车管理.jsx b/src/prototypes/oneos-web-ops/pages/35-替换车管理.jsx index 353c09f..26d6a1b 100644 --- a/src/prototypes/oneos-web-ops/pages/35-替换车管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/35-替换车管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 运维管理 - 车辆业务 - 替换车管理(2026年3月3日版本) +import { OperationActions } from '../../../common/OperationActions'; var VR_KPI_STYLE = '' + '.vr-kpi-stats-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-bottom:16px;}' @@ -43,7 +44,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var DatePicker = antd.DatePicker; var Select = antd.Select; @@ -551,34 +551,38 @@ const Component = function () { function getOperationButtons(record, isHistory) { if (isHistory) { - var viewBtn = React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { message.info('查看(跳转替换车管理-查看)'); } }, '查看'); - var toPermanentBtn = record.replaceType === '临时替换' ? React.createElement(Button, { key: 'toPermanent', type: 'link', size: 'small', onClick: function () { _toPermanentModalRecord[1](record); _toPermanentModalVisible[1](true); } }, '转永久替换') : null; - return React.createElement('div', { style: { display: 'flex', gap: 4, flexWrap: 'wrap' } }, viewBtn, toPermanentBtn); + var more = []; + if (record.replaceType === '临时替换') { + more.push({ key: 'toPermanent', label: '转永久替换', onClick: function () { _toPermanentModalRecord[1](record); _toPermanentModalVisible[1](true); } }); + } + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('查看(跳转替换车管理-查看)'); } }, + more: more + }); } var status = record.approvalStatus; - var items = []; - if (['待审批', '审批中', '审批驳回', '未提交', '撤回'].indexOf(status) !== -1) { - items.push(React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { message.info('查看(跳转替换车管理-查看)'); } }, '查看')); - } - if (['未提交', '审批驳回', '撤回'].indexOf(status) !== -1) { - items.push(React.createElement(Button, { key: 'edit', type: 'link', size: 'small', onClick: function () { message.info('编辑(跳转替换车管理-编辑)'); } }, '编辑')); - } + var viewStatuses = ['待审批', '审批中', '审批驳回', '未提交', '撤回']; + var editStatuses = ['未提交', '审批驳回', '撤回']; + var more = []; if (['撤回', '审批驳回'].indexOf(status) !== -1) { - items.push(React.createElement(Button, { + more.push({ key: 'delete', - type: 'link', - size: 'small', + label: '删除', danger: true, onClick: function () { _deleteModalRecord[1](record); _deleteModalVisible[1](true); } - }, '删除')); + }); } if (status === '审批中') { - items.push(React.createElement(Button, { key: 'withdraw', type: 'link', size: 'small', danger: true, onClick: function () { _withdrawModalRecord[1](record); _withdrawModalVisible[1](true); } }, '撤回')); + more.push({ key: 'withdraw', label: '撤回', danger: true, onClick: function () { _withdrawModalRecord[1](record); _withdrawModalVisible[1](true); } }); } - return React.createElement('div', { style: { display: 'flex', gap: 4, flexWrap: 'wrap' } }, items); + return React.createElement(OperationActions, { + view: viewStatuses.indexOf(status) !== -1 ? { onClick: function () { message.info('查看(跳转替换车管理-查看)'); } } : undefined, + edit: editStatuses.indexOf(status) !== -1 ? { onClick: function () { message.info('编辑(跳转替换车管理-编辑)'); } } : undefined, + more: more + }); } var tableColumns = [ @@ -827,15 +831,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: styles.page }, React.createElement('style', null, VR_KPI_STYLE + pageStyles), - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '运维管理' }, - { title: '车辆业务' }, - { title: '替换车管理' } - ] - }), - React.createElement(Button, { type: 'link', onClick: function () { _requirementModalVisible[1](true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', marginBottom: 16 } }, React.createElement(Button, { type: 'link', onClick: function () { _requirementModalVisible[1](true); } }, '查看需求说明') ), React.createElement(Card, { style: { marginBottom: 16 } }, React.createElement('div', { style: styles.cardBody }, diff --git a/src/prototypes/oneos-web-ops/pages/36-新增故障.jsx b/src/prototypes/oneos-web-ops/pages/36-新增故障.jsx index 384676c..d9569e8 100644 --- a/src/prototypes/oneos-web-ops/pages/36-新增故障.jsx +++ b/src/prototypes/oneos-web-ops/pages/36-新增故障.jsx @@ -13,7 +13,6 @@ const Component = function () { var Form = antd.Form; var Row = antd.Row; var Col = antd.Col; - var Breadcrumb = antd.Breadcrumb; var Layout = antd.Layout; var message = antd.message; var Card = antd.Card; @@ -266,18 +265,7 @@ const Component = function () { `), React.createElement('div', { className: 'arco-grouped-form-page' }, React.createElement('div', { className: 'arco-grouped-form-page-content' }, - React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, flexWrap: 'nowrap', gap: 12 } }, - React.createElement(Breadcrumb, { - separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'), - items: [ - { title: '首页' }, - { title: '运维管理' }, - { title: '车辆业务' }, - { title: React.createElement('a', { href: '#', onClick: function (e) { e.preventDefault(); requestExit(); } }, '故障管理') }, - { title: React.createElement('span', { style: { color: '#1d2129' } }, '新建故障单') } - ] - }), - React.createElement(Button, { + React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, flexWrap: 'nowrap', gap: 12 } }, React.createElement(Button, { type: 'link', icon: React.createElement(FileTextIcon, null), style: { display: 'flex', alignItems: 'center', gap: 4, padding: '0 4px', color: '#165dff', fontWeight: 500, flexShrink: 0 }, diff --git a/src/prototypes/oneos-web-ops/pages/37-新增后装设备.jsx b/src/prototypes/oneos-web-ops/pages/37-新增后装设备.jsx index 28efd49..d206e46 100644 --- a/src/prototypes/oneos-web-ops/pages/37-新增后装设备.jsx +++ b/src/prototypes/oneos-web-ops/pages/37-新增后装设备.jsx @@ -73,15 +73,6 @@ const Component = function () { return React.createElement( 'div', { style: styles.page }, - React.createElement( - 'div', - { style: styles.breadcrumb }, - React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'), - React.createElement('span', { style: { marginRight: '8px' } }, '/'), - React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'), - React.createElement('span', { style: { marginRight: '8px' } }, '/'), - React.createElement('span', { style: styles.breadcrumbCurrent }, '新增后装设备') - ), React.createElement( 'div', { style: styles.card }, diff --git a/src/prototypes/oneos-web-ops/pages/38-异动管理-编辑.jsx b/src/prototypes/oneos-web-ops/pages/38-异动管理-编辑.jsx index fc85323..95f7421 100644 --- a/src/prototypes/oneos-web-ops/pages/38-异动管理-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/38-异动管理-编辑.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Input = antd.Input; @@ -480,9 +479,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '异动管理' }, { title: '编辑' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/39-异动管理-查看.jsx b/src/prototypes/oneos-web-ops/pages/39-异动管理-查看.jsx index dfb16ea..a3f263c 100644 --- a/src/prototypes/oneos-web-ops/pages/39-异动管理-查看.jsx +++ b/src/prototypes/oneos-web-ops/pages/39-异动管理-查看.jsx @@ -7,7 +7,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Input = antd.Input; @@ -278,9 +277,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '异动管理' }, { title: '查看' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/40-异动管理-结束异动.jsx b/src/prototypes/oneos-web-ops/pages/40-异动管理-结束异动.jsx index 37e20bf..1c2f753 100644 --- a/src/prototypes/oneos-web-ops/pages/40-异动管理-结束异动.jsx +++ b/src/prototypes/oneos-web-ops/pages/40-异动管理-结束异动.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Input = antd.Input; @@ -356,9 +355,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '异动管理' }, { title: '结束异动' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/41-异动管理-新增.jsx b/src/prototypes/oneos-web-ops/pages/41-异动管理-新增.jsx index 9e2b3a2..037e9e8 100644 --- a/src/prototypes/oneos-web-ops/pages/41-异动管理-新增.jsx +++ b/src/prototypes/oneos-web-ops/pages/41-异动管理-新增.jsx @@ -8,7 +8,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Input = antd.Input; @@ -456,9 +455,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '异动管理' }, { title: '新增' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/42-异动管理.jsx b/src/prototypes/oneos-web-ops/pages/42-异动管理.jsx index 790e88c..f39a4c9 100644 --- a/src/prototypes/oneos-web-ops/pages/42-异动管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/42-异动管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 运维管理 - 车辆业务 - 异动管理(列表页) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -8,7 +9,6 @@ const Component = function () { var antd = window.antd; var App = antd.App; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Button = antd.Button; var Table = antd.Table; @@ -757,33 +757,21 @@ const Component = function () { { title: '操作', key: 'action', - width: 200, + width: 168, fixed: 'right', render: function (_, r) { - var nodes = [ - React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { message.info('跳转异动-查看页(原型)ID:' + r.id); } }, '查看') - ]; + var more = []; if (String(r.approvalStatus || '') === '审批中') { - nodes.push(React.createElement(Button, { - key: 'withdraw', - type: 'link', - size: 'small', - danger: true, - onClick: function () { handleWithdrawMovement(r.id); } - }, '撤回')); + more.push({ key: 'withdraw', label: '撤回', danger: true, onClick: function () { handleWithdrawMovement(r.id); } }); } if (String(r.approvalStatus || '') === '审批完成' && !isMovementEnded(r)) { - nodes.push(React.createElement(Button, { - key: 'end', - type: 'link', - size: 'small', - onClick: function () { handleEndMovement(r.id); } - }, '结束异动')); + more.push({ key: 'end', label: '结束异动', onClick: function () { handleEndMovement(r.id); } }); } - if (canShowMovementEdit(r.approvalStatus)) { - nodes.push(React.createElement(Button, { key: 'edit', type: 'link', size: 'small', onClick: function () { message.info('跳转异动-编辑页(原型,表单与新增页一致)ID:' + r.id); } }, '编辑')); - } - return React.createElement(React.Fragment, null, nodes); + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('跳转异动-查看页(原型)ID:' + r.id); } }, + edit: canShowMovementEdit(r.approvalStatus) ? { onClick: function () { message.info('跳转异动-编辑页(原型,表单与新增页一致)ID:' + r.id); } } : undefined, + more: more + }); } } ]; @@ -791,9 +779,7 @@ const Component = function () { return React.createElement(App, null, React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, - React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '异动管理' }] }), - React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') + React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 } }, React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明') ), React.createElement(Modal, { diff --git a/src/prototypes/oneos-web-ops/pages/44-证照管理.jsx b/src/prototypes/oneos-web-ops/pages/44-证照管理.jsx index f9572fa..6483ba0 100644 --- a/src/prototypes/oneos-web-ops/pages/44-证照管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/44-证照管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 运维管理 - 车辆业务 - 证照管理(台账列表页) +import { OperationActions } from '../../../common/OperationActions'; const { useState, useEffect, useMemo, useRef } = React; const moment = window.moment || window.dayjs; @@ -2324,18 +2325,13 @@ const Component = function () { { title: '操作', key: 'action', - width: 64, + width: 168, + fixed: 'right', onHeaderCell: listColumnHeaderCell, render: (record) => ( - + goLicenseEditPage(record.plateNo, allLicenses) }]} + /> ) } ]; diff --git a/src/prototypes/oneos-web-ops/pages/45-停车场管理.jsx b/src/prototypes/oneos-web-ops/pages/45-停车场管理.jsx index 4e11892..4eee3aa 100644 --- a/src/prototypes/oneos-web-ops/pages/45-停车场管理.jsx +++ b/src/prototypes/oneos-web-ops/pages/45-停车场管理.jsx @@ -480,17 +480,8 @@ const Component = function () { return (
- {/* 面包屑 */} -
-
- 基本数据维护 - / - 停车场 -
- -
+ + {currentView === 'list' && ( diff --git a/src/prototypes/oneos-web-ops/pages/46-型号参数-编辑.jsx b/src/prototypes/oneos-web-ops/pages/46-型号参数-编辑.jsx index 9574549..185f713 100644 --- a/src/prototypes/oneos-web-ops/pages/46-型号参数-编辑.jsx +++ b/src/prototypes/oneos-web-ops/pages/46-型号参数-编辑.jsx @@ -5,7 +5,6 @@ const Component = function () { var useState = React.useState; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -211,16 +210,7 @@ const Component = function () { ]; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '基本数据维护' }, - { title: '型号参数' }, - { title: '编辑' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('型号参数'), size: 'small', style: cardStyle }, + React.createElement(Card, { title: CardTitleWithBar('型号参数'), size: 'small', style: cardStyle }, React.createElement('div', { style: formRowStyle }, React.createElement(FormItem, { label: '品牌', required: true, error: formErrors[0].brand }, React.createElement(Select, { placeholder: '请选择品牌', style: inputStyle, value: brand[0], onChange: function (v) { brand[1](v); }, allowClear: true, options: brandOptions, status: formErrors[0].brand ? 'error' : undefined }) diff --git a/src/prototypes/oneos-web-ops/pages/47-型号参数-查看.jsx b/src/prototypes/oneos-web-ops/pages/47-型号参数-查看.jsx index 4336b34..7e3dc88 100644 --- a/src/prototypes/oneos-web-ops/pages/47-型号参数-查看.jsx +++ b/src/prototypes/oneos-web-ops/pages/47-型号参数-查看.jsx @@ -6,7 +6,6 @@ const Component = function () { var useMemo = React.useMemo; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -91,16 +90,7 @@ const Component = function () { } return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '基本数据维护' }, - { title: '型号参数' }, - { title: '查看' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('型号参数'), size: 'small', style: cardStyle }, + React.createElement(Card, { title: CardTitleWithBar('型号参数'), size: 'small', style: cardStyle }, React.createElement('div', { style: formRowStyle }, React.createElement(FormItem, { label: '品牌' }, React.createElement(Select, { value: detail.brand, disabled: true, style: inputStyle, options: brandOptions }) diff --git a/src/prototypes/oneos-web-ops/pages/48-型号参数-新增.jsx b/src/prototypes/oneos-web-ops/pages/48-型号参数-新增.jsx index e9ead31..928507d 100644 --- a/src/prototypes/oneos-web-ops/pages/48-型号参数-新增.jsx +++ b/src/prototypes/oneos-web-ops/pages/48-型号参数-新增.jsx @@ -6,7 +6,6 @@ const Component = function () { var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Input = antd.Input; var Select = antd.Select; @@ -184,16 +183,7 @@ const Component = function () { ]; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '基本数据维护' }, - { title: '型号参数' }, - { title: '新增型号参数' } - ] - }) - ), - React.createElement(Card, { title: CardTitleWithBar('型号参数'), size: 'small', style: cardStyle }, + React.createElement(Card, { title: CardTitleWithBar('型号参数'), size: 'small', style: cardStyle }, React.createElement('div', { style: formRowStyle }, React.createElement(FormItem, { label: '品牌', required: true, error: formErrors[0].brand }, React.createElement(Select, { placeholder: '请选择品牌', style: inputStyle, value: brand[0], onChange: function (v) { brand[1](v); }, allowClear: true, options: brandOptions, status: formErrors[0].brand ? 'error' : undefined }) diff --git a/src/prototypes/oneos-web-ops/pages/49-型号参数.jsx b/src/prototypes/oneos-web-ops/pages/49-型号参数.jsx index b17a3b4..6256314 100644 --- a/src/prototypes/oneos-web-ops/pages/49-型号参数.jsx +++ b/src/prototypes/oneos-web-ops/pages/49-型号参数.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 基本数据维护 - 型号参数(布局参考车辆租赁合同) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -7,7 +8,6 @@ const Component = function () { var useCallback = React.useCallback; var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; var Card = antd.Card; var Select = antd.Select; var Button = antd.Button; @@ -150,28 +150,20 @@ const Component = function () { { title: '操作', key: 'action', - width: 160, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement(Space, { size: 4 }, - React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { message.info('查看(原型)'); } }, '查看'), - React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { message.info('编辑(原型)'); } }, '编辑'), - React.createElement(Button, { type: 'link', size: 'small', danger: true, style: { padding: 0 }, onClick: function () { openDeleteConfirm(record); } }, '删除') - ); + return React.createElement(OperationActions, { + view: { onClick: function () { message.info('查看(原型)'); } }, + edit: { onClick: function () { message.info('编辑(原型)'); } }, + more: [{ key: 'delete', label: '删除', danger: true, onClick: function () { openDeleteConfirm(record); } }] + }); } } ]; return React.createElement('div', { style: layoutStyle }, - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { - items: [ - { title: '基本数据维护' }, - { title: '型号参数' } - ] - }) - ), - React.createElement(Card, { style: { marginBottom: 16 } }, + React.createElement(Card, { style: { marginBottom: 16 } }, React.createElement('div', { style: { display: 'grid', diff --git a/src/prototypes/oneos-web-procurement/index.tsx b/src/prototypes/oneos-web-procurement/index.tsx index 756f59c..cd31ebc 100644 --- a/src/prototypes/oneos-web-procurement/index.tsx +++ b/src/prototypes/oneos-web-procurement/index.tsx @@ -4,6 +4,7 @@ */ import '../../common/oneosWebLegacy/legacyGlobals'; import React from 'react'; +import '../vehicle-management/style.css'; import Page1 from './pages/01-三方退租车管理.jsx'; import Page2 from './pages/02-三方退租申请-新增.jsx'; import { OneosWebLegacyShell } from '../../common/oneosWebLegacy/OneosWebLegacyShell'; diff --git a/src/prototypes/oneos-web-procurement/pages/01-三方退租车管理.jsx b/src/prototypes/oneos-web-procurement/pages/01-三方退租车管理.jsx index b40142e..131b3d9 100644 --- a/src/prototypes/oneos-web-procurement/pages/01-三方退租车管理.jsx +++ b/src/prototypes/oneos-web-procurement/pages/01-三方退租车管理.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 采购管理 - 三方退租车管理(审批完成后按车辆生成退车任务) +import { OperationActions } from '../../../common/OperationActions'; var ONEOS_ANT_TABLE_GLOBAL_FIX = [ '.ant-table-container .ant-table-header { margin-bottom: 0 !important; }', @@ -720,7 +721,6 @@ const Component = function () { var Tag = antd.Tag; var Tooltip = antd.Tooltip; var Cascader = antd.Cascader; - var Breadcrumb = antd.Breadcrumb; function createEmptyFilters() { return { plateNo: '', bizNo: '', returnRegion: undefined, taskStatus: undefined }; @@ -934,27 +934,17 @@ const Component = function () { { title: '操作', key: 'action', - width: 120, + width: 168, fixed: 'right', render: function (_, r) { - var nodes = []; + var more = []; if (r.taskStatus !== '已完成') { - nodes.push(React.createElement(Button, { - key: 'edit', - type: 'link', - size: 'small', - style: { color: '#165dff', fontWeight: 600, padding: '0 4px' }, - onClick: function () { openEditDrawer(r); } - }, '办理')); + more.unshift({ key: 'handle', label: '办理', onClick: function () { openEditDrawer(r); } }); } - nodes.push(React.createElement(Button, { - key: 'view', - type: 'link', - size: 'small', - style: { padding: '0 4px' }, - onClick: function () { openViewDrawer(r); } - }, '查看')); - return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 2 } }, nodes); + return React.createElement(OperationActions, { + view: { onClick: function () { openViewDrawer(r); } }, + more: more + }); } } ]; @@ -1025,10 +1015,7 @@ const Component = function () { return React.createElement('div', { className: 'trm-page', style: styles.page }, React.createElement('style', null, TRM_KPI_STYLE), React.createElement('style', null, PAGE_STYLE), - React.createElement('div', { style: { marginBottom: 16 } }, - React.createElement(Breadcrumb, { items: [{ title: '采购管理' }, { title: '三方退租车管理' }] }) - ), - React.createElement('div', { style: { marginBottom: 16 } }, + React.createElement('div', { style: { marginBottom: 16 } }, React.createElement('h1', { style: { margin: 0, fontSize: 22, fontWeight: 700, color: '#0f172a' } }, '三方退租车管理'), React.createElement('p', { style: { margin: '6px 0 0', fontSize: 14, color: '#64748b' } }, '三方退租申请审批完成后,按车辆自动生成退车办理任务;当前操作人:', CURRENT_USER diff --git a/src/prototypes/oneos-web-procurement/pages/02-三方退租申请-新增.jsx b/src/prototypes/oneos-web-procurement/pages/02-三方退租申请-新增.jsx index 82bf846..86e9ddc 100644 --- a/src/prototypes/oneos-web-procurement/pages/02-三方退租申请-新增.jsx +++ b/src/prototypes/oneos-web-procurement/pages/02-三方退租申请-新增.jsx @@ -1,5 +1,6 @@ // 【重要】必须使用 const Component 作为组件变量名 // 采购管理 - 三方退租申请(列表 + 新增/编辑/查看) +import { OperationActions } from '../../../common/OperationActions'; const Component = function () { var useState = React.useState; @@ -747,11 +748,6 @@ const Component = function () { React.createElement('line', { x1: 12, y1: 3, x2: 12, y2: 15 })); } - function IconBack() { - return React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, - React.createElement('polyline', { points: '15 18 9 12 15 6' })); - } - function IconTruck() { return React.createElement('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, React.createElement('rect', { x: 1, y: 3, width: 15, height: 13 }), @@ -767,8 +763,6 @@ const Component = function () { '.tra-page .tra-page-topbar{display:flex;justify-content:flex-end;align-items:center;margin-bottom:16px}' + '.tra-page .tra-form-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}' + '.tra-page .tra-form-topbar__left{display:flex;align-items:center;gap:10px;min-width:0}' + - '.tra-page .tra-back-btn{display:inline-flex;align-items:center;gap:4px;padding:0 4px 0 0;font-size:14px;font-weight:600;color:var(--tra-text)!important;height:auto}' + - '.tra-page .tra-back-btn:hover{color:var(--tra-primary)!important}' + '.tra-page .tra-page-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px}' + '.tra-page .tra-page-hero{margin-bottom:20px}' + '.tra-page .tra-page-hero__title{font-size:22px;font-weight:700;color:var(--tra-text);line-height:1.3;margin:0 0 6px}' + @@ -1062,34 +1056,17 @@ const Component = function () { { title: '操作', key: 'action', - width: 108, + width: 168, fixed: 'right', render: function (_, record) { - var moreItems = getListRowMoreMenuItems(record); - return React.createElement('div', { className: 'tra-row-actions' }, - React.createElement(Button, { - type: 'link', - size: 'small', - onClick: function () { openFormFromRecord(record, 'view'); } - }, '查看'), - moreItems.length - ? React.createElement(Dropdown, { - trigger: ['hover'], - placement: 'bottomRight', - menu: { items: moreItems } - }, - React.createElement(Tooltip, { title: '更多' }, - React.createElement('span', { - className: 'tra-action-more-btn', - role: 'button', - tabIndex: 0, - 'aria-label': '更多操作', - onClick: function (e) { e.stopPropagation(); } - }, React.createElement(IconMore)) - ) - ) - : null - ); + var rawMore = getListRowMoreMenuItems(record).filter(function (item) { return item && item.key && item.label; }); + var editItem = rawMore.find(function (item) { return item.key === 'edit'; }); + var more = rawMore.filter(function (item) { return item.key !== 'edit'; }); + return React.createElement(OperationActions, { + view: { onClick: function () { openFormFromRecord(record, 'view'); } }, + edit: editItem ? { onClick: editItem.onClick } : undefined, + more: more + }); } } ]; @@ -1442,10 +1419,9 @@ const Component = function () { React.createElement( 'div', { className: 'tra-form-topbar__left' }, - React.createElement(Button, { - type: 'link', - className: 'tra-back-btn', - icon: React.createElement(IconBack), + React.createElement('button', { + type: 'button', + className: 'vm-btn vm-btn-back', onClick: handleFormBack }, '返回'), readOnly && currentRecord diff --git a/src/prototypes/oneos-web-requirements/index.tsx b/src/prototypes/oneos-web-requirements/index.tsx deleted file mode 100644 index 009391e..0000000 --- a/src/prototypes/oneos-web-requirements/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @name 需求说明 - * 自 ONE-OS web端/需求说明 归档 - */ -import React, { useMemo, useState } from 'react'; -import { Card, Col, Input, List, Row, Typography } from 'antd'; - -const DOC_MAP = import.meta.glob('../../resources/oneos-web-legacy/需求说明/**/*', { - query: '?raw', - import: 'default', - eager: true, -}); - -const FILES = Object.keys(DOC_MAP) - .map((key) => key.replace('../../resources/oneos-web-legacy/需求说明/', '')) - .filter(Boolean) - .sort((a, b) => a.localeCompare(b, 'zh-CN')) - .map((rel) => ({ rel, title: rel })); - -export default function OneosWebRequirementsPage() { - const [query, setQuery] = useState(''); - const [active, setActive] = useState(FILES[0]?.rel ?? ''); - - const filtered = useMemo(() => { - const q = query.trim(); - if (!q) return FILES; - return FILES.filter((item) => item.title.includes(q)); - }, [query]); - - const content = useMemo(() => { - if (!active) return ''; - const key = `../../resources/oneos-web-legacy/需求说明/${active}`; - return String(DOC_MAP[key] ?? '无法加载文档'); - }, [active]); - - return ( - - - setQuery(e.target.value)} style={{ width: 160 }} />}> - ( - setActive(item.rel)}> - {item.title} - - )} - /> - - - - - {content} - - - - ); -} diff --git a/src/prototypes/oneos-web-vehicle-asset/index.tsx b/src/prototypes/oneos-web-vehicle-asset/index.tsx deleted file mode 100644 index 422df34..0000000 --- a/src/prototypes/oneos-web-vehicle-asset/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @name 车辆管理 - * 自 ONE-OS web端 原稿复刻(2 个页面) - */ -import '../../common/oneosWebLegacy/legacyGlobals'; -import React from 'react'; -import Page1 from './pages/01-车辆管理.jsx'; -import Page2 from './pages/02-车辆管理-查看.jsx'; -import { OneosWebLegacyShell } from '../../common/oneosWebLegacy/OneosWebLegacyShell'; - -const pages = [ - { id: 'u8f66u8f86u7ba1u7406', title: '车辆管理', component: Page1 }, - { id: 'u8f66u8f86u7ba1u7406-u67e5u770b', title: '车辆管理-查看', component: Page2 }, -]; - -export default function OneosWebVehicleAsset() { - return ( - - ); -} diff --git a/src/prototypes/oneos-web-vehicle-asset/pages/01-车辆管理.jsx b/src/prototypes/oneos-web-vehicle-asset/pages/01-车辆管理.jsx deleted file mode 100644 index 1271fa0..0000000 --- a/src/prototypes/oneos-web-vehicle-asset/pages/01-车辆管理.jsx +++ /dev/null @@ -1,3328 +0,0 @@ -// 【重要】必须使用 const Component 作为组件变量名 -// 车辆资产管理 - 车辆管理模块(中后台 Ant Design 原型) - -/** @see web端/styles/oneos-ant-table-global-fix.js */ -var ONEOS_ANT_TABLE_GLOBAL_FIX = [ - '.ant-table-container .ant-table-header { margin-bottom: 0 !important; }', - '.ant-table-container .ant-table-body { margin-top: 0 !important; }', - '.ant-table-container .ant-table-body > table, .ant-table-content table { margin-top: 0 !important; }', - '.ant-table-tbody > tr.ant-table-measure-row, .ant-table-tbody > tr.ant-table-measure-row > td, .ant-table-tbody > tr.ant-table-measure-row > th { display: none !important; height: 0 !important; max-height: 0 !important; min-height: 0 !important; padding: 0 !important; margin: 0 !important; border: none !important; line-height: 0 !important; font-size: 0 !important; overflow: hidden !important; visibility: hidden !important; pointer-events: none !important; }' -]; - -var VM_PAGE_STYLE = ONEOS_ANT_TABLE_GLOBAL_FIX.concat([ - '.vm-page { padding: 24px 24px 32px; min-height: 100vh; box-sizing: border-box; background: linear-gradient(165deg, #f1f5f9 0%, #f8fafc 50%, #f1f5f9 100%); font-family: Inter, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; color: #0f172a; }', - '.vm-page .lc-filter-card.ant-card { border-radius: 16px !important; border: 1px solid #e2e8f0 !important; box-shadow: 0 4px 20px -4px rgba(15, 23, 42, 0.03) !important; margin-bottom: 16px; }', - '.vm-page .lc-filter-card > .ant-card-head { border-bottom: 1px solid #f1f5f9 !important; min-height: auto; padding: 12px 20px !important; }', - '.vm-page .lc-filter-card > .ant-card-head .ant-card-head-title { font-size: 15px !important; font-weight: 700 !important; color: #0f172a !important; padding: 0 !important; }', - '.vm-page .lc-filter-card > .ant-card-body { padding: 16px 20px 20px !important; }', - '.vm-page .lc-filter-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px 24px; align-items: center; }', - '@media (max-width: 1280px) { .vm-page .lc-filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }', - '@media (max-width: 640px) { .vm-page .lc-filter-grid { grid-template-columns: 1fr; } }', - '.vm-page .lc-filter-field { display: flex; align-items: center; gap: 12px; min-width: 0; min-height: 32px; }', - '.vm-page .lc-filter-field-label { flex: 0 0 88px; text-align: right; font-size: 13px; font-weight: 500; color: #475569; line-height: 32px; white-space: nowrap; }', - '.vm-page .lc-filter-field-control { flex: 1; min-width: 0; }', - '.vm-page .lc-filter-field-control .ant-input, .vm-page .lc-filter-field-control .ant-input-affix-wrapper, .vm-page .lc-filter-field-control .ant-select .ant-select-selector, .vm-page .lc-filter-field-control .ant-cascader .ant-select-selector { width: 100%; height: 32px !important; min-height: 32px !important; border-radius: 8px !important; box-sizing: border-box; }', - '.vm-page .lc-filter-actions { display: flex; justify-content: flex-end; align-items: center; gap: 8px; margin-top: 16px; padding-top: 16px; border-top: 1px solid #f1f5f9; flex-wrap: wrap; }', - '.vm-page .lc-table-section { margin-bottom: 0; display: flex; flex-direction: column; min-height: 0; }', - '.vm-page .lc-table-toolbar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px 16px; margin-bottom: 8px; min-height: 32px; }', - '.vm-page .lc-table-toolbar-meta { font-size: 13px; color: #64748b; }', - '.vm-page .lc-table-toolbar-meta strong { font-weight: 700; color: #0f172a; font-variant-numeric: tabular-nums; }', - '.vm-page .lc-table-toolbar-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-left: auto; }', - '.vm-page .lc-table-card { background: #fff; border-radius: 16px; border: 1px solid #e2e8f0; box-shadow: 0 4px 20px -4px rgba(15, 23, 42, 0.03); overflow: hidden; }', - '.vm-page .lc-list-table .ant-table-thead > tr > th { background: #f8fafc !important; color: #475569 !important; font-weight: 700 !important; font-size: 13px !important; border-bottom: 1px solid #e2e8f0 !important; padding: 12px 16px !important; white-space: nowrap !important; }', - '.vm-page .lc-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td { padding: 12px 16px !important; font-size: 13px; }', - '.vm-page .lc-list-table .ant-table-tbody > tr:not(.ant-table-measure-row):hover > td { background: #f8fafc !important; }', - '.vm-page .lc-list-table .ant-pagination { margin: 0 !important; padding: 12px 16px !important; border-top: 1px solid #f1f5f9; }', - '.vm-page .lc-action-btn { font-weight: 600 !important; color: #10b981 !important; padding: 0 !important; min-height: 44px; }', - '.vm-page .vm-action-more-btn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 8px; color: #64748b; cursor: pointer; transition: background 0.15s ease, color 0.15s ease; }', - '.vm-page .vm-action-more-btn:hover { background: #f1f5f9; color: #334155; }', - '.vm-page .vm-action-more-btn:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }', - '.vm-page .vm-btn-req { border-radius: 8px !important; border: 1px solid #cbd5e1 !important; font-weight: 600 !important; color: #475569 !important; box-shadow: 0 1px 2px rgba(0,0,0,0.05) !important; }', - '.vm-page .vm-col-title-with-tip { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; white-space: nowrap; }', - '.vm-page .vm-col-title-tip { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; color: #94a3b8; border: 1px solid #e2e8f0; background: #fff; cursor: help; line-height: 0; flex-shrink: 0; transition: color 0.15s ease, border-color 0.15s ease; }', - '.vm-page .vm-col-title-tip:hover { color: #64748b; border-color: #cbd5e1; }', - '.vm-page .vm-col-title-tip:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }', - '.vm-page .vm-vehicle-identity-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; white-space: normal; line-height: 1.35; }', - '.vm-page .vm-vehicle-identity-plate { font-size: 13px; font-weight: 600; color: #0f172a; font-variant-numeric: tabular-nums; }', - '.vm-page .vm-plate-link.vm-vehicle-identity-plate { padding: 0; cursor: pointer; background: none; border: none; text-align: left; }', - '.vm-page .vm-plate-link.vm-vehicle-identity-plate:hover { color: #10b981; text-decoration: underline; text-underline-offset: 2px; }', - '.vm-page .vm-plate-link.vm-vehicle-identity-plate:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; border-radius: 4px; }', - '.vm-page .vm-vehicle-identity-vin { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 12px; color: #64748b; letter-spacing: 0.02em; word-break: break-all; }', - '.vm-page .vm-plate-link.vm-vehicle-identity-vin { padding: 0; cursor: pointer; background: none; border: none; text-align: left; width: 100%; font-weight: 400; }', - '.vm-page .vm-plate-link.vm-vehicle-identity-vin:hover { color: #10b981; text-decoration: underline; text-underline-offset: 2px; }', - '.vm-page .vm-plate-link.vm-vehicle-identity-vin:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; border-radius: 4px; }', - '.vm-page .vm-vehicle-identity-brand { font-size: 12px; color: #475569; }', - '.vm-page .lc-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td.vm-vehicle-identity-td { white-space: normal !important; overflow: visible !important; vertical-align: middle !important; }', - '.vm-page .lc-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td.vm-stacked-cell-td { white-space: normal !important; overflow: visible !important; vertical-align: middle !important; }', - '.vm-page .vm-stacked-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; white-space: normal; line-height: 1.35; }', - '.vm-page .vm-stacked-cell-line { font-size: 13px; color: #0f172a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }', - '.vm-page .vm-stacked-cell-line--primary { font-weight: 600; }', - '.vm-page .vm-stacked-cell-line--code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 12px; color: #64748b; letter-spacing: 0.01em; }', - '.vm-page .vm-stacked-cell-line--sub { font-size: 12px; color: #475569; }', - '.vm-page .vm-oneline-text { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; font-size: 13px; color: #0f172a; }', - '.vm-page .vm-insurance-expire-date { font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }', - '.vm-page .lc-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td.vm-expire-date-td { white-space: normal !important; overflow: visible !important; vertical-align: middle !important; }', - '.vm-page .vm-expire-date-cell { display: flex; flex-direction: row; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; white-space: normal; line-height: 1.35; }', - '.vm-page .vm-expire-remain-tag { margin: 0 !important; flex-shrink: 0; border-radius: 6px !important; font-weight: 600 !important; font-size: 11px !important; line-height: 1.2 !important; }', - '.vm-page .lc-alert-stats-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; }', - '@media (max-width: 1600px) { .vm-page .lc-alert-stats-row { grid-template-columns: repeat(4, minmax(0, 1fr)); } }', - '@media (max-width: 960px) { .vm-page .lc-alert-stats-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }', - '@media (max-width: 640px) { .vm-page .lc-alert-stats-row { grid-template-columns: 1fr; } }', - '.vm-page .lc-alert-card { display: flex; align-items: center; gap: 14px; padding: 14px 30px 14px 14px; border-radius: 12px; border: 1px solid #e2e8f0; background: #fff; position: relative; overflow: hidden; min-width: 0; min-height: 76px; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04); }', - '.vm-page .lc-alert-card-main { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 6px; }', - '.vm-page .lc-alert-card-icon { flex-shrink: 0; width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: linear-gradient(145deg, #0d7a63 0%, #065f4a 100%); color: #fff; box-shadow: 0 4px 12px rgba(6, 95, 74, 0.28); }', - '.vm-page .lc-alert-card-icon svg { display: block; flex-shrink: 0; }', - '.vm-page .lc-alert-card-val { font-size: 24px; font-weight: 800; line-height: 1.1; color: #0f172a; font-variant-numeric: tabular-nums; flex-shrink: 0; }', - '.vm-page .lc-alert-card-title { width: 100%; min-width: 0; font-size: 13px; font-weight: 600; color: #64748b; line-height: 1.35; white-space: normal; }', - '.vm-page .lc-alert-card-tip-anchor { position: absolute; top: 8px; right: 8px; z-index: 2; line-height: 0; }', - '.vm-page .lc-alert-card-tip { width: 18px; height: 18px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; color: #94a3b8; background: rgba(255,255,255,0.92); border: 1px solid #e2e8f0; cursor: help; line-height: 0; }', - '.vm-page .lc-alert-card-tip:hover { color: #64748b; border-color: #cbd5e1; background: #fff; }', - '.vm-page .lc-alert-card--total, .vm-page .lc-alert-card--normal, .vm-page .lc-alert-card--warning, .vm-page .lc-alert-card--nonOperating, .vm-page .lc-alert-card--unuploaded, .vm-page .lc-alert-card--license, .vm-page .lc-alert-card--insurance { background: #fff; border-color: #e2e8f0; }', - '.vm-page .lc-alert-card-clickable { cursor: pointer; transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease; }', - '.vm-page .lc-alert-card-clickable:hover { box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08); }', - '.vm-page .lc-alert-card-clickable:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }', - '.vm-page .lc-alert-card-active { box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25) !important; border-color: #10b981 !important; }', - '.vm-page .lc-alert-card--with-side { padding-right: 16px; }', - '.vm-page .lc-alert-card--with-side .lc-alert-card-tip-anchor { right: 108px; }', - '.vm-page .vm-kpi-card-side { flex: 0 0 auto; display: flex; align-items: stretch; margin-left: auto; padding-left: 12px; border-left: 1px solid #e2e8f0; min-width: 92px; }', - '.vm-page .vm-kpi-card-side-item { display: flex; flex-direction: column; justify-content: center; gap: 4px; min-width: 0; padding: 0 4px 0 12px; border-radius: 8px; cursor: pointer; transition: background 0.15s ease; }', - '.vm-page .vm-kpi-card-side-item:hover { background: #f8fafc; }', - '.vm-page .vm-kpi-card-side-item:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }', - '.vm-page .vm-kpi-card-side-item--active { background: #ecfdf5; }', - '.vm-page .vm-kpi-card-side-title { font-size: 11px; font-weight: 600; color: #64748b; line-height: 1.3; white-space: nowrap; }', - '.vm-page .vm-kpi-card-side-val { font-size: 18px; font-weight: 800; color: #0f172a; font-variant-numeric: tabular-nums; line-height: 1.1; }', - '.vm-page .lc-filter-field-control .ant-select-multiple .ant-select-selector { height: auto !important; min-height: 32px !important; padding-top: 2px !important; padding-bottom: 2px !important; }', - '.vm-page .vm-online-status { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; line-height: 1.35; }', - '.vm-page .vm-online-status .vm-online-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }', - '.vm-page .vm-online-status--on .vm-online-dot { background: #10b981; box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.22); }', - '.vm-page .vm-online-status--off .vm-online-dot { background: #ef4444; box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.18); }', - '.vm-page .vm-online-status .vm-online-label { color: #0f172a; font-weight: 500; }', - '.vm-page .vm-gps-location-cell { display: flex; flex-direction: column; gap: 4px; min-width: 0; white-space: normal; line-height: 1.35; }', - '.vm-page .vm-gps-location-row { display: flex; align-items: center; min-width: 0; }', - '.vm-page .vm-gps-location-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: #0f172a; max-width: 100%; }', - '.vm-page .vm-gps-location-row--status { min-height: 20px; }', - '.vm-page .vm-gps-location-time { font-size: 12px; color: #64748b; font-variant-numeric: tabular-nums; letter-spacing: 0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }', - '.vm-page .lc-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td.vm-gps-location-td { white-space: normal !important; overflow: visible !important; vertical-align: middle !important; }', - '.vm-page .vm-mileage-cell { display: flex; flex-direction: row; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; white-space: normal; line-height: 1.35; }', - '.vm-page .vm-mileage-value { font-size: 13px; font-weight: 600; color: #0f172a; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }', - '.vm-page .vm-mileage-source-tag { margin: 0 !important; flex-shrink: 0; border-radius: 6px !important; font-size: 11px !important; font-weight: 600 !important; line-height: 1.2 !important; }', - '.vm-page .lc-list-table .ant-table-tbody > tr:not(.ant-table-measure-row) > td.vm-mileage-cell-td { white-space: normal !important; overflow: visible !important; vertical-align: middle !important; }', - '.vm-page .vm-handover-situation-cell { display: flex; flex-direction: column; gap: 4px; min-width: 0; white-space: normal; line-height: 1.35; }', - '.vm-page .vm-handover-situation-mile { font-size: 13px; font-weight: 600; color: #0f172a; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }', - '.vm-page .vm-handover-situation-region { font-size: 12px; color: #475569; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }', - '.vm-page .vm-handover-situation-datetime { font-size: 12px; color: #64748b; font-variant-numeric: tabular-nums; letter-spacing: 0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }', - '.vm-page .vm-ops-manager-cell { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; font-size: 13px; color: #0f172a; }', - '.vm-page .vm-ops-manager-cell--clickable { cursor: pointer; transition: color 0.15s ease; }', - '.vm-page .vm-ops-manager-cell--clickable:hover { color: #10b981; }', - '.vm-page .vm-ops-manager-cell--clickable:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; border-radius: 4px; }', - '.vm-page .vm-ops-manager-cell--placeholder { color: #94a3b8; }', - '.vm-page .vm-col-title-with-action { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; white-space: nowrap; }', - '.vm-page .vm-col-title-edit-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; min-width: 22px; padding: 0; border: none; border-radius: 6px; background: transparent; color: #64748b; cursor: pointer; line-height: 0; flex-shrink: 0; transition: color 0.15s ease, background 0.15s ease; }', - '.vm-page .vm-col-title-edit-btn:hover { color: #059669; background: #ecfdf5; }', - '.vm-page .vm-col-title-edit-btn:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }', - '.vm-page .vm-ops-manager-modal-body { display: flex; flex-direction: column; gap: 16px; }', - '.vm-page .vm-ops-manager-modal-card { padding: 14px 16px; border-radius: 12px; background: #f8fafc; border: 1px solid #e2e8f0; display: flex; flex-direction: column; gap: 10px; }', - '.vm-page .vm-ops-manager-modal-card-row { display: flex; align-items: flex-start; gap: 12px; min-width: 0; font-size: 13px; line-height: 1.5; }', - '.vm-page .vm-ops-manager-modal-card-label { flex: 0 0 108px; color: #64748b; font-weight: 500; white-space: nowrap; }', - '.vm-page .vm-ops-manager-modal-card-value { flex: 1; min-width: 0; color: #0f172a; font-weight: 600; word-break: break-word; }', - '.vm-page .vm-ops-manager-modal-field { display: flex; flex-direction: column; gap: 8px; }', - '.vm-page .vm-ops-manager-modal-field-label { font-size: 13px; font-weight: 600; color: #0f172a; }', - '.vm-page .vm-import-modal { display: flex; flex-direction: column; gap: 14px; }', - '.vm-page .vm-import-step-card { padding: 16px 18px; border-radius: 12px; background: #f8fafc; border: 1px solid #e2e8f0; }', - '.vm-page .vm-import-step-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }', - '.vm-page .vm-import-step-badge { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 999px; background: linear-gradient(135deg, #10b981, #059669); color: #fff; font-size: 12px; font-weight: 700; flex-shrink: 0; }', - '.vm-page .vm-import-step-title { font-size: 15px; font-weight: 700; color: #0f172a; line-height: 1.35; }', - '.vm-page .vm-import-download-btn.ant-btn { min-height: 44px; height: 44px; padding: 0 18px; border-radius: 10px !important; font-weight: 600; }', - '.vm-page .vm-import-uploader.ant-upload-wrapper { display: block; }', - '.vm-page .vm-import-uploader.ant-upload-wrapper .ant-upload-drag { border: 1.5px dashed #cbd5e1 !important; border-radius: 12px !important; background: #fff !important; padding: 24px 16px !important; transition: border-color 0.2s ease, background 0.2s ease; }', - '.vm-page .vm-import-uploader.ant-upload-wrapper .ant-upload-drag:hover { border-color: #34d399 !important; background: #f0fdf4 !important; }', - '.vm-page .vm-import-uploader.ant-upload-wrapper .ant-upload-drag.ant-upload-drag-hover { border-color: #10b981 !important; background: #ecfdf5 !important; }', - '.vm-page .vm-import-upload-icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; margin: 0 auto 10px; border-radius: 12px; background: #ecfdf5; color: #059669; }', - '.vm-page .vm-import-upload-title { margin: 0; font-size: 15px; font-weight: 600; color: #0f172a; }', - '.vm-page .vm-import-upload-hint { margin: 6px 0 0; font-size: 13px; color: #64748b; line-height: 1.45; }', - '.vm-page .vm-import-fail { padding: 12px 14px; border-radius: 10px; background: #fef2f2; border: 1px solid #fecaca; }', - '.vm-page .vm-import-fail-title { font-size: 13px; font-weight: 700; color: #b91c1c; margin-bottom: 8px; }', - '.vm-page .vm-import-fail-item { font-size: 12px; color: #7f1d1d; line-height: 1.5; margin-bottom: 4px; }', - '.vm-import-modal-wrap .vm-import-close-btn.ant-btn { min-height: 44px; height: 44px; padding: 0 16px; border-radius: 10px !important; }', - '.vm-page .vm-modal-hint { font-size: 13px; color: #64748b; margin-bottom: 12px; line-height: 1.55; }', - '.vm-multi-plate-popover.ant-popover { max-width: none !important; }', - '.vm-multi-plate-popover .ant-popover-inner { width: 288px !important; max-width: calc(100vw - 32px) !important; padding: 0 !important; border-radius: 12px !important; box-shadow: 0 8px 28px rgba(15, 23, 42, 0.12) !important; overflow: hidden; }', - '.vm-multi-plate-popover .ant-popover-inner-content { padding: 0 !important; }', - '.vm-multi-plate-popover .vm-multi-plate-pop { width: 100%; padding: 16px 16px 14px; box-sizing: border-box; display: flex; flex-direction: column; gap: 12px; }', - '.vm-multi-plate-popover .vm-multi-plate-pop-textarea.ant-input-textarea { margin: 0 !important; width: 100%; display: block; }', - '.vm-multi-plate-popover .vm-multi-plate-pop-textarea textarea { width: 100% !important; box-sizing: border-box !important; border-radius: 8px !important; border-color: #e2e8f0 !important; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important; font-size: 13px !important; line-height: 1.5 !important; padding: 10px 12px !important; min-height: 120px !important; resize: none !important; box-shadow: none !important; }', - '.vm-multi-plate-popover .vm-multi-plate-pop-textarea textarea:hover { border-color: #cbd5e1 !important; }', - '.vm-multi-plate-popover .vm-multi-plate-pop-textarea textarea:focus { border-color: #10b981 !important; box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.18) !important; }', - '.vm-multi-plate-popover .vm-multi-plate-pop-actions { display: flex; justify-content: flex-end; align-items: center; margin: 0; padding: 4px 0 2px; }', - '.vm-multi-plate-popover .vm-multi-plate-pop-actions .ant-btn { min-height: 36px; height: 36px; padding: 0 16px; border-radius: 8px !important; font-size: 13px; font-weight: 600; line-height: 1; }', - '.vm-page .vm-multi-plate-trigger { cursor: pointer; width: 100%; }', - '.vm-page .vm-multi-plate-trigger.ant-input-affix-wrapper { border-radius: 8px !important; align-items: center !important; }', - '.vm-page .vm-multi-plate-trigger .ant-input { cursor: pointer; text-align: left !important; line-height: 30px !important; height: 30px !important; padding-top: 0 !important; padding-bottom: 0 !important; }', - '.vm-page .vm-multi-plate-trigger .ant-input::placeholder { text-align: left; color: #bfbfbf; }', - '.vm-page .vm-list-topbar { display: flex; justify-content: flex-end; align-items: center; margin-bottom: 16px; }', - '.vm-page .vm-detail-topbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }', - '.vm-page .vm-detail-topbar-title { margin: 0; font-size: 18px; font-weight: 700; color: #0f172a; line-height: 1.3; }', - '.vm-page .vm-detail-back-btn.ant-btn { min-height: 44px; height: 44px; padding: 0 16px; border-radius: 10px !important; border-color: #e2e8f0 !important; font-weight: 600 !important; color: #334155 !important; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04) !important; transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease; }', - '.vm-page .vm-detail-back-btn.ant-btn:hover { color: #059669 !important; border-color: #a7f3d0 !important; background: #ecfdf5 !important; }', - '.vm-page .vm-detail-back-btn.ant-btn:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }', - '.vm-page .vm-detail-card { background: #fff; border-radius: 16px; border: 1px solid #e2e8f0; padding: 24px; margin-bottom: 16px; box-shadow: 0 4px 20px -4px rgba(15, 23, 42, 0.05); }', - '.vm-page .vm-section-title { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; font-size: 15px; font-weight: 700; color: #0f172a; }', - '.vm-page .vm-section-title::before { content: ""; width: 3px; height: 14px; border-radius: 2px; background: linear-gradient(180deg, #10b981, #34d399); flex-shrink: 0; }', - '.vm-page .vm-section-title--follow { margin-top: 24px; }', - '.vm-page .vm-detail-hero { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 16px 24px; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #f1f5f9; }', - '.vm-page .vm-detail-hero-identity { min-width: 0; flex: 1 1 240px; }', - '.vm-page .vm-detail-plate-xl { font-size: 24px; font-weight: 800; color: #0f172a; font-variant-numeric: tabular-nums; letter-spacing: 0.03em; line-height: 1.2; }', - '.vm-page .vm-detail-meta-line { margin-top: 6px; font-size: 13px; color: #64748b; line-height: 1.45; }', - '.vm-page .vm-detail-meta-line--mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; letter-spacing: 0.02em; }', - '.vm-page .vm-detail-hero-tags { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: flex-end; }', - '.vm-page .vm-detail-hero-aside { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; min-width: 0; flex: 0 1 360px; max-width: 100%; text-align: right; }', - '.vm-page .vm-detail-hero-location { font-size: 13px; font-weight: 500; color: #334155; line-height: 1.45; max-width: 100%; word-break: break-word; }', - '.vm-page .vm-detail-hero-gps-time { font-size: 12px; color: #64748b; line-height: 1.4; font-variant-numeric: tabular-nums; }', - '.vm-page .vm-detail-quick-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 20px; }', - '@media (max-width: 960px) { .vm-page .vm-detail-quick-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }', - '@media (max-width: 480px) { .vm-page .vm-detail-quick-stats { grid-template-columns: 1fr; } }', - '.vm-page .vm-detail-stat-card { min-width: 0; padding: 12px 14px; border-radius: 12px; background: #f8fafc; border: 1px solid #e2e8f0; }', - '.vm-page .vm-detail-stat-label { margin-bottom: 4px; font-size: 12px; font-weight: 500; color: #64748b; }', - '.vm-page .vm-detail-stat-value { font-size: 14px; font-weight: 600; color: #0f172a; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }', - '.vm-page .vm-detail-stat-value--rich { overflow: visible; white-space: normal; text-overflow: clip; }', - '.vm-page .vm-detail-stat-value--rich .vm-mileage-cell { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }', - '.vm-page .vm-detail-field-value .vm-expire-date-cell { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }', - '.vm-page .vm-detail-fields-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px 16px; }', - '@media (max-width: 1280px) { .vm-page .vm-detail-fields-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }', - '@media (max-width: 640px) { .vm-page .vm-detail-fields-grid { grid-template-columns: 1fr; } }', - '.vm-page .vm-detail-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding: 12px 14px; border-radius: 10px; background: #fff; border: 1px solid #e2e8f0; transition: border-color 0.15s ease, box-shadow 0.15s ease; }', - '.vm-page .vm-detail-field:hover { border-color: #cbd5e1; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); }', - '.vm-page .vm-detail-field-label { font-size: 12px; font-weight: 500; color: #64748b; line-height: 1.4; }', - '.vm-page .vm-detail-field-value { min-width: 0; font-size: 14px; font-weight: 500; color: #0f172a; line-height: 1.45; word-break: break-word; }', - '.vm-page .vm-detail-field-value .vm-stacked-cell-line { white-space: normal; }', - '.vm-page .vm-expand-bar { margin-top: 4px; padding-top: 16px; text-align: center; border-top: 1px solid #f1f5f9; }', - '.vm-page .vm-expand-bar .ant-btn { min-height: 44px; font-weight: 600; }', - '.vm-page .vm-detail-tabs .ant-tabs-nav { margin-bottom: 16px; }', - '.vm-page .vm-detail-tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { font-weight: 600; color: #059669; }', - '.vm-page .vm-detail-tabs .ant-tabs-ink-bar { background: #10b981; }', - '.vm-page .vm-tab-card { padding: 20px 0 0; }', - '.vm-page .vm-model-param-tab { display: flex; flex-direction: column; gap: 16px; padding-top: 4px; }', - '.vm-page .vm-model-param-section { padding: 16px 18px; border-radius: 12px; background: #f8fafc; border: 1px solid #e2e8f0; }', - '.vm-page .vm-model-param-section-title { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; font-weight: 700; color: #0f172a; line-height: 1.35; }', - '.vm-page .vm-model-param-section-title::before { content: ""; width: 3px; height: 14px; border-radius: 2px; background: linear-gradient(180deg, #10b981, #34d399); flex-shrink: 0; }', - '.vm-page .vm-model-param-section .vm-detail-fields-grid { margin-bottom: 0; }', - '.vm-page .vm-model-param-table-wrap { margin-top: 4px; border-radius: 10px; overflow: hidden; border: 1px solid #e2e8f0; background: #fff; }', - '.vm-page .vm-model-param-table-wrap .ant-table-thead > tr > th { background: #f1f5f9 !important; font-weight: 600; color: #475569; }', - '.vm-page .vm-model-param-table-wrap .ant-table-tbody > tr > td { background: #fff; }', - '.vm-page .vm-tab-placeholder { padding: 8px 0; font-size: 14px; color: #64748b; }', - '.vm-page .vm-kv-link { font-weight: 600; color: #10b981; cursor: pointer; background: none; border: none; text-align: left; }', - '.vm-page .vm-kv-link:hover { text-decoration: underline; }', - '.vm-page .vm-kv-link:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; border-radius: 4px; }' -]).join('\n'); - -var VM_PRIMARY_BTN_STYLE = { - borderRadius: 8, - fontWeight: 600, - background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', - border: 'none' -}; - -// 样例数据:Excel 真实数据抽样 100 条,覆盖各品牌-型号组合 -var VM_SAMPLE_VEHICLE_DATA = [ - { id: "1", plateNo: "粤AGR8556", vin: "LB9A32A20R0LS1343", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "广州开创大道停车场", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "广东省广州市", gpsTime: "2026-06-23 14:07:17", regDate: "2025-02-19", inspectExpire: "2027-02-28", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "2", plateNo: "粤AGP4435", vin: "LB9A32A20R0LS1357", vehicleNo: "1357", color: "白色", year: "-", purchaseDate: "-", parking: "-", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "2040-06-20", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "异动中", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "广东省广州市", gpsTime: "2026-06-23 14:17:35", regDate: "2025-06-20", inspectExpire: "2026-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "3", plateNo: "粤AGP5156", vin: "LB9A32A20R0LS1360", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "-", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "广州新运多租赁有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2026060901-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "25468.00", location: "广东省广州市", gpsTime: "2026-06-23 14:17:34", regDate: "2025-02-25", inspectExpire: "2027-02-28", lastDeliveryTime: "2026-06-11 12:21:19", lastDeliveryMile: "25468.00", lastReturnTime: "2026-06-06 09:58:00", lastReturnMile: "25372.00", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "4", plateNo: "沪A66921F", vin: "LKLG7C4E0NA774726", vehicleNo: "22FHD0003", color: "白", year: "-", purchaseDate: "-", parking: "-", ownership: "上海宇速物流有限公司", scrapDate: "-", ratingTime: "2026-09-30 00:00:00", operateCompany: "上海宇速物流有限公司", vehicleSource: "自有", leaseCompany: "上海宇速物流有限公司", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "上海利合供应链管理有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2026011301-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "6210.00", location: "上海市奉贤区", gpsTime: "2026-06-23 14:17:35", regDate: "2022-09-14", inspectExpire: "2026-09-11", lastDeliveryTime: "2023-07-11 17:00:00", lastDeliveryMile: "6210.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "5", plateNo: "沪A02720F", vin: "LKLG7C4E0NA774743", vehicleNo: "22FHD0020", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "上海宇速物流有限公司", scrapDate: "-", ratingTime: "2026-09-30 00:00:00", operateCompany: "上海宇速物流有限公司", vehicleSource: "自有", leaseCompany: "上海宇速物流有限公司", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "南京威路物流有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2025110402", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "31233.00", location: "河南省开封市", gpsTime: "2026-06-23 14:07:16", regDate: "2022-09-09", inspectExpire: "2026-07-17", lastDeliveryTime: "2025-10-01 00:00:00", lastDeliveryMile: "31233.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "6", plateNo: "沪A05675F", vin: "LKLG7C4E0NA774757", vehicleNo: "22FHD0034", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "上海宇速物流有限公司", scrapDate: "-", ratingTime: "2026-10-31 00:00:00", operateCompany: "上海宇速物流有限公司", vehicleSource: "自有", leaseCompany: "上海宇速物流有限公司", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "南京威路物流有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2025110402", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "45822.00", location: "江苏省常州市", gpsTime: "2026-06-23 14:07:15", regDate: "2022-10-20", inspectExpire: "2026-09-11", lastDeliveryTime: "2025-10-01 00:00:00", lastDeliveryMile: "45822.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "7", plateNo: "浙F00688F", vin: "LA9GG64L0NBAF4015", vehicleNo: "33Q", color: "红", year: "-", purchaseDate: "-", parking: "-", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-23-ZL-611", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "202.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-06-21", inspectExpire: "2026-06-30", lastDeliveryTime: "2022-07-04 00:00:00", lastDeliveryMile: "202.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "8", plateNo: "浙F07588F", vin: "LA9GG64L0NBAF4094", vehicleNo: "01Q", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "127012.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2023-03-15", inspectExpire: "2027-03-31", lastDeliveryTime: "2025-05-20 09:56:16", lastDeliveryMile: "79924.00", lastReturnTime: "2026-06-08 15:04:00", lastReturnMile: "127012.00", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "9", plateNo: "浙F06618F", vin: "LA9GG64L0NBAF4175", vehicleNo: "50Q", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-23-ZL-120", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "256.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-11-21", inspectExpire: "2026-11-30", lastDeliveryTime: "2022-12-21 00:00:00", lastDeliveryMile: "256.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "10", plateNo: "浙F06218F", vin: "LA9HE60A2PBAF4020", vehicleNo: "-", color: "白/绿", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:36", regDate: "2023-05-17", inspectExpire: "2027-05-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "11", plateNo: "浙F02608F", vin: "LA9HE60A3PBAF4012", vehicleNo: "-", color: "白/绿", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "嘉兴港区韵达快递有限公司", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT20251111001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "97556.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2023-05-18", inspectExpire: "2027-05-31", lastDeliveryTime: "2026-06-19 12:46:41", lastDeliveryMile: "97556.00", lastReturnTime: "2026-06-05 00:00:00", lastReturnMile: "97337.00", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "12", plateNo: "粤A08177F", vin: "LNXNEGRR0SR318195", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "广州现代停车场", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "上海心坦物流有限公司", department: "业务五部", manager: "秦挺", contractNo: "LNZLHT2026032301-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "2566.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 08:29:34", regDate: "2025-11-27", inspectExpire: "2026-11-30", lastDeliveryTime: "2026-03-25 12:56:37", lastDeliveryMile: "2566.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "13", plateNo: "粤AG02973", vin: "LB9A32A20P0LS1226", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "开创大道云埔宏仁便民停车场", ownership: "现代氢能科技(广州)有限公司", scrapDate: "2038-12-18", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "恒运", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "异常", insuranceStatus: "异常", mileage: "-", location: "广东省", gpsTime: "-", regDate: "-", inspectExpire: "-", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-", vehicleLedgerType: "非运营车辆" }, - { id: "14", plateNo: "粤AFM6602", vin: "LB9A32A20P0LS1257", vehicleNo: "-", color: "白", year: "-", purchaseDate: "2025-12-24", parking: "嘉兴公司楼下氢能展厅", ownership: "现代氢能科技(广州)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "恒运", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "杭州冠泽物流有限公司", department: "业务二部", manager: "董剑煜", contractNo: "LNZLHT 20260423002", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "2973.00", location: "浙江省湖州市", gpsTime: "2026-06-23 13:56:26", regDate: "2024-01-12", inspectExpire: "2027-01-31", lastDeliveryTime: "2026-04-27 15:05:00", lastDeliveryMile: "2973.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "15", plateNo: "粤AFN3997", vin: "LB9A32A20P0LS1260", vehicleNo: "-", color: "白", year: "-", purchaseDate: "2025-12-24", parking: "嘉兴公司楼下氢能展厅", ownership: "现代氢能科技(广州)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "恒运", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "上海明纳物流有限公司", department: "业务二部", manager: "谯云", contractNo: "20251127001", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "908.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:23:42", regDate: "2024-01-12", inspectExpire: "2027-01-31", lastDeliveryTime: "2026-04-04 15:11:17", lastDeliveryMile: "908.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "16", plateNo: "沪A03561F", vin: "LMRKH9AC0R1004086", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "成都宇通服务站停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-06-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "11578.00", location: "四川省成都市", gpsTime: "2026-06-23 14:17:35", regDate: "2024-06-05", inspectExpire: "2027-06-30", lastDeliveryTime: "2026-04-08 13:05:00", lastDeliveryMile: "11181.00", lastReturnTime: "2026-06-05 19:06:00", lastReturnMile: "11578.00", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "17", plateNo: "沪A33319F", vin: "LMRKH9AC0R1004105", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-06-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "嘉兴玲利供应链科技有限公司", department: "业务一部", manager: "陈高伟", contractNo: "LNZLHT20251201001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "45195.00", location: "浙江省湖州市", gpsTime: "2026-06-23 14:17:35", regDate: "2024-06-04", inspectExpire: "2027-06-30", lastDeliveryTime: "2025-12-12 13:09:50", lastDeliveryMile: "45195.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "18", plateNo: "沪A06192F", vin: "LMRKH9AC0R1004119", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-06-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-25-ZL-045", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "15004.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 13:58:52", regDate: "2024-06-03", inspectExpire: "2027-06-30", lastDeliveryTime: "2026-05-11 17:51:00", lastDeliveryMile: "15004.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "19", plateNo: "沪AGU2217", vin: "LSFGL23Z0ND103018", vehicleNo: "8", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-11-30 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2025-12-03 08:54:11", regDate: "2022-11-07", inspectExpire: "2026-11-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "20", plateNo: "沪AGN1890", vin: "LSFGL23Z1ND200812", vehicleNo: "37", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-08-31 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "LNZLHTSH2023071301", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "4596.00", location: "浙江省嘉兴市", gpsTime: "2025-10-30 14:26:54", regDate: "2022-08-05", inspectExpire: "2026-08-31", lastDeliveryTime: "2023-07-17 14:10:00", lastDeliveryMile: "4596.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "21", plateNo: "沪AGU0727", vin: "LSFGL23Z1ND214418", vehicleNo: "136", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "嘉兴金小悦停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-11-30 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "上海浦江特种气体有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2025031301", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "52614.00", location: "上海市奉贤区", gpsTime: "2026-06-23 14:17:34", regDate: "2022-11-07", inspectExpire: "2026-11-30", lastDeliveryTime: "2025-10-30 11:44:00", lastDeliveryMile: "52614.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "22", plateNo: "浙F07878F", vin: "LA9HE60A0NBAF4028", vehicleNo: "-", color: "红", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "2038-05-19", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2022-06-18", inspectExpire: "2027-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "23", plateNo: "浙F06900F", vin: "LA9HE60A0NBAF4031", vehicleNo: "-", color: "红", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "2038-05-19", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2022-06-27", inspectExpire: "2027-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "24", plateNo: "浙F06881F", vin: "LA9HE60A0PBAF4002", vehicleNo: "-", color: "白/绿", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "嘉兴市南湖区大桥镇雪观运输装卸服务站", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT20251010001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "72849.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2023-05-19", inspectExpire: "2027-05-31", lastDeliveryTime: "2025-10-11 08:36:23", lastDeliveryMile: "72849.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "25", plateNo: "京A29256F", vin: "LCFZ1KRD0R0Z00122", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "北京一汽宏特停车场", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "北京市大兴区", gpsTime: "2026-06-05 18:17:42", regDate: "2024-07-19", inspectExpire: "2026-07-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "26", plateNo: "京A26337F", vin: "LCFZ1KRD2R0Z00123", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "北京一汽宏特停车场", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "北京市大兴区", gpsTime: "2026-06-04 17:31:15", regDate: "2024-07-19", inspectExpire: "2026-07-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "27", plateNo: "京A11949F", vin: "LCFZ1KRD4R0Z00124", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "北京一汽宏特停车场", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "北京市大兴区", gpsTime: "2026-06-01 17:11:51", regDate: "2024-07-19", inspectExpire: "2026-07-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "28", plateNo: "浙FK808挂", vin: "LJRC14383J2027744", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江锦昌仓储有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "浙江锦昌仓储有限公司", vehicleType: "重型集装箱半挂车", brand: "通华", model: "重型集装箱半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2018-12-29", inspectExpire: "2027-04-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "29", plateNo: "浙FHU33挂", vin: "LJRC14383L2004239", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型集装箱半挂车", brand: "通华", model: "重型集装箱半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2020-05-08", inspectExpire: "2026-05-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "30", plateNo: "浙FK660挂", vin: "LJRC14384K2003180", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江锦昌仓储有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "浙江锦昌仓储有限公司", vehicleType: "重型集装箱半挂车", brand: "通华", model: "重型集装箱半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2019-03-14", inspectExpire: "2027-04-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "31", plateNo: "京A22896F", vin: "LCFU4VRE5S0Z02157", vehicleNo: "-", color: "蓝/黑", year: "-", purchaseDate: "-", parking: "成都宇通服务站停车场", ownership: "海珀特科技(北京)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛总部", vehicleSource: "外租", leaseCompany: "海珀特科技(北京)有限公司", vehicleType: "重型半挂牵引车", brand: "楚风", model: "49吨牵引车头", customer: "成都易达创想物流科技有限责任公司", department: "业务五部", manager: "秦挺", contractNo: "LNZLHT2026040701-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "四川省", gpsTime: "-", regDate: "2025-12-17", inspectExpire: "2026-12-31", lastDeliveryTime: "2026-04-11 00:00:00", lastDeliveryMile: "0.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "32", plateNo: "京A39361F", vin: "LCFU4VRE6S0Z02152", vehicleNo: "-", color: "蓝/黑", year: "-", purchaseDate: "-", parking: "成都宇通服务站停车场", ownership: "海珀特科技(北京)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛总部", vehicleSource: "外租", leaseCompany: "海珀特科技(北京)有限公司", vehicleType: "重型半挂牵引车", brand: "楚风", model: "49吨牵引车头", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "四川省", gpsTime: "-", regDate: "2025-12-16", inspectExpire: "2026-12-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "33", plateNo: "京A09086F", vin: "LCFU4VREXS0Z02154", vehicleNo: "-", color: "蓝/黑", year: "-", purchaseDate: "-", parking: "成都宇通服务站停车场", ownership: "海珀特科技(北京)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "-", vehicleSource: "外租", leaseCompany: "海珀特科技(北京)有限公司", vehicleType: "重型半挂牵引车", brand: "楚风", model: "49吨牵引车头", customer: "成都易达创想物流科技有限责任公司", department: "业务五部", manager: "秦挺", contractNo: "LNZLHT2026040701-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "-", mileage: "-", location: "-", gpsTime: "-", regDate: "-", inspectExpire: "2026-12-31", lastDeliveryTime: "2026-05-20 15:45:00", lastDeliveryMile: "3706.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "34", plateNo: "观光车001", vin: "LA9GG68L4PBAF0001", vehicleNo: "-", color: "蓝白", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "观光车", brand: "舒捷", model: "SJ型蓄电池观光车", customer: "浙江氢能产业发展有限公司", department: "业务一部", manager: "陈高伟", contractNo: "LNZLHTJX23102701", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2023-08-09", inspectExpire: "2099-08-09", lastDeliveryTime: "2024-01-02 16:56:05", lastDeliveryMile: "31.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "35", plateNo: "观光车002", vin: "LA9GG68L4PBAF0002", vehicleNo: "-", color: "蓝白", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "观光车", brand: "舒捷", model: "SJ型蓄电池观光车", customer: "浙江氢能产业发展有限公司", department: "业务一部", manager: "陈高伟", contractNo: "LNZLHTJX23102701", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2023-08-09", inspectExpire: "2099-08-09", lastDeliveryTime: "2024-01-02 16:56:10", lastDeliveryMile: "19.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "36", plateNo: "粤ACT2533", vin: "LC0DE6CB8J1001384", vehicleNo: "-", color: "黑", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "羚牛氢能科技(广东)有限公司", vehicleType: "小型普通客车", brand: "腾势", model: "公务用车/小客车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "-", location: "广东省", gpsTime: "-", regDate: "2019-05-27", inspectExpire: "2027-05-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "37", plateNo: "沪BDB9161", vin: "LC0DF4CD8S0303140", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "-", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "小型普通客车", brand: "腾势", model: "公务用车/小客车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "上海市", gpsTime: "-", regDate: "-", inspectExpire: "-", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "38", plateNo: "沪ADP7226", vin: "LJ1E6A2U7L7739433", vehicleNo: "-", color: "蓝色", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "2026-12-31 00:00:00", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "蒲红霞", vehicleType: "小型普通客车", brand: "其他", model: "公务用车/小客车", customer: "羚牛公务车专用", department: "-", manager: "-", contractNo: "LNGWC20240105", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2020-12-22", inspectExpire: "2026-12-31", lastDeliveryTime: "2022-06-30 00:00:00", lastDeliveryMile: "0.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "39", plateNo: "浙FFJ2966", vin: "LSKG49C24PA143875", vehicleNo: "-", color: "灰", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "小型普通客车", brand: "其他", model: "公务用车/小客车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2025-02-14", inspectExpire: "2026-07-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "40", plateNo: "浙FK973挂", vin: "LJT93VRJ4A0012440", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型集装箱半挂车", brand: "明威", model: "重型集装箱半挂车", customer: "嘉兴玲利供应链科技有限公司", department: "业务一部", manager: "陈高伟", contractNo: "LNZLHT20251021002", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2011-03-02", inspectExpire: "2027-03-31", lastDeliveryTime: "2025-11-04 10:59:00", lastDeliveryMile: "0.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "41", plateNo: "沪E9652挂", vin: "LJT93VRJXA0012443", vehicleNo: "-", color: "红", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-07-31 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型集装箱半挂车", brand: "明威", model: "重型集装箱半挂车", customer: "嘉兴玲利供应链科技有限公司", department: "业务一部", manager: "陈高伟", contractNo: "LNZLHT20250928001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2010-07-23", inspectExpire: "2026-07-31", lastDeliveryTime: "2025-10-12 12:22:00", lastDeliveryMile: "7619.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "42", plateNo: "沪BH391挂", vin: "LA9935J21F0YWT578", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-09-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型集装箱半挂车", brand: "鲁郸万通牌", model: "重型集装箱半挂车", customer: "嘉兴玲利供应链科技有限公司", department: "业务一部", manager: "陈高伟", contractNo: "2026010101", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2015-09-09", inspectExpire: "2026-09-30", lastDeliveryTime: "2026-02-01 11:00:00", lastDeliveryMile: "0.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "43", plateNo: "沪AE890挂", vin: "LA99P3405K0WXS081", vehicleNo: "-", color: "红", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-03-31 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型平板半挂车", brand: "万风", model: "重型平板半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2019-03-11", inspectExpire: "2027-03-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "44", plateNo: "氢能叉车001", vin: "LB9A32A28R0LS0001", vehicleNo: "-", color: "蓝白绿", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛总部", vehicleSource: "自有", leaseCompany: "-", vehicleType: "叉车", brand: "其他", model: "氢能叉车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "-", inspectExpire: "-", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "45", plateNo: "沪EB8290", vin: "LGAX2BG42H1003112", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "北京一汽宏特修理厂停车场", ownership: "上海宇速物流有限公司", scrapDate: "-", ratingTime: "2026-03-31 00:00:00", operateCompany: "上海宇速物流有限公司", vehicleSource: "挂靠", leaseCompany: "上海宇速物流有限公司", vehicleType: "重型厢式货车", brand: "东风", model: "挂靠油车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "上海市", gpsTime: "-", regDate: "2017-03-22", inspectExpire: "2027-03-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "46", plateNo: "浙FD85611", vin: "LJ2MTKBM1R1049307", vehicleNo: "-", color: "白", year: "-", purchaseDate: "2024-11-08", parking: "平湖停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "小型普通客车", brand: "远程牌", model: "公务用车/小客车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2024-11-20", inspectExpire: "2026-11-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "47", plateNo: "沪FR0572", vin: "LZFH18R14HD010462", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-06-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "红岩", model: "35吨牵引车头", customer: "嘉兴大森物流有限公司", department: "业务二部", manager: "刘念念", contractNo: "20251017", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "458272.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2017-06-23", inspectExpire: "2026-06-30", lastDeliveryTime: "2024-10-18 10:00:00", lastDeliveryMile: "458272.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "48", plateNo: "粤AGR5028", vin: "LB9A32A20R0LS1374", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "开创大道云埔宏仁便民停车场", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "广州长运冷链服务有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2025112201", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "1596.00", location: "广东省广州市", gpsTime: "2026-06-23 14:17:35", regDate: "2025-02-19", inspectExpire: "2027-02-28", lastDeliveryTime: "2025-11-28 14:18:48", lastDeliveryMile: "1596.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "49", plateNo: "沪A65087F", vin: "LKLG7C4E0NA774760", vehicleNo: "22FHD0037", color: "白", year: "-", purchaseDate: "-", parking: "佛山汽车运输集团公交分公司塱沙充电站", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-11-30 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "嘉兴市京宝物流有限公司", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT 20260412001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "80219.00", location: "浙江省金华市", gpsTime: "2026-06-23 14:17:36", regDate: "2022-11-09", inspectExpire: "2026-11-01", lastDeliveryTime: "2026-04-12 20:39:52", lastDeliveryMile: "80219.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "50", plateNo: "浙F09378F", vin: "LA9GG64L0NBAF4189", vehicleNo: "-", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:36", regDate: "2023-01-09", inspectExpire: "2027-01-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "51", plateNo: "粤A03859F", vin: "LNXNEGRR0SR318200", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "广州现代停车场", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "武汉至上云合供应链管理有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2026032001-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "880.00", location: "湖北省武汉市", gpsTime: "2026-06-23 14:17:24", regDate: "2025-11-27", inspectExpire: "2026-11-30", lastDeliveryTime: "2026-04-19 18:05:00", lastDeliveryMile: "880.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "52", plateNo: "粤AFP1332", vin: "LB9A32A20P0LS1372", vehicleNo: "-", color: "白", year: "-", purchaseDate: "2025-12-30", parking: "广州开创大道停车场", ownership: "现代氢能科技(广州)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "恒运", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "-", department: "-", manager: "-", contractNo: "LNZLHT2026040201-042", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "858.00", location: "广东省广州市", gpsTime: "2026-06-23 14:15:39", regDate: "2024-01-12", inspectExpire: "2027-01-31", lastDeliveryTime: "2026-04-23 11:09:14", lastDeliveryMile: "858.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "53", plateNo: "沪A32399F", vin: "LMRKH9AC0R1004122", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "2038-05-19", ratingTime: "-", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-25-ZL-045", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "125095.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2024-06-04", inspectExpire: "2027-06-30", lastDeliveryTime: "2026-05-11 16:51:46", lastDeliveryMile: "125095.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "54", plateNo: "沪AGR7901", vin: "LSFGL23Z1ND214421", vehicleNo: "133", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "韶关宝氢科技停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-11-30 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2022-11-07", inspectExpire: "2026-11-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "55", plateNo: "浙F05777F", vin: "LA9HE60A0PBAF4016", vehicleNo: "-", color: "白/绿", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "嘉兴市南湖区新丰镇乐果货物运输服务部(个体工商户)", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT20251216001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "104217.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2023-05-18", inspectExpire: "2027-05-31", lastDeliveryTime: "2025-12-17 10:25:00", lastDeliveryMile: "104217.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "56", plateNo: "京A45651F", vin: "LCFZ1KRD6P0Z00686", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "北京一汽宏特停车场", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "北京市大兴区", gpsTime: "2026-06-05 12:38:03", regDate: "2024-07-19", inspectExpire: "2026-07-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "57", plateNo: "浙FK800挂", vin: "LJRC14385J2027745", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江锦昌仓储有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "浙江锦昌仓储有限公司", vehicleType: "重型集装箱半挂车", brand: "通华", model: "重型集装箱半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2018-12-29", inspectExpire: "2027-04-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "58", plateNo: "粤AGR5288", vin: "LB9A32A20R0LS1388", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "无锡铭康物流有限公司-1", department: "业务二部", manager: "董剑煜", contractNo: "20260316001", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "26010.00", location: "江苏省苏州市", gpsTime: "2026-06-23 14:17:35", regDate: "2025-01-20", inspectExpire: "2027-02-28", lastDeliveryTime: "2026-03-23 16:14:11", lastDeliveryMile: "26010.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "59", plateNo: "沪A09100F", vin: "LKLG7C4E0NA774774", vehicleNo: "22FHD0051", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-11-30 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "南京威路物流有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2025110402", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "24045.00", location: "江苏省南京市", gpsTime: "2026-06-23 14:07:16", regDate: "2022-11-01", inspectExpire: "2026-09-11", lastDeliveryTime: "2025-10-01 00:00:00", lastDeliveryMile: "24045.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "60", plateNo: "浙F07559F", vin: "LA9GG64L0NBAF4192", vehicleNo: "47Q", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-23-ZL-120", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "116285.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-11-21", inspectExpire: "2026-11-30", lastDeliveryTime: "2025-09-30 10:13:45", lastDeliveryMile: "116285.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "61", plateNo: "粤A00828F", vin: "LNXNEGRR0SR319444", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "广州现代停车场", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "嘉兴智奇供应链管理有限公司", department: "业务二部", manager: "刘念念", contractNo: "20260105", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "2547.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 00:54:54", regDate: "2025-11-28", inspectExpire: "2026-11-30", lastDeliveryTime: "2026-03-26 09:07:57", lastDeliveryMile: "2547.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "62", plateNo: "粤AGG4191", vin: "LB9A32A20R0LS1049", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "25335.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2024-05-27", inspectExpire: "2027-05-31", lastDeliveryTime: "2025-08-02 13:00:00", lastDeliveryMile: "7855.00", lastReturnTime: "2026-06-11 13:40:04", lastReturnMile: "25335.00", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "63", plateNo: "沪A09133F", vin: "LMRKH9AC1R1004095", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-06-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2024-06-04", inspectExpire: "2027-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "64", plateNo: "沪AGW6270", vin: "LSFGL23Z1ND214435", vehicleNo: "114", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-09-30 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2025-11-14 13:55:25", regDate: "2022-09-26", inspectExpire: "2026-09-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "65", plateNo: "浙F07939F", vin: "LA9HE60A1PBAF4008", vehicleNo: "-", color: "白/绿", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "嘉兴港区众通快递有限公司", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT 20241205001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "94145.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2023-05-19", inspectExpire: "2027-05-31", lastDeliveryTime: "2025-06-17 13:17:57", lastDeliveryMile: "94145.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "66", plateNo: "京A19945F", vin: "LCFZ1KRD6R0Z00125", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "四川汶川客户停车场", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "2038-05-19", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "四川邦达蜀运供应链管理有限公司", department: "业务五部", manager: "秦挺", contractNo: "LNZLHT2026012805-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "66023.00", location: "四川省德阳市", gpsTime: "2026-06-23 14:17:45", regDate: "2024-07-19", inspectExpire: "2027-07-31", lastDeliveryTime: "2026-01-20 11:20:19", lastDeliveryMile: "66023.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "67", plateNo: "浙FK698挂", vin: "LJRC14387J2027746", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江锦昌仓储有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "浙江锦昌仓储有限公司", vehicleType: "重型集装箱半挂车", brand: "通华", model: "重型集装箱半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2018-12-29", inspectExpire: "2027-04-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "68", plateNo: "粤AGP9338", vin: "LB9A32A20R0LS1391", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "嘉兴益顺冷链物流有限公司", department: "业务二部", manager: "尚建华", contractNo: "20250801001", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "347.00", location: "浙江省绍兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2025-02-19", inspectExpire: "2027-02-28", lastDeliveryTime: "2025-11-24 16:34:32", lastDeliveryMile: "347.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "69", plateNo: "沪A03565F", vin: "LKLG7C4E0NA774788", vehicleNo: "22FHD0065", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-10-31 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "南京威路物流有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2025110402", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "41010.00", location: "江苏省常州市", gpsTime: "2026-06-23 14:17:34", regDate: "2022-10-29", inspectExpire: "2026-09-08", lastDeliveryTime: "2025-10-01 00:00:00", lastDeliveryMile: "41010.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "70", plateNo: "浙F09968F", vin: "LA9GG64L1NBAF4167", vehicleNo: "39Q", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "龙王路停车场", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-23-ZL-611", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "230.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-09-09", inspectExpire: "2026-09-30", lastDeliveryTime: "2022-10-10 00:00:00", lastDeliveryMile: "230.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "71", plateNo: "粤A03676F", vin: "LNXNEGRR0SR319458", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "嘉兴秀洲加氢站停车场", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "嘉兴智奇供应链管理有限公司", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT 20260413001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "8893.00", location: "浙江省宁波市", gpsTime: "2026-06-23 14:18:19", regDate: "2025-11-28", inspectExpire: "2026-11-30", lastDeliveryTime: "2026-04-19 19:30:00", lastDeliveryMile: "8893.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "72", plateNo: "粤AGH8490", vin: "LB9A32A20R0LS1052", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "佛山汽车运输集团公交分公司塱沙充电站", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "广东氢沣科技有限公司", department: "业务三部", manager: "吴纬涛", contractNo: "LNZLHT2026040201-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "15560.00", location: "广东省佛山市", gpsTime: "2026-06-23 14:17:36", regDate: "2024-05-24", inspectExpire: "2027-05-31", lastDeliveryTime: "2026-05-13 14:05:16", lastDeliveryMile: "15560.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "73", plateNo: "沪A69522F", vin: "LMRKH9AC1R1004100", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-06-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2024-06-04", inspectExpire: "2027-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "74", plateNo: "沪AGK9821", vin: "LSFGL23Z2ND201130", vehicleNo: "45", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-08-31 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2025-07-29 12:03:00", regDate: "2022-08-05", inspectExpire: "2026-08-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "75", plateNo: "浙F31228F", vin: "LA9HE60A1PBAF4011", vehicleNo: "-", color: "白/绿", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "嘉兴港区韵达快递有限公司", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT 20251026001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "116749.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2023-05-19", inspectExpire: "2027-05-31", lastDeliveryTime: "2025-10-26 13:07:30", lastDeliveryMile: "116749.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "76", plateNo: "京A16308F", vin: "LCFZ1KRD7R0Z00120", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "北京一汽宏特停车场", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "北京市大兴区", gpsTime: "2026-06-03 13:52:34", regDate: "2024-07-24", inspectExpire: "2026-07-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "77", plateNo: "浙FK368挂", vin: "LJRC14388K2003179", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "浙江锦昌仓储有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "浙江锦昌仓储有限公司", vehicleType: "重型集装箱半挂车", brand: "通华", model: "重型集装箱半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2019-03-20", inspectExpire: "2027-04-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-" }, - { id: "78", plateNo: "粤AGP5368", vin: "LB9A32A20R0LS1407", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "佛山汽车运输集团公交分公司塱沙充电站", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "广东粤祥食品供应链运营管理有限公司", department: "业务六部", manager: "钟祥", contractNo: "LNZLHT2025091502", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "19446.00", location: "广东省佛山市", gpsTime: "2026-06-23 14:17:34", regDate: "2025-01-07", inspectExpire: "2027-02-28", lastDeliveryTime: "2026-02-10 16:13:00", lastDeliveryMile: "19446.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "79", plateNo: "沪A58185F", vin: "LKLG7C4E0NA774791", vehicleNo: "22FHD0068", color: "白", year: "-", purchaseDate: "-", parking: "-", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-10-31 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "沈阳聚德物流有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT2026022402-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "15192.00", location: "浙江省金华市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-10-29", inspectExpire: "2026-08-22", lastDeliveryTime: "2023-12-31 12:00:00", lastDeliveryMile: "15192.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "80", plateNo: "浙F08133F", vin: "LA9GG64L1NBAF4170", vehicleNo: "40Q", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "-", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-23-ZL-611", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "160.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-09-09", inspectExpire: "2026-09-30", lastDeliveryTime: "2022-10-10 00:00:00", lastDeliveryMile: "160.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "81", plateNo: "粤A01629F", vin: "LNXNEGRR0SR319461", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "佛山龙天重庆红专停车场", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "重庆金时源供应链有限公司", department: "业务五部", manager: "秦挺", contractNo: "LNZLHT2026012602-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "893.00", location: "重庆市大渡口区", gpsTime: "2026-06-23 14:26:01", regDate: "2025-11-28", inspectExpire: "2026-11-30", lastDeliveryTime: "2026-01-23 17:58:00", lastDeliveryMile: "893.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "82", plateNo: "粤AGE4862", vin: "LB9A32A20R0LS1066", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "异常", insuranceStatus: "正常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2024-05-24", inspectExpire: "2027-05-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "83", plateNo: "沪A39286F", vin: "LMRKH9AC1R1004114", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "成都宇通服务站停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "四川群彬物流有限公司", department: "业务五部", manager: "秦挺", contractNo: "LNZLHT2026010701-042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "4357.00", location: "四川省成都市", gpsTime: "2026-06-23 14:17:35", regDate: "2024-06-03", inspectExpire: "2027-06-30", lastDeliveryTime: "2025-12-24 15:21:00", lastDeliveryMile: "4357.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "84", plateNo: "沪AF58471", vin: "LSFGL23Z2ND214427", vehicleNo: "147", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "-", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-11-30 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省杭州市", gpsTime: "2026-06-23 14:17:34", regDate: "2022-11-07", inspectExpire: "2026-11-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "85", plateNo: "浙F03268F", vin: "LA9HE60A2NBAF4029", vehicleNo: "-", color: "红", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:34", regDate: "2022-06-27", inspectExpire: "2027-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "86", plateNo: "京A05989F", vin: "LCFZ1KRD8P0Z00687", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "-", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "北京市大兴区", gpsTime: "2026-06-05 13:51:33", regDate: "2024-06-04", inspectExpire: "2026-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "87", plateNo: "浙FKV19挂", vin: "LJRC2237602011262", vehicleNo: "-", color: "红色", year: "-", purchaseDate: "-", parking: "-", ownership: "嘉兴市鼎义物流有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴市鼎义物流有限公司", vehicleType: "重型集装箱半挂车", brand: "通华", model: "重型集装箱半挂车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "-", location: "浙江省", gpsTime: "-", regDate: "2012-08-03", inspectExpire: "2026-08-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "离线", projectName: "-", vehicleLedgerType: "非运营车辆" }, - { id: "88", plateNo: "粤AGQ3155", vin: "LB9A32A20R0LS1410", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "广州开创大道停车场", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "广东省广州市", gpsTime: "2026-06-23 14:17:35", regDate: "2025-02-19", inspectExpire: "2027-02-28", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "89", plateNo: "沪A39585F", vin: "LKLG7C4E0NA774807", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "上海宇速物流有限公司", scrapDate: "-", ratingTime: "2026-10-31 00:00:00", operateCompany: "上海宇速物流有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "西安御盛合供应链管理有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT20260108-3042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "32351.00", location: "陕西省西安市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-10-28", inspectExpire: "2026-08-14", lastDeliveryTime: "2025-07-25 16:45:00", lastDeliveryMile: "32351.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "90", plateNo: "浙F32233F", vin: "LA9GG64L1NBAF4184", vehicleNo: "53Q", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "-", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "嘉兴市乍浦港口经营有限公司", department: "业务一部", manager: "陈高伟", contractNo: "JXGW-GC-23-ZL-120", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "265.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-11-21", inspectExpire: "2026-11-30", lastDeliveryTime: "2022-12-21 00:00:00", lastDeliveryMile: "265.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "91", plateNo: "粤A03331F", vin: "LNXNEGRR0SR321369", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-22 16:22:19", regDate: "2025-12-03", inspectExpire: "2026-12-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "92", plateNo: "粤AG17852", vin: "LB9A32A21P0LS1235", vehicleNo: "-", color: "白", year: "-", purchaseDate: "2025-12-30", parking: "广州开创大道停车场", ownership: "现代氢能科技(广州)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "恒运", vehicleType: "轻型厢式货车", brand: "现代", model: "4.5吨货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "广东省广州市", gpsTime: "2026-06-08 13:29:16", regDate: "2023-12-15", inspectExpire: "2026-12-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "93", plateNo: "沪A60552F", vin: "LMRKH9AC1R1004128", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "乌鲁木齐隆盛达停车场", ownership: "上海羚牛氢运物联网科技有限公司", scrapDate: "-", ratingTime: "2026-06-30 00:00:00", operateCompany: "上海羚牛氢运物联网科技有限公司", vehicleSource: "自有", leaseCompany: "上海羚牛氢运物联网科技有限公司", vehicleType: "重型半挂牵引车", brand: "宇通", model: "49吨牵引车头", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "新疆维吾尔自治区乌鲁木齐市", gpsTime: "2026-06-23 14:17:34", regDate: "2024-06-04", inspectExpire: "2027-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "94", plateNo: "沪AGZ9860", vin: "LSFGL23Z3ND105023", vehicleNo: "17", color: "白/绿/灰", year: "-", purchaseDate: "-", parking: "乌鲁木齐隆盛达停车场", ownership: "羚牛新能源科技(上海)有限公司", scrapDate: "-", ratingTime: "2026-10-31 00:00:00", operateCompany: "羚牛新能源科技(上海)有限公司", vehicleSource: "自有", leaseCompany: "羚牛新能源科技(上海)有限公司", vehicleType: "轻型厢式货车", brand: "跃进", model: "4.5吨冷链车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-10-09", inspectExpire: "2026-10-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "95", plateNo: "浙F07000F", vin: "LA9HE60A2NBAF4032", vehicleNo: "-", color: "红", year: "-", purchaseDate: "-", parking: "汇通检测站停车场", ownership: "浙江羚牛氢能科技有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "自有", leaseCompany: "浙江羚牛氢能科技有限公司", vehicleType: "重型厢式货车", brand: "飞驰", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "待运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "异常", mileage: "0.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-06-27", inspectExpire: "2026-06-30", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "96", plateNo: "京A43005F", vin: "LCFZ1KRD8R0Z00126", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "北京一汽宏特停车场", ownership: "北京氢运羚壹供应链管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "北京氢运羚壹供应链管理有限公司", vehicleSource: "自有", leaseCompany: "北京氢运羚壹供应链管理有限公司", vehicleType: "重型厢式货车", brand: "楚风", model: "18吨厢式货车", customer: "-", department: "-", manager: "-", contractNo: "-", operateStatus: "可运营", vehicleStatus: "未备车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "0.00", location: "北京市大兴区", gpsTime: "2026-06-23 11:41:31", regDate: "2024-07-19", inspectExpire: "2026-07-31", lastDeliveryTime: "-", lastDeliveryMile: "-", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "97", plateNo: "粤AGP3508", vin: "LB9A32A20R0LS1424", vehicleNo: "-", color: "白色", year: "-", purchaseDate: "-", parking: "平湖停车场", ownership: "广州开发区交投氢能运营管理有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "外租", leaseCompany: "广州开发区交投氢能运营管理有限公司", vehicleType: "轻型厢式货车", brand: "现代", model: "帕力安牌4.5吨冷链车", customer: "浙江洋开供应链管理有限公司", department: "业务二部", manager: "刘念念", contractNo: "20250815001", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "465.00", location: "浙江省杭州市", gpsTime: "2026-06-23 14:17:35", regDate: "2025-02-25", inspectExpire: "2027-01-31", lastDeliveryTime: "2025-09-30 15:16:20", lastDeliveryMile: "465.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "98", plateNo: "沪A37785F", vin: "LKLG7C4E0NA774810", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "-", ownership: "上海宇速物流有限公司", scrapDate: "-", ratingTime: "2026-11-30 00:00:00", operateCompany: "上海宇速物流有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "苏龙", model: "海格牌18吨双飞翼货车", customer: "西安御盛合供应链管理有限公司", department: "业务三部", manager: "金可鹏", contractNo: "LNZLHT20260108-3042", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "789.00", location: "陕西省西安市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-11-10", inspectExpire: "2026-09-26", lastDeliveryTime: "2025-07-29 14:35:59", lastDeliveryMile: "789.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "99", plateNo: "浙F01208F", vin: "LA9GG64L1NBAF4198", vehicleNo: "-", color: "白/蓝/绿", year: "-", purchaseDate: "-", parking: "-", ownership: "嘉兴氢能产业发展股份有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "浙江羚牛氢能科技有限公司", vehicleSource: "外租", leaseCompany: "嘉兴氢能产业发展股份有限公司", vehicleType: "重型半挂牵引车", brand: "飞驰", model: "49吨牵引车头", customer: "羚牛氢能-物流中心", department: "业务二部", manager: "尚建华", contractNo: "JXLN-23-019", operateStatus: "自营", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "186.00", location: "浙江省嘉兴市", gpsTime: "2026-06-23 14:17:35", regDate: "2022-12-22", inspectExpire: "2026-12-31", lastDeliveryTime: "2023-02-28 00:00:00", lastDeliveryMile: "186.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" }, - { id: "100", plateNo: "粤A02298F", vin: "LNXNEGRR0SR321372", vehicleNo: "-", color: "白", year: "-", purchaseDate: "-", parking: "嘉兴公司楼下氢能展厅", ownership: "羚牛氢能科技(广东)有限公司", scrapDate: "-", ratingTime: "-", operateCompany: "羚牛氢能科技(广东)有限公司", vehicleSource: "自有", leaseCompany: "-", vehicleType: "重型厢式货车", brand: "现代", model: "帕力安牌18吨双飞翼货车", customer: "嘉兴市京宝物流有限公司", department: "业务二部", manager: "刘念念", contractNo: "LNZLHT 20260510001", operateStatus: "租赁", vehicleStatus: "已交车", licenseStatus: "正常", insuranceStatus: "正常", mileage: "43434.00", location: "浙江省金华市", gpsTime: "2026-06-23 14:17:21", regDate: "2025-12-02", inspectExpire: "2026-12-31", lastDeliveryTime: "2026-05-14 09:05:57", lastDeliveryMile: "43434.00", lastReturnTime: "-", lastReturnMile: "-", outStatus: "无", onlineStatus: "在线", projectName: "-" } -]; - -/** 停车场名称 → 所属区域(省-市),与停车场管理台账一致 */ -var VM_PARKING_REGION_BY_NAME = { - '广州开创大道停车场': '广东省-广州市', - '开创大道云埔宏仁便民停车场': '广东省-广州市', - '广州现代停车场': '广东省-广州市', - '平湖停车场': '浙江省-嘉兴市', - '嘉兴公司楼下氢能展厅': '浙江省-嘉兴市', - '嘉兴秀洲加氢站停车场': '浙江省-嘉兴市', - '嘉兴金小悦停车场': '浙江省-嘉兴市', - '汇通检测站停车场': '浙江省-嘉兴市', - '龙王路停车场': '浙江省-嘉兴市', - '成都宇通服务站停车场': '四川省-成都市', - '北京一汽宏特停车场': '北京市-北京市', - '北京一汽宏特修理厂停车场': '北京市-北京市', - '佛山汽车运输集团公交分公司塱沙充电站': '广东省-佛山市', - '佛山龙天重庆红专停车场': '广东省-佛山市', - '韶关宝氢科技停车场': '广东省-韶关市', - '乌鲁木齐隆盛达停车场': '新疆维吾尔自治区-乌鲁木齐市', - '四川汶川客户停车场': '四川省-阿坝藏族羌族自治州' -}; - -/** 样例数据:按省-市生成完整 GPS 地址(区/路/号) */ -var VM_MOCK_FULL_ADDRESS_SUFFIX_BY_CITY = { - '广东省-广州市': ['黄埔区开创大道188号', '天河区黄埔大道西100号', '番禺区南村镇兴业大道1号'], - '上海市-上海市': ['奉贤区南桥镇解放东路200号', '浦东新区张江路88号', '嘉定区安亭镇墨玉路66号'], - '浙江省-嘉兴市': ['南湖区中环南路1288号', '秀洲区高照街道秀园路356号', '平湖市当湖街道新华南路88号'], - '浙江省-杭州市': ['余杭区文一西路969号', '萧山区市心北路200号'], - '浙江省-湖州市': ['吴兴区二环西路188号', '南浔区人瑞路66号'], - '浙江省-金华市': ['婺城区宾虹路288号', '金东区东市街100号'], - '浙江省-绍兴市': ['越城区解放北路188号'], - '浙江省-宁波市': ['鄞州区南部商务区天童南路535号'], - '江苏省-常州市': ['武进区常武中路18号', '新北区通江路88号'], - '江苏省-苏州市': ['工业园区星湖街328号'], - '江苏省-南京市': ['江宁区清水亭西路88号'], - '河南省-开封市': ['龙亭区东京大道西段168号'], - '四川省-成都市': ['双流区大件路白家段888号', '武侯区科园南路88号'], - '四川省-德阳市': ['旌阳区长江东路168号'], - '北京市-北京市': ['大兴区兴华大街188号', '朝阳区望京街10号'], - '陕西省-西安市': ['未央区明光路100号', '雁塔区科技路88号'], - '广东省-佛山市': ['南海区桂城街道灯湖东路66号', '顺德区大良街道凤翔路18号'], - '湖北省-武汉市': ['洪山区珞狮路122号'], - '重庆市-重庆市': ['大渡口区春晖路88号'], - '新疆维吾尔自治区-乌鲁木齐市': ['头屯河区黄河路168号'] -}; - -var VM_MOCK_FULL_ADDRESS_BY_PROVINCE = { - '浙江省': ['嘉兴市南湖区中环南路1288号', '杭州市余杭区文一西路969号', '湖州市吴兴区二环西路188号'], - '广东省': ['广州市黄埔区开创大道188号', '佛山市南海区桂城街道灯湖东路66号'], - '四川省': ['成都市双流区大件路白家段888号'], - '上海市': ['奉贤区南桥镇解放东路200号'] -}; - -/** 运维负责人候选:角色为运维专员/运维助理/运维主管;弹窗选择器展示全部样例人员,区域匹配项优先排序 */ -var VM_OPS_MANAGER_ROLES = ['运维专员', '运维助理', '运维主管']; -/** 联调后对接权限中心;运维负责人编辑权限码 */ -var VM_OPS_MANAGER_EDIT_PERMISSION = 'vehicle:opsManager:edit'; -var VM_OPS_STAFF = [ - { name: '张明辉', role: '运维主管', regions: ['浙江省-嘉兴市'] }, - { name: '魏山', role: '运维专员', regions: ['浙江省-嘉兴市'] }, - { name: '陈高伟', role: '运维主管', regions: ['浙江省-嘉兴市'] }, - { name: '何苗苗', role: '运维助理', regions: ['浙江省-嘉兴市'] }, - { name: '林峰', role: '运维专员', regions: ['浙江省-嘉兴市', '浙江省-湖州市'] }, - { name: '李强', role: '运维专员', regions: ['广东省-广州市'] }, - { name: '王东东', role: '运维主管', regions: ['广东省-广州市'] }, - { name: '周婷', role: '运维助理', regions: ['广东省-广州市'] }, - { name: '尚建华', role: '运维助理', regions: ['上海市-上海市'] }, - { name: '马超', role: '运维主管', regions: ['上海市-上海市'] }, - { name: '刘强', role: '运维专员', regions: ['四川省-成都市'] }, - { name: '孙伟', role: '运维专员', regions: ['浙江省-湖州市'] }, - { name: '赵静', role: '运维专员', regions: ['北京市-北京市'] }, - { name: '吴磊', role: '运维助理', regions: ['广东省-佛山市'] }, - { name: '郑凯', role: '运维主管', regions: ['新疆维吾尔自治区-乌鲁木齐市'] } -]; - -/** 合同编码 → 项目名称(联调后由租赁合同 API 返回;样例用于补全车辆台账) */ -var VM_CONTRACT_PROJECT_BY_NO = { - 'JXGW-GC-23-ZL-611': '乍浦港口氢能运输项目(611)', - 'JXGW-GC-23-ZL-120': '乍浦港口氢能运输项目(120)', - 'JXGW-GC-25-ZL-045': '乍浦港口氢能运输项目(045)', - 'LNZLHT2025110402': '南京威路物流干线运输项目', - 'LNZLHTJX23102701': '浙江氢能产业发展租赁项目', - 'LNGWC20240105': '羚牛公务车项目', - 'JXLN-23-019': '羚牛物流中心项目', - 'LNZLHTSH2023071301': '上海宇速干线租赁项目' -}; - -// 与「保险采购」台账一致;联调后改由证照/保险管理 API 按车辆返回 -var VM_IPC_STORAGE_KEY = 'oneos_ipc_insurance_v1'; -var VM_INSURANCE_SEED = { - '沪A03561F': { - compulsory: { policyNo: 'PDZA202533048200000123', endDate: '2026-12-31' }, - commercial: { policyNo: 'PDAA202533048200000456', endDate: '2026-12-31' } - } -}; -var _vmInsuranceLedgerCache = null; - -function vmNormalizePlateNo(plate) { - return (plate || '').trim().toUpperCase(); -} - -/** 多车牌:优先按行解析,单行内仍支持逗号分隔 */ -function vmParseMultiPlates(text) { - var raw = (text || '').trim(); - if (!raw) return []; - var lines = raw.split(/\r?\n/).map(function (line) { return line.trim(); }).filter(Boolean); - var expanded = []; - var i; - var line; - var parts; - var j; - for (i = 0; i < lines.length; i++) { - line = lines[i]; - if (/[,,、;;]/.test(line)) { - parts = line.split(/[,,、;;]+/); - for (j = 0; j < parts.length; j++) { - if (parts[j].trim()) expanded.push(parts[j].trim()); - } - } else { - expanded.push(line); - } - } - var seen = {}; - var out = []; - for (i = 0; i < expanded.length; i++) { - var key = vmNormalizePlateNo(expanded[i]); - if (key && !seen[key]) { - seen[key] = true; - out.push(key); - } - } - return out; -} - -function vmGetVehicleLedgerKey(record) { - var plate = record && record.plateNo ? String(record.plateNo).trim() : ''; - if (plate && plate !== '-') return plate; - var vin = record && record.vin ? String(record.vin).trim() : ''; - return vin && vin !== '-' ? vin : ''; -} - -function vmGetInsuranceLedger() { - var merged = {}; - var seedKeys = Object.keys(VM_INSURANCE_SEED); - var i; - for (i = 0; i < seedKeys.length; i++) { - merged[seedKeys[i]] = VM_INSURANCE_SEED[seedKeys[i]]; - } - try { - if (typeof localStorage !== 'undefined') { - var raw = localStorage.getItem(VM_IPC_STORAGE_KEY); - if (raw) { - var stored = JSON.parse(raw); - if (stored && typeof stored === 'object') { - Object.keys(stored).forEach(function (k) { - merged[k] = stored[k]; - }); - } - } - } - } catch (e) { /* ignore */ } - return merged; -} - -function vmGetInsuranceLedgerCached() { - if (!_vmInsuranceLedgerCache) _vmInsuranceLedgerCache = vmGetInsuranceLedger(); - return _vmInsuranceLedgerCache; -} - -function vmFormatDateYmd(raw) { - if (vmIsEmptyDisplayValue(raw)) return ''; - var s = String(raw).trim(); - var m = s.match(/^(\d{4})-(\d{2})-(\d{2})/); - if (m) return m[1] + '-' + m[2] + '-' + m[3]; - return s; -} - -/** 距到期日天数:正=剩余,负=已过期 */ -function vmDaysUntilExpire(dateStr) { - var ymd = vmFormatDateYmd(dateStr); - if (!ymd) return null; - var parts = ymd.split('-'); - var y = parseInt(parts[0], 10); - var m = parseInt(parts[1], 10) - 1; - var d = parseInt(parts[2], 10); - if (isNaN(y) || isNaN(m) || isNaN(d)) return null; - var end = new Date(y, m, d, 23, 59, 59, 999); - var today = new Date(); - today.setHours(0, 0, 0, 0); - return Math.ceil((end.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); -} - -function vmRenderExpireRemainTag(dateStr) { - var days = vmDaysUntilExpire(dateStr); - if (days == null) return null; - var Tag = window.antd && window.antd.Tag; - if (!Tag) return null; - var tagProps = { className: 'vm-expire-remain-tag' }; - if (days < 0) { - return React.createElement(Tag, Object.assign({}, tagProps, { - color: 'error', - 'aria-label': '已过期' + Math.abs(days) + '天' - }), '已过期' + Math.abs(days) + '天'); - } - if (days <= 30) { - return React.createElement(Tag, Object.assign({}, tagProps, { - color: 'warning', - 'aria-label': '剩余' + days + '天' - }), '剩余' + days + '天'); - } - return React.createElement(Tag, Object.assign({}, tagProps, { - color: 'success', - 'aria-label': '剩余' + days + '天' - }), '剩余' + days + '天'); -} - -function vmExpireDateOnCell() { - return { className: 'vm-expire-date-td' }; -} - -function vmRenderExpireDateCell(dateRaw, forList) { - var dateText = vmFormatDateYmd(dateRaw); - if (!dateText) return forList ? '' : VM_EMPTY_DISPLAY_LABEL; - return React.createElement('div', { className: 'vm-expire-date-cell' }, - React.createElement('span', { - className: 'vm-oneline-text vm-insurance-expire-date', - title: dateText - }, dateText), - vmRenderExpireRemainTag(dateRaw) - ); -} - -function vmGetInsuranceEndDateFromLedger(ledgerKey, typeKey, ledger) { - var item = ledger[ledgerKey] && ledger[ledgerKey][typeKey]; - if (!item || vmIsEmptyDisplayValue(item.policyNo)) return ''; - return vmFormatDateYmd(item.endDate); -} - -function vmPrototypeInsuranceEndDate(record, typeKey) { - var plate = vmGetVehicleLedgerKey(record); - if (!plate) return ''; - var src = plate + '|' + typeKey; - var h = 0; - var i; - for (i = 0; i < src.length; i++) h = ((h << 5) - h) + src.charCodeAt(i); - h = Math.abs(h); - if (record && record.insuranceStatus === '异常') { - if (typeKey === 'compulsory' && h % 4 === 0) return ''; - if (typeKey === 'commercial' && h % 4 === 1) return ''; - if (h % 4 === 2) { - return vmFormatDateYmd('2025-' + String((h % 12) + 1).padStart(2, '0') + '-' + String((h % 27) + 1).padStart(2, '0')); - } - } - var year = 2026 + (typeKey === 'commercial' ? (h % 2) : 0); - return year + '-' + String((h % 12) + 1).padStart(2, '0') + '-' + String((h % 28) + 1).padStart(2, '0'); -} - -function vmResolveVehicleInsuranceEndDate(record, typeKey) { - var ledgerKey = vmGetVehicleLedgerKey(record); - if (!ledgerKey) return ''; - var fromLedger = vmGetInsuranceEndDateFromLedger(ledgerKey, typeKey, vmGetInsuranceLedgerCached()); - if (fromLedger) return fromLedger; - return vmPrototypeInsuranceEndDate(record, typeKey); -} - -function vmRenderInsuranceExpireDate(record, typeKey, forList) { - var dateText = vmResolveVehicleInsuranceEndDate(record, typeKey); - return vmRenderExpireDateCell(dateText, forList); -} - -/** 外部样式表路径,构建时可在入口设置 window.VM_STYLESHEET_HREF */ -var VM_STYLESHEET_DEFAULT_HREF = 'styles/vehicle-management.css'; - -function vmEnsureStylesheet() { - if (typeof document === 'undefined') return; - var href = (typeof window !== 'undefined' && window.VM_STYLESHEET_HREF) || VM_STYLESHEET_DEFAULT_HREF; - if (document.getElementById('vm-page-stylesheet')) return; - var link = document.createElement('link'); - link.id = 'vm-page-stylesheet'; - link.rel = 'stylesheet'; - link.href = href; - document.head.appendChild(link); -} - -function vmSvgIcon(paths, size) { - return React.createElement('svg', { - viewBox: '0 0 24 24', - width: size || 16, - height: size || 16, - fill: 'none', - stroke: 'currentColor', - strokeWidth: 2, - strokeLinecap: 'round', - strokeLinejoin: 'round', - 'aria-hidden': true - }, paths.map(function (p, i) { - if (p.tag === 'circle') return React.createElement('circle', { key: i, cx: p.cx, cy: p.cy, r: p.r }); - if (p.tag === 'line') return React.createElement('line', { key: i, x1: p.x1, y1: p.y1, x2: p.x2, y2: p.y2 }); - return React.createElement('path', { key: i, d: p.d }); - })); -} - -var VM_ICONS = { - doc: vmSvgIcon([{ d: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z' }, { d: 'M14 2v6h6' }], 14), - search: vmSvgIcon([{ tag: 'circle', cx: 11, cy: 11, r: 8 }, { tag: 'line', x1: 21, y1: 21, x2: 16.65, y2: 16.65 }], 14), - export: vmSvgIcon([{ d: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4' }, { d: 'M7 10l5 5 5-5' }, { tag: 'line', x1: 12, y1: 15, x2: 12, y2: 3 }], 14), - upload: vmSvgIcon([{ d: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4' }, { d: 'M17 8l-5-5-5 5' }, { tag: 'line', x1: 12, y1: 3, x2: 12, y2: 15 }], 14), - back: vmSvgIcon([{ tag: 'line', x1: 19, y1: 12, x2: 5, y2: 12 }, { d: 'M12 19l-7-7 7-7' }], 16), - edit: vmSvgIcon([{ d: 'M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7' }, { d: 'M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z' }], 14), - truck: vmSvgIcon([{ d: 'M1 3h15v13H1zM16 8h4l3 3v5h-7V8z' }, { tag: 'circle', cx: 5.5, cy: 18.5, r: 2.5 }, { tag: 'circle', cx: 18.5, cy: 18.5, r: 2.5 }], 18), - empty: vmSvgIcon([{ tag: 'circle', cx: 12, cy: 12, r: 10 }, { tag: 'line', x1: 8, y1: 12, x2: 16, y2: 12 }], 40) -}; - -var VM_KPI_ICONS = { - all: VM_ICONS.truck, - operating: vmSvgIcon([{ tag: 'circle', cx: 12, cy: 12, r: 10 }, { d: 'M8 12l2 2 4-4' }], 22), - stock: vmSvgIcon([{ d: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z' }, { d: 'M9 22V12h6v10' }], 22), - nonOperating: vmSvgIcon([{ d: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z' }, { tag: 'line', x1: 9, y1: 9, x2: 15, y2: 15 }, { tag: 'line', x1: 15, y1: 9, x2: 9, y2: 15 }], 22), - exit: vmSvgIcon([{ d: 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4' }, { d: 'M16 17l5-5-5-5' }, { tag: 'line', x1: 21, y1: 12, x2: 9, y2: 12 }], 22), - licenseAbnormal: vmSvgIcon([{ d: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z' }, { d: 'M14 2v6h6' }, { tag: 'line', x1: 12, y1: 11, x2: 12, y2: 17 }, { tag: 'line', x1: 12, y1: 8, x2: 12.01, y2: 8 }], 22), - insuranceAbnormal: vmSvgIcon([{ d: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z' }], 22) -}; - -function vmMoreIcon() { - return React.createElement('svg', { viewBox: '0 0 16 16', width: 16, height: 16, fill: 'currentColor', 'aria-hidden': true }, - React.createElement('circle', { cx: 8, cy: 3, r: 1.5 }), - React.createElement('circle', { cx: 8, cy: 8, r: 1.5 }), - React.createElement('circle', { cx: 8, cy: 13, r: 1.5 }) - ); -} - -function vmOperateStatusColor(status) { - if (status === '租赁') return 'processing'; - if (status === '自营') return 'geekblue'; - if (status === '可运营') return 'success'; - if (status === '待运营') return 'warning'; - if (status === '退出运营') return 'default'; - return 'default'; -} - -function vmAlertStatusColor(status) { - if (status === '异常') return 'error'; - if (status === '正常') return 'success'; - return 'default'; -} - -function vmVehicleSourceColor(source) { - if (source === '自有') return 'success'; - if (source === '外租') return 'processing'; - if (source === '挂靠') return 'warning'; - return 'default'; -} - -/** 从 GPS 定位地址解析「省-市」运营城市 */ -function vmFormatOperateCityFromGps(location) { - if (vmIsEmptyDisplayValue(location)) return ''; - var loc = String(location).trim(); - var municipalities = ['北京市', '上海市', '天津市', '重庆市']; - var i; - for (i = 0; i < municipalities.length; i++) { - if (loc.indexOf(municipalities[i]) === 0) { - return municipalities[i] + '-' + municipalities[i]; - } - } - var provMatch = loc.match(/^(.*?(?:省|自治区))/); - if (!provMatch) return ''; - var province = provMatch[1]; - var rest = loc.slice(provMatch[1].length); - var cityMatch = rest.match(/^(.+?市)/); - if (cityMatch) return province + '-' + cityMatch[1]; - cityMatch = rest.match(/^(.+?(?:州|盟|地区))/); - if (cityMatch) return province + '-' + cityMatch[1]; - return province; -} - -function vmPickMockRoadSuffix(record) { - var idNum = parseInt(record && record.id, 10) || 0; - var roads = ['示范路', '兴业大道', '创业路', '新华南路', '中环南路', '解放东路', '科技路']; - return roads[idNum % roads.length] + (100 + idNum % 800) + '号'; -} - -function vmBuildFullLocationAddress(record) { - var loc = vmDisplayFieldText(record && record.location); - if (!loc) return ''; - var cityKey = vmFormatOperateCityFromGps(loc); - var idNum = parseInt(record && record.id, 10) || 0; - var suffixes; - var suffix; - var prefix; - if (cityKey && VM_MOCK_FULL_ADDRESS_SUFFIX_BY_CITY[cityKey]) { - suffixes = VM_MOCK_FULL_ADDRESS_SUFFIX_BY_CITY[cityKey]; - suffix = suffixes[idNum % suffixes.length]; - prefix = cityKey.replace('-', ''); - if (suffix.indexOf(prefix) === 0) return suffix; - return prefix + suffix; - } - if (VM_MOCK_FULL_ADDRESS_BY_PROVINCE[loc]) { - suffixes = VM_MOCK_FULL_ADDRESS_BY_PROVINCE[loc]; - return suffixes[idNum % suffixes.length]; - } - if (/[区县]/.test(loc) && loc.length > 4) { - return loc + vmPickMockRoadSuffix(record); - } - if (cityKey) { - return cityKey.replace('-', '') + '城区' + vmPickMockRoadSuffix(record); - } - return loc + vmPickMockRoadSuffix(record); -} - -function vmResolveLocationFullAddress(record) { - var direct = vmDisplayFieldText(record && record.locationAddress); - if (direct) return direct; - return vmBuildFullLocationAddress(record); -} - -function vmResolveVehicleLedgerType(record) { - return String(record && record.vehicleLedgerType ? record.vehicleLedgerType : '运营车辆').trim(); -} - -function vmIsNonOperatingVehicle(record) { - return vmResolveVehicleLedgerType(record) === '非运营车辆'; -} - -/** 所有营运车辆:排除非运营车辆与退出运营 */ -function vmIsOperatingFleetVehicle(record) { - if (!record) return false; - if (vmIsNonOperatingVehicle(record)) return false; - if (record.operateStatus === '退出运营') return false; - return true; -} - -function vmMatchOperateCityFilter(record, selectedCities) { - if (!selectedCities || !selectedCities.length) return true; - var city = vmFormatOperateCityFromGps(record && record.location); - if (!city) return false; - return selectedCities.indexOf(city) >= 0; -} - -var VM_OPERATE_CITY_SEED = [ - '北京市-北京市', '上海市-上海市', '天津市-天津市', '重庆市-重庆市', - '广东省-广州市', '广东省-深圳市', '广东省-佛山市', '广东省-韶关市', - '浙江省-嘉兴市', '浙江省-杭州市', '浙江省-湖州市', '浙江省-金华市', - '江苏省-常州市', '江苏省-南京市', '江苏省-苏州市', - '四川省-成都市', '河南省-开封市', '陕西省-西安市', - '新疆维吾尔自治区-乌鲁木齐市' -]; - -function vmBuildOperateCityOptions(records) { - var seen = {}; - var opts = []; - var i; - var key; - function add(city) { - if (!city || seen[city]) return; - seen[city] = true; - opts.push({ label: city, value: city }); - } - Object.keys(VM_PARKING_REGION_BY_NAME).forEach(function (name) { - add(VM_PARKING_REGION_BY_NAME[name]); - }); - for (i = 0; i < VM_OPERATE_CITY_SEED.length; i++) { - add(VM_OPERATE_CITY_SEED[i]); - } - if (records && records.length) { - for (i = 0; i < records.length; i++) { - add(vmFormatOperateCityFromGps(records[i].location)); - } - } - opts.sort(function (a, b) { - return String(a.label).localeCompare(String(b.label), 'zh-CN'); - }); - return opts; -} - -function vmBuildProjectNameOptions(records) { - var seen = {}; - var opts = []; - function add(name) { - var text = name ? String(name).trim() : ''; - if (!text || seen[text]) return; - seen[text] = true; - opts.push({ label: text, value: text }); - } - Object.keys(VM_CONTRACT_PROJECT_BY_NO).forEach(function (key) { - add(VM_CONTRACT_PROJECT_BY_NO[key]); - }); - if (records && records.length) { - var i; - for (i = 0; i < records.length; i++) { - add(vmResolveVehicleProjectName(records[i])); - } - } - opts.sort(function (a, b) { - return String(a.label).localeCompare(String(b.label), 'zh-CN'); - }); - return opts; -} - -function vmParseRegionParts(regionText) { - var text = vmIsEmptyDisplayValue(regionText) ? '' : String(regionText).trim(); - if (!text) return { province: '', city: '' }; - var dash = text.indexOf('-'); - if (dash >= 0) { - return { - province: text.slice(0, dash).trim(), - city: text.slice(dash + 1).trim() - }; - } - if (text.indexOf('省') >= 0 || text.indexOf('自治区') >= 0 || text.indexOf('市') === text.length - 1) { - return { province: text, city: '' }; - } - return { province: '', city: text }; -} - -function vmRegionCityMatch(cityA, cityB) { - if (!cityA || !cityB) return false; - return cityA === cityB || cityA.indexOf(cityB) >= 0 || cityB.indexOf(cityA) >= 0; -} - -function vmMatchOpsStaffRegion(staffRegion, parkingRegion) { - var staff = vmParseRegionParts(staffRegion); - var parking = vmParseRegionParts(parkingRegion); - if (!staff.province && !staff.city) return false; - if (!parking.province && !parking.city) return false; - if (staff.province && parking.province && staff.province !== parking.province) return false; - if (staff.city) { - return vmRegionCityMatch(parking.city, staff.city); - } - return staff.province === parking.province; -} - -function vmGetParkingRegionByName(parkingName) { - if (vmIsEmptyDisplayValue(parkingName)) return ''; - return VM_PARKING_REGION_BY_NAME[String(parkingName).trim()] || ''; -} - -function vmEnrichVehicleHandoverRegions(record) { - var next = Object.assign({}, record); - function fillRegion(timeKey, regionKey) { - if (vmIsEmptyDisplayValue(next[timeKey])) return; - if (!vmIsEmptyDisplayValue(next[regionKey])) return; - var parkingRegion = vmGetParkingRegionByName(next.parking); - if (parkingRegion) { - next[regionKey] = parkingRegion; - return; - } - var city = vmFormatOperateCityFromGps(next.location); - if (city) next[regionKey] = city; - } - fillRegion('lastDeliveryTime', 'lastDeliveryRegion'); - fillRegion('lastReturnTime', 'lastReturnRegion'); - return next; -} - -function vmEnrichVehicleLocationAddress(record) { - var next = Object.assign({}, record); - if (!vmIsEmptyDisplayValue(next.location) && vmIsEmptyDisplayValue(next.locationAddress)) { - next.locationAddress = vmBuildFullLocationAddress(next); - } - return next; -} - -function vmPrepareVehicleSampleData() { - var list = []; - var i; - for (i = 0; i < VM_SAMPLE_VEHICLE_DATA.length; i++) { - list.push(vmEnrichVehicleLocationAddress(vmEnrichVehicleHandoverRegions(VM_SAMPLE_VEHICLE_DATA[i]))); - } - return list; -} - -var VM_PREPARED_VEHICLE_DATA = vmPrepareVehicleSampleData(); - -function vmGetAutoOpsManagers(record) { - var parkingRegion = vmGetParkingRegionByName(record && record.parking); - if (!parkingRegion) return []; - var names = []; - var seen = {}; - var i; - var j; - var staff; - var regions; - for (i = 0; i < VM_OPS_STAFF.length; i++) { - staff = VM_OPS_STAFF[i]; - if (VM_OPS_MANAGER_ROLES.indexOf(staff.role) < 0) continue; - regions = staff.regions || []; - for (j = 0; j < regions.length; j++) { - if (vmMatchOpsStaffRegion(regions[j], parkingRegion)) { - if (!seen[staff.name]) { - seen[staff.name] = true; - names.push(staff.name); - } - break; - } - } - } - return names; -} - -function vmResolveVehicleOpsManagers(record) { - if (record && Array.isArray(record.opsManagers)) { - return record.opsManagers.slice(); - } - return vmGetAutoOpsManagers(record); -} - -function vmGetOpsManagerCandidates(record) { - var parkingRegion = vmGetParkingRegionByName(record && record.parking); - if (!parkingRegion) return []; - var list = []; - var seen = {}; - var i; - var j; - var staff; - var regions; - for (i = 0; i < VM_OPS_STAFF.length; i++) { - staff = VM_OPS_STAFF[i]; - if (VM_OPS_MANAGER_ROLES.indexOf(staff.role) < 0) continue; - regions = staff.regions || []; - for (j = 0; j < regions.length; j++) { - if (vmMatchOpsStaffRegion(regions[j], parkingRegion)) { - if (!seen[staff.name]) { - seen[staff.name] = true; - list.push({ name: staff.name, role: staff.role }); - } - break; - } - } - } - return list; -} - -function vmGetAllOpsManagerSelectOptions(record) { - var parkingRegion = vmGetParkingRegionByName(record && record.parking); - var matched = {}; - var list = []; - var i; - var staff; - var candidates = parkingRegion ? vmGetOpsManagerCandidates(record) : []; - for (i = 0; i < candidates.length; i++) { - matched[candidates[i].name] = true; - } - for (i = 0; i < VM_OPS_STAFF.length; i++) { - staff = VM_OPS_STAFF[i]; - if (VM_OPS_MANAGER_ROLES.indexOf(staff.role) < 0) continue; - list.push({ - label: staff.name, - value: staff.name - }); - } - list.sort(function (a, b) { - var am = matched[a.value] ? 0 : 1; - var bm = matched[b.value] ? 0 : 1; - if (am !== bm) return am - bm; - return String(a.value).localeCompare(String(b.value), 'zh-CN'); - }); - return list; -} - -function vmGetAllOpsManagerStaffSelectOptions() { - var list = []; - var i, staff; - for (i = 0; i < VM_OPS_STAFF.length; i++) { - staff = VM_OPS_STAFF[i]; - if (VM_OPS_MANAGER_ROLES.indexOf(staff.role) < 0) continue; - list.push({ - label: staff.name, - value: staff.name - }); - } - list.sort(function (a, b) { - return String(a.value).localeCompare(String(b.value), 'zh-CN'); - }); - return list; -} - -function vmFormatOpsManagersText(record) { - var names = vmResolveVehicleOpsManagers(record); - if (!names.length) return '未分配'; - return names.join(','); -} - -var VM_INFO_TIP_ICON_PATHS = [ - { tag: 'circle', cx: 12, cy: 12, r: 10 }, - { tag: 'line', x1: 12, y1: 16, x2: 12, y2: 12 }, - { tag: 'line', x1: 12, y1: 8, x2: 12.01, y2: 8 } -]; - -var VM_KPI_CARD_DEFS = [ - { key: 'all', type: 'total', title: '所有营运车辆', desc: '营运体系内全部车辆,不含非运营车辆与退出运营车辆' }, - { key: 'operating', type: 'normal', title: '运营中', desc: '运营状态为租赁、自营的车辆(已交车在客户处)' }, - { key: 'stock', type: 'warning', title: '库存', desc: '运营状态为可运营、待运营的车辆(在库车辆)' }, - { key: 'nonOperating', type: 'nonOperating', title: '非运营车辆', desc: '导入车辆类型为「非运营车辆」的台账车辆' }, - { key: 'exit', type: 'unuploaded', title: '退出运营', desc: '运营状态为退出运营的车辆(已退出运营体系)' }, - { key: 'licenseAbnormal', type: 'license', title: '证照异常', desc: '行驶证检验有效期已过期的车辆(不含等评时间)' }, - { key: 'insuranceAbnormal', type: 'insurance', title: '保险异常', desc: '保险状态为异常的车辆(交强险或商业险缺失、已过期或停保/退保)' } -]; - -var VM_STATUS_COLUMN_TIPS = { - operateStatus: '租赁/自营:已交车在客户处;可运营:在库可备车交车;待运营:在库但证照或保险异常禁交车;退出运营:已退出运营体系', - vehicleStatus: '表示车辆是否被业务占位:未备车/已备车/待交车/已交车/待还车等;维修、调拨、异动等占用中不可交车', - licenseStatus: '正常:行驶证检验有效期在有效期内;异常:检验有效期已过期;无:已出库', - insuranceStatus: '正常:交强险、商业险均在有效期内;异常:缺失、过期或停保/退保;无:已退出运营', - outStatus: '无:未出库;三方退租出库/销售出库/报废出库:车辆已离场' -}; - -var VM_MILEAGE_COLUMN_TIP = '显示最后一次交车记录/还车记录/车机里程数;已对接车机时必定展示车机里程(无数据时显示 0 km)'; - -/** 批量导入模板列;表头带 * 为必填(模板中红色显示,仅运营车辆校验) */ -var VM_IMPORT_TEMPLATE_TIP = '非运营车辆不做必填项校验'; - -var VM_IMPORT_TEMPLATE_COLUMNS = [ - { header: '车辆类型', key: 'vehicleLedgerType', required: false, sample: '运营车辆' }, - { header: '车牌号', key: 'plateNo', required: false, sample: '粤A99999F' }, - { header: '车辆识别代码', key: 'vin', required: true, sample: 'LNXNEGRR0SR399999' }, - { header: '品牌', key: 'brand', required: true, sample: '现代' }, - { header: '型号', key: 'model', required: true, sample: '帕力安牌4.5吨冷链车' }, - { header: '停放区域', key: 'parking', required: true, sample: '平湖停车场' }, - { header: '行驶公里数', key: 'mileage', required: false, sample: '12580.50' }, - { header: '登记所有权', key: 'ownership', required: true, sample: '羚牛氢能科技(广东)有限公司' }, - { header: '运营公司', key: 'operateCompany', required: true, sample: '羚牛氢能科技(广东)有限公司' }, - { header: '车辆来源', key: 'vehicleSource', required: true, sample: '自有' }, - { header: '租赁公司', key: 'leaseCompany', required: false, sample: '' }, - { header: '车辆编号', key: 'vehicleNo', required: false, sample: 'DEMO001' }, - { header: '车辆颜色', key: 'color', required: false, sample: '白色' }, - { header: '出厂年份', key: 'year', required: false, sample: '2025' }, - { header: '采购入库时间', key: 'purchaseDate', required: false, sample: '2025-12-24' } -]; - -var VM_VEHICLE_LEDGER_TYPE_ENUM = ['运营车辆', '非运营车辆']; - -var VM_VEHICLE_SOURCE_ENUM = ['挂靠', '外租', '自有']; - -var VM_IMPORT_HEADER_ALIASES = { - '车辆识别代号': 'vin', - VIN: 'vin', - '车身颜色': 'color', - '归属停车场': 'parking', - '停车场': 'parking' -}; - -function vmBuildModelParamBrandMap() { - var map = {}; - var i; - var row; - var brand; - var model; - for (i = 0; i < VM_SAMPLE_VEHICLE_DATA.length; i++) { - row = VM_SAMPLE_VEHICLE_DATA[i]; - brand = row.brand && row.brand !== '-' ? String(row.brand).trim() : ''; - model = row.model && row.model !== '-' ? String(row.model).trim() : ''; - if (!brand) continue; - if (!map[brand]) map[brand] = {}; - if (model) map[brand][model] = true; - } - return map; -} - -var VM_MODEL_PARAM_BRAND_MAP = vmBuildModelParamBrandMap(); - -function vmBuildParkingNameSet() { - var set = {}; - var keys = Object.keys(VM_PARKING_REGION_BY_NAME); - var i; - for (i = 0; i < keys.length; i++) set[keys[i]] = true; - for (i = 0; i < VM_SAMPLE_VEHICLE_DATA.length; i++) { - if (VM_SAMPLE_VEHICLE_DATA[i].parking && VM_SAMPLE_VEHICLE_DATA[i].parking !== '-') { - set[String(VM_SAMPLE_VEHICLE_DATA[i].parking).trim()] = true; - } - } - return set; -} - -var VM_PARKING_NAME_SET = vmBuildParkingNameSet(); - -function vmImportDisplayHeader(col) { - return (col.required ? '*' : '') + col.header; -} - -function vmEscapeCsvCell(val) { - var s = val == null ? '' : String(val); - return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s; -} - -function vmParseCsvLine(line) { - var result = []; - var cur = ''; - var inQuote = false; - var i; - var c; - for (i = 0; i < line.length; i++) { - c = line[i]; - if (c === '"') { - inQuote = !inQuote; - continue; - } - if (c === ',' && !inQuote) { - result.push(cur.trim()); - cur = ''; - continue; - } - cur += c; - } - result.push(cur.trim()); - return result; -} - -function vmNormalizeImportHeaderKey(header) { - var h = String(header || '').trim().replace(/^\uFEFF/, '').replace(/^\*/, ''); - var col = null; - var i; - for (i = 0; i < VM_IMPORT_TEMPLATE_COLUMNS.length; i++) { - if (VM_IMPORT_TEMPLATE_COLUMNS[i].header === h) { - col = VM_IMPORT_TEMPLATE_COLUMNS[i]; - break; - } - } - if (col) return col.key; - return VM_IMPORT_HEADER_ALIASES[h] || null; -} - -function vmValidateVinCode(vin, required) { - var s = String(vin || '').trim().toUpperCase(); - if (!s) { - if (required) return { ok: false, reason: '车辆识别代码不能为空' }; - return { ok: true, value: '' }; - } - if (s.length !== 17) return { ok: false, reason: '车辆识别代码须为17位' }; - if (!/^[A-HJ-NPR-Z0-9]{17}$/.test(s)) return { ok: false, reason: '车辆识别代码格式不正确' }; - return { ok: true, value: s }; -} - -function vmIsOperatingImportRow(vehicleLedgerType) { - return String(vehicleLedgerType || '').trim() === '运营车辆'; -} - -function vmIsImportTipLine(firstCell) { - var text = String(firstCell || '').trim(); - return text.indexOf(VM_IMPORT_TEMPLATE_TIP) >= 0 - || text.indexOf('车辆来源枚举') === 0 - || text.indexOf('车辆类型枚举') >= 0 - || text.indexOf('带*为必填') >= 0; -} - -function vmValidateImportMileage(value) { - var s = String(value || '').trim(); - if (!s) return { ok: true, value: '' }; - if (!/^\d+(\.\d{1,2})?$/.test(s)) return { ok: false, reason: '行驶公里数须为整数或最多两位小数' }; - return { ok: true, value: s }; -} - -function vmValidateImportYear(value) { - var s = String(value || '').trim(); - if (!s) return { ok: true, value: '' }; - if (!/^\d{4}$/.test(s)) return { ok: false, reason: '出厂年份格式须为YYYY' }; - var yearNum = Number(s); - var currentYear = new Date().getFullYear(); - if (yearNum > currentYear) return { ok: false, reason: '出厂年份不能晚于当前年份' }; - return { ok: true, value: s }; -} - -function vmValidateImportPurchaseDate(value) { - var s = String(value || '').trim(); - if (!s) return { ok: true, value: '' }; - if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return { ok: false, reason: '采购入库时间格式须为YYYY-MM-DD' }; - var parts = s.split('-'); - var dt = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2])); - if (isNaN(dt.getTime()) || dt.getFullYear() !== Number(parts[0]) || dt.getMonth() !== Number(parts[1]) - 1 || dt.getDate() !== Number(parts[2])) { - return { ok: false, reason: '采购入库时间日期无效' }; - } - var today = new Date(); - today.setHours(0, 0, 0, 0); - if (dt.getTime() > today.getTime()) return { ok: false, reason: '采购入库时间不能晚于当前日期' }; - return { ok: true, value: s }; -} - -function vmValidateImportRow(row, rowIndex, context) { - var rowNo = rowIndex + 1; - var fail = function (reason) { - return { ok: false, reason: reason, rowNo: rowNo, row: row }; - }; - var vehicleLedgerType = String(row.vehicleLedgerType || '').trim(); - if (!vehicleLedgerType) return fail('车辆类型不能为空'); - if (VM_VEHICLE_LEDGER_TYPE_ENUM.indexOf(vehicleLedgerType) < 0) return fail('车辆类型须为运营车辆或非运营车辆'); - var requireFields = vmIsOperatingImportRow(vehicleLedgerType); - var vinResult = vmValidateVinCode(row.vin, requireFields); - if (!vinResult.ok) return fail(vinResult.reason); - var brand = String(row.brand || '').trim(); - var model = String(row.model || '').trim(); - var parking = String(row.parking || '').trim(); - var ownership = String(row.ownership || '').trim(); - var operateCompany = String(row.operateCompany || '').trim(); - var vehicleSource = String(row.vehicleSource || '').trim(); - var plateNo = String(row.plateNo || '').trim(); - if (requireFields && !brand) return fail('品牌不能为空'); - if (requireFields && !model) return fail('型号不能为空'); - if (requireFields && !parking) return fail('停放区域不能为空'); - if (requireFields && !ownership) return fail('登记所有权不能为空'); - if (requireFields && !operateCompany) return fail('运营公司不能为空'); - if (requireFields && !vehicleSource) return fail('车辆来源不能为空'); - if (vehicleSource && VM_VEHICLE_SOURCE_ENUM.indexOf(vehicleSource) < 0) return fail('车辆来源须为挂靠、外租或自有'); - if (brand && !VM_MODEL_PARAM_BRAND_MAP[brand]) return fail('品牌不在型号参数表中'); - if (brand && model && !VM_MODEL_PARAM_BRAND_MAP[brand][model]) return fail('型号与品牌不匹配或不在型号参数表中'); - if (parking && !VM_PARKING_NAME_SET[parking]) return fail('停放区域不在停车场名称台账中'); - var mileageResult = vmValidateImportMileage(row.mileage); - if (!mileageResult.ok) return fail(mileageResult.reason); - var yearResult = vmValidateImportYear(row.year); - if (!yearResult.ok) return fail(yearResult.reason); - var purchaseResult = vmValidateImportPurchaseDate(row.purchaseDate); - if (!purchaseResult.ok) return fail(purchaseResult.reason); - if (plateNo && context.plateSeen[plateNo]) return fail('车牌号在导入文件中重复'); - if (vinResult.value && context.vinSeen[vinResult.value]) return fail('车辆识别代码在导入文件中重复'); - if (plateNo && context.existingPlates[plateNo]) return fail('车牌号已存在于车辆台账'); - if (vinResult.value && context.existingVins[vinResult.value]) return fail('车辆识别代码已存在于车辆台账'); - if (plateNo) context.plateSeen[plateNo] = true; - if (vinResult.value) context.vinSeen[vinResult.value] = true; - return { - ok: true, - value: { - vehicleLedgerType: vehicleLedgerType, - plateNo: plateNo, - vin: vinResult.value, - brand: brand, - model: model, - parking: parking, - mileage: mileageResult.value, - ownership: ownership, - operateCompany: operateCompany, - vehicleSource: vehicleSource, - leaseCompany: String(row.leaseCompany || '').trim(), - vehicleNo: String(row.vehicleNo || '').trim(), - color: String(row.color || '').trim(), - year: yearResult.value, - purchaseDate: purchaseResult.value - } - }; -} - -function vmParseVehicleImportText(text) { - var lines = String(text || '').split(/\r?\n/).map(function (l) { return l.trim(); }).filter(Boolean); - if (!lines.length) return []; - var headerLineIndex = 0; - var i; - for (i = 0; i < lines.length; i++) { - var probeCells = vmParseCsvLine(lines[i]); - if (vmIsImportTipLine(probeCells[0])) continue; - var matched = 0; - var j; - for (j = 0; j < probeCells.length; j++) { - if (vmNormalizeImportHeaderKey(probeCells[j])) matched++; - } - if (matched >= 3) { - headerLineIndex = i; - break; - } - } - var headerCells = vmParseCsvLine(lines[headerLineIndex]); - var colIndex = {}; - var key; - for (i = 0; i < headerCells.length; i++) { - key = vmNormalizeImportHeaderKey(headerCells[i]); - if (key) colIndex[key] = i; - } - var hasHeader = Object.keys(colIndex).length >= 3; - var dataLines = hasHeader ? lines.slice(headerLineIndex + 1) : lines.slice(headerLineIndex); - var fallbackIndexByKey = {}; - for (i = 0; i < VM_IMPORT_TEMPLATE_COLUMNS.length; i++) { - fallbackIndexByKey[VM_IMPORT_TEMPLATE_COLUMNS[i].key] = i; - } - var pick = function (cells, fieldKey) { - if (hasHeader && colIndex[fieldKey] != null) return (cells[colIndex[fieldKey]] || '').trim(); - var idx = fallbackIndexByKey[fieldKey]; - return idx != null ? (cells[idx] || '').trim() : ''; - }; - var rows = []; - for (i = 0; i < dataLines.length; i++) { - var cells = vmParseCsvLine(dataLines[i]); - if (!cells.some(function (c) { return c; })) continue; - var firstCell = (cells[0] || '').trim(); - if (vmIsImportTipLine(firstCell)) continue; - var row = {}; - for (var j = 0; j < VM_IMPORT_TEMPLATE_COLUMNS.length; j++) { - row[VM_IMPORT_TEMPLATE_COLUMNS[j].key] = pick(cells, VM_IMPORT_TEMPLATE_COLUMNS[j].key); - } - rows.push(row); - } - return rows; -} - -function vmValidateVehicleImportRows(rows, existingList) { - var context = { - plateSeen: {}, - vinSeen: {}, - existingPlates: {}, - existingVins: {} - }; - var i; - var item; - for (i = 0; i < (existingList || []).length; i++) { - item = existingList[i]; - if (item.plateNo && item.plateNo !== '-') context.existingPlates[String(item.plateNo).trim()] = true; - if (item.vin && item.vin !== '-') context.existingVins[String(item.vin).trim().toUpperCase()] = true; - } - var failedList = []; - var accepted = []; - for (i = 0; i < (rows || []).length; i++) { - var result = vmValidateImportRow(rows[i], i, context); - if (!result.ok) { - failedList.push({ - rowNo: result.rowNo, - plateNo: rows[i].plateNo || '-', - vin: rows[i].vin || '-', - reason: result.reason - }); - continue; - } - accepted.push(result.value); - } - if (failedList.length) return { ok: false, failedList: failedList, accepted: accepted }; - return { ok: true, accepted: accepted, failedList: [] }; -} - -function vmMapImportRowToVehicleRecord(row, index) { - return { - id: 'imp-' + Date.now() + '-' + index, - vehicleLedgerType: row.vehicleLedgerType || '运营车辆', - plateNo: row.plateNo || '-', - vin: row.vin || '-', - vehicleNo: row.vehicleNo || '-', - color: row.color || '-', - year: row.year || '-', - purchaseDate: row.purchaseDate || '-', - parking: row.parking || '-', - ownership: row.ownership || '-', - scrapDate: '-', - ratingTime: '-', - operateCompany: row.operateCompany || '-', - vehicleSource: row.vehicleSource || '-', - leaseCompany: row.leaseCompany || '-', - vehicleType: '-', - brand: row.brand || '-', - model: row.model || '-', - customer: '-', - department: '-', - manager: '-', - contractNo: '-', - operateStatus: '待运营', - vehicleStatus: '未备车', - licenseStatus: '正常', - insuranceStatus: '正常', - mileage: row.mileage || '-', - location: '-', - gpsTime: '-', - regDate: '-', - inspectExpire: '-', - lastDeliveryTime: '-', - lastDeliveryMile: '-', - lastReturnTime: '-', - lastReturnMile: '-', - outStatus: '无', - onlineStatus: '离线', - projectName: '-' - }; -} - -function vmDownloadVehicleImportTemplate() { - var cols = VM_IMPORT_TEMPLATE_COLUMNS; - var headerCells = cols.map(function (col) { - var label = vmImportDisplayHeader(col); - var style = col.required ? 'color:#dc2626;font-weight:bold;' : 'font-weight:bold;'; - return '' + label + ''; - }).join(''); - var sampleCells = cols.map(function (col) { - return '' + (col.sample || '') + ''; - }).join(''); - var tipRow = '' + VM_IMPORT_TEMPLATE_TIP + ''; - var html = '' + tipRow + '' + headerCells + '' + sampleCells + '
'; - var blob = new Blob(['\uFEFF' + html], { type: 'application/vnd.ms-excel;charset=utf-8' }); - var url = URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = url; - a.download = '车辆导入模板.xls'; - a.click(); - URL.revokeObjectURL(url); -} - -function vmMatchKpiCategory(record, kpiKey) { - if (!record) return false; - var status = record.operateStatus; - if (kpiKey === 'all') return vmIsOperatingFleetVehicle(record); - if (kpiKey === 'operating') return status === '租赁' || status === '自营'; - if (kpiKey === 'stock') return status === '可运营' || status === '待运营'; - if (kpiKey === 'nonOperating') return vmIsNonOperatingVehicle(record); - if (kpiKey === 'exit') return status === '退出运营'; - if (kpiKey === 'licenseAbnormal') return vmIsLicenseExpiredByInspectExpire(record); - if (kpiKey === 'insuranceAbnormal') return record.insuranceStatus === '异常'; - return true; -} - -function vmCountKpiCategory(list, kpiKey) { - var count = 0; - var i; - for (i = 0; i < list.length; i++) { - if (vmMatchKpiCategory(list[i], kpiKey)) count++; - } - return count; -} - -function vmIsLicenseExpiredByInspectExpire(record) { - if (!record || vmIsEmptyDisplayValue(record.inspectExpire)) return false; - var days = vmDaysUntilExpire(record.inspectExpire); - if (days == null) return false; - return days < 0; -} - -function vmIsInsuranceExpiredByDate(dateStr) { - var days = vmDaysUntilExpire(dateStr); - if (days == null) return true; - return days < 0; -} - -function vmGetInsuranceAbnormalTooltip(record) { - if (!record) return ''; - var compulsoryExpired = vmIsInsuranceExpiredByDate(vmResolveVehicleInsuranceEndDate(record, 'compulsory')); - var commercialExpired = vmIsInsuranceExpiredByDate(vmResolveVehicleInsuranceEndDate(record, 'commercial')); - var parts = []; - if (compulsoryExpired) parts.push('交强险已到期'); - if (commercialExpired) parts.push('商业险已到期'); - return parts.length ? parts.join('/') : ''; -} - -function vmGetLicenseAbnormalTooltip(record) { - if (!record) return ''; - if (vmIsEmptyDisplayValue(record.inspectExpire)) return ''; - var days = vmDaysUntilExpire(record.inspectExpire); - if (days == null) return ''; - if (days < 0) return '行驶证检验有效期已过期'; - return ''; -} - -function vmIsEmptyDisplayValue(val) { - if (val === null || val === undefined) return true; - var s = String(val).trim(); - return !s || s === '-'; -} - -var VM_EMPTY_DISPLAY_LABEL = '无'; - -function vmDisplayUILabel(val) { - if (vmIsEmptyDisplayValue(val)) return VM_EMPTY_DISPLAY_LABEL; - return String(val).trim(); -} - -/** 列表页空值展示为空白(详情/表单仍用 vmDisplayUILabel 显示「无」) */ -function vmDisplayListLabel(val) { - if (vmIsEmptyDisplayValue(val)) return ''; - return String(val).trim(); -} - -function vmFormatBrandModel(brand, model) { - var b = vmIsEmptyDisplayValue(brand) ? '' : String(brand).trim(); - var m = vmIsEmptyDisplayValue(model) ? '' : String(model).trim(); - if (b && m) return b + '-' + m; - return b || m || ''; -} - -function vmRenderVehicleIdentityCell(record, onViewDetail) { - var plateRaw = record && record.plateNo; - var vinRaw = record && record.vin; - var plateDisplay = vmDisplayListLabel(plateRaw); - var vinDisplay = vmDisplayListLabel(vinRaw); - var brandModel = vmFormatBrandModel(record && record.brand, record && record.model); - var plateEl = !vmIsEmptyDisplayValue(plateRaw) && onViewDetail - ? React.createElement('button', { - type: 'button', - className: 'vm-plate-link vm-vehicle-identity-plate', - onClick: function (e) { e.stopPropagation(); onViewDetail(record); }, - title: '查看车辆详情' - }, plateDisplay) - : React.createElement('div', { className: 'vm-vehicle-identity-plate' }, plateDisplay); - var vinEl = !vmIsEmptyDisplayValue(vinRaw) && onViewDetail - ? React.createElement('button', { - type: 'button', - className: 'vm-plate-link vm-vehicle-identity-vin', - onClick: function (e) { e.stopPropagation(); onViewDetail(record); }, - title: '查看车辆详情' - }, vinDisplay) - : React.createElement('div', { className: 'vm-vehicle-identity-vin', title: vinDisplay || undefined }, vinDisplay); - return React.createElement('div', { className: 'vm-vehicle-identity-cell' }, - plateEl, - vinEl, - brandModel ? React.createElement('div', { className: 'vm-vehicle-identity-brand' }, brandModel) : null - ); -} - -function vmRenderOnlineStatusCell(text) { - if (vmIsEmptyDisplayValue(text)) return null; - var on = text === '在线'; - return React.createElement('span', { className: 'vm-online-status vm-online-status--' + (on ? 'on' : 'off') }, - React.createElement('span', { className: 'vm-online-dot', 'aria-hidden': true }), - React.createElement('span', { className: 'vm-online-label' }, text) - ); -} - -function vmRenderGpsLocationCell(record) { - var addressText = vmResolveLocationFullAddress(record) || vmDisplayFieldText(record && record.location); - var addressDisplay = vmDisplayListLabel(addressText); - var gpsDisplay = vmDisplayListLabel(record && record.gpsTime); - var onlineStatus = record && record.onlineStatus; - var hasOnline = !vmIsEmptyDisplayValue(onlineStatus); - if (!hasOnline && !addressDisplay && !gpsDisplay) { - return ''; - } - return React.createElement('div', { className: 'vm-gps-location-cell' }, - hasOnline ? React.createElement('div', { className: 'vm-gps-location-row vm-gps-location-row--status' }, - vmRenderOnlineStatusCell(onlineStatus) - ) : null, - React.createElement('div', { className: 'vm-gps-location-row vm-gps-location-row--address' }, - React.createElement('span', { - className: 'vm-gps-location-text', - title: addressDisplay || undefined - }, addressDisplay) - ), - React.createElement('div', { className: 'vm-gps-location-row vm-gps-location-row--time' }, - React.createElement('span', { - className: 'vm-gps-location-time', - title: gpsDisplay || undefined - }, gpsDisplay) - ) - ); -} - -function vmDisplayFieldText(val) { - if (vmIsEmptyDisplayValue(val)) return ''; - return String(val).trim(); -} - -function vmRenderOneLineText(text) { - var display = vmDisplayListLabel(text); - return React.createElement('span', { - className: 'vm-oneline-text', - title: display || undefined - }, display); -} - -function vmHasVehicleTerminal(record) { - if (!record) return false; - if (record.hasVehicleTerminal === true) return true; - if (record.hasVehicleTerminal === false) return false; - return !vmIsEmptyDisplayValue(record.gpsTime); -} - -function vmParseRecordDateTime(dateStr) { - if (vmIsEmptyDisplayValue(dateStr)) return null; - var t = Date.parse(String(dateStr).trim().replace(' ', 'T')); - return isNaN(t) ? null : t; -} - -function vmResolveHandoverMileage(record) { - if (!record) return { value: '', sourceKey: '' }; - var deliveryTime = vmParseRecordDateTime(record.lastDeliveryTime); - var returnTime = vmParseRecordDateTime(record.lastReturnTime); - var deliveryMile = vmDisplayFieldText(record.lastDeliveryMile); - var returnMile = vmDisplayFieldText(record.lastReturnMile); - if (deliveryTime != null && returnTime != null) { - if (returnTime > deliveryTime) return { value: returnMile, sourceKey: 'lastReturn' }; - if (deliveryTime > returnTime) return { value: deliveryMile, sourceKey: 'lastDelivery' }; - if (returnMile) return { value: returnMile, sourceKey: 'lastReturn' }; - if (deliveryMile) return { value: deliveryMile, sourceKey: 'lastDelivery' }; - return { value: '', sourceKey: '' }; - } - if (deliveryTime != null) return { value: deliveryMile, sourceKey: 'lastDelivery' }; - if (returnTime != null) return { value: returnMile, sourceKey: 'lastReturn' }; - return { value: '', sourceKey: '' }; -} - -function vmGetTelematicsMileageValue(record) { - return vmDisplayFieldText(record && record.mileage); -} - -function vmFormatMileageDisplay(mile, options) { - var forceShow = options && options.forceShow; - var val = vmDisplayFieldText(mile); - if (!val) return forceShow ? '0 km' : ''; - return val + ' km'; -} - -function vmResolveVehicleMileage(record) { - if (!record) return { value: '', sourceKey: '' }; - if (vmHasVehicleTerminal(record)) { - return { value: vmGetTelematicsMileageValue(record), sourceKey: 'telematics' }; - } - return vmResolveHandoverMileage(record); -} - -function vmMileageSourceLabel(sourceKey) { - if (sourceKey === 'telematics') return '车机'; - if (sourceKey === 'lastDelivery') return '最后一次交车'; - if (sourceKey === 'lastReturn') return '最后一次还车'; - return ''; -} - -function vmMileageSourceTagColor(sourceKey) { - if (sourceKey === 'telematics') return 'processing'; - if (sourceKey === 'lastDelivery') return 'blue'; - if (sourceKey === 'lastReturn') return 'purple'; - return 'default'; -} - -function vmRenderMileageCell(record, forList) { - var resolved = vmResolveVehicleMileage(record); - if (!resolved.sourceKey) return forList ? '' : VM_EMPTY_DISPLAY_LABEL; - var isTelematics = resolved.sourceKey === 'telematics'; - var display = resolved.value; - if (!display && !isTelematics) return forList ? '' : VM_EMPTY_DISPLAY_LABEL; - var label = vmMileageSourceLabel(resolved.sourceKey); - var mileText = vmFormatMileageDisplay(display, { forceShow: isTelematics }); - var Tag = window.antd && window.antd.Tag; - var title = label ? (mileText + '(' + label + ')') : mileText; - return React.createElement('div', { className: 'vm-mileage-cell', title: title }, - (isTelematics || mileText) ? React.createElement('div', { className: 'vm-mileage-value', title: mileText }, mileText) : null, - label && Tag ? React.createElement(Tag, { - className: 'vm-mileage-source-tag', - color: vmMileageSourceTagColor(resolved.sourceKey) - }, label) : null - ); -} - -function vmSplitDateTimeParts(dateStr) { - if (vmIsEmptyDisplayValue(dateStr)) return { date: '', time: '' }; - var s = String(dateStr).trim(); - var parts = s.split(/\s+/); - if (parts.length >= 2) { - var clock = parts[1]; - if (clock.length >= 5) clock = clock.slice(0, 5); - return { date: parts[0], time: clock }; - } - if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return { date: s, time: '' }; - return { date: s, time: '' }; -} - -function vmFormatHandoverMileageText(mile) { - return vmFormatMileageDisplay(mile, { forceShow: false }); -} - -function vmFormatHandoverDateTimeOneLine(dateStr) { - if (vmIsEmptyDisplayValue(dateStr)) return ''; - var parts = vmSplitDateTimeParts(dateStr); - if (!parts.date && !parts.time) return ''; - if (parts.date && parts.time) return parts.date + ' ' + parts.time; - return parts.date || parts.time; -} - -function vmResolveHandoverRecordRegion(record, regionKey, timeKey) { - if (vmIsEmptyDisplayValue(record && record[timeKey])) return ''; - var direct = record && record[regionKey]; - if (!vmIsEmptyDisplayValue(direct)) return String(direct).trim(); - return ''; -} - -function vmRenderHandoverSituationCell(mile, time, region) { - var mileText = vmFormatHandoverMileageText(mile); - var regionText = vmIsEmptyDisplayValue(region) ? '' : String(region).trim(); - var dateTimeText = vmFormatHandoverDateTimeOneLine(time); - if (!mileText && !regionText && !dateTimeText) return ''; - var fullTime = vmDisplayFieldText(time); - var titleParts = [mileText, regionText, dateTimeText || fullTime].filter(Boolean); - return React.createElement('div', { className: 'vm-handover-situation-cell', title: titleParts.join(' / ') || undefined }, - mileText ? React.createElement('div', { className: 'vm-handover-situation-mile', title: mileText }, mileText) : null, - regionText ? React.createElement('div', { className: 'vm-handover-situation-region', title: regionText }, regionText) : null, - dateTimeText ? React.createElement('div', { className: 'vm-handover-situation-datetime', title: dateTimeText }, dateTimeText) : null - ); -} - -function vmRenderLastDeliverySituationCell(record) { - if (!record) return null; - return vmRenderHandoverSituationCell( - record.lastDeliveryMile, - record.lastDeliveryTime, - vmResolveHandoverRecordRegion(record, 'lastDeliveryRegion', 'lastDeliveryTime') - ); -} - -function vmRenderLastReturnSituationCell(record) { - if (!record) return null; - return vmRenderHandoverSituationCell( - record.lastReturnMile, - record.lastReturnTime, - vmResolveHandoverRecordRegion(record, 'lastReturnRegion', 'lastReturnTime') - ); -} - -function vmRenderDetailDateValue(dateRaw) { - var text = vmFormatDateYmd(dateRaw) || vmDisplayFieldText(dateRaw); - return text ? text : VM_EMPTY_DISPLAY_LABEL; -} - -function vmRenderDetailExpireValue(dateRaw) { - var node = vmRenderExpireDateCell(dateRaw); - if (node && typeof node === 'object' && node.$$typeof) return node; - return VM_EMPTY_DISPLAY_LABEL; -} - -function vmRenderDetailQuickStat(label, value) { - if (value && typeof value === 'object' && value.$$typeof) { - return React.createElement('div', { className: 'vm-detail-stat-card' }, - React.createElement('div', { className: 'vm-detail-stat-label' }, label), - React.createElement('div', { className: 'vm-detail-stat-value vm-detail-stat-value--rich' }, value) - ); - } - var text = vmDisplayUILabel(value); - return React.createElement('div', { className: 'vm-detail-stat-card' }, - React.createElement('div', { className: 'vm-detail-stat-label' }, label), - React.createElement('div', { className: 'vm-detail-stat-value', title: text === VM_EMPTY_DISPLAY_LABEL ? undefined : text }, text) - ); -} - -function vmRenderSalespersonCell(record) { - var department = vmDisplayListLabel(record && record.department); - var salesperson = vmDisplayListLabel(record && record.manager); - return React.createElement('div', { className: 'vm-stacked-cell' }, - React.createElement('div', { - className: 'vm-stacked-cell-line vm-stacked-cell-line--primary', - title: department || undefined - }, department), - React.createElement('div', { - className: 'vm-stacked-cell-line vm-stacked-cell-line--sub', - title: salesperson || undefined - }, salesperson) - ); -} - -function vmNormalizeContractNo(contractNo) { - if (vmIsEmptyDisplayValue(contractNo)) return ''; - return String(contractNo).replace(/\s+/g, '').trim(); -} - -function vmDeriveProjectNameFromContract(record) { - var customer = vmDisplayFieldText(record && record.customer); - var operateStatus = record && record.operateStatus; - if (!customer) return ''; - var shortName = customer - .replace(/(个体工商户)/g, '') - .replace(/\(个体工商户\)/g, '') - .replace(/股份有限公司/g, '') - .replace(/有限责任公司/g, '') - .replace(/有限公司/g, '') - .trim(); - if (!shortName) shortName = customer; - if (/租赁|运输|物流|港口|快递|供应链|氢能|公务车/.test(shortName)) { - return operateStatus === '自营' ? shortName + '(自营)' : shortName; - } - return shortName + (operateStatus === '自营' ? '自营项目' : '租赁项目'); -} - -function vmResolveVehicleProjectName(record) { - var direct = vmDisplayFieldText(record && record.projectName); - if (direct) return direct; - var contractKey = vmNormalizeContractNo(record && record.contractNo); - if (!contractKey) return ''; - if (VM_CONTRACT_PROJECT_BY_NO[contractKey]) return VM_CONTRACT_PROJECT_BY_NO[contractKey]; - return vmDeriveProjectNameFromContract(record); -} - -function vmRenderProjectCell(record) { - var customer = vmDisplayListLabel(record && record.customer); - var contractCode = vmDisplayListLabel(record && record.contractNo); - var projectName = vmDisplayListLabel(vmResolveVehicleProjectName(record)); - return React.createElement('div', { className: 'vm-stacked-cell' }, - React.createElement('div', { - className: 'vm-stacked-cell-line vm-stacked-cell-line--primary', - title: customer || undefined - }, customer), - React.createElement('div', { - className: 'vm-stacked-cell-line vm-stacked-cell-line--code', - title: contractCode || undefined - }, contractCode), - React.createElement('div', { - className: 'vm-stacked-cell-line vm-stacked-cell-line--sub', - title: projectName || undefined - }, projectName) - ); -} - -const Component = function () { - var useState = React.useState; - var useCallback = React.useCallback; - var antd = window.antd; - - var Cascader = antd.Cascader; - var Select = antd.Select; - var Input = antd.Input; - var Button = antd.Button; - var Table = antd.Table; - var Space = antd.Space; - var Dropdown = antd.Dropdown; - var Modal = antd.Modal; - var Upload = antd.Upload; - var Card = antd.Card; - var Tabs = antd.Tabs; - var Tag = antd.Tag; - var Tooltip = antd.Tooltip; - var Popover = antd.Popover; - var Form = antd.Form; - var Row = antd.Row; - var Col = antd.Col; - var message = antd.message; - var App = antd.App; - - // 联调后对接权限中心;原型默认 admin 可编辑车辆,permissions 含 vehicle:opsManager:edit 可编辑运维负责人 - var CURRENT_USER = { id: 'u_admin', name: '系统管理员', role: 'admin', permissions: [VM_OPS_MANAGER_EDIT_PERMISSION] }; - var isAdmin = CURRENT_USER.role === 'admin'; - var canEditOpsManager = (CURRENT_USER.permissions || []).indexOf(VM_OPS_MANAGER_EDIT_PERMISSION) >= 0; - - // 筛选项状态 - var _plateNosPending = useState(''); - var _plateNosApplied = useState(''); - var _multiPlateOpen = useState(false); - var _multiPlateDraft = useState(''); - var _operateCityFilter = useState([]); - var _brand = useState(undefined); - var _model = useState(undefined); - var _customer = useState(undefined); - var _department = useState(undefined); - var _projectNameFilter = useState(undefined); - var _contractNo = useState(undefined); - var _ownership = useState(undefined); - var _operateCompany = useState(undefined); - var _operateStatusFilter = useState(undefined); - var _vehicleSource = useState(undefined); - var _leaseCompany = useState(undefined); - var _commercialInsurance = useState(undefined); - var _compulsoryInsurance = useState(undefined); - var _parkingFilter = useState(undefined); - var _areaRegion = useState([]); - - var _uploadModalVisible = useState(false); - var _confirmModalVisible = useState(false); - var _ocrLoadingVisible = useState(false); - var _currentRow = useState(null); - var _plateForm = useState({ vin: '', plateNo: '' }); - var _plateError = useState(''); - var _detailRecord = useState(null); - var _detailTab = useState('基本信息'); - var _filterExpanded = useState(false); - var _requirementModalVisible = useState(false); - var _editModalVisible = useState(false); - var _editRecord = useState(null); - var _editForm = useState({}); - var _kpiTab = useState('all'); - var _opsManagerModalMode = useState(null); - var _opsManagerModalRecord = useState(null); - var _opsManagerBatchIds = useState([]); - var _opsManagerDraftSelected = useState([]); - var _batchImportModalVisible = useState(false); - var _batchImportResult = useState(null); - - // 省-市 地区数据(定位城市筛选用) - var regionOptions = [ - { value: 'guangdong', label: '广东省', children: [{ value: 'guangzhou', label: '广州市' }, { value: 'shenzhen', label: '深圳市' }] }, - { value: 'beijing', label: '北京市', children: [{ value: 'beijing', label: '北京市' }] }, - { value: 'shanghai', label: '上海市', children: [{ value: 'shanghai', label: '上海市' }] } - ]; - - // 品牌、型号、客户、部门、合同、登记所有权(模拟下拉数据) - var brandOptions = [{ label: '比亚迪', value: 'byd' }, { label: '特斯拉', value: 'tsl' }, { label: '蔚来', value: 'nio' }]; - var modelOptions = [{ label: '汉EV', value: 'han' }, { label: 'Model 3', value: 'm3' }, { label: 'ET5', value: 'et5' }]; - var customerOptions = [{ label: '无', value: 'none' }, { label: '客户A', value: 'c1' }, { label: '客户B', value: 'c2' }]; - var departmentOptions = [{ label: '无', value: 'none' }, { label: '华南区', value: 'd1' }, { label: '华东区', value: 'd2' }]; - var contractOptions = [{ label: 'HT-2024-001', value: 'HT-2024-001' }, { label: 'HT-2024-002', value: 'HT-2024-002' }]; - var ownershipOptions = [{ label: '某某租赁公司', value: 'o1' }, { label: '某某科技有限公司', value: 'o2' }]; - var operateCompanyOptions = [ - { label: '羚牛运营(嘉兴)', value: '羚牛运营(嘉兴)' }, - { label: '羚牛运营(上海)', value: '羚牛运营(上海)' }, - { label: '羚牛运营(广东)', value: '羚牛运营(广东)' } - ]; - var vehicleSourceOptions = [ - { label: '自有', value: '自有' }, - { label: '外租', value: '外租' }, - { label: '挂靠', value: '挂靠' } - ]; - var leaseCompanyOptions = [ - { label: '某某租赁公司', value: '某某租赁公司' }, - { label: '某某科技有限公司', value: '某某科技有限公司' }, - { label: '第三方融资租赁有限公司', value: '第三方融资租赁有限公司' }, - { label: '无', value: '-' } - ]; - var insuranceStatusOptions = [ - { label: '正常', value: '正常' }, - { label: '异常', value: '异常' } - ]; - var parkingOptions = [ - { label: '天河停车场', value: '天河停车场' }, - { label: '黄埔停车场', value: '黄埔停车场' }, - { label: '朝阳停车场', value: '朝阳停车场' }, - { label: '福田停车场', value: '福田停车场' }, - { label: '浦东停车场', value: '浦东停车场' }, - { label: '南山停车场', value: '南山停车场' }, - { label: '番禺停车场', value: '番禺停车场' }, - { label: '大兴停车场', value: '大兴停车场' }, - { label: '龙岗停车场', value: '龙岗停车场' }, - { label: '白云停车场', value: '白云停车场' }, - { label: '西城停车场', value: '西城停车场' }, - { label: '虹口停车场', value: '虹口停车场' }, - { label: '昌平停车场', value: '昌平停车场' }, - { label: '-', value: '-' } - ]; - var operateStatusOptions = [ - { label: '租赁', value: '租赁' }, - { label: '自营', value: '自营' }, - { label: '可运营', value: '可运营' }, - { label: '待运营', value: '待运营' }, - { label: '退出运营', value: '退出运营' } - ]; - var vehicleStatusOptions = [ - { label: '待验车', value: '待验车' }, - { label: '未备车', value: '未备车' }, - { label: '已备车', value: '已备车' }, - { label: '待交车', value: '待交车' }, - { label: '已交车', value: '已交车' }, - { label: '待还车', value: '待还车' }, - { label: '销售中', value: '销售中' }, - { label: '替换中', value: '替换中' }, - { label: '调拨中', value: '调拨中' }, - { label: '异动中', value: '异动中' }, - { label: '三方退租中', value: '三方退租中' }, - { label: '无', value: '无' } - ]; - var yearOptions = (function () { - var y = new Date().getFullYear(); - var opts = []; - for (var i = y; i >= y - 12; i--) opts.push({ label: String(i), value: String(i) }); - return opts; - })(); - - // 表格数据(Excel 真实数据前 100 条,内联) - var _tableData = useState(VM_PREPARED_VEHICLE_DATA); - - var dataSource = _tableData[0]; - var operateCityOptions = vmBuildOperateCityOptions(dataSource); - var projectNameOptions = vmBuildProjectNameOptions(dataSource); - var appliedMultiPlates = vmParseMultiPlates(_plateNosApplied[0]); - var pendingMultiPlates = vmParseMultiPlates(_plateNosPending[0]); - var displayDataSource = dataSource.filter(function (row) { - if (appliedMultiPlates.length) { - if (appliedMultiPlates.indexOf(vmNormalizePlateNo(row.plateNo)) < 0) return false; - } - if (!vmMatchOperateCityFilter(row, _operateCityFilter[0])) return false; - if (_projectNameFilter[0] && vmResolveVehicleProjectName(row) !== _projectNameFilter[0]) return false; - return vmMatchKpiCategory(row, _kpiTab[0]); - }); - var listTotal = displayDataSource.length; - var kpiCounts = { - all: vmCountKpiCategory(dataSource, 'all'), - operating: vmCountKpiCategory(dataSource, 'operating'), - stock: vmCountKpiCategory(dataSource, 'stock'), - nonOperating: vmCountKpiCategory(dataSource, 'nonOperating'), - exit: vmCountKpiCategory(dataSource, 'exit'), - licenseAbnormal: vmCountKpiCategory(dataSource, 'licenseAbnormal'), - insuranceAbnormal: vmCountKpiCategory(dataSource, 'insuranceAbnormal') - }; - - var handleExport = useCallback(function () { - message.success('导出功能(原型演示):当前列表共 ' + listTotal + ' 条'); - }, [listTotal]); - - var handleBatchImport = useCallback(function () { - _batchImportResult[1](null); - _batchImportModalVisible[1](true); - }, []); - - var closeBatchImportModal = useCallback(function () { - _batchImportModalVisible[1](false); - _batchImportResult[1](null); - }, []); - - var downloadVehicleImportTemplate = useCallback(function () { - vmDownloadVehicleImportTemplate(); - message.success('车辆导入模板已下载'); - }, []); - - var handleVehicleImportUpload = useCallback(function (file) { - var name = (file && file.name ? file.name : '').toLowerCase(); - if (!/\.(csv|xls|xlsx|txt)$/.test(name)) { - message.warning('请上传 .csv、.xls 或 .xlsx 文件'); - return false; - } - var reader = new FileReader(); - reader.onload = function (e) { - var text = e.target && e.target.result ? String(e.target.result) : ''; - if (name.endsWith('.xls') || name.endsWith('.xlsx')) { - if (text.indexOf('= 0) { - text = text.replace(/]*>/gi, '\n').replace(/<\/tr>/gi, '').replace(/]*>/gi, ',').replace(/<\/td>/gi, '').replace(/<[^>]+>/g, '').replace(/ /g, ' '); - } else { - message.warning('Excel 文件请使用下载的模板填写,或另存为 CSV UTF-8 后上传'); - return; - } - } - var rows = vmParseVehicleImportText(text); - if (!rows.length) { - message.error('未识别到有效导入数据,请使用车辆导入模板填写'); - _batchImportResult[1]({ hasFailed: true, failedList: [{ rowNo: '-', plateNo: '-', vin: '-', reason: '文件无有效数据行' }] }); - return; - } - var validation = vmValidateVehicleImportRows(rows, _tableData[0]); - if (!validation.ok) { - _batchImportResult[1]({ hasFailed: true, failedList: validation.failedList }); - message.error('导入校验未通过,共 ' + validation.failedList.length + ' 条错误'); - return; - } - _tableData[1](function (prev) { - var next = prev.slice(); - var i; - for (i = 0; i < validation.accepted.length; i++) { - next.push(vmMapImportRowToVehicleRecord(validation.accepted[i], i)); - } - return next; - }); - message.success('成功导入 ' + validation.accepted.length + ' 条车辆'); - closeBatchImportModal(); - }; - reader.readAsText(file, 'UTF-8'); - return false; - }, [_tableData[0], closeBatchImportModal]); - - var downloadVehicleImportFailedCsv = useCallback(function () { - var result = _batchImportResult[0]; - if (!result || !result.failedList || !result.failedList.length) return; - var lines = ['行号,车牌号,车辆识别代码,失败原因']; - var i; - for (i = 0; i < result.failedList.length; i++) { - var item = result.failedList[i]; - lines.push([ - vmEscapeCsvCell(item.rowNo), - vmEscapeCsvCell(item.plateNo), - vmEscapeCsvCell(item.vin), - vmEscapeCsvCell(item.reason) - ].join(',')); - } - var blob = new Blob(['\uFEFF' + lines.join('\n')], { type: 'text/csv;charset=utf-8' }); - var url = URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = url; - a.download = '车辆导入失败明细.csv'; - a.click(); - URL.revokeObjectURL(url); - }, [_batchImportResult[0]]); - - var goDetail = useCallback(function (record) { - _detailRecord[1](record); - _detailTab[1]('基本信息'); - }, []); - - var backToList = useCallback(function () { - _detailRecord[1](null); - }, []); - - var openUploadModal = useCallback(function (record) { - _currentRow[1](record); - _uploadModalVisible[1](true); - _confirmModalVisible[1](false); - _plateForm[1]({ vin: record.vin || '', plateNo: record.plateNo || '' }); - _plateError[1](''); - }, []); - - var closeUploadModal = useCallback(function () { - _uploadModalVisible[1](false); - _currentRow[1](null); - }, []); - - var startOcrThenConfirm = useCallback(function () { - _uploadModalVisible[1](false); - _ocrLoadingVisible[1](true); - setTimeout(function () { - _ocrLoadingVisible[1](false); - _confirmModalVisible[1](true); - }, 1500); - }, []); - - var closeConfirmModal = useCallback(function () { - _confirmModalVisible[1](false); - _currentRow[1](null); - _plateForm[1]({ vin: '', plateNo: '' }); - _plateError[1](''); - }, []); - - var onPlateFormChange = useCallback(function (field, value) { - _plateError[1](''); - _plateForm[1](function (prev) { - var next = {}; - next[field] = value; - return Object.assign({}, prev, next); - }); - }, []); - - var confirmPlate = useCallback(function () { - var row = _currentRow[0]; - var form = _plateForm[0]; - if (!row) return; - var vinMatch = form.vin && row.vin && form.vin.trim() === row.vin.trim(); - if (!form.vin || !form.plateNo) { - _plateError[1]('请填写车辆识别代码与车牌号'); - return; - } - if (!vinMatch) { - _plateError[1]('车辆识别代码与该车辆不匹配'); - return; - } - message.success('上牌信息已更新(原型演示)'); - closeConfirmModal(); - }, []); - - var openEditModal = useCallback(function (record) { - if (!isAdmin) { - message.warning('仅 Admin 可编辑车辆信息'); - return; - } - _editRecord[1](record); - _editForm[1]({ - parking: record.parking === '-' ? '-' : (record.parking || undefined), - operateStatus: record.operateStatus || undefined, - vehicleStatus: record.vehicleStatus || undefined, - ownership: record.ownership && record.ownership !== '-' ? record.ownership : '', - operateCompany: record.operateCompany || undefined, - vehicleSource: record.vehicleSource || undefined, - leaseCompany: record.leaseCompany && record.leaseCompany !== '-' ? record.leaseCompany : '', - year: record.year || undefined, - purchaseDate: record.purchaseDate || '' - }); - _editModalVisible[1](true); - }, []); - - var closeEditModal = useCallback(function () { - _editModalVisible[1](false); - _editRecord[1](null); - _editForm[1]({}); - }, []); - - var onEditFormChange = useCallback(function (field, value) { - _editForm[1](function (prev) { - var next = Object.assign({}, prev); - next[field] = value; - return next; - }); - }, []); - - var saveEdit = useCallback(function () { - if (!isAdmin) { - message.warning('仅 Admin 可保存'); - return; - } - var record = _editRecord[0]; - var form = _editForm[0]; - if (!record) return; - if (!form.operateStatus || !form.vehicleStatus) { - message.warning('请选择运营状态与车辆状态'); - return; - } - _tableData[1](function (prev) { - return prev.map(function (row) { - if (row.id !== record.id) return row; - var ownershipVal = (form.ownership || '').trim(); - var leaseVal = (form.leaseCompany || '').trim(); - return Object.assign({}, row, { - parking: form.parking || '-', - operateStatus: form.operateStatus, - vehicleStatus: form.vehicleStatus, - ownership: ownershipVal || '-', - operateCompany: form.operateCompany || row.operateCompany, - vehicleSource: form.vehicleSource || row.vehicleSource, - leaseCompany: leaseVal || '-', - year: form.year || row.year, - purchaseDate: form.purchaseDate || row.purchaseDate - }); - }); - }); - if (_detailRecord[0] && _detailRecord[0].id === record.id) { - _detailRecord[1](function (prev) { - if (!prev || prev.id !== record.id) return prev; - var ownershipVal = (form.ownership || '').trim(); - var leaseVal = (form.leaseCompany || '').trim(); - return Object.assign({}, prev, { - parking: form.parking || '-', - operateStatus: form.operateStatus, - vehicleStatus: form.vehicleStatus, - ownership: ownershipVal || '-', - operateCompany: form.operateCompany || prev.operateCompany, - vehicleSource: form.vehicleSource || prev.vehicleSource, - leaseCompany: leaseVal || '-', - year: form.year || prev.year, - purchaseDate: form.purchaseDate || prev.purchaseDate - }); - }); - } - message.success('车辆信息已保存(原型演示)'); - closeEditModal(); - }, []); - - var closeOpsManagerModal = useCallback(function () { - _opsManagerModalMode[1](null); - _opsManagerModalRecord[1](null); - _opsManagerBatchIds[1]([]); - _opsManagerDraftSelected[1]([]); - }, []); - - var openOpsManagerModal = useCallback(function (record) { - if (!canEditOpsManager) { - message.warning('暂无运维负责人编辑权限'); - return; - } - if (!record) return; - _opsManagerModalMode[1]('single'); - _opsManagerModalRecord[1](record); - _opsManagerBatchIds[1]([]); - _opsManagerDraftSelected[1](vmResolveVehicleOpsManagers(record)); - }, [canEditOpsManager]); - - var openBatchOpsManagerModal = useCallback(function () { - if (!canEditOpsManager) { - message.warning('暂无运维负责人编辑权限'); - return; - } - var ids = displayDataSource.map(function (row) { return row.id; }); - if (!ids.length) { - message.info('当前搜索结果无车辆可设置'); - return; - } - _opsManagerModalMode[1]('batch'); - _opsManagerModalRecord[1](null); - _opsManagerBatchIds[1](ids); - _opsManagerDraftSelected[1]([]); - }, [canEditOpsManager, displayDataSource]); - - var saveOpsManagers = useCallback(function () { - var mode = _opsManagerModalMode[0]; - var selected = (_opsManagerDraftSelected[0] || []).slice(); - if (mode === 'batch') { - var ids = _opsManagerBatchIds[0] || []; - if (!ids.length) return; - var idSet = {}; - ids.forEach(function (id) { idSet[id] = true; }); - _tableData[1](function (prev) { - return prev.map(function (row) { - if (!idSet[row.id]) return row; - return Object.assign({}, row, { opsManagers: selected }); - }); - }); - if (_detailRecord[0] && idSet[_detailRecord[0].id]) { - _detailRecord[1](function (prev) { - if (!prev || !idSet[prev.id]) return prev; - return Object.assign({}, prev, { opsManagers: selected }); - }); - } - message.success('已批量设置 ' + ids.length + ' 辆车的运维负责人'); - } else if (mode === 'single') { - var record = _opsManagerModalRecord[0]; - if (!record) return; - _tableData[1](function (prev) { - return prev.map(function (row) { - if (row.id !== record.id) return row; - return Object.assign({}, row, { opsManagers: selected }); - }); - }); - if (_detailRecord[0] && _detailRecord[0].id === record.id) { - _detailRecord[1](function (prev) { - if (!prev || prev.id !== record.id) return prev; - return Object.assign({}, prev, { opsManagers: selected }); - }); - } - message.success('运维负责人已设置,所选人员可在所有车辆业务中操作该车辆'); - } else { - return; - } - closeOpsManagerModal(); - }, [closeOpsManagerModal]); - - var renderOpsManagerCell = useCallback(function (record) { - if (!record) return null; - var names = vmResolveVehicleOpsManagers(record); - var isEmpty = !names.length; - var displayText = vmFormatOpsManagersText(record); - if (!canEditOpsManager) { - return React.createElement('span', { - className: 'vm-ops-manager-cell' + (isEmpty ? ' vm-ops-manager-cell--placeholder' : ''), - title: isEmpty ? '' : displayText - }, displayText); - } - return React.createElement('span', { - className: 'vm-ops-manager-cell vm-ops-manager-cell--clickable' + (isEmpty ? ' vm-ops-manager-cell--placeholder' : ''), - title: isEmpty ? '未分配,点击设置运维负责人' : '点击设置运维负责人', - role: 'button', - tabIndex: 0, - onClick: function (e) { - e.stopPropagation(); - openOpsManagerModal(record); - }, - onKeyDown: function (e) { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - e.stopPropagation(); - openOpsManagerModal(record); - } - } - }, displayText); - }, [canEditOpsManager, openOpsManagerModal]); - - var handleFilterReset = useCallback(function () { - _plateNosPending[1](''); - _plateNosApplied[1](''); - _multiPlateDraft[1](''); - _multiPlateOpen[1](false); - _operateCityFilter[1]([]); - _brand[1](undefined); - _model[1](undefined); - _customer[1](undefined); - _department[1](undefined); - _projectNameFilter[1](undefined); - _contractNo[1](undefined); - _ownership[1](undefined); - _operateCompany[1](undefined); - _operateStatusFilter[1](undefined); - _vehicleSource[1](undefined); - _leaseCompany[1](undefined); - _commercialInsurance[1](undefined); - _compulsoryInsurance[1](undefined); - _parkingFilter[1](undefined); - _areaRegion[1]([]); - _kpiTab[1]('all'); - }, []); - - var handleMultiPlateOpenChange = useCallback(function (open) { - _multiPlateOpen[1](open); - if (open) _multiPlateDraft[1](_plateNosPending[0] || ''); - }, []); - - var handleMultiPlateDraftClear = useCallback(function () { - _multiPlateDraft[1](''); - _plateNosPending[1](''); - _plateNosApplied[1](''); - _multiPlateOpen[1](false); - }, []); - - var confirmMultiPlateDraft = useCallback(function () { - _plateNosPending[1]((_multiPlateDraft[0] || '').trim()); - _multiPlateOpen[1](false); - }, []); - - var handleFilterQuery = useCallback(function () { - var pendingText = (_plateNosPending[0] || '').trim(); - _plateNosApplied[1](pendingText); - var plates = vmParseMultiPlates(pendingText); - if (plates.length) { - var hitCount = _tableData[0].filter(function (row) { - return plates.indexOf(vmNormalizePlateNo(row.plateNo)) >= 0; - }).length; - message.success('已按 ' + plates.length + ' 个车牌筛选,命中 ' + hitCount + ' 条记录'); - return; - } - message.success('已应用筛选条件(原型)'); - }, []); - - // 状态类字段:枚举为「无」或空值、占位「-」时界面统一显示「无」(详情/表单) - var formatStatusDisplay = function (val) { - if (val === '无' || vmIsEmptyDisplayValue(val)) return VM_EMPTY_DISPLAY_LABEL; - return val; - }; - - // 列表页:空值显示空白,有效枚举(含「无」)照常展示 - var formatListStatusDisplay = function (val) { - if (vmIsEmptyDisplayValue(val)) return ''; - return val; - }; - - var getMoreMenuItems = function (record) { - var items = []; - if (isAdmin) { - items.push({ key: 'edit', label: '编辑', onClick: function () { openEditModal(record); } }); - } - if (canEditOpsManager) { - items.push({ key: 'opsManager', label: '修改运维负责人', onClick: function () { openOpsManagerModal(record); } }); - } - return items; - }; - - var renderFilterField = useCallback(function (label, control) { - return React.createElement('div', { className: 'lc-filter-field' }, - React.createElement('span', { className: 'lc-filter-field-label' }, label), - React.createElement('div', { className: 'lc-filter-field-control' }, control) - ); - }, []); - - var vmSelectFilterOption = function (input, opt) { - return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; - }; - - var renderStatusTag = useCallback(function (text, colorFn) { - var display = formatStatusDisplay(text); - if (!display) return null; - return React.createElement(Tag, { className: 'vm-status-tag', color: colorFn(text) }, display); - }, []); - - var renderRecordStatusTag = useCallback(function (text, colorFn, abnormalTip, forList) { - var display = forList ? formatListStatusDisplay(text) : formatStatusDisplay(text); - if (!display) return null; - var tag = React.createElement(Tag, { className: 'vm-status-tag', color: colorFn(text) }, display); - if (abnormalTip) { - tag = React.createElement(Tooltip, { title: abnormalTip }, tag); - } - return tag; - }, []); - - var renderStatusColumnTitle = useCallback(function (label, tip) { - return React.createElement('span', { className: 'vm-col-title-with-tip' }, - label, - React.createElement(Tooltip, { title: tip, placement: 'top', overlayStyle: { maxWidth: 360 } }, - React.createElement('span', { - className: 'vm-col-title-tip', - role: 'img', - 'aria-label': label + '说明', - tabIndex: 0, - onClick: function (e) { e.stopPropagation(); }, - onKeyDown: function (e) { e.stopPropagation(); } - }, vmSvgIcon(VM_INFO_TIP_ICON_PATHS, 10)) - ) - ); - }, []); - - var handleKpiCardClick = useCallback(function (key) { - _kpiTab[1](key); - }, []); - - var renderKpiCard = useCallback(function (card) { - var active = _kpiTab[0] === card.key; - var count = kpiCounts[card.key]; - return React.createElement('div', { - key: card.key, - role: 'button', - tabIndex: 0, - className: 'lc-alert-card lc-alert-card--' + card.type + ' lc-alert-card-clickable' + (active ? ' lc-alert-card-active' : ''), - onClick: function () { handleKpiCardClick(card.key); }, - onKeyDown: function (e) { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - handleKpiCardClick(card.key); - } - } - }, - React.createElement('div', { className: 'lc-alert-card-tip-anchor' }, - React.createElement(Tooltip, { title: card.desc, placement: 'topRight', overlayStyle: { maxWidth: 360 } }, - React.createElement('span', { - className: 'lc-alert-card-tip', - role: 'img', - 'aria-label': card.title + '说明', - onClick: function (e) { e.stopPropagation(); }, - onMouseDown: function (e) { e.stopPropagation(); } - }, vmSvgIcon(VM_INFO_TIP_ICON_PATHS, 10)) - ) - ), - React.createElement('div', { className: 'lc-alert-card-icon', 'aria-hidden': true }, VM_KPI_ICONS[card.key]), - React.createElement('div', { className: 'lc-alert-card-main' }, - React.createElement('div', { className: 'lc-alert-card-title', title: card.title }, card.title), - React.createElement('div', { className: 'lc-alert-card-val' }, count) - ) - ); - }, [_kpiTab[0], kpiCounts, handleKpiCardClick]); - - var renderOperateCityColumnTitle = useCallback(function () { - return React.createElement('span', { className: 'vm-col-title-with-tip' }, - '运营城市', - React.createElement(Tooltip, { title: 'GPS最后一次回传的城市', placement: 'top' }, - React.createElement('span', { - className: 'vm-col-title-tip', - role: 'img', - 'aria-label': 'GPS最后一次回传的城市', - tabIndex: 0, - onClick: function (e) { e.stopPropagation(); }, - onKeyDown: function (e) { e.stopPropagation(); } - }, vmSvgIcon(VM_INFO_TIP_ICON_PATHS, 10)) - ) - ); - }, []); - - var renderOpsManagerColumnTitle = useCallback(function () { - return React.createElement('span', { className: 'vm-col-title-with-action' }, - '运维负责人', - canEditOpsManager ? React.createElement(Tooltip, { title: '批量设置当前搜索结果运维负责人', placement: 'top' }, - React.createElement('button', { - type: 'button', - className: 'vm-col-title-edit-btn', - 'aria-label': '批量设置运维负责人', - onClick: function (e) { - e.stopPropagation(); - openBatchOpsManagerModal(); - } - }, VM_ICONS.edit) - ) : null - ); - }, [canEditOpsManager, openBatchOpsManagerModal]); - - var renderMileageColumnTitle = useCallback(function () { - return React.createElement('span', { className: 'vm-col-title-with-tip' }, - '里程数', - React.createElement(Tooltip, { title: VM_MILEAGE_COLUMN_TIP, placement: 'top' }, - React.createElement('span', { - className: 'vm-col-title-tip', - role: 'img', - 'aria-label': VM_MILEAGE_COLUMN_TIP, - tabIndex: 0, - onClick: function (e) { e.stopPropagation(); }, - onKeyDown: function (e) { e.stopPropagation(); } - }, vmSvgIcon(VM_INFO_TIP_ICON_PATHS, 10)) - ) - ); - }, []); - - var columns = [ - { - title: '车辆信息', - key: 'vehicleIdentity', - width: 200, - fixed: 'left', - onCell: function () { return { className: 'vm-vehicle-identity-td' }; }, - render: function (_, record) { - return vmRenderVehicleIdentityCell(record, goDetail); - } - }, - { title: renderStatusColumnTitle('运营状态', VM_STATUS_COLUMN_TIPS.operateStatus), dataIndex: 'operateStatus', key: 'operateStatus', width: 110, render: function (_, record) { return renderRecordStatusTag(record.operateStatus, vmOperateStatusColor, '', true); } }, - { - title: renderOperateCityColumnTitle(), - key: 'operateCity', - width: 140, - render: function (_, record) { - var city = vmFormatOperateCityFromGps(record.location); - var locationTitle = vmIsEmptyDisplayValue(record.location) ? undefined : record.location; - return React.createElement('span', { title: locationTitle }, vmDisplayListLabel(city)); - } - }, - { - title: '所属项目', - key: 'projectInfo', - width: 200, - onCell: function () { return { className: 'vm-stacked-cell-td' }; }, - render: function (_, record) { return vmRenderProjectCell(record); } - }, - { - title: '业务员', - key: 'salesperson', - width: 120, - onCell: function () { return { className: 'vm-stacked-cell-td' }; }, - render: function (_, record) { return vmRenderSalespersonCell(record); } - }, - { title: '停放区域', dataIndex: 'parking', key: 'parking', width: 140, render: vmRenderOneLineText }, - { - title: renderMileageColumnTitle(), - key: 'mileage', - width: 170, - onCell: function () { return { className: 'vm-mileage-cell-td' }; }, - render: function (_, record) { return vmRenderMileageCell(record, true); } - }, - { title: renderStatusColumnTitle('车辆状态', VM_STATUS_COLUMN_TIPS.vehicleStatus), dataIndex: 'vehicleStatus', key: 'vehicleStatus', width: 120, render: function (_, record) { return renderRecordStatusTag(record.vehicleStatus, function () { return 'blue'; }, '', true); } }, - { title: renderStatusColumnTitle('证照状态', VM_STATUS_COLUMN_TIPS.licenseStatus), dataIndex: 'licenseStatus', key: 'licenseStatus', width: 100, render: function (_, record) { - var tip = record.licenseStatus === '异常' ? vmGetLicenseAbnormalTooltip(record) : ''; - return renderRecordStatusTag(record.licenseStatus, vmAlertStatusColor, tip, true); - } }, - { - title: '行驶证检验有效期', - dataIndex: 'inspectExpire', - key: 'inspectExpire', - width: 150, - onCell: vmExpireDateOnCell, - render: function (_, record) { return vmRenderExpireDateCell(record.inspectExpire, true); } - }, - { - title: '等评时间', - dataIndex: 'ratingTime', - key: 'ratingTime', - width: 150, - onCell: vmExpireDateOnCell, - render: function (_, record) { return vmRenderExpireDateCell(record.ratingTime, true); } - }, - { title: renderStatusColumnTitle('保险状态', VM_STATUS_COLUMN_TIPS.insuranceStatus), dataIndex: 'insuranceStatus', key: 'insuranceStatus', width: 100, render: function (_, record) { - var tip = record.insuranceStatus === '异常' ? vmGetInsuranceAbnormalTooltip(record) : ''; - return renderRecordStatusTag(record.insuranceStatus, vmAlertStatusColor, tip, true); - } }, - { - title: '交强险到期时间', - key: 'compulsoryInsuranceExpire', - width: 150, - onCell: vmExpireDateOnCell, - render: function (_, record) { return vmRenderInsuranceExpireDate(record, 'compulsory', true); } - }, - { - title: '商业险到期时间', - key: 'commercialInsuranceExpire', - width: 150, - onCell: vmExpireDateOnCell, - render: function (_, record) { return vmRenderInsuranceExpireDate(record, 'commercial', true); } - }, - { title: renderStatusColumnTitle('出库状态', VM_STATUS_COLUMN_TIPS.outStatus), dataIndex: 'outStatus', key: 'outStatus', width: 130, render: function (_, record) { return renderRecordStatusTag(record.outStatus, function () { return 'default'; }, '', true); } }, - { title: '登记所有权', dataIndex: 'ownership', key: 'ownership', width: 160, ellipsis: true, render: vmRenderOneLineText }, - { title: '运营公司', dataIndex: 'operateCompany', key: 'operateCompany', width: 150, ellipsis: true, render: vmRenderOneLineText }, - { title: '车辆来源', dataIndex: 'vehicleSource', key: 'vehicleSource', width: 90, render: function (_, record) { return renderRecordStatusTag(record.vehicleSource, vmVehicleSourceColor, '', true); } }, - { title: '租赁公司', dataIndex: 'leaseCompany', key: 'leaseCompany', width: 160, ellipsis: true, render: vmRenderOneLineText }, - { title: '车辆编号', dataIndex: 'vehicleNo', key: 'vehicleNo', width: 100, render: vmRenderOneLineText }, - { title: '车身颜色', dataIndex: 'color', key: 'color', width: 90, render: vmRenderOneLineText }, - { title: '出厂年份', dataIndex: 'year', key: 'year', width: 90, render: vmRenderOneLineText }, - { title: '采购入库时间', dataIndex: 'purchaseDate', key: 'purchaseDate', width: 120, render: vmRenderOneLineText }, - { - title: '强制报废日期', - dataIndex: 'scrapDate', - key: 'scrapDate', - width: 150, - onCell: vmExpireDateOnCell, - render: function (_, record) { return vmRenderExpireDateCell(record.scrapDate, true); } - }, - { - title: '上次交车情况', - key: 'lastDeliverySituation', - width: 156, - onCell: function () { return { className: 'vm-stacked-cell-td' }; }, - render: function (_, record) { return vmRenderLastDeliverySituationCell(record); } - }, - { - title: '上次还车情况', - key: 'lastReturnSituation', - width: 156, - onCell: function () { return { className: 'vm-stacked-cell-td' }; }, - render: function (_, record) { return vmRenderLastReturnSituationCell(record); } - }, - { - title: '当前位置', - key: 'gpsLocation', - width: 300, - onCell: function () { return { className: 'vm-gps-location-td' }; }, - render: function (_, record) { return vmRenderGpsLocationCell(record); } - }, - { - title: renderOpsManagerColumnTitle(), - key: 'opsManagers', - width: 176, - render: function (_, record) { return renderOpsManagerCell(record); } - }, - { - title: '操作', - key: 'action', - width: 120, - fixed: 'right', - render: function (_, record) { - return React.createElement('div', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } }, - React.createElement(Button, { key: 'view', type: 'link', size: 'small', className: 'lc-action-btn', onClick: function () { goDetail(record); } }, '查看'), - React.createElement(Dropdown, { key: 'more', menu: { items: getMoreMenuItems(record) }, trigger: ['click'] }, - React.createElement(Tooltip, { title: '更多操作' }, - React.createElement('span', { - className: 'vm-action-more-btn', - role: 'button', - tabIndex: 0, - 'aria-label': '更多操作', - onClick: function (e) { e.stopPropagation(); } - }, vmMoreIcon()) - ) - ) - ); - } - } - ]; - - var filterControlStyle = { width: '100%', height: 32, borderRadius: 8 }; - var selectDropdownStyle = { borderRadius: 8 }; - - vmEnsureStylesheet(); - - var emptyNode = React.createElement('div', { className: 'vm-empty-wrap' }, - VM_ICONS.empty, - React.createElement('div', { style: { marginTop: 12 } }, '暂无符合条件的车辆') - ); - - // 车辆详情页:顶部返回 + 信息层级卡片 + Tab - var detailRecord = _detailRecord[0]; - if (detailRecord) { - var r = detailRecord; - var detailField = function (label, valueNode, fieldKey) { - return React.createElement('div', { key: fieldKey || label, className: 'vm-detail-field' }, - React.createElement('div', { className: 'vm-detail-field-label' }, label), - React.createElement('div', { className: 'vm-detail-field-value' }, valueNode) - ); - }; - var detailLocationText = vmResolveLocationFullAddress(r); - var detailLicenseTip = r.licenseStatus === '异常' ? vmGetLicenseAbnormalTooltip(r) : ''; - var detailInsuranceTip = r.insuranceStatus === '异常' ? vmGetInsuranceAbnormalTooltip(r) : ''; - var detailTextValue = function (val) { - return vmDisplayUILabel(val); - }; - var detailTabSection = function (title, body, sectionKey) { - return React.createElement('section', { key: sectionKey, className: 'vm-model-param-section' }, - React.createElement('div', { className: 'vm-model-param-section-title' }, title), - body - ); - }; - var assetStatusFields = [ - detailField('车辆来源', renderRecordStatusTag(r.vehicleSource, vmVehicleSourceColor, ''), 'vehicleSource'), - detailField('登记所有权', detailTextValue(r.ownership), 'ownership'), - detailField('运营公司', detailTextValue(r.operateCompany), 'operateCompany'), - detailField('租赁公司', detailTextValue(r.leaseCompany), 'leaseCompany') - ]; - var modelParamText = function (val, fallback) { - if (fallback !== undefined && vmIsEmptyDisplayValue(val)) return fallback; - return vmDisplayUILabel(val); - }; - var modelParamSection = detailTabSection; - var tabContent = function (text) { - return React.createElement('div', { className: 'vm-tab-placeholder' }, text); - }; - var maintenanceTableData = [ - { key: '1', no: 1, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '' }, - { key: '2', no: 2, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '' }, - { key: '3', no: 3, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '5000' }, - { key: '4', no: 4, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '5000' }, - { key: '5', no: 5, item: '变速器油', kmCycle: '60000', monthCycle: '24', labor: '0', material: '571', total: '571', lastKm: '5000' } - ]; - var maintenanceColumns = [ - { title: '序号', dataIndex: 'no', key: 'no', width: 60 }, - { title: '养护项目', dataIndex: 'item', key: 'item', width: 120 }, - { title: '保养公里周期(km)', dataIndex: 'kmCycle', key: 'kmCycle', width: 140 }, - { title: '保养时间周期(月)', dataIndex: 'monthCycle', key: 'monthCycle', width: 130 }, - { title: '工时费(元)', dataIndex: 'labor', key: 'labor', width: 100 }, - { title: '材料费(元)', dataIndex: 'material', key: 'material', width: 100 }, - { title: '合计', dataIndex: 'total', key: 'total', width: 80 }, - { title: '上次保养公里数(KM)', dataIndex: 'lastKm', key: 'lastKm', width: 140 } - ]; - var modelParamContent = React.createElement('div', { className: 'vm-tab-card vm-model-param-tab' }, - modelParamSection('型号参数', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('品牌', modelParamText(r.brand, '苏龙'), 'mp-brand'), - detailField('型号', modelParamText(r.model, '海格牌KLQ5180XYKFCEV'), 'mp-model'), - detailField('车辆类型', modelParamText(r.vehicleType), 'mp-vehicleType'), - detailField('燃料种类', '氢', 'mp-fuelType'), - detailField('整车尺寸', '5995mm × 2145mm × 3130mm', 'mp-size'), - detailField('车牌颜色', modelParamText(r.color, '绿牌'), 'mp-plateColor') - ]), 'base'), - modelParamSection('轮胎情况', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('轮胎数量', '8', 'mp-tireCount'), - detailField('轮胎规格', '15/80R22.5', 'mp-tireSpec') - ]), 'tire'), - modelParamSection('电气系统', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('电池类型', '磷酸铁锂', 'mp-batteryType'), - detailField('电池厂家', '宁德时代新能源科技股份有限公司', 'mp-batteryVendor'), - detailField('储电量', '100000 kWh', 'mp-capacity'), - detailField('续航里程', '200 KM', 'mp-electricRange') - ]), 'electric'), - modelParamSection('供氢系统', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('氢瓶容量', '140 L', 'mp-cylinder'), - detailField('仪表盘模式', 'MPa', 'mp-gauge'), - detailField('续航里程', '1000 KM', 'mp-hydrogenRange'), - detailField('供氢系统厂家', '江苏国富氢能技术装备股份有限公司', 'mp-hydrogenVendor') - ]), 'hydrogen'), - modelParamSection('其他系统', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('冷机生产企业', '开利空调冷冻设备(上海)有限公司', 'mp-coldVendor'), - detailField('电堆生产企业', '亿华通动力科技股份有限公司', 'mp-stackVendor') - ]), 'other'), - modelParamSection('保养参数', React.createElement('div', { className: 'vm-model-param-table-wrap' }, - React.createElement(Table, { - className: 'vm-model-param-table', - size: 'small', - rowKey: 'key', - columns: maintenanceColumns, - dataSource: maintenanceTableData, - pagination: false, - scroll: { x: 960 } - }) - ), 'maintenance') - ); - var basicInfoTabContent = React.createElement('div', { className: 'vm-tab-card vm-model-param-tab' }, - detailTabSection('档案信息', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('出厂年份', detailTextValue(r.year), 'year'), - detailField('采购入库日期', vmRenderDetailDateValue(r.purchaseDate), 'purchaseDate'), - detailField('强制报废日期', vmRenderDetailExpireValue(r.scrapDate), 'scrapDate'), - detailField('等评时间', vmRenderDetailExpireValue(r.ratingTime), 'ratingTime-r1') - ]), 'bi-archive'), - detailTabSection('业务信息', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('运营状态', renderRecordStatusTag(r.operateStatus, vmOperateStatusColor, ''), 'operateStatus'), - detailField('客户名称', detailTextValue(r.customer), 'customer'), - detailField('合同编码', detailTextValue(r.contractNo), 'contractNo'), - detailField('项目名称', detailTextValue(vmResolveVehicleProjectName(r)), 'projectName') - ]), 'bi-business'), - detailTabSection('证照信息', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('车辆状态', renderRecordStatusTag(r.vehicleStatus, function () { return 'blue'; }, ''), 'vehicleStatus'), - detailField('证照状态', renderRecordStatusTag(r.licenseStatus, vmAlertStatusColor, detailLicenseTip), 'licenseStatus'), - detailField('行驶证有效期', vmRenderDetailExpireValue(r.inspectExpire), 'inspectExpire'), - detailField('等评时间', vmRenderDetailExpireValue(r.ratingTime), 'ratingTime-r3') - ]), 'bi-license'), - detailTabSection('保险与出库', React.createElement('div', { className: 'vm-detail-fields-grid' }, [ - detailField('保险状态', renderRecordStatusTag(r.insuranceStatus, vmAlertStatusColor, detailInsuranceTip), 'insuranceStatus'), - detailField('交强险到期时间', vmRenderInsuranceExpireDate(r, 'compulsory'), 'compulsoryInsurance'), - detailField('商业险到期时间', vmRenderInsuranceExpireDate(r, 'commercial'), 'commercialInsurance'), - detailField('出库状态', renderRecordStatusTag(r.outStatus, function () { return 'default'; }, ''), 'outStatus') - ]), 'bi-insurance'), - detailTabSection('资产状况', React.createElement('div', { className: 'vm-detail-fields-grid' }, assetStatusFields), 'bi-asset') - ); - var detailTabItems = [ - { key: '基本信息', label: '基本信息', children: basicInfoTabContent }, - { key: '型号参数', label: '型号参数', children: modelParamContent }, - { key: '后装设备', label: '后装设备', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('后装设备列表(原型占位)')) }, - { key: '证照信息', label: '证照信息', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('证照信息(原型占位)')) }, - { key: '租赁记录', label: '租赁记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('租赁记录(原型占位)')) }, - { key: '保险记录', label: '保险记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('保险记录(原型占位)')) }, - { key: '维修记录', label: '维修记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('维修记录(原型占位)')) }, - { key: '事故记录', label: '事故记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('事故记录(原型占位)')) }, - { key: '故障记录', label: '故障记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('故障记录(原型占位)')) }, - { key: '违章记录', label: '违章记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('违章记录(原型占位)')) }, - { key: '异动记录', label: '异动记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('异动记录(原型占位)')) }, - { key: '调拨记录', label: '调拨记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('调拨记录(原型占位)')) }, - { key: '整备记录', label: '整备记录', children: React.createElement('div', { className: 'vm-tab-card' }, tabContent('整备记录(原型占位)')) } - ]; - return React.createElement(App, null, - React.createElement('div', { className: 'vm-page vm-detail-shell' }, - React.createElement('style', null, VM_PAGE_STYLE), - React.createElement('div', { className: 'vm-detail-topbar' }, - React.createElement(Button, { className: 'vm-detail-back-btn', icon: VM_ICONS.back, onClick: backToList }, '返回列表'), - React.createElement('h1', { className: 'vm-detail-topbar-title' }, '车辆详情') - ), - React.createElement('div', { className: 'vm-detail-card' }, - React.createElement('div', { className: 'vm-detail-hero' }, - React.createElement('div', { className: 'vm-detail-hero-identity' }, - React.createElement('div', { className: 'vm-detail-plate-xl' }, vmDisplayUILabel(r.plateNo)), - React.createElement('div', { className: 'vm-detail-meta-line' }, vmDisplayUILabel(r.brand) + ' · ' + vmDisplayUILabel(r.model)), - React.createElement('div', { className: 'vm-detail-meta-line vm-detail-meta-line--mono' }, 'VIN ' + vmDisplayUILabel(r.vin)) - ), - React.createElement('div', { className: 'vm-detail-hero-aside' }, - React.createElement('div', { className: 'vm-detail-hero-tags' }, - vmRenderOnlineStatusCell(r.onlineStatus) - ), - React.createElement('div', { className: 'vm-detail-hero-location', title: detailLocationText || undefined }, vmDisplayUILabel(detailLocationText)), - React.createElement('div', { className: 'vm-detail-hero-gps-time' }, 'GPS最后上传 ' + vmDisplayUILabel(r.gpsTime)) - ) - ), - React.createElement('div', { className: 'vm-detail-quick-stats' }, - vmRenderDetailQuickStat('运营城市', vmFormatOperateCityFromGps(r.location)), - vmRenderDetailQuickStat('里程数', vmRenderMileageCell(r)), - vmRenderDetailQuickStat('停放区域', r.parking), - vmRenderDetailQuickStat('运维负责人', vmFormatOpsManagersText(r)) - ) - ), - React.createElement('div', { className: 'vm-detail-card vm-detail-tabs' }, - React.createElement(Tabs, { - activeKey: _detailTab[0], - onChange: function (key) { _detailTab[1](key); }, - items: detailTabItems - }) - ) - ) - ); - } - - var requirementText = '# 车辆管理\n一个「车辆资产管理」后台的「车辆管理」模块,功能从上到下依次为:\n\n1.筛选:\n1.1.车牌号:点击输入框展开多车牌选择器,每行一个车牌号(支持从 Excel 批量复制粘贴,单行内仍支持逗号分隔);点击「确认」保存所选车牌,再点击「查询」后精确匹配并展示全部命中车辆、未命中的不显示,触发器显示已选车牌摘要;\n1.2.运营城市:选择器,支持输入地级市名称模糊搜索、多选,选项格式为省-市(取自 GPS 解析及停车场区域台账);\n1.3.品牌:选择器;\n1.4.型号:选择器;\n1.5.客户名称:选择器,支持输入模糊匹配,占位「请选择或输入客户名称」;\n1.6.归属业务部门:选择器;\n1.7.项目名称:选择器,支持输入模糊匹配,占位「请选择或输入项目名称」,选项取自台账解析的项目名及合同映射;\n1.8.合同编码:选择器,支持输入模糊匹配;\n1.9.登记所有权:选择器,支持输入模糊匹配;\n1.10.运营公司:选择器,支持模糊搜索;\n1.11.运营状态:租赁、自营、可运营、待运营、退出运营;\n1.12.车辆来源:自有、外租、挂靠;\n1.13.租赁公司:选择器,支持模糊搜索;\n1.14.商业险状态:正常、异常;\n1.15.交强险状态:正常、异常;\n1.16.停车场:选择器,支持输入;\n1.17.定位城市:省-市级联选择;默认折叠仅展示一行(4 项),可点击「展开更多筛选项」;\n\n2.KPI 分类(筛选与列表之间):\n所有营运车辆 / 运营中 / 库存 / 非运营车辆 / 退出运营 / 证照异常 / 保险异常;所有营运车辆=营运体系内全部车辆(不含非运营车辆与退出运营车辆),运营中=租赁+自营,库存=可运营+待运营,非运营车辆=导入车辆类型为「非运营车辆」的台账车辆,退出运营=退出运营,证照异常=行驶证检验有效期已过期(不含等评时间),保险异常=保险状态异常;点击卡片筛选列表,卡片右上角可查看说明;\n\n3.列表:\n工具栏右侧:导出、批量导入;批量导入:点击后弹窗分两步(下载模板、上传文件),弹窗内无长说明;可下载「车辆导入模板」(模板首行红色提示「非运营车辆不做必填项校验」,必填列表头红色并带*,仅运营车辆校验);模板首列为车辆类型(枚举运营车辆/非运营车辆),其余字段为车牌号、*车辆识别代码、*品牌、*型号、*停放区域、行驶公里数、*登记所有权、*运营公司、*车辆来源、租赁公司、车辆编号、车辆颜色、出厂年份、采购入库时间;运营车辆执行全部必填与格式校验,非运营车辆不做必填项校验(有值时仍校验格式与唯一性);车辆识别代码17位校验,品牌/型号按型号参数表校验,停放区域按停车场名称校验,行驶公里数为整数或最多两位小数,车辆来源枚举挂靠/外租/自有,出厂年份YYYY且不能晚于当前年份,采购入库时间YYYY-MM-DD且不能晚于当前日期,车牌号与车辆识别代码须唯一;上传后系统校验,失败可下载失败明细;\n运营城市取 GPS 最后定位位置,显示格式为「省-市」,表头含说明「GPS最后一次回传的城市」;\n运营状态、车辆状态、证照状态、保险状态、出库状态列表头含说明图标(依据《车辆状态说明》);\n证照状态=异常时,悬浮提示「行驶证检验有效期已过期」(仅依据检验有效期是否过期判断,不含等评时间);保险状态=异常时,悬浮提示「交强险已到期/商业险已到期」(按到期日判断);\n字段依次为:车辆信息(车牌号、车架号可点击查看详情,品牌-型号三行)、运营状态、运营城市(GPS最后一次回传的城市,省-市格式)、所属项目(第一行客户名称、第二行合同编码、第三行项目名称;项目名称优先取车辆字段,为空时按合同编码关联租赁合同,仍无则按客户名称推导)、业务员(业务部门/业务员两行)、停放区域(单行)、里程数(表头含说明「显示最后一次交车记录/还车记录/车机里程数」;已对接车机显示车机里程并附「车机」标签,无车机里程数据时仍展示「0 km」,不回退交还车里程;未对接车机取最后一次交车或还车记录里程并附「最后一次交车」或「最后一次还车」标签)、车辆状态、证照状态、行驶证检验有效期、等评时间、保险状态、交强险到期时间、商业险到期时间、出库状态、登记所有权、运营公司、车辆来源、租赁公司、车辆编号、车身颜色、出厂年份、采购入库时间、强制报废日期、上次交车情况(第一行上次交车里程数+km,第二行上次交车省-市,第三行上次交车时间日期与时分同一行显示,省-市取自最后一次交车记录)、上次还车情况(同上,省-市取自最后一次还车记录)、当前位置(第一行在线/离线状态含绿/红点,第二行完整地址如省市区路号,第三行GPS最后上传时间)、运维负责人(按停车场所属区域默认匹配运维专员/运维助理/运维主管;需单独权限 vehicle:opsManager:edit,无权限时单元格只读、表头无编辑图标、操作列不展示「修改运维负责人」;有权限时可点击单元格或操作列「修改运维负责人」打开单车「设置运维负责人」弹窗,或点击表头右侧编辑图标对当前搜索结果全部车辆批量设置(批量弹窗展示适用范围);下方均为多选选择器(支持输入姓名模糊搜索),提交后即时生效;多人单行显示,英文逗号分隔)、操作;所有有效期/到期日列显示 YYYY-MM-DD,并附剩余天数标签(剩余x天/已过期x天);\n车辆详情:顶部「返回列表」按钮(无面包屑);Hero 区左侧车牌/品牌型号/VIN,右上角在线状态后展示车辆当前位置与 GPS 最后上传时间(不展示运营状态、车辆状态标签);四项指标为运营城市、里程数(与列表一致含来源标签)、停放区域、运维负责人;Tab 区首项为「基本信息」(含基本信息字段网格与「资产状况」,默认选中),其后为型号参数及各类记录 Tab;基本信息四行十七项(出厂年份/采购入库日期/强制报废日期/等评时间;运营状态/客户名称/合同编码/项目名称;车辆状态/证照状态/行驶证有效期/等评时间;保险状态/交强险到期时间/商业险到期时间/出库状态),不含车辆识别代码;所有到期时间附剩余x天或已过期x天标签;资产状况首行:车辆来源/登记所有权/运营公司/租赁公司;\n\n(状态字段规则见《车辆状态说明》产品需求文档。)\n'; - - var multiPlateTriggerText = ''; - if (pendingMultiPlates.length) { - if (pendingMultiPlates.length <= 2) multiPlateTriggerText = pendingMultiPlates.join('、'); - else multiPlateTriggerText = '已选 ' + pendingMultiPlates.length + ' 个车牌'; - } - - var buildFilterFields = function () { - return [ - renderFilterField('车牌号', React.createElement(Popover, { - open: _multiPlateOpen[0], - onOpenChange: handleMultiPlateOpenChange, - trigger: 'click', - placement: 'bottomLeft', - overlayClassName: 'vm-multi-plate-popover', - overlayInnerStyle: { width: 288, padding: 0, boxSizing: 'border-box' }, - content: React.createElement('div', { className: 'vm-multi-plate-pop' }, - React.createElement(Input.TextArea, { - className: 'vm-multi-plate-pop-textarea', - value: _multiPlateDraft[0], - onChange: function (e) { _multiPlateDraft[1](e.target.value); }, - placeholder: '请输入车牌号,多个车牌跨行输入', - autoSize: { minRows: 5, maxRows: 8 } - }), - React.createElement('div', { className: 'vm-multi-plate-pop-actions' }, - React.createElement(Button, { size: 'small', type: 'primary', onClick: confirmMultiPlateDraft, style: VM_PRIMARY_BTN_STYLE }, '确认') - ) - ) - }, - React.createElement(Input, { - className: 'vm-multi-plate-trigger', - readOnly: true, - allowClear: !!multiPlateTriggerText, - placeholder: '请选择或输入车牌号', - value: multiPlateTriggerText, - style: filterControlStyle, - onClick: function () { _multiPlateOpen[1](true); }, - onClear: function (e) { - if (e && e.stopPropagation) e.stopPropagation(); - handleMultiPlateDraftClear(); - }, - suffix: React.createElement('svg', { - width: 14, - height: 14, - viewBox: '0 0 24 24', - fill: 'none', - stroke: '#94a3b8', - strokeWidth: 2, - style: { pointerEvents: 'none' } - }, React.createElement('polyline', { points: '6 9 12 15 18 9' })) - }) - )), - renderFilterField('运营城市', React.createElement(Select, { - mode: 'multiple', - placeholder: '请选择或输入地级市搜索', - style: filterControlStyle, - options: operateCityOptions, - value: _operateCityFilter[0], - onChange: function (vals) { _operateCityFilter[1](vals || []); }, - allowClear: true, - showSearch: true, - filterOption: vmSelectFilterOption, - optionFilterProp: 'label', - maxTagCount: 'responsive', - dropdownStyle: selectDropdownStyle - })), - renderFilterField('品牌', React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: brandOptions, value: _brand[0], onChange: _brand[1], allowClear: true, dropdownStyle: selectDropdownStyle })), - renderFilterField('型号', React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: modelOptions, value: _model[0], onChange: _model[1], allowClear: true, dropdownStyle: selectDropdownStyle })), - renderFilterField('客户名称', React.createElement(Select, { placeholder: '请选择或输入客户名称', style: filterControlStyle, options: customerOptions, value: _customer[0], onChange: _customer[1], allowClear: true, showSearch: true, filterOption: vmSelectFilterOption, dropdownStyle: selectDropdownStyle })), - renderFilterField('归属业务部门', React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: departmentOptions, value: _department[0], onChange: _department[1], allowClear: true, dropdownStyle: selectDropdownStyle })), - renderFilterField('项目名称', React.createElement(Select, { - placeholder: '请选择或输入项目名称', - style: filterControlStyle, - options: projectNameOptions, - value: _projectNameFilter[0], - onChange: _projectNameFilter[1], - allowClear: true, - showSearch: true, - filterOption: vmSelectFilterOption, - optionFilterProp: 'label', - dropdownStyle: selectDropdownStyle - })), - renderFilterField('合同编码', React.createElement(Select, { placeholder: '请选择或输入', style: filterControlStyle, options: contractOptions, value: _contractNo[0], onChange: _contractNo[1], allowClear: true, showSearch: true, filterOption: vmSelectFilterOption, dropdownStyle: selectDropdownStyle })), - renderFilterField('登记所有权', React.createElement(Select, { placeholder: '请选择登记所有权', style: filterControlStyle, options: ownershipOptions, value: _ownership[0], onChange: _ownership[1], allowClear: true, showSearch: true, filterOption: vmSelectFilterOption, dropdownStyle: selectDropdownStyle })), - renderFilterField('运营公司', React.createElement(Select, { placeholder: '请选择或输入运营公司名称', style: filterControlStyle, options: operateCompanyOptions, value: _operateCompany[0], onChange: _operateCompany[1], allowClear: true, showSearch: true, filterOption: vmSelectFilterOption, dropdownStyle: selectDropdownStyle })), - renderFilterField('运营状态', React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: operateStatusOptions, value: _operateStatusFilter[0], onChange: _operateStatusFilter[1], allowClear: true, dropdownStyle: selectDropdownStyle })), - renderFilterField('车辆来源', React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: vehicleSourceOptions, value: _vehicleSource[0], onChange: _vehicleSource[1], allowClear: true, dropdownStyle: selectDropdownStyle })), - renderFilterField('租赁公司', React.createElement(Select, { placeholder: '请选择或搜索', style: filterControlStyle, options: leaseCompanyOptions, value: _leaseCompany[0], onChange: _leaseCompany[1], allowClear: true, showSearch: true, filterOption: vmSelectFilterOption, dropdownStyle: selectDropdownStyle })), - renderFilterField('商业险状态', React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: insuranceStatusOptions, value: _commercialInsurance[0], onChange: _commercialInsurance[1], allowClear: true, dropdownStyle: selectDropdownStyle })), - renderFilterField('交强险状态', React.createElement(Select, { placeholder: '请选择', style: filterControlStyle, options: insuranceStatusOptions, value: _compulsoryInsurance[0], onChange: _compulsoryInsurance[1], allowClear: true, dropdownStyle: selectDropdownStyle })), - renderFilterField('停车场', React.createElement(Select, { placeholder: '请选择或输入', style: filterControlStyle, options: parkingOptions, value: _parkingFilter[0], onChange: _parkingFilter[1], allowClear: true, showSearch: true, filterOption: vmSelectFilterOption, dropdownStyle: selectDropdownStyle })), - renderFilterField('定位城市', React.createElement(Cascader, { options: regionOptions, placeholder: '请选择省-市', style: filterControlStyle, value: _areaRegion[0].length ? _areaRegion[0] : undefined, onChange: function (v) { _areaRegion[1](v || []); }, allowClear: true, changeOnSelect: true })) - ]; - }; - - var opsManagerModalMode = _opsManagerModalMode[0]; - var opsManagerModalOpen = opsManagerModalMode === 'single' || opsManagerModalMode === 'batch'; - var opsManagerModalIsBatch = opsManagerModalMode === 'batch'; - var opsManagerModalRecord = _opsManagerModalRecord[0]; - var opsManagerSelectOptions = opsManagerModalIsBatch - ? vmGetAllOpsManagerStaffSelectOptions() - : (opsManagerModalRecord ? vmGetAllOpsManagerSelectOptions(opsManagerModalRecord) : []); - var opsManagerBatchCount = (_opsManagerBatchIds[0] || []).length; - - return React.createElement(App, null, - React.createElement('div', { className: 'vm-page' }, - React.createElement('style', null, VM_PAGE_STYLE), - React.createElement('div', { className: 'vm-list-topbar' }, - React.createElement(Button, { type: 'default', className: 'vm-btn-req', icon: VM_ICONS.doc, onClick: function () { _requirementModalVisible[1](true); }, 'aria-label': '查看需求说明' }, '查看需求说明') - ), - - React.createElement(Card, { className: 'lc-filter-card', title: '筛选条件', bordered: false }, - React.createElement('div', { className: 'lc-filter-grid' }, (function () { - var items = buildFilterFields(); - var limit = _filterExpanded[0] ? items.length : 4; - var out = []; - for (var i = 0; i < limit && i < items.length; i++) out.push(items[i]); - return out; - })()), - React.createElement('div', { className: 'lc-filter-actions' }, - buildFilterFields().length > 4 ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { _filterExpanded[1](!_filterExpanded[0]); }, style: { marginRight: 'auto', paddingLeft: 0 } }, _filterExpanded[0] ? '收起' : '展开更多筛选项') : null, - React.createElement(Button, { onClick: handleFilterReset, style: { borderRadius: 8 } }, '重置'), - React.createElement(Button, { type: 'primary', icon: VM_ICONS.search, onClick: handleFilterQuery, style: VM_PRIMARY_BTN_STYLE }, '查询') - ) - ), - - React.createElement('div', { className: 'lc-alert-stats-row' }, - VM_KPI_CARD_DEFS.map(function (card) { return renderKpiCard(card); }) - ), - - React.createElement('div', { className: 'lc-table-section' }, - React.createElement('div', { className: 'lc-table-toolbar' }, - React.createElement('div', { className: 'lc-table-toolbar-actions' }, - React.createElement(Button, { icon: VM_ICONS.export, onClick: handleExport, style: { borderRadius: 8 } }, '导出'), - React.createElement(Button, { icon: VM_ICONS.upload, onClick: handleBatchImport, style: { borderRadius: 8 } }, '批量导入') - ) - ), - React.createElement('div', { className: 'lc-table-card' }, - React.createElement(Table, { - className: 'lc-list-table', - rowKey: 'id', - columns: columns, - dataSource: displayDataSource, - scroll: { x: 3490 }, - size: 'middle', - locale: { emptyText: emptyNode }, - pagination: { total: listTotal, showSizeChanger: true, showQuickJumper: true, showTotal: function (t) { return '共 ' + t + ' 条记录'; }, defaultPageSize: 10, pageSizeOptions: ['10', '20', '50'] } - }) - ) - ), - - React.createElement(Modal, { - title: '批量导入车辆', - open: _batchImportModalVisible[0], - wrapClassName: 'vm-modal-wrap vm-import-modal-wrap', - onCancel: closeBatchImportModal, - footer: [ - React.createElement(Button, { key: 'close', className: 'vm-import-close-btn', onClick: closeBatchImportModal }, '关闭') - ], - width: 560 - }, - React.createElement('div', { className: 'vm-import-modal' }, - React.createElement('section', { className: 'vm-import-step-card', 'aria-label': '下载导入模板' }, - React.createElement('div', { className: 'vm-import-step-head' }, - React.createElement('span', { className: 'vm-import-step-badge', 'aria-hidden': true }, '1'), - React.createElement('span', { className: 'vm-import-step-title' }, '下载导入模板') - ), - React.createElement(Button, { - type: 'primary', - className: 'vm-import-download-btn', - icon: VM_ICONS.export, - onClick: downloadVehicleImportTemplate, - style: VM_PRIMARY_BTN_STYLE - }, '下载车辆导入模板') - ), - React.createElement('section', { className: 'vm-import-step-card', 'aria-label': '上传导入文件' }, - React.createElement('div', { className: 'vm-import-step-head' }, - React.createElement('span', { className: 'vm-import-step-badge', 'aria-hidden': true }, '2'), - React.createElement('span', { className: 'vm-import-step-title' }, '上传填写好的文件') - ), - React.createElement(Upload.Dragger, { - className: 'vm-import-uploader', - accept: '.csv,.xls,.xlsx', - multiple: false, - beforeUpload: handleVehicleImportUpload, - showUploadList: false - }, - React.createElement('div', { className: 'vm-import-upload-icon', 'aria-hidden': true }, VM_ICONS.upload), - React.createElement('p', { className: 'vm-import-upload-title' }, '点击或拖拽文件到此处上传'), - React.createElement('p', { className: 'vm-import-upload-hint' }, '支持 .csv、.xls、.xlsx') - ) - ), - _batchImportResult[0] && _batchImportResult[0].hasFailed ? React.createElement('div', { className: 'vm-import-fail', role: 'alert' }, - React.createElement('div', { className: 'vm-import-fail-title' }, '校验未通过(共 ' + _batchImportResult[0].failedList.length + ' 条)'), - _batchImportResult[0].failedList.slice(0, 6).map(function (item, idx) { - return React.createElement('div', { key: idx, className: 'vm-import-fail-item' }, - '第 ', item.rowNo, ' 行:', item.plateNo, ' / ', item.vin, ' — ', item.reason - ); - }), - _batchImportResult[0].failedList.length > 6 ? React.createElement('div', { className: 'vm-import-fail-item', style: { color: '#94a3b8' } }, '……还有 ', _batchImportResult[0].failedList.length - 6, ' 条') : null, - React.createElement(Button, { type: 'link', size: 'small', onClick: downloadVehicleImportFailedCsv, style: { paddingLeft: 0, marginTop: 4, minHeight: 44 } }, '下载失败明细') - ) : null - ) - ), - - React.createElement(Modal, { - title: opsManagerModalIsBatch ? '批量设置运维负责人' : '设置运维负责人', - open: opsManagerModalOpen, - wrapClassName: 'vm-modal-wrap', - width: 560, - onCancel: closeOpsManagerModal, - destroyOnClose: true, - footer: [ - React.createElement(Button, { key: 'cancel', onClick: closeOpsManagerModal }, '取消'), - React.createElement(Button, { key: 'submit', type: 'primary', onClick: saveOpsManagers, style: VM_PRIMARY_BTN_STYLE }, opsManagerModalIsBatch ? '批量提交' : '提交') - ] - }, - React.createElement('div', { className: 'vm-ops-manager-modal-body' }, - opsManagerModalIsBatch ? React.createElement('div', { className: 'vm-ops-manager-modal-card' }, - React.createElement('div', { className: 'vm-ops-manager-modal-card-row' }, - React.createElement('span', { className: 'vm-ops-manager-modal-card-label' }, '适用范围'), - React.createElement('span', { className: 'vm-ops-manager-modal-card-value' }, '当前搜索结果,共 ' + opsManagerBatchCount + ' 辆车') - ) - ) : null, - React.createElement('div', { className: 'vm-ops-manager-modal-field' }, - React.createElement('label', { className: 'vm-ops-manager-modal-field-label', htmlFor: 'vm-ops-manager-select' }, '运维负责人'), - React.createElement(Select, { - id: 'vm-ops-manager-select', - mode: 'multiple', - style: { width: '100%' }, - placeholder: '请选择或输入姓名搜索', - value: _opsManagerDraftSelected[0], - options: opsManagerSelectOptions, - onChange: function (vals) { _opsManagerDraftSelected[1](vals || []); }, - allowClear: true, - showSearch: true, - filterOption: vmSelectFilterOption, - optionFilterProp: 'label', - maxTagCount: 'responsive', - dropdownStyle: selectDropdownStyle - }) - ) - ) - ), - - // 车辆上牌:上传/识别中 - React.createElement(Modal, { - title: '上传行驶证', - open: _uploadModalVisible[0], - wrapClassName: 'vm-modal-wrap', - onCancel: closeUploadModal, - footer: [ - React.createElement(Button, { key: 'cancel', onClick: closeUploadModal }, '取消'), - React.createElement(Button, { key: 'ok', type: 'primary', onClick: startOcrThenConfirm, style: VM_PRIMARY_BTN_STYLE }, '开始识别') - ] - }, - React.createElement('div', { className: 'vm-modal-hint' }, '上传行驶证照片后将自动 OCR 识别车辆识别代码与车牌号'), - React.createElement(Upload.Dragger, { accept: 'image/*', multiple: false, style: { borderRadius: 12 } }, - React.createElement('p', { className: 'ant-upload-drag-icon', style: { marginBottom: 8 } }, VM_ICONS.upload), - React.createElement('p', { style: { margin: 0, fontWeight: 600, color: '#334155' } }, '点击或拖拽行驶证照片到此区域') - ) - ), - - React.createElement(Modal, { - title: '识别中,请勿关闭页面', - open: _ocrLoadingVisible[0], - wrapClassName: 'vm-modal-wrap', - footer: null, - closable: false, - maskClosable: false - }, React.createElement('div', { style: { textAlign: 'center', padding: '24px 0' } }, '正在识别行驶证信息...')), - - React.createElement(Modal, { - title: '确认上牌信息', - open: _confirmModalVisible[0], - wrapClassName: 'vm-modal-wrap', - onCancel: closeConfirmModal, - width: 640, - footer: [ - React.createElement(Button, { key: 'cancel', onClick: closeConfirmModal }, '取消'), - React.createElement(Button, { key: 'ok', type: 'primary', onClick: confirmPlate, style: VM_PRIMARY_BTN_STYLE }, '确认') - ] - }, React.createElement('div', { style: { display: 'flex', gap: 24 } }, - React.createElement('div', { style: { flex: '0 0 240px' } }, - React.createElement('div', { style: { marginBottom: 8, color: '#666' } }, '行驶证照片'), - React.createElement('img', { src: 'https://picsum.photos/240/160', alt: '行驶证', style: { width: '100%', borderRadius: 8 } }) - ), - React.createElement('div', { style: { flex: 1 } }, - React.createElement('div', { style: { marginBottom: 12 } }, - React.createElement('span', { style: { marginRight: 8 } }, '车辆识别代码'), - React.createElement(Input, { - value: _plateForm[0].vin, - onChange: function (e) { onPlateFormChange('vin', e.target.value); }, - placeholder: '根据行驶证反写,可编辑', - style: { width: '100%' } - }) - ), - React.createElement('div', { style: { marginBottom: 12 } }, - React.createElement('span', { style: { marginRight: 8 } }, '车牌号'), - React.createElement(Input, { - value: _plateForm[0].plateNo, - onChange: function (e) { onPlateFormChange('plateNo', e.target.value); }, - placeholder: '根据行驶证反写,可编辑', - style: { width: '100%' } - }) - ), - _plateError[0] ? React.createElement('div', { style: { color: '#ff4d4f', fontSize: 12 } }, _plateError[0]) : null - ) - )), - React.createElement(Modal, { - title: '需求说明', - open: _requirementModalVisible[0], - wrapClassName: 'vm-modal-wrap', - onCancel: function () { _requirementModalVisible[1](false); }, - width: 720, - footer: React.createElement(Button, { onClick: function () { _requirementModalVisible[1](false); } }, '关闭') - }, React.createElement('div', { className: 'vm-prd-content' }, requirementText)), - - React.createElement(Modal, { - title: '编辑车辆信息', - open: _editModalVisible[0], - wrapClassName: 'vm-modal-wrap', - onCancel: closeEditModal, - width: 720, - destroyOnClose: true, - footer: [ - React.createElement(Button, { key: 'cancel', onClick: closeEditModal }, '取消'), - React.createElement(Button, { key: 'save', type: 'primary', onClick: saveEdit, style: VM_PRIMARY_BTN_STYLE }, '保存') - ] - }, _editRecord[0] ? React.createElement('div', null, - React.createElement('div', { className: 'vm-edit-banner' }, - '车牌号:', React.createElement('span', { style: { fontWeight: 600, color: 'rgba(0,0,0,0.85)' } }, vmDisplayFieldText(_editRecord[0].plateNo)), - ' 识别代码:', React.createElement('span', { style: { color: 'rgba(0,0,0,0.85)' } }, vmDisplayFieldText(_editRecord[0].vin)) - ), - React.createElement(Form, { layout: 'vertical' }, - React.createElement(Row, { gutter: 16 }, - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '车辆归属停车场', required: true }, - React.createElement(Select, { - placeholder: '请选择停车场', - style: { width: '100%' }, - options: parkingOptions, - value: _editForm[0].parking, - onChange: function (v) { onEditFormChange('parking', v); }, - allowClear: true, - showSearch: true, - filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '运营状态', required: true }, - React.createElement(Select, { - placeholder: '请选择运营状态', - style: { width: '100%' }, - options: operateStatusOptions, - value: _editForm[0].operateStatus, - onChange: function (v) { onEditFormChange('operateStatus', v); } - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '车辆状态', required: true }, - React.createElement(Select, { - placeholder: '请选择车辆状态', - style: { width: '100%' }, - options: vehicleStatusOptions, - value: _editForm[0].vehicleStatus, - onChange: function (v) { onEditFormChange('vehicleStatus', v); } - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '登记所有权' }, - React.createElement(Input, { - placeholder: '请输入登记所有权', - value: _editForm[0].ownership || '', - onChange: function (e) { onEditFormChange('ownership', e.target.value); }, - allowClear: true - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '运营公司' }, - React.createElement(Select, { - placeholder: '请选择运营公司', - style: { width: '100%' }, - options: operateCompanyOptions, - value: _editForm[0].operateCompany, - onChange: function (v) { onEditFormChange('operateCompany', v); }, - allowClear: true, - showSearch: true, - filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '车辆来源' }, - React.createElement(Select, { - placeholder: '请选择车辆来源', - style: { width: '100%' }, - options: vehicleSourceOptions, - value: _editForm[0].vehicleSource, - onChange: function (v) { onEditFormChange('vehicleSource', v); }, - allowClear: true - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '租赁公司' }, - React.createElement(Input, { - placeholder: '请输入租赁公司,无则留空', - value: _editForm[0].leaseCompany || '', - onChange: function (e) { onEditFormChange('leaseCompany', e.target.value); }, - allowClear: true - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '出厂年份' }, - React.createElement(Select, { - placeholder: '请选择出厂年份', - style: { width: '100%' }, - options: yearOptions, - value: _editForm[0].year, - onChange: function (v) { onEditFormChange('year', v); }, - allowClear: true - }) - ) - ), - React.createElement(Col, { span: 12 }, - React.createElement(Form.Item, { label: '采购入库时间', extra: '格式:YYYY-MM-DD' }, - React.createElement(Input, { - placeholder: '请输入采购入库时间', - value: _editForm[0].purchaseDate || '', - onChange: function (e) { onEditFormChange('purchaseDate', e.target.value); }, - allowClear: true - }) - ) - ) - ) - ) - ) : null) - ) - ); -}; - -export default Component; diff --git a/src/prototypes/oneos-web-vehicle-asset/pages/02-车辆管理-查看.jsx b/src/prototypes/oneos-web-vehicle-asset/pages/02-车辆管理-查看.jsx deleted file mode 100644 index 9c61de7..0000000 --- a/src/prototypes/oneos-web-vehicle-asset/pages/02-车辆管理-查看.jsx +++ /dev/null @@ -1,1583 +0,0 @@ -// 【重要】必须使用 const Component 作为组件变量名 -// 运维管理 - 车辆数据 - 车辆详情(只读展示) - -const Component = function () { - var useState = React.useState; - - var antd = window.antd; - var Breadcrumb = antd.Breadcrumb; - var Card = antd.Card; - var Tabs = antd.Tabs; - var Table = antd.Table; - var Tag = antd.Tag; - var Badge = antd.Badge; - var Alert = antd.Alert; - var Button = antd.Button; - var Tooltip = antd.Tooltip; - var Select = antd.Select; - var DatePicker = antd.DatePicker; - var RangePicker = DatePicker.RangePicker; - var message = antd.message; - var Image = antd.Image; - - // 顶部概览 mock 数据(与图片一致) - var overview = { - plateNo: '浙F06900F', - plateTag: '租赁', - vin: 'LA9HE60A0NBAF4031', - vehicleNo: '22FHD0007', - owner: '浙江羚牛氢能科技有限公司', - operateCompany: '羚牛运营(嘉兴)', - vehicleSource: '自有', - leaseCompany: '上海迅杰物流有限公司', - contractNo: 'LNZLHTSH2023071301', - // 与「车辆管理」列表一致:车辆状态、出库状态、证照状态、保险状态枚举见 web端/车辆管理.jsx 注释 - vehicleStatus: '已交车', - outStatus: '无', - licenseStatus: '正常', - insuranceStatus: '正常', - location: '浙江省嘉兴市平湖市XXXXXXXXXXX街道XXXXXXXXXXX号XXXXXXXx', - bodyColor: '白色', - purchaseDate: '2026-09-09', - customerName: '上海迅杰物流有限公司', - gpsLastTime: '2026-09-09 10:50', - manufactureYear: '2025', - parkingPlace: '-', - bizDept: '业务三部', - ratingTime: '2027-09-09', - forceScrapDate: '2026-09-09', - nextInspectionTime: '2026-09-09', - bizManager: '金可鹏' - }; - - // 型号参数 tab 数据 - var modelParams = { - brand: '苏龙', - model: '海格牌KLQ5180XYKFCEV', - vehicleType: '18吨双飞翼货车', - fuelType: '氢', - cabinColor: '绿牌', - wholeSize: '5995mm x 2145mm x 3130mm' - }; - var tireInfo = { tireCount: '8', tireSpec: '15/80R22.5' }; - var electricSystem = { - batteryType: '磷酸铁锂', - batteryVendor: 'xxxxxxxxxxxxx企业名称', - capacity: '100000 kWh', - range: '200 KM' - }; - var hydrogenSystem = { - vendor: '某某供氢系统科技有限公司', - cylinderCapacity: 'xxx L', - gaugeMode: 'MPa', - range: '1000 KM' - }; - var otherSystem = { - coldMachineVendor: 'xxxxxxxx企业', - stackVendor: 'XXXXXXXX企业' - }; - - /** 证照管理 Tab:八类证照只读展示(字段口径对齐「证照管理-编辑」) */ - var LICENSE_VIEW_ANCHOR_DATE = '2026-06-01'; - var licensePhotoPlaceholder = 'data:image/svg+xml,' + encodeURIComponent('暂无影像'); - var licenseViewBundle = { - driverLicense: { - photos: ['https://picsum.photos/seed/vd-zjf-lic/600/400', 'https://picsum.photos/seed/vd-zjf-lic2/600/400'], - regDate: '2025-07-01', - issueDate: '2025-07-01', - scrapDate: '2038-12-31', - expireDate: '2027-07-31', - shNextEvaluation: '' - }, - transportLicense: { - photos: ['https://picsum.photos/seed/vd-zjf-trans/600/400'], - licenseNo: '浙字330482001234号', - issueDate: '2025-07-15', - expireDate: '2026-07-14', - inspectValidUntil: '2026-07-20' - }, - registrationCert: { photos: ['https://picsum.photos/seed/vd-zjf-reg/600/400'] }, - specialEquipCert: { photos: ['https://picsum.photos/seed/vd-zjf-spec/600/400'] }, - specialEquipDecal: { - photos: ['https://picsum.photos/seed/vd-zjf-decal/600/400'], - nextInspectDate: '2026-07-20' - }, - hydrogenCard: { - cardNo: 'H2-3304-0690-0088', - cardType: '中石化加氢卡', - balance: 8650.5, - issueDate: '2025-03-10 10:15', - issueUser: '能源管理部-张晓' - }, - safetyValve: { - photos: ['https://picsum.photos/seed/vd-zjf-valve/600/400'], - inspectDate: '2025-05-10', - nextInspectDate: '2026-05-09' - }, - pressureGauge: { - photos: ['https://picsum.photos/seed/vd-zjf-gauge/600/400'], - inspectDate: '2025-12-15', - nextInspectDate: '2026-06-14' - } - }; - var CERT_NAV_ITEMS = [ - { key: 'driverLicense', label: '行驶证' }, - { key: 'transportLicense', label: '道路运输证' }, - { key: 'registrationCert', label: '登记证' }, - { key: 'specialEquipCert', label: '特种设备使用登记证' }, - { key: 'specialEquipDecal', label: '特种设备使用标识' }, - { key: 'hydrogenCard', label: '加氢卡' }, - { key: 'safetyValve', label: '安全阀' }, - { key: 'pressureGauge', label: '压力表' } - ]; - - var maintenanceList = [ - { key: '1', item: '变速器油', kmCycle: '60000', monthCycle: '24', laborCost: '0', materialCost: '571', total: '571', lastKm: '' }, - { key: '2', item: '变速器油', kmCycle: '60000', monthCycle: '24', laborCost: '0', materialCost: '571', total: '571', lastKm: '' }, - { key: '3', item: '变速器油', kmCycle: '60000', monthCycle: '24', laborCost: '0', materialCost: '571', total: '571', lastKm: '5000' }, - { key: '4', item: '变速器油', kmCycle: '60000', monthCycle: '24', laborCost: '0', materialCost: '571', total: '571', lastKm: '5000' }, - { key: '5', item: '变速器油', kmCycle: '60000', monthCycle: '24', laborCost: '0', materialCost: '571', total: '571', lastKm: '5000' } - ]; - - var activeTab = useState('model'); - var licenseActiveNav = useState('driverLicense'); - var rearFilterDraft = useState({ installRange: null, deviceType: undefined }); - var rearFilterApplied = useState({ installRange: null, deviceType: undefined }); - var leaseFilterDraft = useState({ contractCode: undefined, projectName: undefined, customerName: undefined }); - var leaseFilterApplied = useState({ contractCode: undefined, projectName: undefined, customerName: undefined }); - var accidentFilterDraft = useState({ accidentTimeRange: null, customerName: undefined }); - var accidentFilterApplied = useState({ accidentTimeRange: null, customerName: undefined }); - var violationFilterDraft = useState({ violationTimeRange: null, payStatus: undefined, violationCustomer: undefined }); - var violationFilterApplied = useState({ violationTimeRange: null, payStatus: undefined, violationCustomer: undefined }); - var moveRecordFilterDraft = useState({ moveStartRange: null, destination: undefined, moveType: undefined }); - var moveRecordFilterApplied = useState({ moveStartRange: null, destination: undefined, moveType: undefined }); - var transferRecordFilterDraft = useState({ transferDateRange: null, transferPerson: undefined, receivePerson: undefined }); - var transferRecordFilterApplied = useState({ transferDateRange: null, transferPerson: undefined, receivePerson: undefined }); - var insuranceFilterDraft = useState({ company: undefined, payTimeRange: null }); - var insuranceFilterApplied = useState({ company: undefined, payTimeRange: null }); - - var rearDeviceTypeOptions = [ - { value: 'GPS', label: 'GPS' }, - { value: '尾板', label: '尾板' }, - { value: '车身广告', label: '车身广告' }, - { value: 'G7安全套件', label: 'G7安全套件' }, - { value: 'G7普通设备', label: 'G7普通设备' }, - { value: 'G7温控设备', label: 'G7温控设备' }, - { value: '备胎', label: '备胎' } - ]; - var rearDeviceMockData = [ - { key: 'rd1', deviceType: 'GPS', supplier: '深圳某某物联科技', actionType: '安装', actionTime: '2024-06-10 09:30', operator: '张三' }, - { key: 'rd2', deviceType: '尾板', supplier: '上海凯卓液压设备', actionType: '安装', actionTime: '2024-07-02 14:15', operator: '李四' }, - { key: 'rd3', deviceType: '车身广告', supplier: '广州传媒广告', actionType: '拆卸', actionTime: '2024-08-18 11:00', operator: '王五' }, - { key: 'rd4', deviceType: 'G7安全套件', supplier: 'G7易流科技', actionType: '安装', actionTime: '2024-09-05 16:40', operator: '赵六' }, - { key: 'rd5', deviceType: 'G7普通设备', supplier: 'G7易流科技', actionType: '拆卸', actionTime: '2024-10-12 08:20', operator: '张三' }, - { key: 'rd6', deviceType: 'G7温控设备', supplier: 'G7易流科技', actionType: '安装', actionTime: '2024-11-01 13:45', operator: '陈静' }, - { key: 'rd7', deviceType: '备胎', supplier: '某某汽配供应链', actionType: '安装', actionTime: '2024-11-20 10:00', operator: '李四' }, - { key: 'rd8', deviceType: 'GPS', supplier: '深圳某某物联科技', actionType: '拆卸', actionTime: '2025-01-08 15:30', operator: '王五' } - ]; - - function matchRearInstallRange(valStr, range) { - if (!range || !range[0] || !range[1]) return true; - var s = ''; - var e = ''; - if (range[0] && range[0].format) s = range[0].format('YYYY-MM-DD'); - if (range[1] && range[1].format) e = range[1].format('YYYY-MM-DD'); - if (!s && !e) return true; - var v = String(valStr || '').slice(0, 10); - if (s && v < s) return false; - if (e && v > e) return false; - return true; - } - function fmtRearActionTime(v) { - if (v === null || v === undefined) return '-'; - var str = String(v).trim(); - if (!str) return '-'; - if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}/.test(str)) return str.slice(0, 16); - if (/^\d{4}-\d{2}-\d{2}$/.test(str)) return str + ' 00:00'; - return str; - } - function filterOption(input, option) { - var label = (option && (option.label || option.children)) || ''; - return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0; - } - function fmtDateYMD(v) { - if (v === null || v === undefined) return '-'; - var s = String(v).trim(); - if (!s || s === '-') return '-'; - if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10); - try { - var d = new Date(s.replace(/-/g, '/')); - if (isNaN(d.getTime())) return s; - var p2 = function (n) { return n < 10 ? '0' + n : '' + n; }; - return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate()); - } catch (e) { - return s; - } - } - - var rearDraft = rearFilterDraft[0]; - var setRearDraft = rearFilterDraft[1]; - var rearApplied = rearFilterApplied[0]; - var setRearApplied = rearFilterApplied[1]; - var rearFiltered = rearDeviceMockData.filter(function (row) { - if (!matchRearInstallRange(row.actionTime, rearApplied.installRange)) return false; - if (rearApplied.deviceType && row.deviceType !== rearApplied.deviceType) return false; - return true; - }); - - var leaseMockData = [ - { key: 'ls1', contractCode: 'LNZLHTSH2023071301', projectName: '嘉兴冷链城配A线', customerName: '上海迅杰物流有限公司', deliveryDate: '2024-03-15', deliveryPerson: '金可鹏', returnDate: '2024-09-10', returnPerson: '王明' }, - { key: 'ls2', contractCode: 'HT-2024-009', projectName: '平湖氢能示范运营', customerName: '上海迅杰物流有限公司', deliveryDate: '2025-01-08', deliveryPerson: '李华', returnDate: '-', returnPerson: '-' }, - { key: 'ls3', contractCode: 'ZL-SH-2023-066', projectName: '上海港短驳项目', customerName: '江苏某某供应链有限公司', deliveryDate: '2023-11-20', deliveryPerson: '赵强', returnDate: '2024-05-30', returnPerson: '陈静' }, - { key: 'ls4', contractCode: 'LNZLHTSH2023071301', projectName: '沪浙干线运输', customerName: '浙江某某快运', deliveryDate: '2022-08-01', deliveryPerson: '张三', returnDate: '2023-07-14', returnPerson: '李四' }, - { key: 'ls5', contractCode: 'HT-2024-112', projectName: '长三角城配网络', customerName: '上海迅杰物流有限公司', deliveryDate: '2024-11-01', deliveryPerson: '周伟', returnDate: '2025-02-20', returnPerson: '吴芳' } - ]; - function leaseUniqOptions(rows, field) { - var seen = {}; - var out = []; - for (var i = 0; i < rows.length; i++) { - var v = rows[i][field]; - if (v && v !== '-' && !seen[v]) { - seen[v] = true; - out.push({ value: v, label: v }); - } - } - return out; - } - var leaseContractOptions = leaseUniqOptions(leaseMockData, 'contractCode'); - var leaseProjectOptions = leaseUniqOptions(leaseMockData, 'projectName'); - var leaseCustomerOptions = leaseUniqOptions(leaseMockData, 'customerName'); - var leaseDraft = leaseFilterDraft[0]; - var setLeaseDraft = leaseFilterDraft[1]; - var leaseApplied = leaseFilterApplied[0]; - var setLeaseApplied = leaseFilterApplied[1]; - var leaseFiltered = leaseMockData.filter(function (row) { - if (leaseApplied.contractCode && row.contractCode !== leaseApplied.contractCode) return false; - if (leaseApplied.projectName && row.projectName !== leaseApplied.projectName) return false; - if (leaseApplied.customerName && row.customerName !== leaseApplied.customerName) return false; - return true; - }); - - function matchAccidentDateRange(valStr, range) { - if (!range || !range[0] || !range[1]) return true; - var s = ''; - var e = ''; - if (range[0] && range[0].format) s = range[0].format('YYYY-MM-DD'); - if (range[1] && range[1].format) e = range[1].format('YYYY-MM-DD'); - if (!s && !e) return true; - var v = String(valStr || '').slice(0, 10); - if (s && v < s) return false; - if (e && v > e) return false; - return true; - } - function cellOrDash(v) { - if (v === null || v === undefined) return '-'; - var s = String(v).trim(); - return s ? s : '-'; - } - - var accidentMockData = [ - { - key: 'ac1', - accidentCode: 'SG202508250001', - accidentTime: '2025-08-25', - location: '北京市大兴区京开高速辅路K12段', - accidentType: '撞固定物', - customerName: '北京海龙运输有限公司', - ourLossAmount: '', - otherLossAmount: '', - liability: '全责', - status: '未结案', - closeTime: '', - otherFee: '', - remark: '' - }, - { - key: 'ac2', - accidentCode: 'SG202504180012', - accidentTime: '2025-04-18', - location: '上海市浦东新区外环高速出口匝道', - accidentType: '追尾', - customerName: '上海迅杰物流有限公司', - ourLossAmount: '12800.00', - otherLossAmount: '5600.00', - liability: '主责', - status: '已结案', - closeTime: '2025-05-30', - otherFee: '800.00', - remark: '对方车辆已理赔完毕' - }, - { - key: 'ac3', - accidentCode: 'SG202502030088', - accidentTime: '2025-02-03', - location: '浙江省嘉兴市平湖市乍兴公路与平廊线交叉口', - accidentType: '剐蹭', - customerName: '上海迅杰物流有限公司', - ourLossAmount: '2100.00', - otherLossAmount: '', - liability: '同责', - status: '未结案', - closeTime: '', - otherFee: '', - remark: '' - }, - { - key: 'ac4', - accidentCode: 'SG202411120156', - accidentTime: '2024-11-12', - location: '广东省深圳市南山区南海大道与东滨路交汇处', - accidentType: '侧翻', - customerName: '江苏某某供应链有限公司', - ourLossAmount: '', - otherLossAmount: '32000.00', - liability: '次责', - status: '已结案', - closeTime: '2024-12-20', - otherFee: '1200.50', - remark: '货物转运产生费用' - }, - { - key: 'ac5', - accidentCode: 'SG202409051203', - accidentTime: '2024-09-05', - location: '杭州市余杭区文一西路隧道内', - accidentType: '撞固定物', - customerName: '浙江某某快运有限公司', - ourLossAmount: '8600.00', - otherLossAmount: '', - liability: '全责', - status: '未结案', - closeTime: '', - otherFee: '', - remark: '待交警责任认定书' - } - ]; - var accidentCustomerOptions = leaseUniqOptions(accidentMockData, 'customerName'); - var accidentDraft = accidentFilterDraft[0]; - var setAccidentDraft = accidentFilterDraft[1]; - var accidentApplied = accidentFilterApplied[0]; - var setAccidentApplied = accidentFilterApplied[1]; - var accidentFiltered = accidentMockData.filter(function (row) { - if (!matchAccidentDateRange(row.accidentTime, accidentApplied.accidentTimeRange)) return false; - if (accidentApplied.customerName && row.customerName !== accidentApplied.customerName) return false; - return true; - }); - - var violationPayStatusOptions = [ - { value: '未缴费', label: '未缴费' }, - { value: '已缴费', label: '已缴费' } - ]; - var violationMockData = [ - { - key: 'vz1', - violationCode: 'WZ202602010001', - violationBehavior: '闯红灯', - violationTime: '2026-02-01', - fineAmount: '100.00', - payStatus: '未缴费', - points: '6', - processed: '未处理', - violationCustomer: '上海馨想事成物流有限公司', - photo: '', - remark: '' - }, - { - key: 'vz2', - violationCode: 'WZ202511200088', - violationBehavior: '超速20%以下', - violationTime: '2025-11-20', - fineAmount: '200.00', - payStatus: '已缴费', - points: '3', - processed: '已处理', - violationCustomer: '上海迅杰物流有限公司', - photo: 'mock-url', - remark: '线上已缴清' - }, - { - key: 'vz3', - violationCode: 'WZ202508150045', - violationBehavior: '违法停车', - violationTime: '2025-08-15', - fineAmount: '150.00', - payStatus: '未缴费', - points: '0', - processed: '未处理', - violationCustomer: '北京海龙运输有限公司', - photo: '', - remark: '' - }, - { - key: 'vz4', - violationCode: 'WZ202506030112', - violationBehavior: '不按导向车道行驶', - violationTime: '2025-06-03', - fineAmount: '100.00', - payStatus: '已缴费', - points: '2', - processed: '已处理', - violationCustomer: '江苏某某供应链有限公司', - photo: '', - remark: '' - }, - { - key: 'vz5', - violationCode: 'WZ202503221956', - violationBehavior: '闯禁令标志', - violationTime: '2025-03-22', - fineAmount: '100.00', - payStatus: '未缴费', - points: '3', - processed: '未处理', - violationCustomer: '上海馨想事成物流有限公司', - photo: '', - remark: '复议中' - } - ]; - var violationCustomerOptions = leaseUniqOptions(violationMockData, 'violationCustomer'); - var violationDraft = violationFilterDraft[0]; - var setViolationDraft = violationFilterDraft[1]; - var violationApplied = violationFilterApplied[0]; - var setViolationApplied = violationFilterApplied[1]; - var violationFiltered = violationMockData.filter(function (row) { - if (!matchAccidentDateRange(row.violationTime, violationApplied.violationTimeRange)) return false; - if (violationApplied.payStatus && row.payStatus !== violationApplied.payStatus) return false; - if (violationApplied.violationCustomer && row.violationCustomer !== violationApplied.violationCustomer) return false; - return true; - }); - - var moveDestinationOptions = [ - { value: '维修站', label: '维修站' }, - { value: '停车场', label: '停车场' }, - { value: '其他', label: '其他' } - ]; - var moveTypeFilterOptions = [ - { value: '维修', label: '维修' }, - { value: '保养', label: '保养' }, - { value: '年审', label: '年审' }, - { value: '其他', label: '其他' } - ]; - var moveRecordMockData = [ - { key: 'mv1', moveStartAt: '2026-02-20 09:30', moveEndExpectedAt: '2026-02-22 18:00', approvalStatus: '待提交', destination: '维修站', destinationName: '天河氢能维修中心', moveType: '维修', expectedMileageKm: '45.5', startMileageKm: '15230.12', startElectricKwh: '68.40', startHydrogen: '28.30', h2Unit: 'MPa', creatorName: '张明', createTime: '2026-02-20 08:10' }, - { key: 'mv2', moveStartAt: '2026-02-21 14:00', moveEndExpectedAt: '2026-02-21 17:30', approvalStatus: '审批中', destination: '停车场', destinationName: '西湖景区停车场', moveType: '保养', expectedMileageKm: '12.0', startMileageKm: '12010.00', startElectricKwh: '55.20', startHydrogen: '60.00', h2Unit: '%', creatorName: '王芳', createTime: '2026-02-21 13:20' }, - { key: 'mv3', moveStartAt: '2026-02-24 08:15', moveEndExpectedAt: '2026-02-24 12:00', approvalStatus: '审批完成', destination: '其他', destinationName: '车管所检测线', moveType: '年审', expectedMileageKm: '8.2', startMileageKm: '8020.50', startElectricKwh: '62.10', startHydrogen: '55.00', h2Unit: '%', creatorName: '李华', createTime: '2026-02-24 07:50' }, - { key: 'mv4', moveStartAt: '2026-02-25 10:00', moveEndExpectedAt: '2026-02-26 09:00', approvalStatus: '驳回', destination: '维修站', destinationName: '张江服务站', moveType: '其他', expectedMileageKm: '30.0', startMileageKm: '9800.00', startElectricKwh: '69.00', startHydrogen: '26.00', h2Unit: 'MPa', creatorName: '赵强', createTime: '2026-02-25 09:40' }, - { key: 'mv5', moveStartAt: '2026-02-26 16:20', moveEndExpectedAt: '2026-02-27 10:00', approvalStatus: '撤回', destination: '停车场', destinationName: '宁波江北停车场', moveType: '维修', expectedMileageKm: '22.8', startMileageKm: '15010.00', startElectricKwh: '58.00', startHydrogen: '22.10', h2Unit: 'MPa', creatorName: '陈静', createTime: '2026-02-26 15:55' }, - { key: 'mv6', moveStartAt: '2026-02-27 07:45', moveEndExpectedAt: '2026-02-27 19:00', approvalStatus: '审批中', destination: '维修站', destinationName: '苏州园区快修站', moveType: '保养', expectedMileageKm: '18.6', startMileageKm: '23105.80', startElectricKwh: '42.50', startHydrogen: '45.20', h2Unit: '%', creatorName: '张明', createTime: '2026-02-27 07:20' }, - { key: 'mv7', moveStartAt: '2026-02-27 11:00', moveEndExpectedAt: '2026-02-28 17:30', approvalStatus: '审批完成', destination: '其他', destinationName: '顺义综合检测场', moveType: '年审', expectedMileageKm: '35.0', startMileageKm: '8890.00', startElectricKwh: '71.20', startHydrogen: '31.50', h2Unit: 'MPa', creatorName: '王芳', createTime: '2026-02-27 10:35' }, - { key: 'mv8', moveStartAt: '2026-02-28 08:30', moveEndExpectedAt: '2026-02-28 14:00', approvalStatus: '待提交', destination: '停车场', destinationName: '两江新区枢纽停车场', moveType: '其他', expectedMileageKm: '6.5', startMileageKm: '17660.25', startElectricKwh: '48.90', startHydrogen: '58.00', h2Unit: '%', creatorName: '李华', createTime: '2026-02-28 08:05' }, - { key: 'mv9', moveStartAt: '2026-02-28 13:15', moveEndExpectedAt: '2026-03-01 09:00', approvalStatus: '驳回', destination: '维修站', destinationName: '成都高新氢能维保站', moveType: '维修', expectedMileageKm: '52.3', startMileageKm: '34500.00', startElectricKwh: '33.60', startHydrogen: '24.80', h2Unit: 'MPa', creatorName: '赵强', createTime: '2026-02-28 12:50' }, - { key: 'mv10', moveStartAt: '2026-03-01 06:00', moveEndExpectedAt: '2026-03-01 11:45', approvalStatus: '审批完成', destination: '其他', destinationName: '青岛港务临时停放点', moveType: '保养', expectedMileageKm: '15.0', startMileageKm: '11220.00', startElectricKwh: '65.00', startHydrogen: '62.50', h2Unit: '%', creatorName: '陈静', createTime: '2026-03-01 05:40' } - ]; - var moveRecordDraft = moveRecordFilterDraft[0]; - var setMoveRecordDraft = moveRecordFilterDraft[1]; - var moveRecordApplied = moveRecordFilterApplied[0]; - var setMoveRecordApplied = moveRecordFilterApplied[1]; - var moveRecordFiltered = moveRecordMockData.filter(function (row) { - if (!matchAccidentDateRange(row.moveStartAt, moveRecordApplied.moveStartRange)) return false; - if (moveRecordApplied.destination && row.destination !== moveRecordApplied.destination) return false; - if (moveRecordApplied.moveType && row.moveType !== moveRecordApplied.moveType) return false; - return true; - }); - - var transferMockData = [ - { key: 'tp1', transferDate: '2026-02-20 09:30', departRegionText: '广东省-广州市', transferPerson: '张明', receiveRegionText: '浙江省-嘉兴市', receivePerson: '王芳', approvalStatus: '待提交', method: '第三方运输', reason: '场站资源调整', vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', carrierName: '顺丰同城运输', departMileageKm: '15230.12', departHydrogen: '28.30', h2Unit: 'MPa', departElectricKwh: '68.40', departParking: '天河智慧停车场', createDate: '2026-02-20 08:10' }, - { key: 'tp2', transferDate: '2026-02-22 14:10', departRegionText: '浙江省-杭州市', transferPerson: '李华', receiveRegionText: '上海市-上海市', receivePerson: '赵强', approvalStatus: '审批完成', method: '司机驾驶', reason: '项目调度需要', vehicleType: 'SUV', brand: '小鹏', model: 'P7', carrierName: '-', departMileageKm: '12010.00', departHydrogen: '60.00', h2Unit: '%', departElectricKwh: '55.20', departParking: '西湖景区停车场', createDate: '2026-02-22 12:40' }, - { key: 'tp3', transferDate: '2026-02-24 11:05', departRegionText: '广东省-深圳市', transferPerson: '王芳', receiveRegionText: '浙江省-宁波市', receivePerson: '陈静', approvalStatus: '审批完成', method: '第三方运输', reason: '维修站资源调整', vehicleType: '轿车', brand: '比亚迪', model: '汉', carrierName: '德邦物流', departMileageKm: '8020.50', departHydrogen: '55.00', h2Unit: '%', departElectricKwh: '62.10', departParking: '南山科技园停车场', createDate: '2026-02-24 10:20' }, - { key: 'tp4', transferDate: '2026-02-25 16:40', departRegionText: '浙江省-嘉兴市', transferPerson: '赵强', receiveRegionText: '上海市-上海市', receivePerson: '李华', approvalStatus: '待提交', method: '司机驾驶', reason: '项目调度需要', vehicleType: '轿车', brand: '蔚来', model: 'ET5', carrierName: '-', departMileageKm: '15010.00', departHydrogen: '22.10', h2Unit: 'MPa', departElectricKwh: '58.00', departParking: '宁波江北停车场', createDate: '2026-02-25 15:55' }, - { key: 'tp5', transferDate: '2026-02-26 09:15', departRegionText: '上海市-上海市', transferPerson: '陈静', receiveRegionText: '广东省-广州市', receivePerson: '张明', approvalStatus: '审批驳回', method: '第三方运输', reason: '车辆调拨申请驳回样例', vehicleType: '厢式货车', brand: '福田', model: 'BJ1180', carrierName: '顺丰同城运输', departMileageKm: '9800.00', departHydrogen: '26.00', h2Unit: 'MPa', departElectricKwh: '69.00', departParking: '张江园区停车场', createDate: '2026-02-26 08:30' }, - { key: 'tp6', transferDate: '2026-02-27 13:30', departRegionText: '浙江省-杭州市', transferPerson: '张明', receiveRegionText: '浙江省-嘉兴市', receivePerson: '王芳', approvalStatus: '撤回', method: '司机驾驶', reason: '计划变更,撤回申请', vehicleType: 'SUV', brand: '小鹏', model: 'P7', carrierName: '-', departMileageKm: '12100.20', departHydrogen: '58.00', h2Unit: '%', departElectricKwh: '54.80', departParking: '西湖景区停车场', createDate: '2026-02-27 12:45' }, - { key: 'tp7', transferDate: '2026-02-28 08:50', departRegionText: '广东省-东莞市', transferPerson: '李华', receiveRegionText: '广东省-深圳市', receivePerson: '赵强', approvalStatus: '审批中', method: '第三方运输', reason: '跨区域调拨', vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', carrierName: '中外运', departMileageKm: '15310.00', departHydrogen: '27.50', h2Unit: 'MPa', departElectricKwh: '67.20', departParking: '松山湖停车场', createDate: '2026-02-28 08:05' }, - { key: 'tp8', transferDate: '2026-03-01 10:10', departRegionText: '浙江省-宁波市', transferPerson: '王芳', receiveRegionText: '上海市-上海市', receivePerson: '陈静', approvalStatus: '待提交', method: '司机驾驶', reason: '项目新增点位支持', vehicleType: '轿车', brand: '蔚来', model: 'ET5', carrierName: '-', departMileageKm: '15120.00', departHydrogen: '21.80', h2Unit: 'MPa', departElectricKwh: '57.50', departParking: '宁波江北停车场', createDate: '2026-03-01 09:30' }, - { key: 'tp9', transferDate: '2026-03-02 15:25', departRegionText: '上海市-上海市', transferPerson: '赵强', receiveRegionText: '浙江省-杭州市', receivePerson: '李华', approvalStatus: '审批中', method: '第三方运输', reason: '场站容量调整', vehicleType: '厢式货车', brand: '福田', model: 'BJ1180', carrierName: '德邦物流', departMileageKm: '9850.00', departHydrogen: '24.20', h2Unit: 'MPa', departElectricKwh: '68.30', departParking: '张江园区停车场', createDate: '2026-03-02 14:40' }, - { key: 'tp10', transferDate: '2026-03-03 09:05', departRegionText: '浙江省-嘉兴市', transferPerson: '陈静', receiveRegionText: '广东省-广州市', receivePerson: '张明', approvalStatus: '待提交', method: '第三方运输', reason: '车辆调拨支持新项目', vehicleType: 'SUV', brand: '小鹏', model: 'P7', carrierName: '顺丰同城运输', departMileageKm: '12220.00', departHydrogen: '56.00', h2Unit: '%', departElectricKwh: '53.90', departParking: '嘉兴南湖停车场', createDate: '2026-03-03 08:20' } - ]; - var transferPersonOptions = leaseUniqOptions(transferMockData, 'transferPerson'); - var transferReceivePersonOptions = leaseUniqOptions(transferMockData, 'receivePerson'); - var transferDraft = transferRecordFilterDraft[0]; - var setTransferDraft = transferRecordFilterDraft[1]; - var transferApplied = transferRecordFilterApplied[0]; - var setTransferApplied = transferRecordFilterApplied[1]; - var transferFiltered = transferMockData.filter(function (row) { - if (!matchAccidentDateRange(row.transferDate, transferApplied.transferDateRange)) return false; - if (transferApplied.transferPerson && row.transferPerson !== transferApplied.transferPerson) return false; - if (transferApplied.receivePerson && row.receivePerson !== transferApplied.receivePerson) return false; - return true; - }); - - var insuranceMockData = [ - { key: 'ins1', company: '中国人民财产保险股份有限公司', insuranceType: '交强险', policyNo: 'PDZA202533048200000123', startDate: '2025-01-01', endDate: '2025-12-31', payTime: '2025-01-05', premium: '950.00', insuredName: '浙江羚牛氢能科技有限公司' }, - { key: 'ins2', company: '中国人民财产保险股份有限公司', insuranceType: '商业险', policyNo: 'PDAA202533048200000456', startDate: '2025-01-01', endDate: '2025-12-31', payTime: '2025-01-05', premium: '12800.50', insuredName: '浙江羚牛氢能科技有限公司' }, - { key: 'ins3', company: '中国平安财产保险股份有限公司', insuranceType: '承运人责任险', policyNo: 'PAIC-HY-2025-8899', startDate: '2025-03-15', endDate: '2026-03-14', payTime: '2025-03-10', premium: '5600.00', insuredName: '浙江羚牛氢能科技有限公司' }, - { key: 'ins4', company: '中国太平洋财产保险股份有限公司', insuranceType: '交强险', policyNo: 'CPIC-JQ-2024-7788', startDate: '2024-06-01', endDate: '2025-05-31', payTime: '2024-05-28', premium: '880.00', insuredName: '上海迅杰物流有限公司' }, - { key: 'ins5', company: '中国人寿财产保险股份有限公司', insuranceType: '商业险', policyNo: 'GPIC-SY-2025-1122', startDate: '2025-07-01', endDate: '2026-06-30', payTime: '2025-06-25', premium: '9850.00', insuredName: '浙江羚牛氢能科技有限公司' }, - { key: 'ins6', company: '阳光财产保险股份有限公司', insuranceType: '交强险', policyNo: 'YGCI-JQ-2025-3301', startDate: '2025-09-01', endDate: '2026-08-31', payTime: '2025-08-28', premium: '950.00', insuredName: '浙江羚牛氢能科技有限公司' } - ]; - var insuranceCompanyOptions = leaseUniqOptions(insuranceMockData, 'company'); - var insuranceDraft = insuranceFilterDraft[0]; - var setInsuranceDraft = insuranceFilterDraft[1]; - var insuranceApplied = insuranceFilterApplied[0]; - var setInsuranceApplied = insuranceFilterApplied[1]; - var insuranceFiltered = insuranceMockData.filter(function (row) { - if (insuranceApplied.company && row.company !== insuranceApplied.company) return false; - if (!matchAccidentDateRange(row.payTime, insuranceApplied.payTimeRange)) return false; - return true; - }); - - var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' }; - var filterItemStyle = { marginBottom: 12 }; - - var styles = { - page: { padding: '16px 24px 40px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontSize: 14 }, - breadcrumb: { marginBottom: 16, color: '#666' }, - breadcrumbSep: { margin: '0 8px', color: '#999' }, - card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }, - cardBody: { padding: '20px 24px' }, - overviewHeader: { display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20, flexWrap: 'wrap' }, - plateNo: { fontSize: 18, fontWeight: 600, color: '#333' }, - overviewGrid: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px 24px' }, - overviewItem: { display: 'flex', gap: 8, minWidth: 0 }, - overviewLabel: { color: '#666', flex: '0 0 110px' }, - overviewValue: { color: '#333', flex: 1 }, - cardTitleBar: { display: 'flex', alignItems: 'center', marginBottom: 16, fontSize: 15, fontWeight: 500 }, - cardTitleBarLine: { width: 4, height: 16, backgroundColor: '#1890ff', marginRight: 8, borderRadius: 2 }, - infoGrid: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '12px 24px', marginBottom: 20 }, - infoItem: { display: 'flex', gap: 8 }, - infoLabel: { color: '#666', flex: '0 0 100px' }, - infoValue: { color: '#333' } - }; - - function InfoRow(props) { - var val = props.value || '-'; - var valueNode = props.ellipsisWithTooltip - ? React.createElement(Tooltip, { title: val }, - React.createElement('span', { - style: Object.assign({}, styles.overviewValue, { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block' }) - }, val) - ) - : React.createElement('span', { style: styles.overviewValue }, val); - return React.createElement('div', { style: styles.overviewItem }, - React.createElement('span', { style: styles.overviewLabel }, props.label + ':'), - valueNode - ); - } - - function CardTitleWithBar(props) { - return React.createElement('div', { style: styles.cardTitleBar }, - React.createElement('span', { style: styles.cardTitleBarLine }), - props.title - ); - } - - function LicViewFieldRow(label, value) { - var show = value != null && String(value).trim() !== '' && String(value).trim() !== '—'; - return React.createElement('div', { style: { display: 'flex', gap: 8, marginBottom: 10, fontSize: 14, lineHeight: '22px' } }, - React.createElement('span', { style: { color: 'rgba(0,0,0,0.45)', width: 168, flexShrink: 0 } }, label + ':'), - React.createElement('span', { style: { color: 'rgba(0,0,0,0.85)' } }, show ? value : '—') - ); - } - - function mergeLicViewStatus(a, b) { - var rank = { error: 0, warning: 1, default: 2, success: 3, processing: 2 }; - var ra = rank[a] != null ? rank[a] : 9; - var rb = rank[b] != null ? rank[b] : 9; - return ra <= rb ? a : b; - } - - function diffDaysFromAnchor(dateStr) { - if (!dateStr) return null; - var exp = new Date(dateStr); - var today = new Date(LICENSE_VIEW_ANCHOR_DATE); - exp.setHours(0, 0, 0, 0); - today.setHours(0, 0, 0, 0); - return Math.ceil((exp.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); - } - - function getLicViewBadgeStatus(key) { - var item = licenseViewBundle[key]; - if (!item) return 'default'; - if (key === 'hydrogenCard') { - return item.cardNo ? 'success' : 'default'; - } - if (!item.photos || !item.photos.length) return 'default'; - if (key === 'driverLicense') { - var dd = diffDaysFromAnchor(item.expireDate); - if (dd == null) return 'success'; - if (dd <= 0) return 'error'; - if (dd <= 90) return 'warning'; - return 'success'; - } - if (key === 'transportLicense') { - var fromDate = function (d) { - var days = diffDaysFromAnchor(d); - if (days == null) return 'success'; - if (days <= 0) return 'error'; - if (days <= 60) return 'warning'; - return 'success'; - }; - return mergeLicViewStatus(fromDate(item.expireDate), fromDate(item.inspectValidUntil)); - } - if (key === 'specialEquipDecal' || key === 'safetyValve' || key === 'pressureGauge') { - var nd = diffDaysFromAnchor(item.nextInspectDate); - if (nd == null) return 'success'; - if (nd <= 0) return 'error'; - if (nd <= 60) return 'warning'; - return 'success'; - } - return 'success'; - } - - function scrollToLicenseAnchor(key, setActive) { - setActive(key); - var el = document.getElementById('vd-lic-' + key); - if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - - function LicViewPhotoGrid(photos, photoLabel) { - var list = photos && photos.length ? photos : []; - if (!list.length) { - return React.createElement('div', { style: { flexShrink: 0 } }, - React.createElement('div', { style: { marginBottom: 8, color: 'rgba(0,0,0,0.45)', fontSize: 13 } }, photoLabel || '证件照片'), - React.createElement(Image, { - width: 168, - height: 112, - style: { objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0', background: '#fafafa' }, - src: licensePhotoPlaceholder, - alt: '暂无影像', - preview: false - }) - ); - } - return React.createElement('div', { style: { flexShrink: 0 } }, - React.createElement('div', { style: { marginBottom: 8, color: 'rgba(0,0,0,0.45)', fontSize: 13 } }, - (photoLabel || '证件照片') + (list.length > 1 ? '(' + list.length + '张)' : '') - ), - React.createElement('div', { style: { display: 'flex', gap: 12, flexWrap: 'wrap' } }, - list.map(function (url, idx) { - return React.createElement(Image, { - key: idx, - width: 168, - height: 112, - style: { objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0', background: '#fafafa' }, - src: url, - alt: (photoLabel || '证照') + '-' + (idx + 1), - preview: true - }); - }) - ) - ); - } - - function LicViewCertCard(cfg) { - var fields = cfg.fields || []; - var right = fields.length - ? React.createElement('div', { - style: { flex: 1, minWidth: 260, display: 'flex', alignItems: 'center', alignSelf: 'stretch' } - }, React.createElement('div', { style: { width: '100%' } }, - fields.map(function (f, i) { return React.createElement('div', { key: i }, LicViewFieldRow(f.label, f.value)); }) - )) - : null; - return React.createElement(Card, { - id: 'vd-lic-' + cfg.id, - size: 'small', - style: { marginBottom: 16, scrollMarginTop: 72 }, - title: cfg.title - }, - cfg.alertTop || null, - React.createElement('div', { style: { display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'stretch' } }, - LicViewPhotoGrid(cfg.photos, cfg.photoLabel), - right - ), - cfg.extraBottom || null - ); - } - - function fmtH2Balance(n) { - var num = Number(n); - if (!Number.isFinite(num)) return '—'; - return '¥ ' + num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); - } - - var lic = licenseViewBundle; - var licNavActive = licenseActiveNav[0]; - var setLicNavActive = licenseActiveNav[1]; - var isShPlate = String(overview.plateNo || '').indexOf('沪') === 0; - - var driverDays = diffDaysFromAnchor(lic.driverLicense.expireDate); - var transportExpDays = diffDaysFromAnchor(lic.transportLicense.expireDate); - var decalDays = diffDaysFromAnchor(lic.specialEquipDecal.nextInspectDate); - - var licenseIndexBar = React.createElement(Card, { - size: 'small', - style: { marginBottom: 16, borderRadius: 12, border: '1px solid #e2e8f0' }, - bodyStyle: { padding: '14px 16px' } - }, - React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 12 } }, - React.createElement('span', { style: { fontSize: 14, fontWeight: 700, color: '#0f172a' } }, '证照分类索引'), - React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10, fontSize: 11, color: '#64748b' } }, - React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } }, React.createElement(Badge, { status: 'success' }), '正常'), - React.createElement(Tooltip, { title: '行驶证≤90天 / 道路运输证≤60天 / 特种设备标识≤60天 / 安全阀≤60天 / 压力表≤60天' }, - React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4, cursor: 'help' } }, React.createElement(Badge, { status: 'warning' }), '临期') - ), - React.createElement(Tooltip, { title: '对应证件检验/有效期已到期' }, - React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4, cursor: 'help' } }, React.createElement(Badge, { status: 'error' }), '已到期') - ), - React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } }, React.createElement(Badge, { status: 'default' }), '未上传') - ) - ), - React.createElement('div', { - className: 'vd-lic-nav-grid', - style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 8 }, - role: 'navigation', - 'aria-label': '证照索引' - }, - CERT_NAV_ITEMS.map(function (nav) { - var active = licNavActive === nav.key; - return React.createElement('button', { - key: nav.key, - type: 'button', - onClick: function () { scrollToLicenseAnchor(nav.key, setLicNavActive); }, - style: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - gap: 8, - padding: '8px 12px', - borderRadius: 8, - fontSize: 13, - fontWeight: active ? 600 : 500, - color: active ? '#065f46' : '#475569', - background: active ? '#ecfdf5' : '#f8fafc', - border: active ? '1px solid #a7f3d0' : '1px solid #f1f5f9', - cursor: 'pointer', - textAlign: 'left' - } - }, - React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, nav.label), - React.createElement(Badge, { status: getLicViewBadgeStatus(nav.key) }) - ); - }) - ) - ); - - var licenseManagementTabContent = React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement('style', null, '@media (max-width:992px){.vd-lic-nav-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}'), - licenseIndexBar, - LicViewCertCard({ - id: 'driverLicense', - title: '行驶证', - photoLabel: '行驶证照片', - photos: lic.driverLicense.photos, - alertTop: driverDays != null && driverDays <= 90 - ? React.createElement(Alert, { - type: driverDays <= 0 ? 'error' : driverDays <= 30 ? 'error' : 'warning', - showIcon: true, - style: { marginBottom: 16, borderRadius: 8 }, - message: '行驶证检验临期提醒', - description: driverDays <= 0 - ? '检验有效期已到期,请尽快安排年检。' - : ('距离检验有效期至还剩 ' + driverDays + ' 天(系统提前 90 天感知)。') - }) - : null, - fields: [ - { label: '注册日期', value: lic.driverLicense.regDate }, - { label: '发证日期', value: lic.driverLicense.issueDate }, - { label: '强制报废日期', value: lic.driverLicense.scrapDate }, - { label: '检验有效期至', value: lic.driverLicense.expireDate } - ], - extraBottom: isShPlate && lic.driverLicense.shNextEvaluation - ? React.createElement('div', { style: { marginTop: 16, padding: 12, background: '#f5f3ff', borderRadius: 8, border: '1px solid #ddd6fe' } }, - LicViewFieldRow('下次等级评定时间', lic.driverLicense.shNextEvaluation) - ) - : null - }), - LicViewCertCard({ - id: 'transportLicense', - title: '道路运输证', - photoLabel: '道路运输证照片', - photos: lic.transportLicense.photos, - alertTop: transportExpDays != null && transportExpDays <= 60 - ? React.createElement(Alert, { - type: transportExpDays <= 0 ? 'error' : transportExpDays <= 15 ? 'error' : 'warning', - showIcon: true, - style: { marginBottom: 16, borderRadius: 8 }, - message: '道路运输证临期提醒', - description: '证件有效期距今日 ' + transportExpDays + ' 天(系统提前 60 天列入年审任务)。' - }) - : null, - fields: [ - { label: '经营许可证号', value: lic.transportLicense.licenseNo }, - { label: '核发时间', value: lic.transportLicense.issueDate }, - { label: '证件有效期', value: lic.transportLicense.expireDate }, - { label: '审验有效期', value: lic.transportLicense.inspectValidUntil } - ] - }), - LicViewCertCard({ - id: 'registrationCert', - title: '登记证', - photoLabel: '登记证照片', - photos: lic.registrationCert.photos, - fields: [] - }), - LicViewCertCard({ - id: 'specialEquipCert', - title: '特种设备使用登记证', - photoLabel: '使用登记证照片', - photos: lic.specialEquipCert.photos, - fields: [] - }), - LicViewCertCard({ - id: 'specialEquipDecal', - title: '特种设备使用标识', - photoLabel: '使用安全标识照片', - photos: lic.specialEquipDecal.photos, - alertTop: decalDays != null && decalDays <= 60 - ? React.createElement(Alert, { - type: decalDays <= 0 || decalDays <= 15 ? 'error' : 'warning', - showIcon: true, - style: { marginBottom: 16, borderRadius: 8 }, - message: '特种设备使用标识检验提醒', - description: decalDays <= 0 - ? ('下次检验日期已逾期 ' + Math.abs(decalDays) + ' 天。') - : ('距离下次检验日期还剩 ' + decalDays + ' 天(提前 60 天感知)。') - }) - : null, - fields: [{ label: '下次检验日期', value: lic.specialEquipDecal.nextInspectDate }] - }), - React.createElement(Card, { - id: 'vd-lic-hydrogenCard', - size: 'small', - style: { marginBottom: 16, scrollMarginTop: 72 }, - title: '加氢卡' - }, - React.createElement('div', { style: { display: 'flex', gap: 24, flexWrap: 'wrap' } }, - React.createElement('div', { - style: { - flex: '0 0 280px', - maxWidth: 320, - padding: 20, - borderRadius: 12, - background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)', - color: '#e2e8f0', - boxShadow: '0 8px 24px rgba(15,23,42,0.15)' - } - }, - React.createElement('div', { style: { fontSize: 11, color: '#94a3b8', marginBottom: 8 } }, lic.hydrogenCard.cardType || '加氢卡'), - React.createElement('div', { style: { fontSize: 18, fontWeight: 700, letterSpacing: 2, marginBottom: 16, fontFamily: 'monospace' } }, - lic.hydrogenCard.cardNo || '—' - ), - React.createElement('div', { style: { fontSize: 22, fontWeight: 800, color: '#34d399' } }, fmtH2Balance(lic.hydrogenCard.balance)) - ), - React.createElement('div', { style: { flex: 1, minWidth: 240 } }, - LicViewFieldRow('加氢卡号', lic.hydrogenCard.cardNo), - LicViewFieldRow('卡类型', lic.hydrogenCard.cardType), - LicViewFieldRow('实时余额', fmtH2Balance(lic.hydrogenCard.balance)), - LicViewFieldRow('配发时间', lic.hydrogenCard.issueDate), - LicViewFieldRow('配发经办人', lic.hydrogenCard.issueUser) - ) - ) - ), - LicViewCertCard({ - id: 'safetyValve', - title: '安全阀', - photoLabel: '安全阀校验报告照片', - photos: lic.safetyValve.photos, - fields: [ - { label: '检验日期', value: lic.safetyValve.inspectDate }, - { label: '下次检验日期', value: lic.safetyValve.nextInspectDate } - ] - }), - LicViewCertCard({ - id: 'pressureGauge', - title: '压力表', - photoLabel: '压力表校验报告照片', - photos: lic.pressureGauge.photos, - fields: [ - { label: '检验日期', value: lic.pressureGauge.inspectDate }, - { label: '下次检验日期', value: lic.pressureGauge.nextInspectDate } - ] - }) - ); - - var tabItems = [ - { - key: 'model', - label: '型号参数', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement(CardTitleWithBar, { title: '型号参数' }), - React.createElement('div', { style: styles.infoGrid }, - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '品牌:'), React.createElement('span', { style: styles.infoValue }, modelParams.brand)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '型号:'), React.createElement('span', { style: styles.infoValue }, modelParams.model)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '车辆类型:'), React.createElement('span', { style: styles.infoValue }, modelParams.vehicleType)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '燃料种类:'), React.createElement('span', { style: styles.infoValue }, modelParams.fuelType)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '车厢颜色:'), React.createElement('span', { style: styles.infoValue }, modelParams.cabinColor)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '整车尺寸:'), React.createElement('span', { style: styles.infoValue }, modelParams.wholeSize)) - ) - ), - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement(CardTitleWithBar, { title: '轮胎情况' }), - React.createElement('div', { style: styles.infoGrid }, - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '轮胎数量:'), React.createElement('span', { style: styles.infoValue }, tireInfo.tireCount)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '轮胎规格:'), React.createElement('span', { style: styles.infoValue }, tireInfo.tireSpec)) - ) - ), - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement(CardTitleWithBar, { title: '电气系统' }), - React.createElement('div', { style: styles.infoGrid }, - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '电池类型:'), React.createElement('span', { style: styles.infoValue }, electricSystem.batteryType)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '电池厂家:'), React.createElement('span', { style: styles.infoValue }, electricSystem.batteryVendor)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '储电量:'), React.createElement('span', { style: styles.infoValue }, electricSystem.capacity)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '续航里程:'), React.createElement('span', { style: styles.infoValue }, electricSystem.range)) - ) - ), - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement(CardTitleWithBar, { title: '供氢系统' }), - React.createElement('div', { style: styles.infoGrid }, - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '供氢系统厂家:'), React.createElement('span', { style: styles.infoValue }, hydrogenSystem.vendor)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '氢瓶容量:'), React.createElement('span', { style: styles.infoValue }, hydrogenSystem.cylinderCapacity)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '仪表盘模式:'), React.createElement('span', { style: styles.infoValue }, hydrogenSystem.gaugeMode)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '续航里程:'), React.createElement('span', { style: styles.infoValue }, hydrogenSystem.range)) - ) - ), - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement(CardTitleWithBar, { title: '其他系统' }), - React.createElement('div', { style: styles.infoGrid }, - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '冷机生产企业:'), React.createElement('span', { style: styles.infoValue }, otherSystem.coldMachineVendor)), - React.createElement('div', { style: styles.infoItem }, React.createElement('span', { style: styles.infoLabel }, '电堆生产企业:'), React.createElement('span', { style: styles.infoValue }, otherSystem.stackVendor)) - ) - ), - React.createElement(Card, { size: 'small' }, - React.createElement(CardTitleWithBar, { title: '保养参数' }), - React.createElement(Table, { - dataSource: maintenanceList, - columns: [ - { title: '序号', dataIndex: 'key', key: 'key', width: 60, render: function (_, __, i) { return i + 1; } }, - { title: '养护项目', dataIndex: 'item', key: 'item', width: 120 }, - { title: '保养公里周期(km)', dataIndex: 'kmCycle', key: 'kmCycle', width: 140 }, - { title: '保养时间周期(月)', dataIndex: 'monthCycle', key: 'monthCycle', width: 140 }, - { title: '工时费(元)', dataIndex: 'laborCost', key: 'laborCost', width: 100 }, - { title: '材料费(元)', dataIndex: 'materialCost', key: 'materialCost', width: 100 }, - { title: '合计', dataIndex: 'total', key: 'total', width: 80 }, - { title: '上次保养公里数(KM)', dataIndex: 'lastKm', key: 'lastKm', render: function (v) { return v || '—'; } } - ], - pagination: false, - size: 'small', - bordered: true - }) - ) - ) - }, - { - key: 'equip', - label: '后装设备', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px 24px', maxWidth: 900, alignItems: 'start' } }, - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '安装时间'), - React.createElement(RangePicker, { - style: { width: '100%' }, - value: rearDraft.installRange, - onChange: function (v) { setRearDraft(function (p) { return Object.assign({}, p, { installRange: v }); }); }, - placeholder: ['开始日期', '结束日期'] - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '设备类型'), - React.createElement(Select, { - placeholder: '请选择设备类型', - style: { width: '100%' }, - value: rearDraft.deviceType, - onChange: function (v) { setRearDraft(function (p) { return Object.assign({}, p, { deviceType: v }); }); }, - allowClear: true, - options: rearDeviceTypeOptions - }) - ) - ), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { onClick: function () { - var empty = { installRange: null, deviceType: undefined }; - setRearDraft(empty); - setRearApplied(empty); - } }, '重置'), - React.createElement(Button, { type: 'primary', onClick: function () { setRearApplied(Object.assign({}, rearDraft)); } }, '查询') - ) - ), - React.createElement(Table, { - size: 'small', - rowKey: 'key', - bordered: true, - dataSource: rearFiltered, - columns: [ - { title: '设备类型', dataIndex: 'deviceType', key: 'deviceType', width: 140 }, - { title: '供应商', dataIndex: 'supplier', key: 'supplier', width: 200, ellipsis: true }, - { title: '类型', dataIndex: 'actionType', key: 'actionType', width: 90 }, - { title: '安装/拆卸时间', dataIndex: 'actionTime', key: 'actionTime', width: 170, render: function (tv) { return fmtRearActionTime(tv); } }, - { title: '操作人', dataIndex: 'operator', key: 'operator', width: 100 } - ], - scroll: { x: 800 }, - pagination: { - pageSize: 10, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; } - } - }) - ) - }, - { key: 'license', label: '证照管理', children: licenseManagementTabContent }, - { - key: 'lease', - label: '租赁记录', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px 24px', alignItems: 'start' } }, - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '合同编码'), - React.createElement(Select, { - placeholder: '请输入或选择合同编码', - style: { width: '100%' }, - value: leaseDraft.contractCode, - onChange: function (v) { setLeaseDraft(function (p) { return Object.assign({}, p, { contractCode: v }); }); }, - allowClear: true, - showSearch: true, - options: leaseContractOptions, - filterOption: filterOption - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '项目名称'), - React.createElement(Select, { - placeholder: '请输入或选择项目名称', - style: { width: '100%' }, - value: leaseDraft.projectName, - onChange: function (v) { setLeaseDraft(function (p) { return Object.assign({}, p, { projectName: v }); }); }, - allowClear: true, - showSearch: true, - options: leaseProjectOptions, - filterOption: filterOption - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '客户名称'), - React.createElement(Select, { - placeholder: '请输入或选择客户名称', - style: { width: '100%' }, - value: leaseDraft.customerName, - onChange: function (v) { setLeaseDraft(function (p) { return Object.assign({}, p, { customerName: v }); }); }, - allowClear: true, - showSearch: true, - options: leaseCustomerOptions, - filterOption: filterOption - }) - ) - ), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { onClick: function () { - var empty = { contractCode: undefined, projectName: undefined, customerName: undefined }; - setLeaseDraft(empty); - setLeaseApplied(empty); - } }, '重置'), - React.createElement(Button, { type: 'primary', onClick: function () { setLeaseApplied(Object.assign({}, leaseDraft)); } }, '查询') - ) - ), - React.createElement(Table, { - size: 'small', - rowKey: 'key', - bordered: true, - dataSource: leaseFiltered, - columns: [ - { title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 180, ellipsis: true }, - { title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 160, ellipsis: true }, - { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true }, - { title: '交车日期', dataIndex: 'deliveryDate', key: 'deliveryDate', width: 120, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '交车人', dataIndex: 'deliveryPerson', key: 'deliveryPerson', width: 100 }, - { title: '还车日期', dataIndex: 'returnDate', key: 'returnDate', width: 120, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '还车人', dataIndex: 'returnPerson', key: 'returnPerson', width: 100, render: function (tv) { return (tv && String(tv) !== '-') ? tv : '-'; } } - ], - scroll: { x: 1100 }, - pagination: { - pageSize: 10, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; } - } - }) - ) - }, - { - key: 'insurance', - label: '保险记录', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px 24px', maxWidth: 900, alignItems: 'start' } }, - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '保险公司'), - React.createElement(Select, { - placeholder: '请选择保险公司', - style: { width: '100%' }, - value: insuranceDraft.company, - onChange: function (v) { setInsuranceDraft(function (p) { return Object.assign({}, p, { company: v }); }); }, - allowClear: true, - showSearch: true, - options: insuranceCompanyOptions, - filterOption: filterOption - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '保险缴费时间'), - React.createElement(RangePicker, { - style: { width: '100%' }, - value: insuranceDraft.payTimeRange, - onChange: function (v) { setInsuranceDraft(function (p) { return Object.assign({}, p, { payTimeRange: v }); }); }, - placeholder: ['开始时间', '结束时间'] - }) - ) - ), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { onClick: function () { - var empty = { company: undefined, payTimeRange: null }; - setInsuranceDraft(empty); - setInsuranceApplied(empty); - } }, '重置'), - React.createElement(Button, { type: 'primary', onClick: function () { setInsuranceApplied(Object.assign({}, insuranceDraft)); } }, '查询') - ) - ), - React.createElement(Table, { - size: 'small', - rowKey: 'key', - bordered: true, - dataSource: insuranceFiltered, - columns: [ - { title: '保险公司', dataIndex: 'company', key: 'company', width: 220, ellipsis: true }, - { title: '险种', dataIndex: 'insuranceType', key: 'insuranceType', width: 120 }, - { title: '保单号', dataIndex: 'policyNo', key: 'policyNo', width: 200, ellipsis: true }, - { title: '保险起期', dataIndex: 'startDate', key: 'startDate', width: 110, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '保险止期', dataIndex: 'endDate', key: 'endDate', width: 110, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '缴费时间', dataIndex: 'payTime', key: 'payTime', width: 110, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '保费(元)', dataIndex: 'premium', key: 'premium', width: 110, align: 'right' }, - { title: '被保险人', dataIndex: 'insuredName', key: 'insuredName', width: 200, ellipsis: true } - ], - scroll: { x: 1400 }, - pagination: { - pageSize: 10, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; } - } - }) - ) - }, - { key: 'repair', label: '维修记录', children: React.createElement('div', { style: { padding: 24, color: '#999' } }, '暂无数据') }, - { key: 'maintain', label: '保养记录', children: React.createElement('div', { style: { padding: 24, color: '#999' } }, '暂无数据') }, - { - key: 'accident', - label: '事故记录', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px 24px', maxWidth: 900, alignItems: 'start' } }, - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '事故时间'), - React.createElement(RangePicker, { - style: { width: '100%' }, - value: accidentDraft.accidentTimeRange, - onChange: function (v) { setAccidentDraft(function (p) { return Object.assign({}, p, { accidentTimeRange: v }); }); }, - placeholder: ['开始日期', '结束日期'] - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '客户名称'), - React.createElement(Select, { - placeholder: '请输入或选择客户名称', - style: { width: '100%' }, - value: accidentDraft.customerName, - onChange: function (v) { setAccidentDraft(function (p) { return Object.assign({}, p, { customerName: v }); }); }, - allowClear: true, - showSearch: true, - options: accidentCustomerOptions, - filterOption: filterOption - }) - ) - ), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { onClick: function () { - var empty = { accidentTimeRange: null, customerName: undefined }; - setAccidentDraft(empty); - setAccidentApplied(empty); - } }, '重置'), - React.createElement(Button, { type: 'primary', onClick: function () { setAccidentApplied(Object.assign({}, accidentDraft)); } }, '查询') - ) - ), - React.createElement(Table, { - size: 'small', - rowKey: 'key', - bordered: true, - dataSource: accidentFiltered, - columns: [ - { title: '事故编码', dataIndex: 'accidentCode', key: 'accidentCode', width: 150, ellipsis: true }, - { title: '事故时间', dataIndex: 'accidentTime', key: 'accidentTime', width: 110, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '事故地点', dataIndex: 'location', key: 'location', width: 220, ellipsis: true }, - { title: '事故类型', dataIndex: 'accidentType', key: 'accidentType', width: 100 }, - { title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true }, - { title: '我方定损金额', dataIndex: 'ourLossAmount', key: 'ourLossAmount', width: 120, render: function (tv) { return cellOrDash(tv); } }, - { title: '对方定损金额', dataIndex: 'otherLossAmount', key: 'otherLossAmount', width: 120, render: function (tv) { return cellOrDash(tv); } }, - { title: '责任划分', dataIndex: 'liability', key: 'liability', width: 90 }, - { title: '事故状态', dataIndex: 'status', key: 'status', width: 90 }, - { title: '结案时间', dataIndex: 'closeTime', key: 'closeTime', width: 110, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '其他费用', dataIndex: 'otherFee', key: 'otherFee', width: 100, render: function (tv) { return cellOrDash(tv); } }, - { title: '备注', dataIndex: 'remark', key: 'remark', width: 160, ellipsis: true, render: function (tv) { return cellOrDash(tv); } } - ], - scroll: { x: 1800 }, - pagination: { - pageSize: 10, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; } - } - }) - ) - }, - { key: 'fault', label: '故障记录', children: React.createElement('div', { style: { padding: 24, color: '#999' } }, '暂无数据') }, - { - key: 'violation', - label: '违章记录', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px 24px', alignItems: 'start' } }, - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '违法时间'), - React.createElement(RangePicker, { - style: { width: '100%' }, - value: violationDraft.violationTimeRange, - onChange: function (v) { setViolationDraft(function (p) { return Object.assign({}, p, { violationTimeRange: v }); }); }, - placeholder: ['开始日期', '结束日期'] - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '缴费状态'), - React.createElement(Select, { - placeholder: '请选择缴费状态', - style: { width: '100%' }, - value: violationDraft.payStatus, - onChange: function (v) { setViolationDraft(function (p) { return Object.assign({}, p, { payStatus: v }); }); }, - allowClear: true, - options: violationPayStatusOptions - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '违章客户'), - React.createElement(Select, { - placeholder: '请输入或选择违章客户', - style: { width: '100%' }, - value: violationDraft.violationCustomer, - onChange: function (v) { setViolationDraft(function (p) { return Object.assign({}, p, { violationCustomer: v }); }); }, - allowClear: true, - showSearch: true, - options: violationCustomerOptions, - filterOption: filterOption - }) - ) - ), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { onClick: function () { - var empty = { violationTimeRange: null, payStatus: undefined, violationCustomer: undefined }; - setViolationDraft(empty); - setViolationApplied(empty); - } }, '重置'), - React.createElement(Button, { type: 'primary', onClick: function () { setViolationApplied(Object.assign({}, violationDraft)); } }, '查询') - ) - ), - React.createElement(Table, { - size: 'small', - rowKey: 'key', - bordered: true, - dataSource: violationFiltered, - columns: [ - { title: '违章编码', dataIndex: 'violationCode', key: 'violationCode', width: 150, ellipsis: true }, - { title: '违法行为', dataIndex: 'violationBehavior', key: 'violationBehavior', width: 140, ellipsis: true }, - { title: '违法时间', dataIndex: 'violationTime', key: 'violationTime', width: 110, render: function (tv) { return fmtDateYMD(tv); } }, - { title: '罚款金额', dataIndex: 'fineAmount', key: 'fineAmount', width: 100, render: function (tv) { return cellOrDash(tv); } }, - { title: '缴费状态', dataIndex: 'payStatus', key: 'payStatus', width: 90 }, - { title: '计分值', dataIndex: 'points', key: 'points', width: 80 }, - { title: '是否处理', dataIndex: 'processed', key: 'processed', width: 90 }, - { title: '违章客户', dataIndex: 'violationCustomer', key: 'violationCustomer', width: 220, ellipsis: true }, - { - title: '违章照片', - key: 'photo', - width: 100, - render: function (_, r) { - if (r.photo) { - return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('预览违章照片(原型)'); } }, '查看'); - } - return '-'; - } - }, - { title: '备注', dataIndex: 'remark', key: 'remark', width: 140, ellipsis: true, render: function (tv) { return cellOrDash(tv); } } - ], - scroll: { x: 1500 }, - pagination: { - pageSize: 10, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; } - } - }) - ) - }, - { - key: 'change', - label: '异动记录', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px 24px', alignItems: 'start' } }, - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '异动开始日期'), - React.createElement(RangePicker, { - style: { width: '100%' }, - value: moveRecordDraft.moveStartRange, - onChange: function (v) { setMoveRecordDraft(function (p) { return Object.assign({}, p, { moveStartRange: v }); }); }, - placeholder: ['开始日期', '结束日期'] - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '异动目的地'), - React.createElement(Select, { - placeholder: '请选择异动目的地', - style: { width: '100%' }, - value: moveRecordDraft.destination, - onChange: function (v) { setMoveRecordDraft(function (p) { return Object.assign({}, p, { destination: v }); }); }, - allowClear: true, - options: moveDestinationOptions - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '异动类型'), - React.createElement(Select, { - placeholder: '请选择异动类型', - style: { width: '100%' }, - value: moveRecordDraft.moveType, - onChange: function (v) { setMoveRecordDraft(function (p) { return Object.assign({}, p, { moveType: v }); }); }, - allowClear: true, - options: moveTypeFilterOptions - }) - ) - ), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { onClick: function () { - var empty = { moveStartRange: null, destination: undefined, moveType: undefined }; - setMoveRecordDraft(empty); - setMoveRecordApplied(empty); - } }, '重置'), - React.createElement(Button, { type: 'primary', onClick: function () { setMoveRecordApplied(Object.assign({}, moveRecordDraft)); } }, '查询') - ) - ), - React.createElement(Table, { - size: 'small', - rowKey: 'key', - bordered: true, - dataSource: moveRecordFiltered, - columns: [ - { title: '异动开始日期', dataIndex: 'moveStartAt', key: 'moveStartAt', width: 160, render: function (tv) { return fmtRearActionTime(tv); } }, - { title: '异动预计结束日期', dataIndex: 'moveEndExpectedAt', key: 'moveEndExpectedAt', width: 170, render: function (tv) { return fmtRearActionTime(tv); } }, - { title: '审批状态', dataIndex: 'approvalStatus', key: 'approvalStatus', width: 100 }, - { title: '异动目的地', dataIndex: 'destination', key: 'destination', width: 100 }, - { title: '目的地名称', dataIndex: 'destinationName', key: 'destinationName', width: 180, ellipsis: true }, - { title: '异动类型', dataIndex: 'moveType', key: 'moveType', width: 90 }, - { title: '预计异动里程(km)', dataIndex: 'expectedMileageKm', key: 'expectedMileageKm', width: 150 }, - { title: '异动开始里程(km)', dataIndex: 'startMileageKm', key: 'startMileageKm', width: 150 }, - { title: '异动开始电量(kWh)', dataIndex: 'startElectricKwh', key: 'startElectricKwh', width: 150 }, - { - title: '异动开始氢量', - key: 'startHydrogen', - width: 130, - render: function (_, r) { - var u = r.h2Unit || ''; - return (r.startHydrogen != null && r.startHydrogen !== '') ? String(r.startHydrogen) + (u ? ' ' + u : '') : '-'; - } - }, - { title: '创建人', dataIndex: 'creatorName', key: 'creatorName', width: 100 }, - { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160, render: function (tv) { return fmtRearActionTime(tv); } } - ], - scroll: { x: 2000 }, - pagination: { - pageSize: 10, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; } - } - }) - ) - }, - { - key: 'transfer', - label: '调拨记录', - children: React.createElement('div', { style: { padding: '0 4px' } }, - React.createElement(Card, { size: 'small', style: { marginBottom: 16 } }, - React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px 24px', alignItems: 'start' } }, - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '调拨日期'), - React.createElement(RangePicker, { - style: { width: '100%' }, - value: transferDraft.transferDateRange, - onChange: function (v) { setTransferDraft(function (p) { return Object.assign({}, p, { transferDateRange: v }); }); }, - placeholder: ['开始日期', '结束日期'] - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '调拨人'), - React.createElement(Select, { - placeholder: '请输入或选择调拨人', - style: { width: '100%' }, - value: transferDraft.transferPerson, - onChange: function (v) { setTransferDraft(function (p) { return Object.assign({}, p, { transferPerson: v }); }); }, - allowClear: true, - showSearch: true, - options: transferPersonOptions, - filterOption: filterOption - }) - ), - React.createElement('div', { style: filterItemStyle }, - React.createElement('div', { style: filterLabelStyle }, '接收人'), - React.createElement(Select, { - placeholder: '请输入或选择接收人', - style: { width: '100%' }, - value: transferDraft.receivePerson, - onChange: function (v) { setTransferDraft(function (p) { return Object.assign({}, p, { receivePerson: v }); }); }, - allowClear: true, - showSearch: true, - options: transferReceivePersonOptions, - filterOption: filterOption - }) - ) - ), - React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 } }, - React.createElement(Button, { onClick: function () { - var empty = { transferDateRange: null, transferPerson: undefined, receivePerson: undefined }; - setTransferDraft(empty); - setTransferApplied(empty); - } }, '重置'), - React.createElement(Button, { type: 'primary', onClick: function () { setTransferApplied(Object.assign({}, transferDraft)); } }, '查询') - ) - ), - React.createElement(Table, { - size: 'small', - rowKey: 'key', - bordered: true, - dataSource: transferFiltered, - columns: [ - { title: '调拨日期', dataIndex: 'transferDate', key: 'transferDate', width: 160, render: function (tv) { return fmtRearActionTime(tv); } }, - { title: '出发区域', dataIndex: 'departRegionText', key: 'departRegionText', width: 130 }, - { title: '调拨人', dataIndex: 'transferPerson', key: 'transferPerson', width: 90 }, - { title: '接收区域', dataIndex: 'receiveRegionText', key: 'receiveRegionText', width: 130 }, - { title: '接收人', dataIndex: 'receivePerson', key: 'receivePerson', width: 90 }, - { title: '审批状态', dataIndex: 'approvalStatus', key: 'approvalStatus', width: 100 }, - { title: '调拨方式', dataIndex: 'method', key: 'method', width: 110 }, - { title: '调拨原因', dataIndex: 'reason', key: 'reason', width: 160, ellipsis: true }, - { title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 100 }, - { title: '品牌', dataIndex: 'brand', key: 'brand', width: 90 }, - { title: '型号', dataIndex: 'model', key: 'model', width: 100, ellipsis: true }, - { title: '运输方名称', dataIndex: 'carrierName', key: 'carrierName', width: 140, ellipsis: true, render: function (tv) { return (tv && String(tv) !== '-') ? tv : '—'; } }, - { title: '出发里程(km)', dataIndex: 'departMileageKm', key: 'departMileageKm', width: 130 }, - { - title: '出发氢量', - key: 'departHydrogen', - width: 110, - render: function (_, r) { - var u = r.h2Unit || ''; - return (r.departHydrogen != null && r.departHydrogen !== '') ? String(r.departHydrogen) + (u ? u : '') : '—'; - } - }, - { title: '出发电量(kWh)', dataIndex: 'departElectricKwh', key: 'departElectricKwh', width: 130 }, - { title: '出发停车场', dataIndex: 'departParking', key: 'departParking', width: 150, ellipsis: true }, - { title: '创建日期', dataIndex: 'createDate', key: 'createDate', width: 160, render: function (tv) { return fmtRearActionTime(tv); } } - ], - scroll: { x: 2400 }, - pagination: { - pageSize: 10, - showSizeChanger: true, - showQuickJumper: true, - showTotal: function (t) { return '共 ' + t + ' 条'; } - } - }) - ) - } - ]; - - var breadcrumbNodes = [ - React.createElement('span', { key: '1' }, '车辆管理'), - React.createElement('span', { key: '2', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '3' }, '车辆数据'), - React.createElement('span', { key: '4', style: styles.breadcrumbSep }, ' / '), - React.createElement('span', { key: '5', style: { color: '#1890ff' } }, '详情') - ]; - - return React.createElement('div', { style: styles.page }, - React.createElement('div', { style: styles.breadcrumb }, breadcrumbNodes), - React.createElement('div', { style: styles.card }, - React.createElement('div', { style: styles.cardBody }, - React.createElement('div', { style: styles.overviewHeader }, - React.createElement('span', { style: styles.plateNo }, overview.plateNo), - React.createElement(Tag, { color: 'blue' }, overview.plateTag) - ), - React.createElement('div', { style: styles.overviewGrid }, - React.createElement(InfoRow, { label: '车架号', value: overview.vin }), - React.createElement(InfoRow, { label: '车辆编号', value: overview.vehicleNo }), - React.createElement(InfoRow, { label: '登记所有权', value: overview.owner }), - React.createElement(InfoRow, { label: '运营公司', value: overview.operateCompany }), - React.createElement(InfoRow, { label: '车辆来源', value: overview.vehicleSource }), - React.createElement(InfoRow, { label: '租赁公司', value: overview.leaseCompany }), - React.createElement(InfoRow, { label: '合同编号', value: overview.contractNo }), - React.createElement(InfoRow, { label: '车辆状态', value: overview.vehicleStatus }), - React.createElement(InfoRow, { label: '出库状态', value: overview.outStatus }), - React.createElement(InfoRow, { label: '证照状态', value: overview.licenseStatus }), - React.createElement(InfoRow, { label: '保险状态', value: overview.insuranceStatus }), - React.createElement(InfoRow, { label: '车辆当前位置', value: overview.location, ellipsisWithTooltip: true }), - React.createElement(InfoRow, { label: '车身颜色', value: overview.bodyColor }), - React.createElement(InfoRow, { label: '采购入库日期', value: overview.purchaseDate }), - React.createElement(InfoRow, { label: '客户名称', value: overview.customerName }), - React.createElement(InfoRow, { label: 'GPS最后上传', value: overview.gpsLastTime }), - React.createElement(InfoRow, { label: '出厂年份', value: overview.manufactureYear }), - React.createElement(InfoRow, { label: '停车位置', value: overview.parkingPlace }), - React.createElement(InfoRow, { label: '业务部门', value: overview.bizDept }), - React.createElement(InfoRow, { label: '等评时间', value: overview.ratingTime }), - React.createElement(InfoRow, { label: '强制报废期', value: overview.forceScrapDate }), - React.createElement(InfoRow, { label: '下次年检时间', value: overview.nextInspectionTime }), - React.createElement(InfoRow, { label: '业务负责人', value: overview.bizManager }) - ) - ) - ), - React.createElement('div', { style: Object.assign({}, styles.card, { marginBottom: 0 }) }, - React.createElement('div', { style: styles.cardBody }, - React.createElement(Tabs, { - activeKey: activeTab[0], - onChange: function (key) { activeTab[1](key); }, - items: tabItems, - type: 'line' - }) - ) - ) - ); -}; - -if (typeof window !== 'undefined') { - window.Component = Component; - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function () { - var rootEl = document.getElementById('root'); - if (rootEl && window.ReactDOM && window.React) { - var root = ReactDOM.createRoot(rootEl); - root.render(React.createElement(Component)); - } - }); - } else { - var rootEl = document.getElementById('root'); - if (rootEl && window.ReactDOM && window.React) { - var root = ReactDOM.createRoot(rootEl); - root.render(React.createElement(Component)); - } - } -} - -export default Component; diff --git a/src/prototypes/oneos-web-workbench/pages/01-工作台.jsx b/src/prototypes/oneos-web-workbench/pages/01-工作台.jsx index 4241073..1c45c4e 100644 --- a/src/prototypes/oneos-web-workbench/pages/01-工作台.jsx +++ b/src/prototypes/oneos-web-workbench/pages/01-工作台.jsx @@ -1,6 +1,8 @@ // 【重要】必须使用 const Component 作为组件变量名 // 数字化资产 ONEOS 运管平台 - 工作台(参照原型布局 + Dashboard 风格) +import { OperationActions } from '../../../common/OperationActions'; + const Component = function () { var useState = React.useState; var useMemo = React.useMemo; @@ -14,7 +16,6 @@ const Component = function () { var Statistic = antd.Statistic; var Tabs = antd.Tabs; var Badge = antd.Badge; - var Breadcrumb = antd.Breadcrumb; var Button = antd.Button; var Dropdown = antd.Dropdown; var Space = antd.Space; @@ -76,6 +77,10 @@ const Component = function () { var wbBarWrapH = 96; function protoNav(hint) { + if (typeof hint === 'string' && hint.indexOf('/prototypes/') === 0 && typeof window !== 'undefined') { + window.location.href = hint; + return; + } message.info('跳转「' + hint + '」(原型,联调配置路由)'); } @@ -1427,7 +1432,7 @@ const Component = function () { { t: '生成交车任务', action: 'genDelivery', accent: 'primary' }, { t: '生成还车任务', action: 'genReturn', accent: 'orange' }, { t: '租赁合同', p: 'web端/车辆租赁合同/车辆租赁合同.jsx' }, - { t: '提车应收款', p: 'web端/财务管理/提车应收款.jsx' }, + { t: '提车应收款', p: '/prototypes/vehicle-pickup-receivable' }, { t: '租赁账单', p: 'web端/业务管理/租赁账单.jsx' }, { t: '还车应结款', p: 'web端/财务管理/还车应结款.jsx' }, { t: '能源账户', p: 'web端/业务管理/能源账户.jsx' }, @@ -1466,7 +1471,7 @@ const Component = function () { finance: { label: '财务', items: [ - { t: '提车应收款', p: 'web端/财务管理/提车应收款.jsx' }, + { t: '提车应收款', p: '/prototypes/vehicle-pickup-receivable' }, { t: '租赁账单', p: 'web端/财务管理/租赁账单.jsx' }, { t: '还车应结款', p: 'web端/财务管理/还车应结款.jsx' }, { t: '审批中心', p: 'web端/审批中心.jsx' }, @@ -1573,30 +1578,20 @@ const Component = function () { { title: '操作', key: 'actions', - width: 140, + width: 168, render: function (_, record) { - return React.createElement(Space, { size: 4 }, - React.createElement(Button, { - type: 'link', - size: 'small', - style: { padding: 0, height: 'auto' }, - onClick: function (e) { - if (e && e.stopPropagation) e.stopPropagation(); - openNoticePreview(record); - } - }, '预览'), - !record.read - ? React.createElement(Button, { - type: 'link', - size: 'small', - style: { padding: 0, height: 'auto' }, - onClick: function (e) { - if (e && e.stopPropagation) e.stopPropagation(); - markNoticeRead(record.id); - } - }, '已读') - : null - ); + var more = []; + if (!record.read) { + more.push({ + key: 'read', + label: '已读', + onClick: function () { markNoticeRead(record.id); } + }); + } + return React.createElement(OperationActions, { + view: { label: '预览', onClick: function () { openNoticePreview(record); } }, + more: more + }); } } ]; @@ -1656,14 +1651,18 @@ const Component = function () { { title: '操作', key: 'action', - width: 160, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } }, - record.vehicleArrived - ? React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { message.info('跳转还车管理-还车页(原型)'); } }, '还车') - : React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { message.info('确认车辆到达(原型,联调对接还车管理)'); } }, '车辆到达') - ); + return React.createElement(OperationActions, { + view: { + label: record.vehicleArrived ? '还车' : '车辆到达', + onClick: function () { + if (record.vehicleArrived) message.info('跳转还车管理-还车页(原型)'); + else message.info('确认车辆到达(原型,联调对接还车管理)'); + } + } + }); } } ]; @@ -1680,10 +1679,12 @@ const Component = function () { { title: '操作', key: 'action', - width: 80, + width: 168, fixed: 'right', render: function (_, record) { - return React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { message.info('跳转年审详情(原型)'); } }, '年审'); + return React.createElement(OperationActions, { + view: { label: '年审', onClick: function () { message.info('跳转年审详情(原型)'); } } + }); } } ]; @@ -1700,8 +1701,10 @@ const Component = function () { { title: '创建人', dataIndex: 'createBy', key: 'createBy', width: 90, ellipsis: true }, { title: '最后修改时间', dataIndex: 'lastModifyTime', key: 'lastModifyTime', width: 160, ellipsis: true }, { title: '最后修改人', dataIndex: 'lastModifyBy', key: 'lastModifyBy', width: 90, ellipsis: true }, - { title: '操作', key: 'action', width: 88, fixed: 'right', render: function () { - return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转交车管理-交车单(原型)'); } }, '交车单'); + { title: '操作', key: 'action', width: 168, fixed: 'right', render: function () { + return React.createElement(OperationActions, { + view: { label: '交车单', onClick: function () { message.info('跳转交车管理-交车单(原型)'); } } + }); } } ]; @@ -1787,24 +1790,20 @@ const Component = function () { { title: '操作', key: 'op', - width: 128, + width: 168, align: 'right', render: function (_, r) { var isDone = r.status === 'done'; var isNotify = r.actionType === 'notify'; - var urgeBtn = !isDone && !isNotify - ? React.createElement(Button, { - type: 'link', - size: 'small', - style: { padding: 0 }, - onClick: function () { pushUrgeNotice(r); } - }, '催办') - : null; var primaryLabel = isDone ? '查看' : (isNotify ? '查看' : (r.actionType === 'approve' ? '去审批' : (r.actionType === 'initiate' ? '去发起' : '处理'))); - return React.createElement(Space, { size: 4 }, - React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { protoNav(r.path); } }, primaryLabel), - urgeBtn - ); + var more = []; + if (!isDone && !isNotify) { + more.push({ key: 'urge', label: '催办', onClick: function () { pushUrgeNotice(r); } }); + } + return React.createElement(OperationActions, { + view: { label: primaryLabel, onClick: function () { protoNav(r.path); } }, + more: more + }); } } ]; @@ -1837,24 +1836,20 @@ const Component = function () { { title: '操作', key: 'op', - width: 128, + width: 168, align: 'right', render: function (_, r) { var isDone = r.status === 'done'; var isNotify = r.actionType === 'notify'; - var urgeBtn = !isDone && !isNotify - ? React.createElement(Button, { - type: 'link', - size: 'small', - style: { padding: 0 }, - onClick: function () { pushUrgeNotice(r); } - }, '催办') - : null; var primaryLabel = isDone ? '查看' : (isNotify ? '查看' : (r.actionType === 'approve' ? '去审批' : (r.actionType === 'initiate' ? '去发起' : '处理'))); - return React.createElement(Space, { size: 4 }, - React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { protoNav(r.path); } }, primaryLabel), - urgeBtn - ); + var more = []; + if (!isDone && !isNotify) { + more.push({ key: 'urge', label: '催办', onClick: function () { pushUrgeNotice(r); } }); + } + return React.createElement(OperationActions, { + view: { label: primaryLabel, onClick: function () { protoNav(r.path); } }, + more: more + }); } } ]; diff --git a/src/prototypes/payment-records/annotation-source.json b/src/prototypes/payment-records/annotation-source.json index 53edb85..aa58e23 100644 --- a/src/prototypes/payment-records/annotation-source.json +++ b/src/prototypes/payment-records/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "payment-records", "pageId": "list", - "updatedAt": 1783589987167, + "updatedAt": 1783800844968, "nodes": [ { "id": "pr-filter", @@ -93,36 +93,6 @@ "assetMap": {}, "directory": { "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - }, { "type": "folder", "id": "pr-doc-root", diff --git a/src/prototypes/payment-records/canvas.excalidraw b/src/prototypes/payment-records/canvas.excalidraw new file mode 100644 index 0000000..50522d0 --- /dev/null +++ b/src/prototypes/payment-records/canvas.excalidraw @@ -0,0 +1,10 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "@axhub/make", + "elements": [], + "appState": { + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/src/prototypes/payment-records/components/FilterPanel.tsx b/src/prototypes/payment-records/components/FilterPanel.tsx index 5672444..f12b4ba 100644 --- a/src/prototypes/payment-records/components/FilterPanel.tsx +++ b/src/prototypes/payment-records/components/FilterPanel.tsx @@ -227,20 +227,18 @@ export function FilterPanel({ records, filters, onChange, onReset, onSearch }: F onClick={() => setExpanded(!expanded)} aria-expanded={expanded} aria-controls={expandPanelId} - > + + data-vm-icon={expanded ? 'chevron-up' : 'filter'}> {expanded ? '收起' : '更多筛选'} {extraActiveCount > 0 && !expanded ? ( {extraActiveCount} ) : null} - - + ) : null} - - +
diff --git a/src/prototypes/payment-records/components/PaymentKpiDetailFilterBar.tsx b/src/prototypes/payment-records/components/PaymentKpiDetailFilterBar.tsx index 569e79b..15dcc7a 100644 --- a/src/prototypes/payment-records/components/PaymentKpiDetailFilterBar.tsx +++ b/src/prototypes/payment-records/components/PaymentKpiDetailFilterBar.tsx @@ -78,23 +78,8 @@ export function PaymentKpiDetailFilterBar({
- - +
diff --git a/src/prototypes/payment-records/components/PaymentKpiDetailModal.tsx b/src/prototypes/payment-records/components/PaymentKpiDetailModal.tsx index ab61830..c7fc83e 100644 --- a/src/prototypes/payment-records/components/PaymentKpiDetailModal.tsx +++ b/src/prototypes/payment-records/components/PaymentKpiDetailModal.tsx @@ -298,10 +298,10 @@ export function PaymentKpiDetailModal({
diff --git a/src/prototypes/payment-records/components/PaymentRecordsTable.tsx b/src/prototypes/payment-records/components/PaymentRecordsTable.tsx index bfa2e3e..a688272 100644 --- a/src/prototypes/payment-records/components/PaymentRecordsTable.tsx +++ b/src/prototypes/payment-records/components/PaymentRecordsTable.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { Button, Table } from 'antd'; +import { Table } from 'antd'; +import { OperationActions } from '../../../common/OperationActions'; import type { ColumnsType } from 'antd/es/table'; import type { BillCandidate, PaymentRecord } from '../types'; import { calcUnclaimedAmount, formatMoney } from '../utils/payment'; @@ -142,12 +143,18 @@ export function PaymentRecordsTable({ { title: '操作', key: 'action', - width: 108, + width: 168, fixed: 'right', render: (_, row) => ( - + onLinkBills(row), + }, + ]} + /> ), }, ], [billPool, expandedRowKeys, onLinkBills, toggleRowExpand]); diff --git a/src/prototypes/payment-records/index.tsx b/src/prototypes/payment-records/index.tsx index f5a194f..80a07e5 100644 --- a/src/prototypes/payment-records/index.tsx +++ b/src/prototypes/payment-records/index.tsx @@ -11,11 +11,11 @@ import zhCN from 'antd/locale/zh_CN'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; import { - AnnotationViewer, type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import seedRecords from './data/records.json'; import seedBills from './data/bills.json'; import { FilterPanel } from './components/FilterPanel'; @@ -247,24 +247,25 @@ function PaymentRecordsContent() {
+ )} - - +
diff --git a/src/prototypes/self-operated-business-ledger/components/LedgerKpiRow.tsx b/src/prototypes/self-operated-business-ledger/components/LedgerKpiRow.tsx index ff54041..c089238 100644 --- a/src/prototypes/self-operated-business-ledger/components/LedgerKpiRow.tsx +++ b/src/prototypes/self-operated-business-ledger/components/LedgerKpiRow.tsx @@ -1,39 +1,122 @@ import React from 'react'; -import { CircleHelp } from 'lucide-react'; +import { + CircleHelp, + CircleDollarSign, + ClipboardList, + CreditCard, + Fuel, + TrendingUp, + Wallet, + type LucideIcon, +} from 'lucide-react'; import type { SelfOperatedKpiSummary } from '../types'; -import { formatMoney } from '../utils/ledger'; +import { formatMoney, profitLossClassName } from '../utils/ledger'; -const CARDS: Array<{ key: keyof SelfOperatedKpiSummary; title: string; desc: string; money?: boolean }> = [ - { key: 'count', title: '台账条数', desc: '当前筛选条件下的全部台账记录条数' }, - { key: 'amount', title: '金额合计', desc: '每条台账「金额」字段逐项相加', money: true }, - { key: 'totalCost', title: '总成本合计', desc: '每条台账「总成本」字段逐项相加', money: true }, - { key: 'hydrogenFee', title: '氢费总计', desc: '每条台账「氢费」字段逐项相加', money: true }, - { key: 'etcFee', title: 'ETC费用总计', desc: '每条台账「ETC费用」字段逐项相加', money: true }, - { key: 'profitLoss', title: '盈亏合计', desc: '每条台账「盈亏」字段逐项相加', money: true }, +type KpiTone = + | 'neutral' + | 'income' + | 'cost' + | 'deduction' + | 'profit'; + +const CARDS: Array<{ + key: keyof SelfOperatedKpiSummary; + title: string; + desc: string; + tone: KpiTone; + icon: LucideIcon; + money?: boolean; +}> = [ + { + key: 'count', + title: '明细条数', + desc: '当前筛选条件下的全部物流业务明细记录条数', + tone: 'neutral', + icon: ClipboardList, + }, + { + key: 'amount', + title: '金额合计', + desc: '当前筛选范围内每条明细「金额」字段逐项相加', + tone: 'income', + icon: CircleDollarSign, + money: true, + }, + { + key: 'totalCost', + title: '总成本合计', + desc: '当前筛选范围内每条明细「总成本」字段逐项相加', + tone: 'cost', + icon: Wallet, + money: true, + }, + { + key: 'hydrogenFee', + title: '氢费总计', + desc: '当前筛选范围内每条明细「氢费」字段逐项相加', + tone: 'deduction', + icon: Fuel, + money: true, + }, + { + key: 'etcFee', + title: 'ETC费用总计', + desc: '当前筛选范围内每条明细「ETC费用」字段逐项相加', + tone: 'deduction', + icon: CreditCard, + money: true, + }, + { + key: 'profitLoss', + title: '盈亏合计', + desc: '当前筛选范围内每条明细「盈亏」字段逐项相加', + tone: 'profit', + icon: TrendingUp, + money: true, + }, ]; interface LedgerKpiRowProps { summary: SelfOperatedKpiSummary; } +function valueClassName(key: keyof SelfOperatedKpiSummary, summary: SelfOperatedKpiSummary): string { + if (key === 'profitLoss') { + return profitLossClassName(summary.profitLoss); + } + return ''; +} + export function LedgerKpiRow({ summary }: LedgerKpiRowProps) { return ( -
+
{CARDS.map((card) => { const raw = summary[card.key]; const value = card.money ? formatMoney(raw) : String(raw); - const profitClass = card.key === 'profitLoss' - ? (summary.profitLoss >= 0 ? 'ldb-kpi-val--profit' : 'ldb-kpi-val--loss') - : ''; + const Icon = card.icon; + return ( -
- - - {card.desc} - - {card.title} - {value} -
+
+
+
+
+ + + + {card.title} + + + {card.desc} + +
+

+ {value} +

+
+
); })}
diff --git a/src/prototypes/self-operated-business-ledger/components/SelfImportModal.tsx b/src/prototypes/self-operated-business-ledger/components/SelfImportModal.tsx new file mode 100644 index 0000000..a98306f --- /dev/null +++ b/src/prototypes/self-operated-business-ledger/components/SelfImportModal.tsx @@ -0,0 +1,89 @@ +import React, { useRef } from 'react'; +import { Download, Upload, X } from 'lucide-react'; + +interface SelfImportModalProps { + open: boolean; + onClose: () => void; + onDownloadTemplate: () => void; + onImport: (file: File) => void; + failedCount?: number; + onDownloadErrorLog?: () => void; +} + +export function SelfImportModal({ + open, + onClose, + onDownloadTemplate, + onImport, + failedCount = 0, + onDownloadErrorLog, +}: SelfImportModalProps) { + const inputRef = useRef(null); + if (!open) return null; + + return ( +
+
event.stopPropagation()} + > +
+

批量导入物流业务明细

+ +
+
+
+
+ 1 + 下载导入模板 +
+ +
+
+
+ 2 + 上传填写好的 Excel 文件 +
+ +
+ {failedCount > 0 && onDownloadErrorLog ? ( +
+

+ 本次有 {failedCount} 条记录导入失败(如必填项缺失、车牌号不存在),可下载错误日志核对。 +

+ +
+ ) : null} +
+
+ +
+
+
+ ); +} diff --git a/src/prototypes/self-operated-business-ledger/components/SelfLedgerTable.tsx b/src/prototypes/self-operated-business-ledger/components/SelfLedgerTable.tsx index ad270ff..ffd8852 100644 --- a/src/prototypes/self-operated-business-ledger/components/SelfLedgerTable.tsx +++ b/src/prototypes/self-operated-business-ledger/components/SelfLedgerTable.tsx @@ -1,27 +1,66 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'; +import { OperationActions } from '../../../common/OperationActions'; import type { SelfOperatedLedgerRow } from '../types'; -import { displayText, formatMoney } from '../utils/ledger'; +import { + computeAmount, + computeProfitLoss, + computeTotalCost, + displayText, + formatDisplayMonth, + formatMoney, + profitLossClassName, +} from '../utils/ledger'; +import { resolveSystemVehicleType } from '../utils/vehicle-ref'; import { DEFAULT_SELF_COLUMN_WIDTHS, + SELF_STICKY_LEFT_KEYS, SELF_TABLE_COLUMNS, type SelfTableColumn, } from './tableColumns'; interface SelfLedgerTableProps { records: SelfOperatedLedgerRow[]; - selectedIds: Set; loading?: boolean; - onToggleRow: (id: string, checked: boolean) => void; - onToggleAll: (checked: boolean) => void; onEdit: (record: SelfOperatedLedgerRow) => void; onDelete: (record: SelfOperatedLedgerRow) => void; } type SortDir = 'asc' | 'desc'; -const CHECK_COL_WIDTH = 48; -const ACTION_COL_WIDTH = 120; +const ACTION_COL_WIDTH = 168; + +function buildStickyLayout(widths: Record) { + let left = 0; + const offsets: Record = {}; + SELF_STICKY_LEFT_KEYS.forEach((key) => { + offsets[key] = left; + left += widths[key] ?? DEFAULT_SELF_COLUMN_WIDTHS[key] ?? 100; + }); + return { offsets }; +} + +function stickyClass(col: SelfTableColumn): string { + if (col.sticky !== 'left') return col.className ?? ''; + const classes = ['sticky-col-left']; + if (col.key === 'plateNo') classes.push('sticky-col-left-last'); + if (col.className) classes.push(col.className); + return classes.join(' '); +} + +function stickyStyle( + col: SelfTableColumn, + layout: ReturnType, + isHeader: boolean, +): React.CSSProperties | undefined { + if (col.sticky !== 'left') return undefined; + const index = SELF_STICKY_LEFT_KEYS.indexOf(col.key as typeof SELF_STICKY_LEFT_KEYS[number]); + if (index < 0) return undefined; + return { + left: layout.offsets[col.key], + zIndex: (isHeader ? 4 : 3) + index, + }; +} function HeaderSort({ title, @@ -47,10 +86,23 @@ function HeaderSort({ } function renderCell(row: SelfOperatedLedgerRow, col: SelfTableColumn) { + if (col.key === 'month') { + return formatDisplayMonth(row); + } + if (col.key === 'systemVehicleType') { + return resolveSystemVehicleType(row.plateNo) || displayText(row.systemVehicleType); + } + if (col.key === 'amount') { + return formatMoney(computeAmount(row)); + } + if (col.key === 'totalCost') { + return formatMoney(computeTotalCost(row)); + } if (col.key === 'profitLoss') { + const profitLoss = computeProfitLoss(row); return ( - = 0 ? 'ldb-kpi-val--profit' : 'ldb-kpi-val--loss'}> - {formatMoney(row.profitLoss)} + + {formatMoney(profitLoss)} ); } @@ -74,10 +126,7 @@ function TableSkeleton() { export function SelfLedgerTable({ records, - selectedIds, loading = false, - onToggleRow, - onToggleAll, onEdit, onDelete, }: SelfLedgerTableProps) { @@ -90,6 +139,18 @@ export function SelfLedgerTable({ if (!sortKey) return records; const dir = sortDir === 'asc' ? 1 : -1; return [...records].sort((a, b) => { + if (sortKey === 'month') { + return formatDisplayMonth(a).localeCompare(formatDisplayMonth(b), 'zh-CN') * dir; + } + if (sortKey === 'amount') { + return (computeAmount(a) - computeAmount(b)) * dir; + } + if (sortKey === 'totalCost') { + return (computeTotalCost(a) - computeTotalCost(b)) * dir; + } + if (sortKey === 'profitLoss') { + return (computeProfitLoss(a) - computeProfitLoss(b)) * dir; + } const av = a[sortKey as keyof SelfOperatedLedgerRow]; const bv = b[sortKey as keyof SelfOperatedLedgerRow]; if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir; @@ -97,16 +158,6 @@ export function SelfLedgerTable({ }); }, [records, sortKey, sortDir]); - const allSelected = sortedRecords.length > 0 && sortedRecords.every((r) => selectedIds.has(r.id)); - const someSelected = sortedRecords.some((r) => selectedIds.has(r.id)) && !allSelected; - const headerCheckRef = useRef(null); - - useEffect(() => { - if (headerCheckRef.current) { - headerCheckRef.current.indeterminate = someSelected; - } - }, [someSelected]); - const handleSort = useCallback((key: string) => { setSortKey((prev) => { if (prev === key) { @@ -128,7 +179,7 @@ export function SelfLedgerTable({ }; }, [columnWidths]); - useEffect(() => { + React.useEffect(() => { const onMove = (event: MouseEvent) => { if (!resizeRef.current) return; const delta = event.clientX - resizeRef.current.startX; @@ -146,6 +197,12 @@ export function SelfLedgerTable({ }; }, []); + const tableMinWidth = ACTION_COL_WIDTH + SELF_TABLE_COLUMNS.reduce( + (sum, col) => sum + (columnWidths[col.key] ?? DEFAULT_SELF_COLUMN_WIDTHS[col.key] ?? 100), + 0, + ); + const stickyLayout = useMemo(() => buildStickyLayout(columnWidths), [columnWidths]); + if (loading) { return (
@@ -158,16 +215,10 @@ export function SelfLedgerTable({ return

暂无符合条件的台账记录

; } - const tableMinWidth = CHECK_COL_WIDTH + ACTION_COL_WIDTH + SELF_TABLE_COLUMNS.reduce( - (sum, col) => sum + (columnWidths[col.key] ?? DEFAULT_SELF_COLUMN_WIDTHS[col.key] ?? 100), - 0, - ); - return (
- {SELF_TABLE_COLUMNS.map((col) => ( ))} @@ -175,31 +226,27 @@ export function SelfLedgerTable({ - {SELF_TABLE_COLUMNS.map((col) => ( - {SELF_TABLE_COLUMNS.map((col) => ( ))} ))} diff --git a/src/prototypes/self-operated-business-ledger/components/tableColumns.ts b/src/prototypes/self-operated-business-ledger/components/tableColumns.ts index 5e4aa28..854cebd 100644 --- a/src/prototypes/self-operated-business-ledger/components/tableColumns.ts +++ b/src/prototypes/self-operated-business-ledger/components/tableColumns.ts @@ -4,27 +4,47 @@ export interface SelfTableColumn { money?: boolean; className?: string; sortable?: boolean; + sticky?: 'left' | 'right'; defaultWidth?: number; } +/** 左侧冻结列(至车牌号码) */ +export const SELF_STICKY_LEFT_KEYS = [ + 'month', + 'dispatchDate', + 'businessName', + 'driver', + 'phone', + 'plateNo', +] as const; + +/** 与 Excel「1.2物流业务台账」表头顺序、文案一致 */ export const SELF_TABLE_COLUMNS: SelfTableColumn[] = [ - { key: 'month', title: '月份', defaultWidth: 72, sortable: true }, - { key: 'dispatchDate', title: '出车日期', defaultWidth: 108, sortable: true }, - { key: 'businessName', title: '业务名称', className: 'ldb-col-name', defaultWidth: 140, sortable: true }, - { key: 'driver', title: '司机', defaultWidth: 88, sortable: true }, - { key: 'phone', title: '电话', defaultWidth: 112, sortable: true }, - { key: 'vehicleUndertaker', title: '车辆承接方', defaultWidth: 108, sortable: true }, - { key: 'plateNo', title: '车牌号码', defaultWidth: 108, sortable: true }, - { key: 'brand', title: '品牌', defaultWidth: 80, sortable: true }, - { key: 'model', title: '型号', className: 'ldb-col-name', defaultWidth: 160, sortable: true }, - { key: 'unitPrice', title: '单价', money: true, defaultWidth: 96, sortable: true }, - { key: 'quantity', title: '数量', money: true, defaultWidth: 72, sortable: true }, - { key: 'amount', title: '金额', money: true, defaultWidth: 104, sortable: true }, - { key: 'hydrogenFee', title: '氢费', money: true, defaultWidth: 96, sortable: true }, - { key: 'etcFee', title: 'ETC费用', money: true, defaultWidth: 96, sortable: true }, - { key: 'totalCost', title: '总成本', money: true, defaultWidth: 104, sortable: true }, - { key: 'profitLoss', title: '盈亏', money: true, defaultWidth: 104, sortable: true }, - { key: 'maintainerName', title: '维护人', defaultWidth: 88, sortable: true }, + { key: 'month', title: '月份', defaultWidth: 72, sticky: 'left' }, + { key: 'dispatchDate', title: '出车日期', defaultWidth: 108, sortable: true, sticky: 'left' }, + { key: 'businessName', title: '业务名称', className: 'ldb-col-name', defaultWidth: 168, sticky: 'left' }, + { key: 'driver', title: '司机', defaultWidth: 80, sticky: 'left' }, + { key: 'phone', title: '电话', defaultWidth: 112, sticky: 'left' }, + { key: 'plateNo', title: '车牌号码', defaultWidth: 104, sticky: 'left', className: 'sob-col-plate' }, + { key: 'systemVehicleType', title: '系统车型', className: 'ldb-col-name', defaultWidth: 168 }, + { key: 'unitPrice', title: '单价', money: true, defaultWidth: 88 }, + { key: 'quantity', title: '数量', money: true, defaultWidth: 64 }, + { key: 'amount', title: '金额', money: true, defaultWidth: 96 }, + { key: 'hydrogenFee', title: '氢费', money: true, defaultWidth: 88 }, + { key: 'etcFee', title: 'ETC费用', money: true, defaultWidth: 88 }, + { key: 'salary', title: '薪资', money: true, defaultWidth: 88 }, + { key: 'electricityFee', title: '电费', money: true, defaultWidth: 80 }, + { key: 'manualReimbursement', title: '人工报销费用', money: true, defaultWidth: 108 }, + { key: 'dailySocialSecurityFee', title: '日社保服务费', money: true, defaultWidth: 108 }, + { key: 'dailyTrailerFee', title: '日挂车费用', money: true, defaultWidth: 96 }, + { key: 'dailyParkingFee', title: '日停车费用', money: true, defaultWidth: 96 }, + { key: 'dailyTireFee', title: '日轮胎费用', money: true, defaultWidth: 96 }, + { key: 'vehicleFee', title: '车辆费用', money: true, defaultWidth: 88 }, + { key: 'totalCost', title: '总成本', money: true, defaultWidth: 96 }, + { key: 'profitLoss', title: '盈亏', money: true, defaultWidth: 96 }, + { key: 'multiTrip', title: '是否多趟', defaultWidth: 80 }, + { key: 'routePricing', title: '线路计价', className: 'ldb-col-name', defaultWidth: 104 }, + { key: 'remark', title: '备注', className: 'ldb-col-name', defaultWidth: 120 }, ]; export const DEFAULT_SELF_COLUMN_WIDTHS: Record = Object.fromEntries( diff --git a/src/prototypes/self-operated-business-ledger/data/rows.json b/src/prototypes/self-operated-business-ledger/data/rows.json index 75e3b9f..96f2d52 100644 --- a/src/prototypes/self-operated-business-ledger/data/rows.json +++ b/src/prototypes/self-operated-business-ledger/data/rows.json @@ -1,8 +1,242 @@ [ - { "id": "s1", "month": "2026-01", "dispatchDate": "2026-01-01", "businessName": "嘉兴金小悦贸易有限公司", "driver": "王召善", "phone": "15112321670", "vehicleUndertaker": "我司", "plateNo": "粤AGQ8393", "brand": "现代", "model": "帕力安牌4.5吨冷链车", "unitPrice": 590, "quantity": 1, "amount": 590, "hydrogenFee": 303.8, "etcFee": 0, "totalCost": 701.86, "profitLoss": -111.86, "maintainerName": "谭晓峰" }, - { "id": "s2", "month": "2026-01", "dispatchDate": "2026-01-01", "businessName": "嘉兴金小悦贸易有限公司", "driver": "杨国平", "phone": "13720987869", "vehicleUndertaker": "我司", "plateNo": "粤AGR3208", "brand": "现代", "model": "帕力安牌4.5吨冷链车", "unitPrice": 610, "quantity": 1, "amount": 610, "hydrogenFee": 304.15, "etcFee": 0, "totalCost": 702.21, "profitLoss": -92.21, "maintainerName": "谭晓峰" }, - { "id": "s3", "month": "2026-01", "dispatchDate": "2026-01-01", "businessName": "嘉兴益顺冷链物流有限公司", "driver": "曹文想", "phone": "18321541863", "vehicleUndertaker": "我司", "plateNo": "粤AGP9835", "brand": "现代", "model": "帕力安牌4.5吨冷链车", "unitPrice": 553.94, "quantity": 1, "amount": 553.94, "hydrogenFee": 215.6, "etcFee": 37.04, "totalCost": 710.36, "profitLoss": -156.42, "maintainerName": "谭晓峰" }, - { "id": "s4", "month": "2026-02", "dispatchDate": "2026-02-03", "businessName": "杭州绿道城配科技有限公司", "driver": "李强", "phone": "13800138001", "vehicleUndertaker": "我司", "plateNo": "浙F02608F", "brand": "现代", "model": "帕力安牌18吨双飞翼货车", "unitPrice": 680, "quantity": 2, "amount": 1360, "hydrogenFee": 420, "etcFee": 56, "totalCost": 923, "profitLoss": 437, "maintainerName": "刘洋" }, - { "id": "s5", "month": "2026-02", "dispatchDate": "2026-02-05", "businessName": "上海诚新贸易有限公司", "driver": "周杰", "phone": "13900139002", "vehicleUndertaker": "第三方车队", "plateNo": "浙F00688F", "brand": "飞驰", "model": "49吨牵引车头", "unitPrice": 1200, "quantity": 1, "amount": 1200, "hydrogenFee": 380, "etcFee": 120, "totalCost": 890, "profitLoss": 310, "maintainerName": "刘洋" }, - { "id": "s6", "month": "2026-02", "dispatchDate": "2026-02-08", "businessName": "深圳智联信息技术有限公司", "driver": "赵敏", "phone": "13600136003", "vehicleUndertaker": "我司", "plateNo": "沪A66921F", "brand": "苏龙", "model": "海格牌KLQ5180XYKFCEV", "unitPrice": 850, "quantity": 1, "amount": 850, "hydrogenFee": 290, "etcFee": 45, "totalCost": 620, "profitLoss": 230, "maintainerName": "谭晓峰" } -] + { + "id": "s1", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴金小悦贸易有限公司", + "driver": "王召善", + "phone": "15112321670", + "plateNo": "粤AGQ8393", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 590.0, + "quantity": 1, + "amount": 590.0, + "hydrogenFee": 303.8, + "etcFee": 0.0, + "salary": 258.06, + "electricityFee": 0.0, + "manualReimbursement": 0.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 701.86, + "profitLoss": -111.86, + "multiTrip": "否", + "routePricing": "", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + }, + { + "id": "s2", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴金小悦贸易有限公司", + "driver": "杨国平", + "phone": "13720987869", + "plateNo": "粤AGR3208", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 610.0, + "quantity": 1, + "amount": 610.0, + "hydrogenFee": 304.15, + "etcFee": 0.0, + "salary": 258.06, + "electricityFee": 0.0, + "manualReimbursement": 0.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 702.21, + "profitLoss": -92.21, + "multiTrip": "否", + "routePricing": "", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + }, + { + "id": "s3", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴金小悦贸易有限公司", + "driver": "杨国君", + "phone": "19036343635", + "plateNo": "粤AGP6680", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 600.0, + "quantity": 1, + "amount": 600.0, + "hydrogenFee": 0.0, + "etcFee": 0.0, + "salary": 258.06, + "electricityFee": 0.0, + "manualReimbursement": 0.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 398.06, + "profitLoss": 201.94, + "multiTrip": "否", + "routePricing": "", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + }, + { + "id": "s4", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴益顺冷链物流有限公司", + "driver": "曹文想", + "phone": "18321541863", + "plateNo": "粤AGP9835", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 553.94, + "quantity": 1, + "amount": 553.94, + "hydrogenFee": 215.6, + "etcFee": 37.04, + "salary": 290.32, + "electricityFee": 0.0, + "manualReimbursement": 5.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 687.96, + "profitLoss": -134.02, + "multiTrip": "否", + "routePricing": "敏胜-梅山", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + }, + { + "id": "s5", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴益顺冷链物流有限公司", + "driver": "黄磊", + "phone": "13357878206", + "plateNo": "粤AGP9825", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 889.27, + "quantity": 1, + "amount": 889.27, + "hydrogenFee": 285.95, + "etcFee": 82.76, + "salary": 306.89, + "electricityFee": 0.0, + "manualReimbursement": 0.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 815.6, + "profitLoss": 73.67, + "multiTrip": "否", + "routePricing": "", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + }, + { + "id": "s6", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴益顺冷链物流有限公司", + "driver": "唐飞", + "phone": "15257514355", + "plateNo": "粤AGP9331", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 1154.98, + "quantity": 1, + "amount": 1154.98, + "hydrogenFee": 448.0, + "etcFee": 72.02, + "salary": 360.03, + "electricityFee": 0.0, + "manualReimbursement": 0.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 1020.05, + "profitLoss": 134.93, + "multiTrip": "否", + "routePricing": "", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + }, + { + "id": "s7", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴益顺冷链物流有限公司", + "driver": "王家豪", + "phone": "17858344668", + "plateNo": "粤AGP6651", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 703.7, + "quantity": 1, + "amount": 703.7, + "hydrogenFee": 0.0, + "etcFee": 89.57, + "salary": 269.77, + "electricityFee": 0.0, + "manualReimbursement": 0.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 499.34, + "profitLoss": 204.36, + "multiTrip": "否", + "routePricing": "", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + }, + { + "id": "s8", + "month": "1", + "dispatchDate": "2026-01-01", + "businessName": "嘉兴益顺冷链物流有限公司", + "driver": "王亚东", + "phone": "18336908069", + "plateNo": "粤AGP6639", + "systemVehicleType": "帕立安4.5T", + "unitPrice": 405.59, + "quantity": 1, + "amount": 405.59, + "hydrogenFee": 0.0, + "etcFee": 0.0, + "salary": 210.15, + "electricityFee": 0.0, + "manualReimbursement": 0.0, + "dailySocialSecurityFee": 40.0, + "dailyTrailerFee": 0.0, + "dailyParkingFee": 0.0, + "dailyTireFee": 20.0, + "vehicleFee": 80.0, + "totalCost": 350.15, + "profitLoss": 55.44, + "multiTrip": "否", + "routePricing": "", + "remark": "", + "year": "2026", + "dailyTrailerServiceFee": 0 + } +] \ No newline at end of file diff --git a/src/prototypes/self-operated-business-ledger/index.tsx b/src/prototypes/self-operated-business-ledger/index.tsx index 12c0603..82a3062 100644 --- a/src/prototypes/self-operated-business-ledger/index.tsx +++ b/src/prototypes/self-operated-business-ledger/index.tsx @@ -1,21 +1,23 @@ /** - * @name 物流业务台账 - * 设计规范:ledger-shared/DESIGN.md · vm-page + ldb-page(与租赁业务台账、租赁合同列表对齐) + * @name 物流业务明细 + * 设计规范:ledger-shared/DESIGN.md · vm-page + ldb-page(与租赁业务明细、租赁合同列表对齐) */ import '../vehicle-management/style.css'; import '../lease-business-ledger/styles/index.css'; +import './styles/index.css'; import React, { useCallback, useMemo, useState } from 'react'; -import { Download, FileUp, Plus, Trash2 } from 'lucide-react'; +import { Download, FileUp, Plus } from 'lucide-react'; import { - AnnotationViewer, type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import seedRows from './data/rows.json'; import { FilterPanel } from './components/FilterPanel'; import { LedgerKpiRow } from './components/LedgerKpiRow'; import { SelfLedgerTable } from './components/SelfLedgerTable'; +import { SelfImportModal } from './components/SelfImportModal'; import { TablePagination } from '../vehicle-management/components/TablePagination'; import { DeleteConfirmModal } from '../lease-business-ledger/components/DeleteConfirmModal'; import { @@ -24,6 +26,12 @@ import { type SelfOperatedLedgerRow, } from './types'; import { applySelfFilters, sumSelfKpi } from './utils/ledger'; +import { + downloadSelfImportErrorLog, + downloadSelfImportTemplate, + parseSelfImportFile, + type SelfImportErrorRow, +} from './utils/batch-io'; import annotationSourceDocument from './annotation-source.json'; const PAGE_SIZE = 10; @@ -34,11 +42,12 @@ export default function SelfOperatedBusinessLedgerApp() { const [appliedFilters, setAppliedFilters] = useState(EMPTY_SELF_FILTERS); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(PAGE_SIZE); - const [selectedIds, setSelectedIds] = useState>(new Set()); const [toast, setToast] = useState(''); const [tableLoading, setTableLoading] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); - const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false); + const [importOpen, setImportOpen] = useState(false); + const [importErrors, setImportErrors] = useState([]); + const [importErrorHeaders, setImportErrorHeaders] = useState([]); const showToast = useCallback((msg: string) => { setToast(msg); @@ -58,7 +67,6 @@ export default function SelfOperatedBusinessLedgerApp() { window.setTimeout(() => { setAppliedFilters(pendingFilters); setPage(1); - setSelectedIds(new Set()); setTableLoading(false); }, 320); }; @@ -67,15 +75,35 @@ export default function SelfOperatedBusinessLedgerApp() { setPendingFilters(EMPTY_SELF_FILTERS); setAppliedFilters(EMPTY_SELF_FILTERS); setPage(1); - setSelectedIds(new Set()); }; - const handleBulkDeleteClick = () => { - if (selectedIds.size === 0) { - showToast('请先勾选需要删除的记录'); - return; + const handleImport = async (file: File) => { + try { + const result = await parseSelfImportFile(file); + setImportErrors(result.errors); + setImportErrorHeaders(result.fileHeaders); + + if (result.success.length === 0 && result.errors.length === 0) { + showToast('未解析到有效数据,请检查模板与表头'); + return; + } + + if (result.success.length > 0) { + setRecords((prev) => [...result.success, ...prev]); + setPage(1); + } + + if (result.errors.length > 0 && result.success.length > 0) { + showToast(`已导入 ${result.success.length} 条;${result.errors.length} 条失败,可下载错误日志`); + } else if (result.errors.length > 0) { + showToast(`${result.errors.length} 条导入失败,请下载错误日志查看原因`); + } else { + setImportOpen(false); + showToast(`已导入 ${result.success.length} 条明细,金额/总成本/盈亏已自动计算`); + } + } catch { + showToast('导入失败,请检查文件格式'); } - setBulkDeleteOpen(true); }; const annotationOptions = useMemo( @@ -93,7 +121,7 @@ export default function SelfOperatedBusinessLedgerApp() { return ( <> -
+
- - +
@@ -129,19 +156,7 @@ export default function SelfOperatedBusinessLedgerApp() {
{ - setSelectedIds((prev) => { - const next = new Set(prev); - if (checked) next.add(id); - else next.delete(id); - return next; - }); - }} - onToggleAll={(checked) => { - setSelectedIds(checked ? new Set(paged.map((r) => r.id)) : new Set()); - }} onEdit={(row) => showToast(`编辑:${row.plateNo} · ${row.dispatchDate}(原型演示)`)} onDelete={(row) => setDeleteTarget(row)} /> @@ -158,9 +173,9 @@ export default function SelfOperatedBusinessLedgerApp() { type="button" className="vm-btn vm-btn-secondary ldb-toolbar-btn" data-annotation-id="sob-btn-add-row" + data-vm-icon="plus" onClick={() => showToast('表格底部新增行(原型演示)')} > - 添加行
@@ -178,29 +193,25 @@ export default function SelfOperatedBusinessLedgerApp() { onConfirm={() => { if (!deleteTarget) return; setRecords((prev) => prev.filter((row) => row.id !== deleteTarget.id)); - setSelectedIds((prev) => { - const next = new Set(prev); - next.delete(deleteTarget.id); - return next; - }); setDeleteTarget(null); showToast(`已删除 ${deleteTarget.plateNo}(原型)`); }} /> - setBulkDeleteOpen(false)} - onConfirm={() => { - const ids = new Set(selectedIds); - setRecords((prev) => prev.filter((row) => !ids.has(row.id))); - setSelectedIds(new Set()); - setBulkDeleteOpen(false); - showToast(`已批量删除 ${ids.size} 条(原型)`); - }} + + setImportOpen(false)} + onDownloadTemplate={downloadSelfImportTemplate} + onImport={handleImport} + failedCount={importErrors.length} + onDownloadErrorLog={ + importErrors.length > 0 + ? () => downloadSelfImportErrorLog(importErrors, importErrorHeaders) + : undefined + } /> - + ); } diff --git a/src/prototypes/self-operated-business-ledger/styles/index.css b/src/prototypes/self-operated-business-ledger/styles/index.css new file mode 100644 index 0000000..2d84050 --- /dev/null +++ b/src/prototypes/self-operated-business-ledger/styles/index.css @@ -0,0 +1,239 @@ +/** + * 物流业务明细 — 页面级样式 + */ + +.vm-page.ldb-page.sob-page .vm-table-section { + gap: 16px; +} + +.vm-page.ldb-page.sob-page .vm-table-card > .ldb-table-wrap { + max-height: calc(100vh - 560px); +} + +/* —— KPI 汇总卡片(6 项关键指标,对齐租赁业务明细) —— */ +.sob-kpi-row { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 10px; + margin-bottom: 12px; +} + +@media (max-width: 1280px) { + .sob-kpi-row { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (max-width: 768px) { + .sob-kpi-row { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +.sob-kpi-card { + --sob-kpi-accent: var(--ln-primary); + --sob-kpi-accent-soft: color-mix(in srgb, var(--sob-kpi-accent) 12%, var(--ln-surface-card)); + --sob-kpi-accent-border: color-mix(in srgb, var(--sob-kpi-accent) 22%, var(--ln-hairline)); + --sob-kpi-icon-bg: color-mix(in srgb, var(--sob-kpi-accent) 14%, var(--ln-surface-card)); + --sob-kpi-icon-color: var(--sob-kpi-accent); + + position: relative; + display: flex; + min-height: 0; + border-radius: var(--ln-radius-control); + border: 1px solid var(--sob-kpi-accent-border); + background: + linear-gradient( + 145deg, + var(--sob-kpi-accent-soft) 0%, + var(--ln-surface-card) 58% + ); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + overflow: hidden; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease, + transform 0.2s ease; +} + +.sob-kpi-card:hover { + border-color: color-mix(in srgb, var(--sob-kpi-accent) 38%, var(--ln-hairline)); + box-shadow: + 0 2px 6px rgba(15, 23, 42, 0.05), + 0 10px 24px rgba(15, 23, 42, 0.07); + transform: translateY(-1px); +} + +.sob-kpi-card-accent { + flex-shrink: 0; + width: 3px; + background: linear-gradient( + 180deg, + var(--sob-kpi-accent) 0%, + color-mix(in srgb, var(--sob-kpi-accent) 55%, transparent) 100% + ); +} + +.sob-kpi-card-body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; + gap: 2px; + padding: 8px 10px 8px 8px; +} + +.sob-kpi-card-head { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +.sob-kpi-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 24px; + height: 24px; + border-radius: var(--ln-radius-xs); + background: var(--sob-kpi-icon-bg); + color: var(--sob-kpi-icon-color); +} + +.sob-kpi-label { + flex: 1; + min-width: 0; + font-size: 0.6875rem; + font-weight: 600; + color: var(--ln-muted); + line-height: 1.25; + letter-spacing: 0.01em; +} + +.sob-kpi-val { + margin: 0; + padding-left: 30px; + font-size: 1.0625rem; + font-weight: 700; + color: var(--ln-ink); + line-height: 1.2; + letter-spacing: -0.02em; +} + +.sob-kpi-val--profit, +.sob-val--rise { + color: #dc2626; +} + +.sob-kpi-val--loss, +.sob-val--fall { + color: #16a34a; +} + +.sob-kpi-tip { + position: relative; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + margin: -2px -4px -2px 0; + border-radius: var(--ln-radius-xs); + color: var(--ln-muted-soft); + cursor: help; +} + +.sob-kpi-tip:hover, +.sob-kpi-tip:focus-visible { + color: var(--ln-ink); + background: color-mix(in srgb, var(--ln-ink) 6%, transparent); + outline: none; +} + +.sob-kpi-tip:focus-visible { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--ln-primary) 35%, transparent); +} + +.sob-kpi-tooltip { + display: none; + position: absolute; + right: 0; + bottom: calc(100% + 8px); + z-index: 20; + width: max-content; + max-width: min(240px, 70vw); + padding: 8px 10px; + border-radius: var(--ln-radius-control); + background: var(--ln-ink); + color: #fff; + font-size: 0.75rem; + font-weight: 400; + line-height: 1.45; + box-shadow: var(--ln-shadow-hover); + pointer-events: none; +} + +.sob-kpi-tip:hover .sob-kpi-tooltip, +.sob-kpi-tip:focus-within .sob-kpi-tooltip { + display: block; +} + +.sob-kpi-card--neutral { + --sob-kpi-accent: #64748b; +} + +.sob-kpi-card--income { + --sob-kpi-accent: var(--ln-primary); +} + +.sob-kpi-card--cost { + --sob-kpi-accent: #7c3aed; +} + +.sob-kpi-card--deduction { + --sob-kpi-accent: #0891b2; +} + +.sob-kpi-card--profit { + --sob-kpi-accent: #059669; +} + +@media (prefers-reduced-motion: reduce) { + .sob-kpi-card { + transition: none; + } + + .sob-kpi-card:hover { + transform: none; + } +} + +.sob-import-error-step { + margin-top: 4px; +} + +.sob-import-error-summary { + margin: 0 0 10px; + font-size: 0.875rem; + color: var(--ln-muted); + line-height: 1.5; +} + +.sob-import-error-summary strong { + color: var(--ln-error); + font-weight: 600; +} + +.ldb-import-modal .vm-upload-zone { + width: 100%; + min-height: 120px; +} + +.ldb-import-modal .vm-upload-zone p { + margin: 8px 0 0; + font-size: 0.875rem; +} diff --git a/src/prototypes/self-operated-business-ledger/types.ts b/src/prototypes/self-operated-business-ledger/types.ts index 3564161..ccb2f25 100644 --- a/src/prototypes/self-operated-business-ledger/types.ts +++ b/src/prototypes/self-operated-business-ledger/types.ts @@ -1,22 +1,34 @@ export interface SelfOperatedLedgerRow { id: string; + /** 导入模板「年份」列 */ + year: string; + /** 导入模板「月份」列(1–12) */ month: string; dispatchDate: string; businessName: string; driver: string; phone: string; - vehicleUndertaker: string; plateNo: string; - brand: string; - model: string; + systemVehicleType: string; unitPrice: number; quantity: number; amount: number; hydrogenFee: number; etcFee: number; + salary: number; + electricityFee: number; + manualReimbursement: number; + dailySocialSecurityFee: number; + dailyTrailerServiceFee: number; + dailyTrailerFee: number; + dailyParkingFee: number; + dailyTireFee: number; + vehicleFee: number; totalCost: number; profitLoss: number; - maintainerName: string; + multiTrip: string; + routePricing: string; + remark: string; } export interface SelfOperatedFilters { @@ -25,10 +37,7 @@ export interface SelfOperatedFilters { dispatchTo: string; businessName: string; plateNos: string[]; - vehicleUndertaker: string; - brand: string; - model: string; - maintainerName: string; + systemVehicleType: string; } export const EMPTY_SELF_FILTERS: SelfOperatedFilters = { @@ -37,10 +46,7 @@ export const EMPTY_SELF_FILTERS: SelfOperatedFilters = { dispatchTo: '', businessName: '', plateNos: [], - vehicleUndertaker: '', - brand: '', - model: '', - maintainerName: '', + systemVehicleType: '', }; export interface SelfOperatedKpiSummary { diff --git a/src/prototypes/self-operated-business-ledger/utils/batch-io.ts b/src/prototypes/self-operated-business-ledger/utils/batch-io.ts new file mode 100644 index 0000000..7c36d5a --- /dev/null +++ b/src/prototypes/self-operated-business-ledger/utils/batch-io.ts @@ -0,0 +1,278 @@ +import * as XLSX from 'xlsx'; +import type { SelfOperatedLedgerRow } from '../types'; +import { isPlateInVehicleSystem, resolveSystemVehicleType } from './vehicle-ref'; +import { applyImportCalculations } from './calc-row'; + +/** 物流业务明细导入模板列(不含系统车型、金额、总成本、盈亏) */ +export const SELF_IMPORT_TEMPLATE_HEADERS = [ + '年份(必填)', + '月份(必填)', + '出车日期(必填)', + '业务名称(必填)', + '司机(必填)', + '电话(必填)', + '车牌号码(必填)', + '单价(必填)', + '数量(必填)', + '氢费', + 'ETC费用', + '薪资(必填)', + '电费', + '人工报销费用', + '日社保服务费', + '日挂车服务费', + '日挂车费用', + '日停车费用', + '日轮胎费用', + '车辆费用', + '是否多趟(必填)', + '线路计价(必填)', + '备注', +] as const; + +export const SELF_IMPORT_REQUIRED_HEADERS = [ + '年份', + '月份', + '出车日期', + '业务名称', + '司机', + '电话', + '车牌号码', + '单价', + '数量', + '薪资', + '是否多趟', + '线路计价', +] as const; + +export interface SelfImportErrorRow { + reason: string; + rawCells: (string | number)[]; +} + +export interface SelfImportParseResult { + success: SelfOperatedLedgerRow[]; + errors: SelfImportErrorRow[]; + fileHeaders: string[]; +} + +const TEMPLATE_SAMPLE: Record = { + '年份(必填)': 2026, + '月份(必填)': 1, + '出车日期(必填)': '2026-01-01', + '业务名称(必填)': '嘉兴金小悦贸易有限公司', + '司机(必填)': '王召善', + '电话(必填)': '15112321670', + '车牌号码(必填)': '粤AGQ8393', + '单价(必填)': 590, + '数量(必填)': 1, + 氢费: 303.8, + ETC费用: 0, + '薪资(必填)': 258.06, + 电费: 0, + 人工报销费用: 0, + 日社保服务费: 40, + 日挂车服务费: 0, + 日挂车费用: 0, + 日停车费用: 0, + 日轮胎费用: 20, + 车辆费用: 80, + '是否多趟(必填)': '否', + '线路计价(必填)': '', + 备注: '', +}; + +const HEADER_TO_KEY: Record = { + 年份: 'year', + 月份: 'month', + 出车日期: 'dispatchDate', + 业务名称: 'businessName', + 司机: 'driver', + 电话: 'phone', + 车牌号码: 'plateNo', + 单价: 'unitPrice', + 数量: 'quantity', + 氢费: 'hydrogenFee', + ETC费用: 'etcFee', + 薪资: 'salary', + 电费: 'electricityFee', + 人工报销费用: 'manualReimbursement', + 日社保服务费: 'dailySocialSecurityFee', + 日挂车服务费: 'dailyTrailerServiceFee', + 日挂车费用: 'dailyTrailerFee', + 日停车费用: 'dailyParkingFee', + 日轮胎费用: 'dailyTireFee', + 车辆费用: 'vehicleFee', + 是否多趟: 'multiTrip', + 线路计价: 'routePricing', + 备注: 'remark', +}; + +const IMPORT_NUMERIC_KEYS = new Set([ + 'unitPrice', + 'quantity', + 'hydrogenFee', + 'etcFee', + 'salary', + 'electricityFee', + 'manualReimbursement', + 'dailySocialSecurityFee', + 'dailyTrailerServiceFee', + 'dailyTrailerFee', + 'dailyParkingFee', + 'dailyTireFee', + 'vehicleFee', +]); + +const IMPORT_TEXT_KEYS = new Set([ + 'year', + 'month', + 'dispatchDate', + 'businessName', + 'driver', + 'phone', + 'plateNo', + 'multiTrip', + 'routePricing', + 'remark', +]); + +const REQUIRED_NUMERIC_HEADERS = new Set(['年份', '月份', '单价', '数量', '薪资']); + +function isCellEmpty(value: unknown): boolean { + if (value === null || value === undefined) return true; + return String(value).trim() === ''; +} + +function parseNumber(value: unknown): number { + if (value === null || value === undefined || value === '') return 0; + const n = Number(String(value).replace(/,/g, '')); + return Number.isFinite(n) ? n : 0; +} + +function parseDateCell(value: unknown): string { + if (value === null || value === undefined || value === '') return ''; + if (typeof value === 'number' && Number.isFinite(value)) { + const base = new Date(Date.UTC(1899, 11, 30)); + base.setUTCDate(base.getUTCDate() + Math.floor(value)); + return base.toISOString().slice(0, 10); + } + const raw = String(value).trim(); + if (/^\d{5}(\.\d+)?$/.test(raw)) { + return parseDateCell(Number(raw)); + } + return raw.slice(0, 10); +} + +function normalizeHeader(header: string): string { + return header.replace(/(必填)/g, '').trim(); +} + +function resolveHeaderKey(header: string): keyof SelfOperatedLedgerRow | undefined { + return HEADER_TO_KEY[normalizeHeader(header)]; +} + +function validateRequiredFields(headers: string[], cells: (string | number)[]): string[] { + const reasons: string[] = []; + const headerIndex = new Map(headers.map((h, i) => [normalizeHeader(h), i])); + + SELF_IMPORT_REQUIRED_HEADERS.forEach((required) => { + const idx = headerIndex.get(required); + const raw = idx === undefined ? undefined : cells[idx]; + if (REQUIRED_NUMERIC_HEADERS.has(required)) { + if (isCellEmpty(raw)) reasons.push(`${required}不能为空`); + return; + } + if (isCellEmpty(raw)) reasons.push(`${required}不能为空`); + }); + + return reasons; +} + +function enrichDraftFromSystem(draft: Partial): void { + if (!draft.plateNo) return; + draft.systemVehicleType = resolveSystemVehicleType(draft.plateNo); +} + +function parseRowDraft( + headers: string[], + cells: (string | number)[], + index: number, +): Partial { + const draft: Partial = { + id: `import-${Date.now()}-${index}`, + }; + + headers.forEach((header, colIndex) => { + const key = resolveHeaderKey(header); + if (!key) return; + const raw = cells[colIndex]; + if (key === 'dispatchDate') { + (draft as Record)[key] = parseDateCell(raw); + } else if (IMPORT_NUMERIC_KEYS.has(key)) { + (draft as Record)[key] = parseNumber(raw); + } else if (IMPORT_TEXT_KEYS.has(key)) { + (draft as Record)[key] = String(raw ?? '').trim(); + } + }); + + enrichDraftFromSystem(draft); + return draft; +} + +export function downloadSelfImportTemplate(): void { + const sampleRow = SELF_IMPORT_TEMPLATE_HEADERS.map((header) => TEMPLATE_SAMPLE[header] ?? ''); + const sheet = XLSX.utils.aoa_to_sheet([Array.from(SELF_IMPORT_TEMPLATE_HEADERS), sampleRow]); + const book = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(book, sheet, '物流业务明细'); + XLSX.writeFile(book, '物流业务明细导入模板.xlsx'); +} + +export function downloadSelfImportErrorLog( + errors: SelfImportErrorRow[], + fileHeaders: string[], +): void { + if (!errors.length) return; + const headers = ['导入失败原因', ...(fileHeaders.length ? fileHeaders : Array.from(SELF_IMPORT_TEMPLATE_HEADERS))]; + const body = errors.map((err) => [err.reason, ...err.rawCells]); + const sheet = XLSX.utils.aoa_to_sheet([headers, ...body]); + const book = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(book, sheet, '导入失败记录'); + XLSX.writeFile(book, `物流业务明细导入失败记录_${Date.now()}.xlsx`); +} + +export async function parseSelfImportFile(file: File): Promise { + const buffer = await file.arrayBuffer(); + const book = XLSX.read(buffer, { type: 'array' }); + const sheet = book.Sheets[book.SheetNames[0]]; + const matrix = XLSX.utils.sheet_to_json<(string | number)[]>(sheet, { header: 1, defval: '' }) as (string | number)[][]; + if (matrix.length < 2) { + return { success: [], errors: [], fileHeaders: [] }; + } + + const fileHeaders = matrix[0].map((cell) => String(cell).trim()); + const success: SelfOperatedLedgerRow[] = []; + const errors: SelfImportErrorRow[] = []; + + matrix.slice(1).forEach((cells, index) => { + if (!cells.some((cell) => String(cell).trim())) return; + + const rawCells = fileHeaders.map((_, colIndex) => cells[colIndex] ?? ''); + const requiredReasons = validateRequiredFields(fileHeaders, cells); + if (requiredReasons.length > 0) { + errors.push({ reason: requiredReasons.join(';'), rawCells }); + return; + } + + const draft = parseRowDraft(fileHeaders, cells, index); + const plateNo = String(draft.plateNo ?? '').trim(); + if (!isPlateInVehicleSystem(plateNo)) { + errors.push({ reason: '车牌号不存在', rawCells }); + return; + } + + success.push(applyImportCalculations(draft)); + }); + + return { success, errors, fileHeaders }; +} diff --git a/src/prototypes/self-operated-business-ledger/utils/calc-row.ts b/src/prototypes/self-operated-business-ledger/utils/calc-row.ts new file mode 100644 index 0000000..b3d80d3 --- /dev/null +++ b/src/prototypes/self-operated-business-ledger/utils/calc-row.ts @@ -0,0 +1,52 @@ +import type { SelfOperatedLedgerRow } from '../types'; +import { computeAmount, computeProfitLoss, computeTotalCost } from './ledger'; +import { resolveSystemVehicleType } from './vehicle-ref'; + +const IMPORT_DEFAULTS: Omit = { + year: '', + month: '', + dispatchDate: '', + businessName: '', + driver: '', + phone: '', + plateNo: '', + systemVehicleType: '', + unitPrice: 0, + quantity: 0, + amount: 0, + hydrogenFee: 0, + etcFee: 0, + salary: 0, + electricityFee: 0, + manualReimbursement: 0, + dailySocialSecurityFee: 0, + dailyTrailerServiceFee: 0, + dailyTrailerFee: 0, + dailyParkingFee: 0, + dailyTireFee: 0, + vehicleFee: 0, + totalCost: 0, + profitLoss: 0, + multiTrip: '', + routePricing: '', + remark: '', +}; + +/** 导入后计算金额、总成本、盈亏(列表公式列) */ +export function applyImportCalculations(draft: Partial): SelfOperatedLedgerRow { + const row: SelfOperatedLedgerRow = { + ...IMPORT_DEFAULTS, + ...draft, + id: draft.id ?? `import-${Date.now()}`, + year: String(draft.year ?? '').trim(), + month: String(draft.month ?? '').trim(), + unitPrice: draft.unitPrice ?? 0, + quantity: draft.quantity ?? 0, + }; + + row.systemVehicleType = resolveSystemVehicleType(row.plateNo); + row.amount = computeAmount(row); + row.totalCost = computeTotalCost(row); + row.profitLoss = computeProfitLoss(row); + return row; +} diff --git a/src/prototypes/self-operated-business-ledger/utils/ledger.ts b/src/prototypes/self-operated-business-ledger/utils/ledger.ts index 925094f..5f962eb 100644 --- a/src/prototypes/self-operated-business-ledger/utils/ledger.ts +++ b/src/prototypes/self-operated-business-ledger/utils/ledger.ts @@ -1,4 +1,5 @@ import type { SelfOperatedFilters, SelfOperatedKpiSummary, SelfOperatedLedgerRow } from '../types'; +import { resolveSystemVehicleType } from './vehicle-ref'; export function formatMoney(value: number): string { return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); @@ -24,28 +25,81 @@ export function displayMoney(value: number | null | undefined, treatZeroAsEmpty return formatMoney(value); } +function roundMoney(value: number): number { + return Math.round(value * 100) / 100; +} + +/** 金额 = 单价 × 数量 */ +export function computeAmount(row: SelfOperatedLedgerRow): number { + return roundMoney(row.unitPrice * row.quantity); +} + +/** 总成本 = 氢费 + 人工报销费用 + ETC + 电费 + 薪资 + 车辆费用 */ +export function computeTotalCost(row: SelfOperatedLedgerRow): number { + return roundMoney( + row.hydrogenFee + + row.manualReimbursement + + row.etcFee + + row.electricityFee + + row.salary + + row.vehicleFee, + ); +} + +export function computeProfitLoss(row: SelfOperatedLedgerRow): number { + return roundMoney(computeAmount(row) - computeTotalCost(row)); +} + +export function profitLossClassName(value: number): string { + if (value > 0) return 'sob-val--rise'; + if (value < 0) return 'sob-val--fall'; + return ''; +} + +const MONTH_DISPLAY_RE = /^\d{4}-\d{2}$/; + +function padMonthPart(value: string): string { + const n = Number.parseInt(value, 10); + if (!Number.isFinite(n) || n < 1 || n > 12) return value.padStart(2, '0'); + return String(n).padStart(2, '0'); +} + +function yearFromDispatchDate(dispatchDate: string): string { + const match = /^(\d{4})-/.exec(dispatchDate); + return match?.[1] ?? String(new Date().getFullYear()); +} + +/** 列表展示:导入的年份、月份两列合并为 YYYY-MM */ +export function formatDisplayMonth(row: SelfOperatedLedgerRow): string { + if (MONTH_DISPLAY_RE.test(row.month)) return row.month; + const year = row.year?.trim() || yearFromDispatchDate(row.dispatchDate); + const monthPart = padMonthPart(row.month); + return `${year}-${monthPart}`; +} + export function sumSelfKpi(rows: SelfOperatedLedgerRow[]): SelfOperatedKpiSummary { return rows.reduce( - (acc, row) => ({ - count: acc.count + 1, - amount: acc.amount + row.amount, - totalCost: acc.totalCost + row.totalCost, - hydrogenFee: acc.hydrogenFee + row.hydrogenFee, - etcFee: acc.etcFee + row.etcFee, - profitLoss: acc.profitLoss + row.profitLoss, - }), + (acc, row) => { + const totalCost = computeTotalCost(row); + const profitLoss = computeProfitLoss(row); + return { + count: acc.count + 1, + amount: acc.amount + computeAmount(row), + totalCost: acc.totalCost + totalCost, + hydrogenFee: acc.hydrogenFee + row.hydrogenFee, + etcFee: acc.etcFee + row.etcFee, + profitLoss: acc.profitLoss + profitLoss, + }; + }, { count: 0, amount: 0, totalCost: 0, hydrogenFee: 0, etcFee: 0, profitLoss: 0 }, ); } export function applySelfFilters(rows: SelfOperatedLedgerRow[], filters: SelfOperatedFilters): SelfOperatedLedgerRow[] { return rows.filter((row) => { - if (filters.month && row.month !== filters.month) return false; + if (filters.month && formatDisplayMonth(row) !== filters.month) return false; if (filters.businessName && row.businessName !== filters.businessName) return false; - if (filters.vehicleUndertaker && row.vehicleUndertaker !== filters.vehicleUndertaker) return false; - if (filters.brand && row.brand !== filters.brand) return false; - if (filters.model && row.model !== filters.model) return false; - if (filters.maintainerName && row.maintainerName !== filters.maintainerName) return false; + if (filters.systemVehicleType && resolveSystemVehicleType(row.plateNo) !== filters.systemVehicleType) return false; if (filters.plateNos.length > 0 && !filters.plateNos.includes(row.plateNo)) return false; if (filters.dispatchFrom && row.dispatchDate < filters.dispatchFrom) return false; if (filters.dispatchTo && row.dispatchDate > filters.dispatchTo) return false; @@ -62,10 +116,17 @@ export function buildSelfOptions(rows: SelfOperatedLedgerRow[], key: keyof SelfO return Array.from(set).sort((a, b) => a.localeCompare(b, 'zh-CN')); } -export const UNDERTAKER_OPTIONS = ['我司', '第三方车队']; +export function buildMonthFilterOptions(rows: SelfOperatedLedgerRow[]): string[] { + const set = new Set(); + rows.forEach((row) => set.add(formatDisplayMonth(row))); + return Array.from(set).sort((a, b) => a.localeCompare(b, 'zh-CN')); +} -export const BRAND_MODEL_MAP: Record = { - 现代: ['帕力安牌4.5吨冷链车', '帕力安牌18吨双飞翼货车'], - 飞驰: ['49吨牵引车头'], - 苏龙: ['海格牌KLQ5180XYKFCEV'], -}; +export function buildSystemVehicleTypeOptions(rows: SelfOperatedLedgerRow[]): string[] { + const set = new Set(); + rows.forEach((row) => { + const label = resolveSystemVehicleType(row.plateNo); + if (label) set.add(label); + }); + return Array.from(set).sort((a, b) => a.localeCompare(b, 'zh-CN')); +} diff --git a/src/prototypes/self-operated-business-ledger/utils/vehicle-ref.ts b/src/prototypes/self-operated-business-ledger/utils/vehicle-ref.ts new file mode 100644 index 0000000..57fc171 --- /dev/null +++ b/src/prototypes/self-operated-business-ledger/utils/vehicle-ref.ts @@ -0,0 +1,58 @@ +import vehicles from '../../vehicle-management/data/vehicles.json'; +import { getVehicleRef, isPlateInVehicleSystem as isPlateInLeaseRegistry } from '../../lease-business-detail/utils/system-ref'; + +type VehicleSeed = { plateNo?: string; brand?: string; model?: string }; + +/** 物流业务明细样例车牌(与车辆主数据 / 运营台账对齐) */ +const SEED_PLATE_MAP: Record = { + 粤AGQ8393: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, + 粤AGR3208: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, + 粤AGP6680: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, + 粤AGP9835: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, + 粤AGP9825: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, + 粤AGP9331: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, + 粤AGP6651: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, + 粤AGP6639: { brand: '现代', model: '帕力安牌4.5吨冷链车' }, +}; + +const plateFromVehicles = new Map(); +(vehicles as VehicleSeed[]).forEach((vehicle) => { + if (!vehicle.plateNo) return; + plateFromVehicles.set(vehicle.plateNo.trim(), { + brand: vehicle.brand ?? '', + model: vehicle.model ?? '', + }); +}); + +export function formatBrandModelDot(brand: string, model: string): string { + if (brand && model) return `${brand}·${model}`; + return brand || model || ''; +} + +/** 根据车牌从车辆主数据解析系统车型:品牌·型号 */ +export function resolveSystemVehicleType(plateNo: string): string { + const plate = plateNo.trim(); + if (!plate) return ''; + + const leaseRef = getVehicleRef(plate); + if (leaseRef?.brandModel) return leaseRef.brandModel; + if (leaseRef?.brand || leaseRef?.model) { + return formatBrandModelDot(leaseRef.brand, leaseRef.model); + } + + const fromRegistry = plateFromVehicles.get(plate); + if (fromRegistry) return formatBrandModelDot(fromRegistry.brand, fromRegistry.model); + + const seed = SEED_PLATE_MAP[plate]; + if (seed) return formatBrandModelDot(seed.brand, seed.model); + + return ''; +} + +export function isPlateInVehicleSystem(plateNo: string): boolean { + const plate = plateNo.trim(); + if (!plate) return false; + return isPlateInLeaseRegistry(plate) + || plateFromVehicles.has(plate) + || Boolean(SEED_PLATE_MAP[plate]); +} diff --git a/src/prototypes/supplier-management/annotation-source.json b/src/prototypes/supplier-management/annotation-source.json index 9dddeee..00490c7 100644 --- a/src/prototypes/supplier-management/annotation-source.json +++ b/src/prototypes/supplier-management/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "supplier-management", "pageId": "list", - "updatedAt": 1783589987168, + "updatedAt": 1783800844968, "nodes": [ { "id": "sm-list-filter", @@ -216,37 +216,6 @@ } }, "directory": { - "nodes": [ - { - "type": "folder", - "id": "oneos-project-nav", - "title": "ONE-OS 原型导航", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-prototype-nav", - "title": "原型导航", - "href": "/prototypes/oneos-prototype-nav", - "target": "self" - }, - { - "type": "folder", - "id": "nav-folder-1782874576229-r8atbu", - "title": "OneOS", - "defaultExpanded": true, - "children": [ - { - "type": "link", - "id": "nav-link-oneos-web-workbench", - "title": "工作台", - "href": "/prototypes/oneos-web-workbench", - "target": "self" - } - ] - } - ] - } - ] + "nodes": [] } } diff --git a/src/prototypes/supplier-management/components/FilterPanel.tsx b/src/prototypes/supplier-management/components/FilterPanel.tsx index a452886..b3a9a8a 100644 --- a/src/prototypes/supplier-management/components/FilterPanel.tsx +++ b/src/prototypes/supplier-management/components/FilterPanel.tsx @@ -160,19 +160,12 @@ export function FilterPanel({ type="button" className="vm-btn vm-btn-link" onClick={() => setExpanded(!expanded)} - > + data-vm-icon={expanded ? 'chevron-up' : 'filter'}> {expanded ? '收起' : '更多筛选'} - - + )} - - +
diff --git a/src/prototypes/supplier-management/components/SupplierTable.tsx b/src/prototypes/supplier-management/components/SupplierTable.tsx index 90d3853..9e32316 100644 --- a/src/prototypes/supplier-management/components/SupplierTable.tsx +++ b/src/prototypes/supplier-management/components/SupplierTable.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { OperationActions } from '../../../common/OperationActions'; import type { SupplierRecord } from '../types'; import { CoopStatusBadge } from './CoopStatusBadge'; @@ -80,13 +81,18 @@ export function SupplierTable({
))} diff --git a/src/prototypes/supplier-management/index.tsx b/src/prototypes/supplier-management/index.tsx index 5859eff..c517983 100644 --- a/src/prototypes/supplier-management/index.tsx +++ b/src/prototypes/supplier-management/index.tsx @@ -7,11 +7,11 @@ import './styles/index.css'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Download, FileUp, Plus } from 'lucide-react'; import { - AnnotationViewer, type AnnotationDirectoryRouteNode, type AnnotationSourceDocument, - type AnnotationViewerOptions, + type AnnotationViewerOptions } from '@axhub/annotation'; +import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host'; import seedSuppliers from './data/suppliers.json'; import { FilterPanel } from './components/FilterPanel'; import { KpiCards } from './components/KpiCards'; @@ -175,9 +175,6 @@ export default function SupplierManagementApp() { /> ) : ( <> -
- -
@@ -241,7 +235,7 @@ export default function SupplierManagementApp() { setImportOpen(false)} onImport={handleImport} /> - diff --git a/src/prototypes/supplier-management/pages/CreatePage.tsx b/src/prototypes/supplier-management/pages/CreatePage.tsx index b5a76b5..d415e01 100644 --- a/src/prototypes/supplier-management/pages/CreatePage.tsx +++ b/src/prototypes/supplier-management/pages/CreatePage.tsx @@ -120,13 +120,9 @@ export function CreatePage({ return (
- +
+ +

主体信息

@@ -254,10 +250,10 @@ export function CreatePage({
- +
- - + +
diff --git a/src/prototypes/task-work-order/.spec/2026-07-10-requirements.md b/src/prototypes/task-work-order/.spec/2026-07-10-requirements.md new file mode 100644 index 0000000..444a0ef --- /dev/null +++ b/src/prototypes/task-work-order/.spec/2026-07-10-requirements.md @@ -0,0 +1,68 @@ +# 任务工单 · 产品需求确认(2026-07-10) + +| 项 | 内容 | +|---|---| +| 模块名称 | 采购管理 → 任务工单 | +| 所属系统 | ONE-OS Web 端(业管发起)+ 小羚羚小程序(执行,后续) | +| 文档状态 | **已确认**,第一版仅业管发起端 | +| 设计基底 | `src/prototypes/vm-shared/DESIGN.md`(vm-page) | + +--- + +## 1. 背景 + +采购车辆时,合同中的里程、政策、维保等条款需由业管拆解为可执行任务,发布给**无法查看合同原文**的运维等相关部门。执行人通过待办处理并反馈;业管统一督办进度与结果。 + +## 2. 目标用户 + +| 角色 | 第一版 | 诉求 | +|------|--------|------| +| 业管 | **本期实现** | 从合同或独立入口发起任务、指派、督办、催办、看结果 | +| 运维 / 执行人 | 后续(OneOS 执行页 + 小羚羚小程序) | 待办处理、反馈;**不可见合同原文与金额** | +| 归口责任人 | 业管指定 | 监督闭环,不因转交变更 | + +## 3. 发起方式(已确认) + +1. **从采购合同发起**:选取合同条款类型 → 结构化任务摘要 → 绑车/周期 → 指派 → 发布 +2. **独立新增**:自定义办理事宜,可**反向关联**采购合同(可选) + +## 4. 权限边界(已确认) + +执行人可见:任务摘要、执行要求、绑定车辆、进度、自己的反馈区。 +执行人不可见:合同全文、PDF、金额、供应商商务条款、跳转合同详情。 + +## 5. 里程任务(已确认) + +- 绑定采购车辆(可多车) +- **周期由业管自定义**(按月、按季、跨年均可,不做系统限制) +- 进度:车机(优先)或 GPS 里程自动统计 +- 达标可自动完成;未达标继续跟踪 / 业管催办 + +## 6. 任务类型 + +里程履约、维保履约、政策兑现、付款/交付节点、条款跟进、通用临时。 + +## 7. 责任链 + +发起人(业管)→ 当前执行人(可转交/下级指派)→ **归口责任人(固定)** + +## 8. 系统集成(后续) + +- 任务同步至 **工作台待办**,点击快速处理 +- **小羚羚小程序**增加任务执行页 +- 第一版 Web 仅业管发起端,执行端用 Toast/说明预留 + +## 9. 本期不做 + +- 执行人 Web 页 / 小程序完整页 +- 合同 OCR / AI 解析 +- 完整采购合同管理模块(mock 合同列表) +- 审批流配置 + +## 10. 验收重点 + +1. 双入口发起(合同 / 独立)可用,独立任务可反向关联合同 +2. 业管详情可见执行人、进度、反馈、时间线、催办 +3. 里程任务:自定义周期 + 绑车 + 自动进度条演示 +4. 列表 KPI、筛选、我发布/我督办视角 +5. 标注说明执行端与工作台同步为后续联调项 diff --git a/src/prototypes/task-work-order/TaskWorkOrderPage.jsx b/src/prototypes/task-work-order/TaskWorkOrderPage.jsx new file mode 100644 index 0000000..2f292df --- /dev/null +++ b/src/prototypes/task-work-order/TaskWorkOrderPage.jsx @@ -0,0 +1,887 @@ +// 采购管理 · 任务工单(业管发起端) +// 设计规范:vm-shared/DESIGN.md · vm-page + +import React, { useCallback, useMemo, useState } from 'react'; +import dayjs from 'dayjs'; +import { + Alert, + Button, + DatePicker, + Drawer, + Form, + Input, + Modal, + Progress, + Select, + Space, + Steps, + Table, + Tag, + Timeline, + Tooltip, + message, +} from 'antd'; +import { TablePagination, DEFAULT_PAGE_SIZE } from '../../common/TablePagination'; +import { OperationActions } from '../../common/OperationActions'; +import { DateRangeFilterField } from '../vehicle-management/components/DateRangeFilterField'; +import { + EXPORT_ICON, + KpiCardButton, + SEARCH_ICON, + STATUS_KPI_ICONS, + VIEW_KPI_ICONS, +} from './components/task-work-order-kpi.jsx'; +import { + buildInitialTasks, + computeMileageProgress, + CURRENT_USER, + MOCK_OWNERS, + MOCK_PURCHASE_CONTRACTS, + MOCK_VEHICLES, + ownerName, + TASK_STATUS_META, + TASK_TYPE_META, + vehicleSummary, +} from './data/task-work-order-seed.js'; + +const { RangePicker } = DatePicker; +const { TextArea } = Input; + +const ONEOS_ANT_TABLE_GLOBAL_FIX = [ + '.ant-table-container .ant-table-header { margin-bottom: 0 !important; }', + '.ant-table-container .ant-table-body { margin-top: 0 !important; }', + '.ant-table-container .ant-table-body > table, .ant-table-content table { margin-top: 0 !important; }', + '.ant-table-tbody > tr.ant-table-measure-row, .ant-table-tbody > tr.ant-table-measure-row > td, .ant-table-tbody > tr.ant-table-measure-row > th { display: none !important; height: 0 !important; max-height: 0 !important; min-height: 0 !important; padding: 0 !important; margin: 0 !important; border: none !important; line-height: 0 !important; font-size: 0 !important; overflow: hidden !important; visibility: hidden !important; pointer-events: none !important; }', + '.vm-list-table .ant-table-thead th,.vm-list-table .ant-table-tbody td{white-space:nowrap;}', +]; + +const VIEW_KPI_CARDS = [ + { key: 'all', title: '全部', desc: '显示全部任务工单' }, + { key: 'published', title: '我发布的', desc: '仅显示由我发起的任务' }, + { key: 'supervise', title: '我督办的', desc: '仅显示归口责任人为我的任务' }, +]; + +const STATUS_KPI_CARDS = [ + { key: 'pending', title: '待处理', desc: '尚未开始执行的任务' }, + { key: 'overdue', title: '已超时', desc: '超过计划周期或 SLA 的任务' }, + { key: 'in_progress', title: '进行中', desc: '正在执行的任务' }, + { key: 'mileage', title: '里程类', desc: '里程履约类,自动统计车机/GPS 进度' }, +]; + +const CLAUSE_TYPE_OPTIONS = [ + { value: 'mileage', label: '里程履约' }, + { value: 'maintenance', label: '维保履约' }, + { value: 'policy', label: '政策兑现' }, + { value: 'payment', label: '付款节点' }, + { value: 'penalty', label: '条款跟进' }, + { value: 'general', label: '自定义事宜' }, +]; + +function nextTaskCode(tasks) { + const n = tasks.length + 1; + return `WO-2026-${String(n).padStart(4, '0')}`; +} + +export default function TaskWorkOrderPage() { + const [tasks, setTasks] = useState(() => buildInitialTasks()); + const [viewTab, setViewTab] = useState('all'); + const [statusKpi, setStatusKpi] = useState(null); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [filterType, setFilterType] = useState(undefined); + const [filterStatus, setFilterStatus] = useState(undefined); + const [filterContract, setFilterContract] = useState(undefined); + const [filterOwner, setFilterOwner] = useState(undefined); + const [filterKeyword, setFilterKeyword] = useState(''); + const [filterDateRange, setFilterDateRange] = useState({ startDate: '', endDate: '' }); + const [detailTask, setDetailTask] = useState(null); + const [contractModalOpen, setContractModalOpen] = useState(false); + const [standaloneModalOpen, setStandaloneModalOpen] = useState(false); + const [contractStep, setContractStep] = useState(0); + const [contractForm] = Form.useForm(); + const [standaloneForm] = Form.useForm(); + + const enrichedTasks = useMemo( + () => + tasks.map((t) => { + const progress = computeMileageProgress(t); + return { ...t, progressInfo: progress }; + }), + [tasks], + ); + + const filteredTasks = useMemo(() => { + return enrichedTasks.filter((t) => { + if (viewTab === 'published' && t.initiatorId !== CURRENT_USER.id) return false; + if (viewTab === 'supervise' && t.accountableOwnerId !== CURRENT_USER.id) return false; + if (filterType && t.taskType !== filterType) return false; + if (filterStatus && t.status !== filterStatus) return false; + if (filterContract && t.contractId !== filterContract) return false; + if (filterOwner && t.currentOwnerId !== filterOwner) return false; + if (filterKeyword) { + const kw = filterKeyword.trim().toLowerCase(); + if ( + !t.title.toLowerCase().includes(kw) && + !t.code.toLowerCase().includes(kw) && + !(t.contractCode || '').toLowerCase().includes(kw) + ) { + return false; + } + } + if (filterDateRange.startDate && (t.createdAt || '').slice(0, 10) < filterDateRange.startDate) return false; + if (filterDateRange.endDate && (t.createdAt || '').slice(0, 10) > filterDateRange.endDate) return false; + if (statusKpi === 'pending' && t.status !== 'pending') return false; + if (statusKpi === 'overdue' && t.status !== 'overdue') return false; + if (statusKpi === 'in_progress' && t.status !== 'in_progress') return false; + if (statusKpi === 'mileage' && t.taskType !== 'mileage') return false; + return true; + }); + }, [enrichedTasks, viewTab, filterType, filterStatus, filterContract, filterOwner, filterKeyword, filterDateRange, statusKpi]); + + const viewKpiCounts = useMemo( + () => ({ + all: enrichedTasks.length, + published: enrichedTasks.filter((t) => t.initiatorId === CURRENT_USER.id).length, + supervise: enrichedTasks.filter((t) => t.accountableOwnerId === CURRENT_USER.id).length, + }), + [enrichedTasks], + ); + + const statusKpiCounts = useMemo(() => { + const base = enrichedTasks.filter((t) => { + if (viewTab === 'published' && t.initiatorId !== CURRENT_USER.id) return false; + if (viewTab === 'supervise' && t.accountableOwnerId !== CURRENT_USER.id) return false; + return true; + }); + return { + pending: base.filter((t) => t.status === 'pending').length, + overdue: base.filter((t) => t.status === 'overdue').length, + in_progress: base.filter((t) => t.status === 'in_progress').length, + mileage: base.filter((t) => t.taskType === 'mileage').length, + }; + }, [enrichedTasks, viewTab]); + + const pagedTasks = useMemo(() => { + const start = (page - 1) * pageSize; + return filteredTasks.slice(start, start + pageSize); + }, [filteredTasks, page, pageSize]); + + const resetFilters = useCallback(() => { + setFilterType(undefined); + setFilterStatus(undefined); + setFilterContract(undefined); + setFilterOwner(undefined); + setFilterKeyword(''); + setFilterDateRange({ startDate: '', endDate: '' }); + setStatusKpi(null); + setPage(1); + }, []); + + const handleUrge = useCallback((task) => { + const now = dayjs().format('YYYY-MM-DD HH:mm'); + setTasks((prev) => + prev.map((t) => + t.id === task.id + ? { + ...t, + timeline: [ + ...(t.timeline || []), + { at: now, action: '催办', operator: CURRENT_USER.name, remark: '已向执行人发送催办,并同步工作台待办' }, + ], + } + : t, + ), + ); + message.success(`已催办「${task.title}」,执行人将收到待办提醒(原型)`); + }, []); + + const openContractWizard = useCallback(() => { + setContractStep(0); + contractForm.resetFields(); + contractForm.setFieldsValue({ + accountableOwnerId: 'u_chen', + currentOwnerId: 'u_zhang', + mileageMode: 'period_avg', + }); + setContractModalOpen(true); + }, [contractForm]); + + const openStandalone = useCallback(() => { + standaloneForm.resetFields(); + standaloneForm.setFieldsValue({ + taskType: 'general', + accountableOwnerId: CURRENT_USER.id, + currentOwnerId: 'u_zhao', + }); + setStandaloneModalOpen(true); + }, [standaloneForm]); + + const submitContractTask = useCallback(() => { + contractForm.validateFields().then((values) => { + const contract = MOCK_PURCHASE_CONTRACTS.find((c) => c.id === values.contractId); + if (!contract) return; + const clauseKey = values.clauseType === 'general' ? null : values.clauseType; + const requirement = + values.requirement || + (clauseKey && contract.clauses[clauseKey]) || + values.customRequirement || + '—'; + const period = values.period || []; + const id = `wo-${Date.now()}`; + const newTask = { + id, + code: nextTaskCode(tasks), + taskType: values.clauseType === 'general' ? 'general' : values.clauseType, + title: values.title || `${TASK_TYPE_META[values.clauseType]?.label || '任务'} · ${contract.code}`, + requirement, + source: 'contract', + contractId: contract.id, + contractCode: contract.code, + vehicleIds: values.vehicleIds || [], + periodStart: period[0] ? dayjs(period[0]).format('YYYY-MM-DD') : '', + periodEnd: period[1] ? dayjs(period[1]).format('YYYY-MM-DD') : '', + mileageTarget: values.mileageTarget, + mileageMode: values.mileageMode || 'period_avg', + initiatorId: CURRENT_USER.id, + accountableOwnerId: values.accountableOwnerId, + currentOwnerId: values.currentOwnerId, + status: 'pending', + createdAt: dayjs().format('YYYY-MM-DD HH:mm'), + feedbacks: [], + timeline: [ + { + at: dayjs().format('YYYY-MM-DD HH:mm'), + action: '发布任务', + operator: CURRENT_USER.name, + remark: `自采购合同 ${contract.code} 发起`, + }, + ], + syncWorkbench: true, + }; + setTasks((prev) => [newTask, ...prev]); + setContractModalOpen(false); + message.success('任务已发布,已同步至执行人工作台待办(原型)'); + }); + }, [contractForm, tasks]); + + const submitStandaloneTask = useCallback(() => { + standaloneForm.validateFields().then((values) => { + const contract = values.contractId + ? MOCK_PURCHASE_CONTRACTS.find((c) => c.id === values.contractId) + : null; + const period = values.period || []; + const newTask = { + id: `wo-${Date.now()}`, + code: nextTaskCode(tasks), + taskType: values.taskType, + title: values.title, + requirement: values.requirement, + source: 'standalone', + contractId: contract?.id, + contractCode: contract?.code, + vehicleIds: values.vehicleIds || [], + periodStart: period[0] ? dayjs(period[0]).format('YYYY-MM-DD') : '', + periodEnd: period[1] ? dayjs(period[1]).format('YYYY-MM-DD') : '', + mileageTarget: values.mileageTarget, + mileageMode: values.mileageMode, + initiatorId: CURRENT_USER.id, + accountableOwnerId: values.accountableOwnerId, + currentOwnerId: values.currentOwnerId, + status: 'pending', + createdAt: dayjs().format('YYYY-MM-DD HH:mm'), + feedbacks: [], + timeline: [ + { + at: dayjs().format('YYYY-MM-DD HH:mm'), + action: '独立新增', + operator: CURRENT_USER.name, + remark: contract ? `反向关联 ${contract.code}` : '未关联合同', + }, + ], + syncWorkbench: true, + }; + setTasks((prev) => [newTask, ...prev]); + setStandaloneModalOpen(false); + message.success('独立任务已创建,已同步工作台待办(原型)'); + }); + }, [standaloneForm, tasks]); + + const contractWatchId = Form.useWatch('contractId', contractForm); + const contractWatchType = Form.useWatch('clauseType', contractForm); + const standaloneWatchType = Form.useWatch('taskType', standaloneForm); + + const contractVehicles = useMemo( + () => MOCK_VEHICLES.filter((v) => !contractWatchId || v.contractId === contractWatchId), + [contractWatchId], + ); + + const selectedContract = MOCK_PURCHASE_CONTRACTS.find((c) => c.id === contractWatchId); + + const columns = [ + { + title: '工单号', + dataIndex: 'code', + width: 130, + fixed: 'left', + render: (v) => {v}, + }, + { + title: '类型', + dataIndex: 'taskType', + width: 96, + render: (v) => { + const m = TASK_TYPE_META[v] || { label: v, color: 'default' }; + return {m.label}; + }, + }, + { + title: '任务摘要', + dataIndex: 'title', + ellipsis: true, + render: (v, r) => ( + + {v} + + ), + }, + { + title: '关联合同', + dataIndex: 'contractCode', + width: 150, + render: (v, r) => + v ? ( + + {v} + + ) : ( + + ), + }, + { + title: '绑定车辆', + dataIndex: 'vehicleIds', + width: 120, + render: (ids) => vehicleSummary(ids), + }, + { + title: '执行周期', + width: 180, + render: (_, r) => + r.periodStart && r.periodEnd ? ( + + {r.periodStart} 至 {r.periodEnd} + + ) : ( + '—' + ), + }, + { + title: '当前执行人', + dataIndex: 'currentOwnerId', + width: 96, + render: (id) => ownerName(id), + }, + { + title: '归口负责', + dataIndex: 'accountableOwnerId', + width: 96, + render: (id) => ownerName(id), + }, + { + title: '进度', + width: 140, + render: (_, r) => { + if (r.taskType === 'mileage' && r.progressInfo) { + return ( +
+ +
{r.progressInfo.label}
+
+ ); + } + return 手动跟踪; + }, + }, + { + title: '状态', + dataIndex: 'status', + width: 88, + render: (v) => { + const m = TASK_STATUS_META[v] || { label: v, color: 'default' }; + return {m.label}; + }, + }, + { + title: '操作', + width: 168, + fixed: 'right', + render: (_, r) => ( + setDetailTask(r) }} + more={ + r.status !== 'completed' && r.status !== 'closed' + ? [{ key: 'urge', label: '催办', onClick: () => handleUrge(r) }] + : [] + } + /> + ), + }, + ]; + + const handleStatusKpiClick = useCallback((key) => { + setStatusKpi((prev) => (prev === key ? null : key)); + setPage(1); + }, []); + + return ( +
+ +
+
+

筛选条件

+
+
+
+
+ + ({ value, label: m.label }))} + className="vm-filter-control" + /> +
+
+ + ({ value: o.id, label: `${o.name}(${o.dept})` }))} + className="vm-filter-control" + /> +
+
+ + +
+
+ + setFilterKeyword(e.target.value)} + className="vm-filter-control" + /> +
+
+
+
+ + +
+
+ +
+ {VIEW_KPI_CARDS.map((card) => ( + { + setViewTab(card.key); + setPage(1); + }} + /> + ))} + {STATUS_KPI_CARDS.map((card) => ( + handleStatusKpiClick(card.key)} + /> + ))} +
+ +
+
+
+ + + +
+
+
+
+
- onToggleAll(e.target.checked)} - aria-label="全选" - /> -
- handleSort(col.key)} - /> + {col.sortable ? ( + handleSort(col.key)} + /> + ) : ( + {col.title} + )} {sortedRecords.map((row) => (
- onToggleRow(row.id, e.target.checked)} - aria-label={`选择 ${row.plateNo}`} - /> - {renderCell(row, col)} -
- - -
+ onEdit(row) }} + more={[ + { + key: 'delete', + label: '删除', + danger: true, + onClick: () => onDelete(row), + }, + ]} + />
{record.creator} {record.createTime} -
- - | - - | - -
+ onView(record) }} + edit={{ onClick: () => onEdit(record) }} + more={[ + { + key: 'delete', + label: '删除', + danger: true, + onClick: () => onDelete(record), + }, + ]} + />
+ +
+ { + setPageSize(ps); + setPage(1); + }} + /> +
+ + + + setDetailTask(null)} + destroyOnClose + extra={ + detailTask && detailTask.status !== 'completed' && detailTask.status !== 'closed' ? ( + + ) : null + } + > + {detailTask && ( + <> + +
+ 执行人在 OneOS 任务执行页 / 小羚羚小程序 / 工作台待办中处理本任务,不可查看合同原文与金额。 +
+ +
+
+ {TASK_TYPE_META[detailTask.taskType]?.label} ·{' '} + + {TASK_STATUS_META[detailTask.status]?.label} + +
+
{detailTask.title}
+
{detailTask.requirement}
+
+ + {detailTask.contractCode && ( +
+
来源合同(仅业管可见)
+ {detailTask.contractCode} + + {detailTask.source === 'standalone' ? '独立任务反向关联' : '合同发起'} + +
+ )} + +
+
执行周期
+ + {detailTask.periodStart || '—'} 至 {detailTask.periodEnd || '—'} + +
+ + {(detailTask.vehicleIds || []).length > 0 && ( +
+
绑定车辆
+ {MOCK_VEHICLES.filter((v) => detailTask.vehicleIds.includes(v.id)).map((v) => ( + + {v.plateNo} · {v.model} · {v.mileage.toLocaleString()} km({v.mileageSource}) + + ))} +
+ )} + + {detailTask.taskType === 'mileage' && detailTask.progressInfo && ( +
+
里程进度(自动)
+ +
{detailTask.progressInfo.label}
+
+ )} + +
+
执行反馈
+ {(detailTask.feedbacks || []).length === 0 ? ( + 暂无反馈 + ) : ( + (detailTask.feedbacks || []).map((f, i) => ( +
+
+ {f.at} · {f.by} +
+ {f.note} + {f.attachments?.length ? ( +
附件:{f.attachments.join('、')}
+ ) : null} +
+ )) + )} +
+ +
+
操作时间线
+ ({ + children: ( +
+
+ {ev.action} · {ev.operator} +
+
{ev.at}
+ {ev.remark ?
{ev.remark}
: null} +
+ ), + }))} + /> +
+ + {detailTask.syncWorkbench && ( + + )} + + )} +
+ + setContractModalOpen(false)} + width={640} + footer={ + + + {contractStep > 0 && ( + + )} + {contractStep < 2 ? ( + + ) : ( + + )} + + } + destroyOnClose + > + +
+ {contractStep === 0 && ( + <> +
选择采购合同与要发布的条款类型;条款正文将转为执行摘要,执行人不可见合同原文。
+ + + + {selectedContract && contractWatchType && contractWatchType !== 'general' && ( + +