feat(miniprogram): split driver plan copy product into three lines

Make copy-to-group text easier to scan: 产品信息 / 计划数量 / 实际数量 as separate lines.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-10-13 20:38:17 +08:00
1 parent 36d27416d3
commit c626023250
2 files changed
+211 -8

No files matched your search

@@ -0,0 +1,201 @@
# 司机运输计划单「复制发群」产品行拆分 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:** 将小程序司机运输计划单「复制发群」中每个产品由单行紧凑格式改为三行(产品信息 / 计划数量 / 实际数量),多产品组间不空行。
**Architecture:** 仅改 `miniprogram/utils/plDriverPlanCopy.js`:`formatProductLine` 改为返回三行字符串数组;`appendInfoSection` 展开 push。头信息、区块顺序、空站/无产品边界、`copyDriverPlanToClipboard` 不变。用一次性 Node 断言脚本验证后删除。
**Tech Stack:** uni-app 小程序工具 JS(ES module)、Node 临时校验。
**Spec:** `docs/superpowers/specs/2026-10-13-mini-pl-driver-plan-copy-product-lines-design.md`
---
## File map
| 文件 | 动作 | 职责 |
|------|------|------|
| `miniprogram/utils/plDriverPlanCopy.js` | Modify | 产品三行输出;导出签名不变 |
| `/tmp` 临时校验脚本 | Create→Delete | Node 断言定稿示例 |
---
### Task 1: 改产品行为三行
**Files:**
- Modify: `miniprogram/utils/plDriverPlanCopy.js`
- [x] **Step 1: 更新规格注释;将 `formatProductLine` 改为返回三行数组;`appendInfoSection` 展开 push**
把文件顶部规格注释改为指向新 design。替换产品格式化与追加逻辑为:
```js
/**
* 司机运输计划单「复制发群」纯文本(不含附件)
* 规格:docs/superpowers/specs/2026-10-13-mini-pl-driver-plan-copy-product-lines-design.md
* (头信息/区块/边界继承 2026-10-12 enrich 设计)
*/
// ... 保留 DASH、FIELD_GAP、stopTypeOf、customerNameOf、textOrDash、
// formatQtyWithUnit、planQtyOf、actualQtyOf 不变 ...
/** 产品三行:产品信息 / 计划数量 / 实际数量 */
function formatProductLines(product) {
const name = textOrDash(product?.productName ?? product?.product_name)
const model = textOrDash(product?.model)
const pack = textOrDash(product?.packageSpec ?? product?.package_spec)
const unit = product?.unit
const planPart = formatQtyWithUnit(planQtyOf(product), unit)
const actualPart = formatQtyWithUnit(actualQtyOf(product), unit)
return [
`- 产品信息:${name}${FIELD_GAP}${model}${FIELD_GAP}${pack}`,
`- 计划数量:${planPart}`,
`- 实际数量:${actualPart}`
]
}
function appendInfoSection(lines, title, stops) {
lines.push(title)
if (!stops.length) {
lines.push(DASH)
return
}
stops.forEach((stop) => {
lines.push(`${customerNameOf(stop)}:`)
const products = Array.isArray(stop?.products) ? stop.products : []
if (!products.length) {
lines.push(`- ${DASH}`)
return
}
products.forEach((product) => {
formatProductLines(product).forEach((line) => lines.push(line))
})
})
}
```
其余 `buildDriverPlanCopyText` / `copyDriverPlanToClipboard` 保持不变。删除旧的 `formatProductLine`。
- [x] **Step 2: 一次性 Node 断言(通过后删除脚本)**
```bash
cat > /tmp/check-driver-plan-copy-lines.mjs << 'EOF'
import { buildDriverPlanCopyText } from './miniprogram/utils/plDriverPlanCopy.js'
const page = {
copySeqNo: 128,
planNo: 'YSJH20260409001',
licensePlate: '沪A12345',
stops: [
{
stopType: 'delivery',
customerName: '甲客户',
products: [
{ productName: '工业酒精', model: '95%', packageSpec: '吨桶', planQty: 12, actualQty: 11.9, unit: '吨' }
]
},
{
stopType: 'delivery',
customerName: '乙客户',
products: [
{ productName: '工业酒精', model: '95%', packageSpec: '吨桶', planQty: 8, actualQty: 7.9, unit: '吨' },
{ productName: '甲醇', model: '99.9%', packageSpec: '吨桶', planQty: 2, actualQty: 2, unit: '吨' }
]
},
{
stopType: 'load',
customerName: '某某化工厂',
products: [
{ productName: '工业酒精', model: '95%', packageSpec: '吨桶', planQty: 20, actualQty: 19.8, unit: '吨' },
{ productName: '甲醇', model: '99.9%', packageSpec: '吨桶', planQty: 2, actualQty: 2, unit: '吨' }
]
}
]
}
const expected = [
'序号:128',
'计划编号:YSJH20260409001',
'车辆:沪A12345',
'送货信息:',
'甲客户:',
'- 产品信息:工业酒精 95% 吨桶',
'- 计划数量:12 吨',
'- 实际数量:11.9 吨',
'乙客户:',
'- 产品信息:工业酒精 95% 吨桶',
'- 计划数量:8 吨',
'- 实际数量:7.9 吨',
'- 产品信息:甲醇 99.9% 吨桶',
'- 计划数量:2 吨',
'- 实际数量:2 吨',
'装货信息:',
'某某化工厂:',
'- 产品信息:工业酒精 95% 吨桶',
'- 计划数量:20 吨',
'- 实际数量:19.8 吨',
'- 产品信息:甲醇 99.9% 吨桶',
'- 计划数量:2 吨',
'- 实际数量:2 吨'
].join('\n')
const actual = buildDriverPlanCopyText(page)
if (actual !== expected) {
console.error('MISMATCH\n--- actual ---\n' + actual + '\n--- expected ---\n' + expected)
process.exit(1)
}
const emptyish = buildDriverPlanCopyText({ planNo: 'X', stops: [] })
const emptyExpected = [
'序号:—',
'计划编号:X',
'车辆:—',
'送货信息:',
'—',
'装货信息:',
'—'
].join('\n')
if (emptyish !== emptyExpected) {
console.error('EMPTY MISMATCH\n' + emptyish)
process.exit(1)
}
console.log('OK')
EOF
# 在仓库根目录执行;若相对 import 失败则改用 file:// 绝对路径
cd /Users/zlwu/work/00code/shanxia2.0 && node --input-type=module -e "
import { buildDriverPlanCopyText } from './miniprogram/utils/plDriverPlanCopy.js'
" 2>/dev/null || true
```
推荐在仓库根直接跑内嵌断言(路径相对仓库根):
```bash
cd /Users/zlwu/work/00code/shanxia2.0
node --input-type=module <<'EOF'
import { buildDriverPlanCopyText } from './miniprogram/utils/plDriverPlanCopy.js'
# ... 同上 page / expected / emptyish 断言 ...
EOF
```
Expected stdout: `OK`
- [x] **Step 3: Commit**
```bash
git add miniprogram/utils/plDriverPlanCopy.js docs/superpowers/plans/2026-10-13-mini-pl-driver-plan-copy-product-lines.md
git commit -m "feat(miniprogram): split driver plan copy product into three lines"
```
---
## Spec coverage checklist
| Spec 项 | Task |
|---------|------|
| D1 只改 plDriverPlanCopy.js | Task 1 |
| D2 多产品不空行 | Task 1 `formatProductLines` 连续 push |
| D3 formatProductLine→三行 | Task 1 |
| D4 / D5 空值与无产品 `- —` | 既有 `appendInfoSection` + `textOrDash` |
| 定稿实例 | Task 1 Step 2 |
+10 -8
View File
@@ -1,6 +1,7 @@
/**
* 司机运输计划单「复制发群」纯文本(不含附件)
* 规格:docs/superpowers/specs/2026-10-12-mini-pl-driver-plan-copy-text-enrich-design.md
* 规格:docs/superpowers/specs/2026-10-13-mini-pl-driver-plan-copy-product-lines-design.md
* (头信息/区块/边界继承 2026-10-12 enrich 设计)
*/
const DASH = '—'
@@ -41,18 +42,19 @@ function actualQtyOf(product) {
return null
}
/** 产品紧凑行:- 名称 型号 包装 计划数量:… 实际数量:… */
function formatProductLine(product) {
/** 产品三行:产品信息 / 计划数量 / 实际数量(组间不空行) */
function formatProductLines(product) {
const name = textOrDash(product?.productName ?? product?.product_name)
const model = textOrDash(product?.model)
const pack = textOrDash(product?.packageSpec ?? product?.package_spec)
const unit = product?.unit
const planPart = formatQtyWithUnit(planQtyOf(product), unit)
const actualPart = formatQtyWithUnit(actualQtyOf(product), unit)
return (
`- ${name}${FIELD_GAP}${model}${FIELD_GAP}${pack}` +
`${FIELD_GAP}计划数量:${planPart}${FIELD_GAP}实际数量:${actualPart}`
)
return [
`- 产品信息:${name}${FIELD_GAP}${model}${FIELD_GAP}${pack}`,
`- 计划数量:${planPart}`,
`- 实际数量:${actualPart}`
]
}
/**
@@ -74,7 +76,7 @@ function appendInfoSection(lines, title, stops) {
return
}
products.forEach((product) => {
lines.push(formatProductLine(product))
formatProductLines(product).forEach((line) => lines.push(line))
})
})
}