Files
shanxia2.0/docs/superpowers/plans/2026-08-06-pl-plan-copy.md
T
2026-08-06 23:33:48 +08:00

286 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 运输计划「复制」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 (`- [x]`) syntax for tracking.
**Goal:** 在运输计划列表支持「复制」:打开新增弹窗并预填可复制字段,用户保存后走现有 `createFull` 建新单。
**Architecture:** 纯前端。列表传入 `copyFromPlanId`(`planId` 为空);`PlPlanFormDialog` 打开时 `planApi.get` → `loadPlanForCopy`(裁剪车辆/承运商/运费/执行态);保存路径与普通新增相同。
**Tech Stack:** Vue 3 + Element Plus + 现有 `planApi`
**Spec:** `docs/superpowers/specs/2026-08-06-pl-plan-copy-design.md`
---
## 文件结构
| 操作 | 文件 | 职责 |
|------|------|------|
| Modify | `web-admin/src/components/business/pl-plan-form-dialog/index.vue` | `copyFromPlanId`、`loadPlanForCopy`、复制态禁用日期联动、产品映射去证书/源 id |
| Modify | `web-admin/src/views/pl/plan/index.vue` | 工具栏 + 查看弹窗「复制」入口 |
| Modify | `docs/superpowers/specs/2026-08-06-pl-plan-copy-design.md` | 状态改为已实现 |
无后端改动。web-admin 无组件单测基建,验收以浏览器走查为准。
---
### Task 1: 表单弹窗支持按源计划预填(复制模式)
**Files:**
- Modify: `web-admin/src/components/business/pl-plan-form-dialog/index.vue`
- [x] **Step 1: 扩展 props 与复制态计算属性**
在现有 props 上增加 `copyFromPlanId`:
```ts
const props = defineProps<{
visible: boolean
planId?: string
/** 有值且无 planId 时:新增模式 + 从该计划预填(复制) */
copyFromPlanId?: string
}>()
const isEdit = computed(() => !!props.planId)
const isCopy = computed(() => !props.planId && !!props.copyFromPlanId)
```
`dialogTitle` / `saveButtonLabel` / `showSaveAndArrange` 继续按 `isEdit` 判断(复制视为新增,标题「新增运输计划」)。
- [x] **Step 2: 复制专用产品行映射**
在 `mapPlanProductToFormRow` 旁新增(或改其调用方),保证复制行:
- 不带源 `plan_product.id`(`id` 省略)
- `actualQty: 0`
- 不带 `purchaseCertUrl` / `transportCertUrl` / `legalUseCertUrl`
- `quantity` 仍用计划数量(与现有 `mapPlanProductToFormRow` 的 quantity 逻辑一致)
```ts
function mapPlanProductToCopyFormRow(item: PlanProduct, index: number): PlanFormProductRow {
const row = mapPlanProductToFormRow(item, index)
return {
...row,
id: undefined,
actualQty: 0,
purchaseCertUrl: undefined,
transportCertUrl: undefined,
legalUseCertUrl: undefined
}
}
```
- [x] **Step 3: 实现 `loadPlanForCopy`**
以 `loadPlanForEdit` 为模板,差异如下:
1. `resetForm()` 后 `planApi.get(planId)`
2. `Object.assign` 时:
- `createDate: todayYmd()`(不用源创建日期)
- `followUserId` / `followUserName`:`resolveCurrentUserId()` / `resolveCurrentUserName()`
- `planDate`、取送货字段、备注、合同相关字段:同编辑(时间用现有 `formatDate`)
- **不要**写入 `vehicleId`
- `products: (data.products || []).map(mapPlanProductToCopyFormRow)`
3. `loadedTransportStatus.value = 'pending'`(新建态)
4. 合同快照加载逻辑同编辑(`getContract` 失败则 `buildContractSnapshotFromPlan`)
5. 产品 enrich 同编辑:`enrichProductsFromContract` → `enrichPlanProductsHazardFromCrm` → `enrichPlanProductLinesAlias` → `refreshContractProductRows`
6. **不要**调用 `ensureSelectedVehicleOption` / `handleVehicleChange` / `restoreFreightFromTransport` / `ensureSelectedCarrierOption`
7. 显式保持:`formData.vehicleId = ''`、`formData.carrierId = ''`、`selectedDriverId = ''`、`vehicleBundle = null`,并 `freightCalc.resetFreightCalc()`
失败处理:
```ts
} catch (e) {
const message = e instanceof Error ? e.message : '加载源计划失败'
ElMessage.error(message.includes('加载') ? message : `加载源计划失败:${message}`)
emit('update:visible', false)
} finally {
detailLoading.value = false
}
```
- [x] **Step 4: 接入 `handleOpened` 与日期 watch**
`handleOpened`:
```ts
function handleOpened() {
loadMasterData().then(async () => {
if (props.planId) {
await loadPlanForEdit(props.planId)
} else if (props.copyFromPlanId) {
await loadPlanForCopy(props.copyFromPlanId)
} else {
resetForm()
}
})
}
```
计划日期联动 watch 开头增加复制跳过:
```ts
watch(
() => formData.planDate,
(next, prev) => {
if (isEdit.value || isCopy.value) return
// ...现有联动逻辑
}
)
```
- [x] **Step 5: 自测表单组件逻辑(代码审查清单)**
确认:
- `isCopy` 时提交仍走 `!props.planId` → `createFull`
- `buildTransportBlock` 在无车辆时不带 transport(同新增)
- `handleClosed` 仍 `resetForm()` 即可(父组件负责清空 `copyFromPlanId`)
- [x] **Step 6: Commit**
```bash
git add web-admin/src/components/business/pl-plan-form-dialog/index.vue
git commit -m "feat(pl): support copy-from plan prefill in plan form dialog"
```
---
### Task 2: 列表页「复制」入口
**Files:**
- Modify: `web-admin/src/views/pl/plan/index.vue`
- [x] **Step 1: 状态与表单绑定**
增加:
```ts
const copyFromPlanId = ref('')
```
模板中表单:
```vue
<PlPlanFormDialog
v-model:visible="formDialogVisible"
:plan-id="editingPlanId"
:copy-from-plan-id="copyFromPlanId"
@success="handlePlanMutationSuccess"
/>
```
- [x] **Step 2: 统一打开表单的互斥赋值**
```ts
function handleAdd() {
editingPlanId.value = ''
copyFromPlanId.value = ''
formDialogVisible.value = true
}
function handleEdit(row: Plan) {
editingPlanId.value = row.id
copyFromPlanId.value = ''
formDialogVisible.value = true
}
function handleCopy(row: Plan) {
editingPlanId.value = ''
copyFromPlanId.value = row.id
formDialogVisible.value = true
}
function handleToolbarCopy() {
if (!selectedPlan.value) return
handleCopy(selectedPlan.value)
}
```
- [x] **Step 3: 工具栏按钮**
在「新增计划」后增加(`btn-ghost`,仅单选可点):
```vue
<el-button
class="btn-ghost"
:disabled="!selectedPlan"
@click="handleToolbarCopy"
>
<el-icon><CopyDocument /></el-icon>复制
</el-button>
```
`CopyDocument` 加入 `@element-plus/icons-vue` 的 import。
- [x] **Step 4: 查看弹窗 hero-actions**
在「编辑」按钮旁增加「复制」(**无** `canEditPlan` 限制,任意状态可复制):
```vue
<el-button class="btn-ghost" @click="handleCopy(viewingPlan)">
<el-icon><CopyDocument /></el-icon>复制
</el-button>
```
放在编辑按钮之后(或编辑之前;与编辑并列即可)。不强制关闭查看弹窗。
- [x] **Step 5: Commit**
```bash
git add web-admin/src/views/pl/plan/index.vue
git commit -m "feat(pl): add copy entry on transport plan list and view dialog"
```
---
### Task 3: 文档收尾与验收
**Files:**
- Modify: `docs/superpowers/specs/2026-08-06-pl-plan-copy-design.md`
- 本计划文件勾选完成项
- [x] **Step 1: 设计文档状态**
将文首 `**状态:** 设计已确认` 改为 `**状态:** 已实现`。
- [x] **Step 2: 更新知识图谱**
```bash
./scripts/update-graph.sh frontend
```
- [x] **Step 3: 浏览器走查**
打开 `http://localhost:3006/#/pl/plan-module/plan`:
1. 勾选 1 条 →「复制」→ 标题「新增运输计划」;创建日期为今天;跟进人为当前用户
2. 合同/基础/产品计划数量/备注/取送货已填;车牌、承运商为空
3. 保存 → 新编号、待安排;源计划不变
4. 对已作废或已归档计划从查看弹窗复制,可打开预填
5. 0 条 / 多条勾选时工具栏「复制」禁用;编辑入口仍正常
- [x] **Step 4: Commit**
```bash
git add docs/superpowers/specs/2026-08-06-pl-plan-copy-design.md docs/superpowers/plans/2026-08-06-pl-plan-copy.md
git commit -m "docs: mark transport plan copy design implemented"
```
---
## Spec 覆盖自检
| Spec 要求 | Task |
|-----------|------|
| 打开新增弹窗预填,保存才建单 | Task 1 + 2 |
| 列表工具栏单选 + 查看弹窗复制 | Task 2 |
| 任意状态可复制 | Task 2 Step 4(无状态门禁) |
| createDate=当天,跟进人=当前用户 | Task 1 Step 3 |
| 计划/取送货日期原样 | Task 1 Step 3 |
| 不复制车辆/承运商/运费 | Task 1 Step 3 |
| 产品仅计划数量,无证书 URL / actualQty | Task 1 Step 2 |
| 复制态关闭日期联动 watch | Task 1 Step 4 |
| 拉源失败关弹窗 | Task 1 Step 3 |
| 无新 API | 全任务 |