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

8.5 KiB
Raw Blame History

运输计划「复制」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

  • 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 判断(复制视为新增,标题「新增运输计划」)。

  • Step 2: 复制专用产品行映射

在 mapPlanProductToFormRow 旁新增(或改其调用方),保证复制行:

  • 不带源 plan_product.id(id 省略)
  • actualQty: 0
  • 不带 purchaseCertUrl / transportCertUrl / legalUseCertUrl
  • quantity 仍用计划数量(与现有 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
  }
}
  • 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()

失败处理:

} catch (e) {
  const message = e instanceof Error ? e.message : '加载源计划失败'
  ElMessage.error(message.includes('加载') ? message : `加载源计划失败:${message}`)
  emit('update:visible', false)
} finally {
  detailLoading.value = false
}
  • Step 4: 接入 handleOpened 与日期 watch

handleOpened:

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
    // ...现有联动逻辑
  }
)
  • Step 5: 自测表单组件逻辑(代码审查清单)

确认:

  • isCopy 时提交仍走 !props.planId → createFull

  • buildTransportBlock 在无车辆时不带 transport(同新增)

  • handleClosed 仍 resetForm() 即可(父组件负责清空 copyFromPlanId)

  • 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"

Task 2: 列表页「复制」入口

Files:

  • Modify: web-admin/src/views/pl/plan/index.vue

  • Step 1: 状态与表单绑定

增加:

const copyFromPlanId = ref('')

模板中表单:

<PlPlanFormDialog
  v-model:visible="formDialogVisible"
  :plan-id="editingPlanId"
  :copy-from-plan-id="copyFromPlanId"
  @success="handlePlanMutationSuccess"
/>
  • Step 2: 统一打开表单的互斥赋值
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)
}
  • Step 3: 工具栏按钮

在「新增计划」后增加(btn-ghost,仅单选可点):

<el-button
  class="btn-ghost"
  :disabled="!selectedPlan"
  @click="handleToolbarCopy"
>
  <el-icon><CopyDocument /></el-icon>复制
</el-button>

CopyDocument 加入 @element-plus/icons-vue 的 import。

  • Step 4: 查看弹窗 hero-actions

在「编辑」按钮旁增加「复制」(无 canEditPlan 限制,任意状态可复制):

<el-button class="btn-ghost" @click="handleCopy(viewingPlan)">
  <el-icon><CopyDocument /></el-icon>复制
</el-button>

放在编辑按钮之后(或编辑之前;与编辑并列即可)。不强制关闭查看弹窗。

  • Step 5: Commit
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

  • 本计划文件勾选完成项

  • Step 1: 设计文档状态

将文首 **状态:** 设计已确认 改为 **状态:** 已实现。

  • Step 2: 更新知识图谱
./scripts/update-graph.sh frontend
  • Step 3: 浏览器走查

打开 http://localhost:3006/#/pl/plan-module/plan:

  1. 勾选 1 条 →「复制」→ 标题「新增运输计划」;创建日期为今天;跟进人为当前用户
  2. 合同/基础/产品计划数量/备注/取送货已填;车牌、承运商为空
  3. 保存 → 新编号、待安排;源计划不变
  4. 对已作废或已归档计划从查看弹窗复制,可打开预填
  5. 0 条 / 多条勾选时工具栏「复制」禁用;编辑入口仍正常
  • Step 4: Commit
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 全任务