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 无组件单测基建,验收以浏览器走查为准。
Files:
Modify: web-admin/src/components/business/pl-plan-form-dialog/index.vue
[x] Step 1: 扩展 props 与复制态计算属性
在现有 props 上增加 copyFromPlanId:
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 判断(复制视为新增,标题「新增运输计划」)。
在 mapPlanProductToFormRow 旁新增(或改其调用方),保证复制行:
plan_product.id(id 省略)actualQty: 0purchaseCertUrl / transportCertUrl / legalUseCertUrlquantity 仍用计划数量(与现有 mapPlanProductToFormRow 的 quantity 逻辑一致)function mapPlanProductToCopyFormRow(item: PlanProduct, index: number): PlanFormProductRow {
const row = mapPlanProductToFormRow(item, index)
return {
...row,
id: undefined,
actualQty: 0,
purchaseCertUrl: undefined,
transportCertUrl: undefined,
legalUseCertUrl: undefined
}
}
loadPlanForCopy以 loadPlanForEdit 为模板,差异如下:
resetForm() 后 planApi.get(planId)Object.assign 时:
createDate: todayYmd()(不用源创建日期)followUserId / followUserName:resolveCurrentUserId() / resolveCurrentUserName()planDate、取送货字段、备注、合同相关字段:同编辑(时间用现有 formatDate)vehicleIdproducts: (data.products || []).map(mapPlanProductToCopyFormRow)loadedTransportStatus.value = 'pending'(新建态)getContract 失败则 buildContractSnapshotFromPlan)enrichProductsFromContract → enrichPlanProductsHazardFromCrm → enrichPlanProductLinesAlias → refreshContractProductRowsensureSelectedVehicleOption / handleVehicleChange / restoreFreightFromTransport / ensureSelectedCarrierOptionformData.vehicleId = ''、formData.carrierId = ''、selectedDriverId = ''、vehicleBundle = null,并 freightCalc.resetFreightCalc()失败处理:
} catch (e) {
const message = e instanceof Error ? e.message : '加载源计划失败'
ElMessage.error(message.includes('加载') ? message : `加载源计划失败:${message}`)
emit('update:visible', false)
} finally {
detailLoading.value = false
}
handleOpened 与日期 watchhandleOpened:
function handleOpened() {
loadMasterData().then(async () => {
if (props.planId) {
await loadPlanForEdit(props.planId)
} else if (props.copyFromPlanId) {
await loadPlanForCopy(props.copyFromPlanId)
} else {
resetForm()
}
})
}
计划日期联动 watch 开头增加复制跳过:
watch(
() => formData.planDate,
(next, prev) => {
if (isEdit.value || isCopy.value) return
// ...现有联动逻辑
}
)
确认:
isCopy 时提交仍走 !props.planId → createFullbuildTransportBlock 在无车辆时不带 transport(同新增)handleClosed 仍 resetForm() 即可(父组件负责清空 copyFromPlanId)
[x] Step 6: Commit
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"
Files:
Modify: web-admin/src/views/pl/plan/index.vue
[x] Step 1: 状态与表单绑定
增加:
const copyFromPlanId = ref('')
模板中表单:
<PlPlanFormDialog
v-model:visible="formDialogVisible"
:plan-id="editingPlanId"
:copy-from-plan-id="copyFromPlanId"
@success="handlePlanMutationSuccess"
/>
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)
}
在「新增计划」后增加(btn-ghost,仅单选可点):
<el-button
class="btn-ghost"
:disabled="!selectedPlan"
@click="handleToolbarCopy"
>
<el-icon><CopyDocument /></el-icon>复制
</el-button>
CopyDocument 加入 @element-plus/icons-vue 的 import。
在「编辑」按钮旁增加「复制」(无 canEditPlan 限制,任意状态可复制):
<el-button class="btn-ghost" @click="handleCopy(viewingPlan)">
<el-icon><CopyDocument /></el-icon>复制
</el-button>
放在编辑按钮之后(或编辑之前;与编辑并列即可)。不强制关闭查看弹窗。
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"
Files:
docs/superpowers/specs/2026-08-06-pl-plan-copy-design.md本计划文件勾选完成项
[x] Step 1: 设计文档状态
将文首 **状态:** 设计已确认 改为 **状态:** 已实现。
./scripts/update-graph.sh frontend
打开 http://localhost:3006/#/pl/plan-module/plan:
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 要求 | 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 | 全任务 |