提交
This commit is contained in:
1 parent
771f010a39
commit
ee4abd361e
102 files changed
+8021
-4094
No files matched your search
@@ -0,0 +1,15 @@
|
||||
-- 渠道服务订单操作流水:财务确认人、作废人、作废时间
|
||||
ALTER TABLE `biz_channel_service_order_txn`
|
||||
ADD COLUMN `finance_confirm_by` varchar(64) DEFAULT NULL COMMENT '财务确认人ID' AFTER `finance_confirm_time`,
|
||||
ADD COLUMN `void_by` varchar(64) DEFAULT NULL COMMENT '作废人ID' AFTER `finance_remark`,
|
||||
ADD COLUMN `void_time` datetime DEFAULT NULL COMMENT '作废时间' AFTER `void_by`;
|
||||
|
||||
-- 历史已确认流水:用 update_by 回填确认人
|
||||
UPDATE `biz_channel_service_order_txn`
|
||||
SET `finance_confirm_by` = `update_by`
|
||||
WHERE `txn_status` = 'yqr' AND `finance_confirm_time` IS NOT NULL AND `finance_confirm_by` IS NULL;
|
||||
|
||||
-- 历史已作废流水:用 update_by / update_time 回填(仅曾提交财务的流水视为财务作废)
|
||||
UPDATE `biz_channel_service_order_txn`
|
||||
SET `void_by` = `update_by`, `void_time` = `update_time`
|
||||
WHERE `txn_status` = 'yzf' AND `finance_submit_time` IS NOT NULL AND `void_by` IS NULL;
|
||||
@@ -1035,8 +1035,11 @@ CREATE TABLE `biz_channel_service_order_txn` (
|
||||
`finance_submit_remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发起确认备注',
|
||||
`finance_submit_attachments` text COLLATE utf8mb4_unicode_ci COMMENT '发起确认附件',
|
||||
`finance_confirm_time` datetime DEFAULT NULL COMMENT '财务确认时间',
|
||||
`finance_confirm_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '财务确认人ID',
|
||||
`finance_confirm_attachments` text COLLATE utf8mb4_unicode_ci COMMENT '财务确认附件',
|
||||
`finance_remark` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '财务确认备注',
|
||||
`void_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '作废人ID',
|
||||
`void_time` datetime DEFAULT NULL COMMENT '作废时间',
|
||||
`balance_before` decimal(18,2) DEFAULT NULL COMMENT '操作前余额(yfk类型)',
|
||||
`balance_after` decimal(18,2) DEFAULT NULL COMMENT '操作后余额(yfk类型)',
|
||||
`tenant_no` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
# 金额/单价千分位输入与展示 — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 在 web-admin 统一金额/单价的「聚焦纯数字、失焦千分位、提交数值」交互,并统一只读列表/详情的千分位展示。
|
||||
|
||||
**Architecture:** 抽取 `money-format.ts` 纯函数 + `AmountInput` 封装 `el-input`;业务页替换金额类 `el-input-number` 与局部重复逻辑;只读处使用 `formatMoneyDisplay`。
|
||||
|
||||
**Tech Stack:** Vue 3、Element Plus、TypeScript、现有 Prettier/ESLint 规范。
|
||||
|
||||
**Design spec:** `docs/superpowers/specs/2026-05-25-money-input-thousand-separator-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| 操作 | 路径 |
|
||||
|------|------|
|
||||
| Create | `web-admin/src/utils/money-format.ts` |
|
||||
| Create | `web-admin/src/components/common/amount-input/index.vue` |
|
||||
| Modify | `web-admin/src/views/pilot-service/channel-service-order/index.vue` |
|
||||
| Modify | `web-admin/src/views/pilot-service/channel-service-order-finance/index.vue` |
|
||||
| Modify | `openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md` |
|
||||
| Later P1+ | `channel/index.vue`, `cell-product-price/index.vue`, `project-price-system/index.vue`, `ProtocolFormDialog.vue`, `payment-info/index.vue`, … |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 金额格式化工具
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/utils/money-format.ts`
|
||||
|
||||
- [ ] **Step 1: 实现三个导出函数**
|
||||
|
||||
```typescript
|
||||
export function addThousandSeparators(intStr: string): string { /* 字符串分段,无前瞻正则 */ }
|
||||
|
||||
export function sanitizeMoneyInput(value: string, precision = 2): string { /* 去逗号、单小数点、截断小数 */ }
|
||||
|
||||
export function formatMoneyDisplay(value: string | number | null | undefined, precision = 2): string
|
||||
|
||||
export function parseMoneyToNumber(value: string, precision = 2): number | null
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 本地快速验证**
|
||||
|
||||
在 Node 或浏览器控制台执行:
|
||||
|
||||
```javascript
|
||||
formatMoneyDisplay(1234567.89) // '1,234,567.89'
|
||||
parseMoneyToNumber('1,234.56') // 1234.56
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: AmountInput 组件
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/components/common/amount-input/index.vue`
|
||||
|
||||
- [ ] **Step 1: 实现组件**
|
||||
|
||||
- `defineModel<number | null>()` 或 `modelValue` + `emit('update:modelValue')`
|
||||
- 内部 `displayText`,`watch(modelValue)` 在非聚焦时同步为 `formatMoneyDisplay`
|
||||
- `@focus` → sanitize 展示
|
||||
- `@input` → `sanitizeMoneyInput` 写回 `displayText`
|
||||
- `@blur` → `parseMoneyToNumber` → emit;`displayText = formatMoneyDisplay`
|
||||
- Props: `min`, `max`, `precision`, `placeholder`, `disabled`, `clearable`
|
||||
|
||||
- [ ] **Step 2: 在渠道服务订单页面临时引用验证**
|
||||
|
||||
仅 import 一个弹窗字段 smoke test(Task 3 会全量替换)。
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 渠道服务订单页(P0)
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pilot-service/channel-service-order/index.vue`
|
||||
|
||||
- [ ] **Step 1: 删除页面内重复函数**
|
||||
|
||||
移除 `addThousandSeparators`、`formatRechargeAmountDisplay`、`sanitizeRechargeAmountInput`、`parseRechargeAmountInput`、`parseAmountInputToNumber`(若仅用于充值)、`rechargeAmountInput` 及相关 handler。
|
||||
|
||||
- [ ] **Step 2: 替换可编辑金额**
|
||||
|
||||
| 位置 | 替换为 |
|
||||
|------|--------|
|
||||
| 发起充值 | `<AmountInput v-model="rechargeForm.amount" :min="0.01" />` |
|
||||
| 申请退款 | `<AmountInput v-model="refundTxnForm.amount" :min="0.01" :max="currentBalance" />` |
|
||||
| 新增/编辑 产品明细单价 | `<AmountInput v-model="row.unitPrice" :min="0" @change="calcItemAmount(row)" />` |
|
||||
|
||||
- [ ] **Step 3: 只读展示**
|
||||
|
||||
将列表/查看/合计中的 `formatAmount`(金额类)改为 `formatMoneyDisplay`,或:
|
||||
|
||||
```typescript
|
||||
const formatAmount = (v: any) => formatMoneyDisplay(v, 2)
|
||||
```
|
||||
|
||||
仅当该文件内 `formatAmount` 全部用于金额时方可整文件替换。
|
||||
|
||||
- [ ] **Step 4: 手工验收**
|
||||
|
||||
按 spec 第 6 节 5 条检查;提交充值/退款 payload 为 number。
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 财务确认页只读列(P1 片段)
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pilot-service/channel-service-order-finance/index.vue`
|
||||
|
||||
- [ ] **Step 1: `formatAmount` → `formatMoneyDisplay`**
|
||||
|
||||
列表金额、详情单价/金额、确认弹窗中的金额文案。
|
||||
|
||||
---
|
||||
|
||||
### Task 5: UI 规范 OpenSpec
|
||||
|
||||
**Files:**
|
||||
- Modify: `openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md`
|
||||
|
||||
- [ ] **Step 1: 新增 Requirement「金额与单价千分位」**
|
||||
|
||||
SHALL 行为表 + `AmountInput` + `formatMoneyDisplay` 路径说明。
|
||||
|
||||
---
|
||||
|
||||
### Task 6: P1 中试服务其余页(可并行子任务)
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/pilot-service/channel/index.vue`(协议价行内)
|
||||
- Modify: `web-admin/src/views/pilot-service/cell-product-price/index.vue`
|
||||
- Modify: `web-admin/src/views/pilot-service/project-price-system/index.vue`
|
||||
- Modify: `web-admin/src/views/pilot-service/checkup-institution/index.vue`、`CheckupPackagePanel.vue`
|
||||
|
||||
每文件重复 Task 3 的替换模式:金额类 `el-input-number` → `AmountInput`;只读 → `formatMoneyDisplay`。
|
||||
|
||||
---
|
||||
|
||||
### Task 7: P2 细胞存储(后续迭代)
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue`
|
||||
- Modify: `web-admin/src/views/cell-storage/payment-info/index.vue`
|
||||
- 只读:`ProtocolViewDialog.vue`、payment/refund confirm 列表
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 图谱更新(Agent 规则)
|
||||
|
||||
- [ ] **Step 1:** `./scripts/update-graph.sh frontend`(完成 P0 后)
|
||||
|
||||
---
|
||||
|
||||
## Self-review(plan ↔ spec)
|
||||
|
||||
| Spec 要求 | Task |
|
||||
|-----------|------|
|
||||
| 聚焦/失焦/提交行为 | Task 1–2 |
|
||||
| 只读千分位 | Task 3–4, 6–7 |
|
||||
| 禁止页面内复制正则 | Task 3 删除重复 |
|
||||
| 数量字段排除 | 各 Task 仅改单价/金额 |
|
||||
| OpenSpec | Task 5 |
|
||||
| 分期 P0–P3 | Task 3–7 |
|
||||
|
||||
## 执行选项
|
||||
|
||||
Plan 已保存至 `docs/superpowers/plans/2026-05-25-money-input-thousand-separator.md`。
|
||||
|
||||
1. **Subagent-Driven** — 按 Task 派发子代理,逐 Task 评审
|
||||
2. **Inline Execution** — 当前会话按 Task 1→3 连续实现(建议先 P0)
|
||||
|
||||
请告知:是否批准设计 spec,以及希望先执行 **P0(工具 + 组件 + 渠道服务订单)** 还是全量分期推进。
|
||||
@@ -0,0 +1,189 @@
|
||||
# Web 管理端金额/单价千分位输入与展示 — 设计说明
|
||||
|
||||
## 背景
|
||||
|
||||
业务人员在录入或核对大额「单价」「金额」时,需要千分位逗号辅助阅读,但输入过程中插入逗号会干扰光标与连续键入。渠道服务订单「发起充值」已在 `channel-service-order/index.vue` 用页面内局部函数实现「聚焦纯数字、失焦千分位」,尚未复用到其他页面。
|
||||
|
||||
本设计将交互固化为**统一工具 + 统一组件**,并明确**可编辑输入**与**只读展示**两类场景,避免再次在 `.vue` 中复制正则或误用前瞻表达式导致编译错误。
|
||||
|
||||
## 目标行为(SHALL)
|
||||
|
||||
| 时机 | 可编辑输入框 | 列表/查看等只读展示 |
|
||||
|------|----------------|---------------------|
|
||||
| 聚焦输入中 | 纯数字,如 `1234567.89`(无逗号) | — |
|
||||
| 失焦后 | 千分位,如 `1,234,567.89` | 始终千分位(同上格式) |
|
||||
| 再次聚焦 | 去掉逗号,恢复纯数字 | — |
|
||||
| 提交/API | `number` 或约定精度的小数,**不受逗号影响** | — |
|
||||
|
||||
**不在范围:** 数量、份数、排序号、百分比系数等非金额字段(仍用 `el-input-number` 整数或原控件)。
|
||||
|
||||
## 范围
|
||||
|
||||
### 纳入(单价 / 金额类)
|
||||
|
||||
**中试服务(pilot-service)**
|
||||
|
||||
| 页面 | 路由 | 可编辑 | 只读展示 |
|
||||
|------|------|--------|----------|
|
||||
| 渠道服务购买订单 | `/#/pilot-service/channel-service-order` | 充值金额、退款金额、产品明细单价;发起充值已改 | 列表余额/购买总额、查看流水与持仓、合计 |
|
||||
| 渠道服务订单财务确认 | `/#/pilot-service/channel-service-order-finance` | — | 列表/详情金额、单价 |
|
||||
| 渠道信息管理 | `/#/pilot-service/channel` | 协议价(元)行内编辑 | 价格选择器单价列 |
|
||||
| 细胞产品价格体系 | `/#/pilot-service/cell-product-price` | 表单单价 | 列表单价 |
|
||||
| 细胞产品项目价格 | `/#/pilot-service/project-price-system` | 表格单价 | 查看单价 |
|
||||
| 体检机构及项目 | `/#/pilot-service/checkup-institution` | 项目费用等 | 套餐合计费用 |
|
||||
|
||||
**细胞存储(cell-storage)**
|
||||
|
||||
| 页面 | 路由 | 可编辑 | 只读展示 |
|
||||
|------|------|--------|----------|
|
||||
| 采集协议单录入 | `/#/cell-storage/protocol` | 合同金额、应缴费、转账金额、采集金额等 | 查看弹窗同字段 |
|
||||
| 缴费信息 | `/#/cell-storage/payment-info` | 退费金额 | 应缴/实缴列 |
|
||||
| 缴费/退费确认(财务) | `/#/cell-storage/payment-confirm`、`refund-confirm` | — | 金额列 |
|
||||
|
||||
**其他(第二批)**
|
||||
|
||||
| 页面 | 说明 |
|
||||
|------|------|
|
||||
| 订单 Demo `system/order/index.vue` | 供货价/售价及金额(若仍为可编辑控件则纳入) |
|
||||
| 研究捐赠 `DonationFormDialog` | 采集金额等 |
|
||||
| 第三方检测公司等 | `feeAmount` 等 `:precision="2"` 的金额 |
|
||||
|
||||
### 明确排除
|
||||
|
||||
- 小程序端 `miniprogram/`
|
||||
- 后端 API(仍接收 JSON number)
|
||||
- 非金额 `el-input-number`(数量、排序、`priceCoefficient` 百分比等)
|
||||
|
||||
## 方案对比
|
||||
|
||||
### 方案 A:每页复制 focus/blur 函数(现状扩展)
|
||||
|
||||
- 优点:无新依赖,单页改动快
|
||||
- 缺点:已出现正则笔误导致整页无法加载;难维护;`formatAmount` 与输入逻辑不一致
|
||||
|
||||
### 方案 B:工具函数 + `AmountInput` 组件(推荐)
|
||||
|
||||
- `@/utils/money-format.ts`:`sanitizeMoneyInput`、`formatMoneyDisplay`、`parseMoneyToNumber`
|
||||
- `@/components/common/amount-input/index.vue`:`v-model` 绑定 `number | null`,封装 focus/blur/input
|
||||
- 只读:`formatMoneyDisplay(n)` 或增强现有列表 `formatAmount` 的展示路径
|
||||
|
||||
- 优点:单测友好、符合 Fresh Mint 组件化、表格/弹窗同一套
|
||||
- 缺点:需逐页替换 `el-input-number`(金额类)
|
||||
|
||||
### 方案 C:全局指令 `v-money-input`
|
||||
|
||||
- 优点:模板改动小
|
||||
- 缺点:难支持 `min`/`max`/`precision`、表格行绑定不清晰、与 Element Plus 集成弱
|
||||
|
||||
**结论:采用方案 B。**
|
||||
|
||||
## 技术设计
|
||||
|
||||
### 1. 工具模块 `web-admin/src/utils/money-format.ts`
|
||||
|
||||
```typescript
|
||||
// 展示:整数千分位 + 最多 precision 位小数(默认 2)
|
||||
export function formatMoneyDisplay(value: string | number | null | undefined, precision?: number): string
|
||||
|
||||
// 输入清洗:去逗号、仅保留数字与一个点
|
||||
export function sanitizeMoneyInput(value: string, precision?: number): string
|
||||
|
||||
// 提交:number | null;空串 / 仅 '.' → null
|
||||
export function parseMoneyToNumber(value: string, precision?: number): number | null
|
||||
```
|
||||
|
||||
实现要点:
|
||||
|
||||
- 千分位用**字符串分段拼接**(`addThousandSeparators`),**禁止**在 `.vue` 内联复杂前瞻正则。
|
||||
- 小数位截断与 `el-input-number` 的 `:precision="2"` 行为对齐。
|
||||
|
||||
### 2. 组件 `AmountInput`
|
||||
|
||||
路径:`web-admin/src/components/common/amount-input/index.vue`
|
||||
|
||||
| Prop | 类型 | 默认 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `modelValue` | `number \| null` | — | 双向绑定数值 |
|
||||
| `min` | `number` | — | 可选下限 |
|
||||
| `max` | `number` | — | 可选上限 |
|
||||
| `precision` | `number` | `2` | 小数位数;单价/金额均为 2 |
|
||||
| `placeholder` | `string` | — | |
|
||||
| `disabled` | `boolean` | `false` | |
|
||||
| `clearable` | `boolean` | `true` | |
|
||||
|
||||
事件:`update:modelValue`、`change`(失焦且数值变化时,便于触发行内 `calcAmount`)
|
||||
|
||||
内部状态:
|
||||
|
||||
- `displayText: ref<string>` — 绑定 `el-input`
|
||||
- `@focus` → `displayText = sanitize(modelValue)`
|
||||
- `@input` → 清洗 + 临时更新 `displayText`;可选同步 `modelValue` 为中间值
|
||||
- `@blur` → `emit(update:modelValue, parseMoneyToNumber(...))`;`displayText = formatMoneyDisplay(...)`
|
||||
|
||||
校验:`min`/`max` 在 blur 或 submit 时由父级 `el-form` rules 负责;组件可在 blur 时 `ElMessage.warning` 可选(默认不弹,避免表格内刷屏)。
|
||||
|
||||
样式:宽度 `100%`;与现有 `el-input` 高度 36px 规范一致。
|
||||
|
||||
### 3. 只读展示
|
||||
|
||||
- 列表列、查看弹窗、合计文案:统一 `formatMoneyDisplay(v)`,前缀 `¥` 仍在模板侧拼接(与现网 `¥{{ formatAmount(...) }}` 一致)。
|
||||
- **迁移策略:** 在 `money-format.ts` 导出 `formatMoneyDisplay`;各页逐步将只读 `formatAmount` 替换为 `formatMoneyDisplay`(或全局别名 `formatAmount = formatMoneyDisplay` 仅用于金额列——需逐文件审查,避免误改非金额 toFixed)。
|
||||
|
||||
建议新增 `formatMoneyAmount(v)` 专用于金额展示,保留原 `formatAmount` 为两位小数无逗号直至迁移完成,**P0 页面直接使用 `formatMoneyDisplay`**。
|
||||
|
||||
### 4. 与 OpenSpec / UI 规范
|
||||
|
||||
在 `openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md`(或归档后 `openspec/specs/ui-conventions/spec.md`)增补一节:
|
||||
|
||||
- 金额/单价**可编辑**字段 SHALL 使用 `AmountInput`(或经评审的等价封装),SHALL 满足聚焦/失焦/提交行为表。
|
||||
- 金额/单价**只读**展示 SHALL 使用 `formatMoneyDisplay`,整数部分千分位、小数位默认 2 位。
|
||||
- SHALL NOT 在业务 `.vue` 中复制千分位正则实现。
|
||||
|
||||
### 5. 渠道服务订单页迁移(含已改充值)
|
||||
|
||||
- 删除页面内 `addThousandSeparators`、`formatRechargeAmountDisplay` 等重复函数,改为 import 工具 + `AmountInput`。
|
||||
- 「发起充值」「申请退款」弹窗金额 → `AmountInput`。
|
||||
- 新增/编辑订单产品明细「单价」列 → `AmountInput`(`@change` 仍调 `calcItemAmount`)。
|
||||
|
||||
### 6. 测试与验收
|
||||
|
||||
**单元(建议)** `web-admin/src/utils/money-format.spec.ts` 或 vitest:
|
||||
|
||||
- `formatMoneyDisplay(1234567.89)` → `1,234,567.89`
|
||||
- `sanitizeMoneyInput('1,234.5')` → `1234.5`
|
||||
- `parseMoneyToNumber('1,234.56')` → `1234.56`
|
||||
|
||||
**手工验收(每批至少 1 页)**
|
||||
|
||||
1. 聚焦输入 `1000000.5`,框内无逗号
|
||||
2. 失焦显示 `1,000,000.5`
|
||||
3. 再聚焦变回 `1000000.5`
|
||||
4. 保存后网络请求 payload 为数字 `1000000.5`
|
||||
5. 列表只读列为千分位
|
||||
|
||||
## 分期交付
|
||||
|
||||
| 阶段 | 内容 | 产出 |
|
||||
|------|------|------|
|
||||
| P0 | 工具 + 组件 + 规范 + 渠道服务订单全页 | 可复用基座 |
|
||||
| P1 | 渠道信息、细胞价格、项目价格、财务确认页只读列 | 中试服务闭环 |
|
||||
| P2 | 细胞存储协议/缴费/退费 | 存储线金额 |
|
||||
| P3 | Demo 订单、捐赠、第三方费用等 | 余量 |
|
||||
|
||||
## 风险与约束
|
||||
|
||||
- **表格行内多个 `AmountInput`**:须 `row-key` 稳定,避免 Vue 复用 DOM 导致显示串行。
|
||||
- **`el-form` 校验**:rules 仍针对 `number` 字段,blur 后须已 `emit` 最新值。
|
||||
- **min/max**:退款金额等保留 `max=currentBalance`,在 `AmountInput` 上透传并在 blur 校验。
|
||||
- **不要**对 `quantity` 等整数字段使用本组件。
|
||||
|
||||
## 非目标(YAGNI)
|
||||
|
||||
- 货币符号内置于组件(仍由模板加 `¥`)
|
||||
- 多币种、汇率
|
||||
- 输入法全角数字自动转换(可后续加)
|
||||
|
||||
---
|
||||
|
||||
**状态:** 待评审
|
||||
**关联实现:** `docs/superpowers/plans/2026-05-25-money-input-thousand-separator.md`
|
||||
Reference in new issue
Block a user