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: 仅前端改动。PlPlanFormDialog 保存成功后通过 success({ planId, openArrange }) 通知列表页;列表页刷新后按需打开现有 PlPlanArrangeDialog。危化品门禁复用 buildMissingHazardCertReadyMessage(保存后 GET 计划详情再判断)。无新后端 API。
Tech Stack: Vue 3 + Element Plus + TypeScript;@/api/pl、@/utils/http/bx(pickBxData)、usePlToxicProductCertCheck。
Spec: docs/superpowers/specs/2026-07-19-pl-plan-save-and-arrange-design.md
| 职责 | 路径 |
|---|---|
| 表单底栏 + 保存/保存并安排 | web-admin/src/components/business/pl-plan-form-dialog/index.vue |
| 列表接事件并打开安排弹窗 | web-admin/src/views/pl/plan/index.vue |
| 归档页隐藏编辑 | web-admin/src/views/pl/plan-archive/index.vue |
| 设计文档(已完成) | docs/superpowers/specs/2026-07-19-pl-plan-save-and-arrange-design.md |
Files:
Modify: web-admin/src/components/business/pl-plan-form-dialog/index.vue
[ ] Step 1: 扩展 emit 与编辑态运输状态
在 <script setup> 中:
Van 加入 @element-plus/icons-vue 的 import。import {
enrichPlanProductsHazardFromCrm,
buildMissingHazardCertReadyMessage
} from '@/composables/usePlToxicProductCertCheck'
import { pickBxData } from '@/utils/http/bx'
(注意:文件已 import enrichPlanProductsHazardFromCrm,合并进同一条 import,勿重复。)
const emit = defineEmits<{
'update:visible': [value: boolean]
success: [payload?: { planId: string; openArrange: boolean }]
}>()
resetForm 中清零;在 loadPlanForEdit 赋值:const loadedTransportStatus = ref('pending')
const loadedHazardCertReady = ref(0)
// resetForm 内:
loadedTransportStatus.value = 'pending'
loadedHazardCertReady.value = 0
// loadPlanForEdit 在 Object.assign 之后:
loadedTransportStatus.value = data.transportStatus || 'pending'
loadedHazardCertReady.value = Number(data.hazardCertReady ?? 0)
const showSaveAndArrange = computed(
() => !isEdit.value || loadedTransportStatus.value === 'pending'
)
const saveButtonLabel = computed(() => (isEdit.value ? '保存' : '创建'))
const saveAndArrangeLabel = computed(() =>
isEdit.value ? '保存并安排运输' : '创建并安排运输'
)
将现有 #footer 替换为:
<template #footer>
<div class="dialog-footer">
<el-button @click="innerVisible = false">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button
:type="showSaveAndArrange ? 'default' : 'primary'"
:class="showSaveAndArrange ? 'btn-ghost' : undefined"
:loading="submitLoading"
@click="handleSubmit('save')"
>
<el-icon><Check /></el-icon>{{ saveButtonLabel }}
</el-button>
<el-button
v-if="showSaveAndArrange"
type="primary"
:loading="submitLoading"
@click="handleSubmit('saveAndArrange')"
>
<el-icon><Van /></el-icon>{{ saveAndArrangeLabel }}
</el-button>
</div>
</template>
handleSubmit用下列实现替换现有 handleSubmit(保留 validateBusinessRules 不变):
async function handleSubmit(mode: 'save' | 'saveAndArrange' = 'save') {
const valid = await basicFormRef.value?.validate().catch(() => false)
if (!valid || !validateBusinessRules()) return
submitLoading.value = true
try {
const commonPayload = buildPlanCommonPayload({ allowClearTransport: isEdit.value })
let planId = props.planId || ''
if (isEdit.value) {
if (!props.planId) return
await planApi.update(props.planId, commonPayload)
planId = props.planId
ElMessage.success('保存成功')
} else {
const res = await planApi.createFull({
...commonPayload,
contractId: formData.contractId
} as Parameters<typeof planApi.createFull>[0])
const created = pickBxData<Plan>(res)
planId = String(created?.id || '')
if (!planId) {
ElMessage.error('创建成功但未返回计划 ID')
return
}
ElMessage.success('创建成功')
}
let openArrange = false
if (mode === 'saveAndArrange' && planId) {
const detailRes = await planApi.get(planId)
const plan = pickBxData<Plan>(detailRes)
const rawProducts = Array.isArray(plan?.products) ? plan.products : []
const enrichedProducts = await enrichPlanProductsHazardFromCrm(rawProducts)
const certMessage = buildMissingHazardCertReadyMessage(plan, enrichedProducts)
if (certMessage) {
await ElMessageBox.alert(
`${certMessage},请至【危化品证上传】页面操作。`,
'无法安排运输',
{ type: 'warning' }
)
openArrange = false
} else {
openArrange = true
}
}
innerVisible.value = false
emit('success', { planId, openArrange })
} finally {
submitLoading.value = false
}
}
说明:拦截文案与 pl-plan-arrange-dialog 保持一致;loadedHazardCertReady 供后续扩展,门禁以 GET 详情为准。
Run: 打开 http://localhost:3006/#/pl/plan-module/plan,点「新增计划」,确认底栏为「取消 · 创建 · 创建并安排运输」(后者主色)。
Expected: 双按钮布局正确;未点提交即可。
git add web-admin/src/components/business/pl-plan-form-dialog/index.vue
git commit -m "$(cat <<'EOF'
feat(pl): add save-and-arrange action on transport plan form dialog
EOF
)"
Files:
Modify: web-admin/src/views/pl/plan/index.vue
[ ] Step 1: 扩展 handlePlanMutationSuccess
将:
async function handlePlanMutationSuccess() {
await fetchData()
await refreshViewIfOpen()
}
替换为:
async function handlePlanMutationSuccess(payload?: { planId?: string; openArrange?: boolean }) {
await fetchData()
await refreshViewIfOpen()
if (payload?.openArrange && payload.planId) {
arrangePlanId.value = payload.planId
arrangeDialogVisible.value = true
}
}
说明:PlPlanArrangeDialog 的 @success="handlePlanMutationSuccess" 无 payload,行为仍为仅刷新,兼容不变。
| 用例 | 操作 | Expected |
|---|---|---|
| V1 | 新增弹窗底栏 | 创建(描边)+ 创建并安排运输(主色) |
| V3 | 普通品「创建并安排运输」 | 保存后自动打开安排运输弹窗 |
| V5 | 安排弹窗点取消 | 列表有该计划,运输状态待安排 |
| V4 | 含危化品且证书未齐 | 计划已保存;Alert 说明原因;不打开安排 |
| V2 | 编辑已安排(直销若仍可编辑) | 无「保存并安排运输」,仅「保存」主色 |
git add web-admin/src/views/pl/plan/index.vue
git commit -m "$(cat <<'EOF'
feat(pl): open arrange dialog after plan save-and-arrange
EOF
)"
Files:
Modify: web-admin/src/views/pl/plan-archive/index.vue
[ ] Step 1: 去掉工具栏编辑按钮
删除模板中约 99–106 行的编辑按钮块:
<el-button
v-if="selectedPlan && selectedPlan.followStatus !== 'cancelled'"
class="btn-ghost"
:disabled="selectedRows.length !== 1"
@click="handleToolbarEdit"
>
<el-icon><Edit /></el-icon>编辑
</el-button>
在 PlPlanViewDialog 的 #hero-actions 中删除编辑按钮(保留归档按钮),即删除:
<el-button
v-if="viewingPlan.followStatus !== 'cancelled'"
class="btn-main"
type="primary"
@click="handleEdit(viewingPlan)"
>
<el-icon><Edit /></el-icon>编辑
</el-button>
若删除后 hero 仅剩归档按钮,将归档按钮改为 type="primary" / class="btn-main"(保持一行一个主色)。
删除:
<PlPlanFormDialog ... />import PlPlanFormDialog ...import { Edit } from ...(若已无其它引用)formDialogVisible、editingPlanIdhandleEdit、handleToolbarEdithandlePlanMutationSuccess 中若仅服务于表单,可改为只服务归档弹窗的 refresh(保留对 archive dialog @success 的刷新逻辑)核对:归档弹窗、查看弹窗仍正常工作。
Run: 打开 http://localhost:3006/#/pl/plan-module/plan-archive
Expected: 工具栏与查看弹窗均无「编辑」;归档/调整归档仍可用。
git add web-admin/src/views/pl/plan-archive/index.vue
git commit -m "$(cat <<'EOF'
fix(pl): hide edit transport plan on archive list page
EOF
)"
Files:
无新文件;按需更新知识图谱
[ ] Step 1: 对照 spec 勾选覆盖
| Spec | Task |
|---|---|
| D1 底栏方案 A | Task 1 |
| D2 前端串联 | Task 1–2 |
| D3 证书未齐备仍保存 | Task 1 Step 3 |
| D4 显隐 pending | Task 1 Step 1 |
| D5 取消安排保留待安排 | Task 2(行为天然满足) |
| D6 归档隐藏编辑 | Task 3 |
| V1–V6 | Task 2 Step 2 + Task 3 Step 4 |
./scripts/update-graph.sh frontend
Expected: 脚本成功结束,无报错。
在 docs/superpowers/specs/2026-07-19-pl-plan-save-and-arrange-design.md 将 **状态:** 待实现 改为 **状态:** 已实现。
success payload 形状在 Task 1 与 Task 2 一致:{ planId, openArrange }。openArrange。