Files
shanxia2.0/docs/superpowers/plans/2026-08-06-pl-plan-vehicle-library-select.md
T
2026-08-06 09:58:20 +08:00

12 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 (- [ ]) syntax for tracking.

Goal: 在运输计划新增/编辑弹窗车牌号旁提供「从车辆库选择」,弹窗分三方/自有 Tab,支持搜索、选择、新增、编辑,并回填计划车辆。

Architecture: 对齐 PlTransportAddressSelectDialog:计划表单旁按钮打开 PlVehicleLibrarySelectDialog(Tab + 列表);嵌套 PlVehicleFormDialog 对照 views/pl/vehicle / views/pl/self-vehicle 完整表单。选择或新增成功后写 vehicleId 并走现有 handleVehicleChange。首期不改造两管理页。

Tech Stack: Vue 3 + Element Plus + 现有 vehicleApi / usePlRemoteVehicleOptions / PlVehicleCertPartTabs

Spec: docs/superpowers/specs/2026-08-06-pl-plan-vehicle-library-select-design.md


文件结构

操作 文件 职责
Create web-admin/src/components/business/pl-vehicle-form-dialog/index.vue 新增/编辑车辆完整表单(vehicleType: 'carrier' | 'self')
Create web-admin/src/components/business/pl-vehicle-library-select-dialog/index.vue 选车大弹窗:Tab + 搜索 + 列表 + 挂载表单弹窗
Modify web-admin/src/components/business/pl-plan-form-dialog/index.vue 车牌旁按钮 + 选车回填

参考(只读对照,勿在首期强行抽离改造):

  • web-admin/src/components/business/pl-transport-address-select-dialog/index.vue
  • web-admin/src/components/business/pl-address-with-library/index.vue
  • web-admin/src/views/pl/vehicle/index.vue(三方表单弹窗)
  • web-admin/src/views/pl/self-vehicle/index.vue(自有表单弹窗)

Task 1: PlVehicleFormDialog — 完整新增/编辑表单

Files:

  • Create: web-admin/src/components/business/pl-vehicle-form-dialog/index.vue

  • Step 1: 建立组件壳与 API

Props / 事件约定:

const props = defineProps<{
  modelValue: boolean
  /** carrier=三方,self=自有;锁定类型,不可切换 */
  vehicleType: 'carrier' | 'self'
  /** 有值则为编辑,无值则为新增 */
  vehicleId?: string | null
}>()

const emit = defineEmits<{
  'update:modelValue': [value: boolean]
  /** 新增成功:返回详情;编辑成功:返回详情(父级刷新列表用) */
  saved: [payload: { mode: 'create' | 'edit'; vehicle: VehicleDetail }]
}>()

弹窗:

  • width="1360px",class="dialog-form-layout",append-to-body,destroy-on-close

  • modal-class="order-module-dialog-mask",style="backdrop-filter: none"

  • 标题:

    • create + carrier → 新增车辆
    • create + self → 新增自有车辆
    • edit + carrier → 编辑车辆
    • edit + self → 编辑自有车辆
  • Step 2: 移植表单 UI 与状态

从 views/pl/vehicle/index.vue / self-vehicle/index.vue 的 el-dialog 表单区对照实现:

  1. 基本信息 form-grid-3:车牌号、挂车号、载重、承运商、车辆状态、备注、车辆照片(AttachmentUploadField,upload-folder="pl/vehicle-photo")
  2. 关联人员子表(添加人员 / 角色 driver|escort / 删除)
  3. PlVehicleCertPartTabs:
    • vehicleType === 'self':enforce-self-vehicle-required(与自有页一致)
    • vehicleType === 'carrier'::enforce-self-vehicle-required="false" 且 :enforce-carrier-vehicle-required="false"(与三方页一致)

人员远程选项:

usePlRemotePersonnelOptions({
  listParams: () => ({
    status: '1',
    personnelAffiliation: props.vehicleType === 'carrier' ? 'carrier' : 'self'
  })
})

证书槽初始化:

  • carrier:ensureVehicleCertSlots(..., trailerNo, 'carrier')
  • self:ensureSelfVehicleCertSlots(..., trailerNo)

校验:复用管理页 validateSubTables 逻辑(人员必选、角色、validatePersonnelTypeRoleCompat、validatePersonnelAffiliationVehicleCompat)。

  • Step 3: open / submit

@open 或 watch(modelValue):

  • 新增:重置表单;vehicleStatus='available';证书空槽;加载人员选项
  • 编辑:vehicleApi.get(vehicleId) 回填;编辑自有时若 detail.vehicleType !== 'self' 则警告并关闭;ensureSelectedCarrierOption;nextTick 后再填证书并 refreshRequiredSlots

提交 payload 与管理页一致(含 personnelList / certList);vehicleType 固定为 prop。

if (isEdit) {
  await vehicleApi.update(formData.id, payload)
  const res = await vehicleApi.get(formData.id)
  const detail = pickBxData<VehicleDetail>(res)!
  emit('saved', { mode: 'edit', vehicle: detail })
} else {
  const res = await vehicleApi.create(payload)
  let detail = pickBxData<VehicleDetail>(res)
  if (!detail?.id) {
    // 若 create 未回详情,不应发生;兜底可再 get —— 优先用返回体
    ElMessage.error('新增成功但未返回车辆信息')
    return
  }
  emit('saved', { mode: 'create', vehicle: detail })
}

成功后关闭弹窗;上传中拦截与管理页相同文案。

  • Step 4: 手工冒烟(可选先挂临时入口)

在浏览器临时打开组件或等 Task 2 挂载后验证:三方/自有各新增一条、各编辑一条,证书与人员校验行为与管理页一致。

  • Step 5: Commit(若用户要求提交时再执行)
git add web-admin/src/components/business/pl-vehicle-form-dialog/index.vue
git commit -m "$(cat <<'EOF'
feat(web-admin): add PlVehicleFormDialog for carrier/self vehicle CRUD

EOF
)"

Task 2: PlVehicleLibrarySelectDialog — Tab 选车弹窗

Files:

  • Create: web-admin/src/components/business/pl-vehicle-library-select-dialog/index.vue

  • Step 1: 弹窗外壳(对齐地址库选择)

<el-dialog
  v-model="innerVisible"
  title="从车辆库选择"
  width="1360px"
  align-center
  destroy-on-close
  append-to-body
  class="dialog-form-layout pl-vehicle-library-select-dialog"
  modal-class="order-module-dialog-mask"
  :close-on-click-modal="false"
  style="backdrop-filter: none"
  @open="handleOpen"
>

结构:

  1. el-tabs:carrier / self(文案:三方车辆 / 自有车辆)
  2. 工具栏:el-input 搜索(placeholder:搜索车牌号,回车/清空检索)+ type="primary"「新增车辆」
  3. ListTable + ListPagination(dialog-list-body-only 风格参考地址库弹窗)
  4. 嵌套 <PlVehicleFormDialog v-model="formVisible" :vehicle-type="activeTab" :vehicle-id="editingId" @saved="handleFormSaved" />
  • Step 2: 列表与选择
async function fetchList() {
  loading.value = true
  try {
    const res = await vehicleApi.list({
      vehicleType: activeTab.value, // 'carrier' | 'self'
      q: keyword.value.trim() || undefined,
      page: pagination.page,
      pageSize: pagination.pageSize
    })
    const { list, total } = pickBxPageResult<Vehicle>(res)
    rows.value = list
    pagination.total = total
  } finally {
    loading.value = false
  }
}

列:序号、车牌号(el-link → handleConfirm)、挂车号、车辆状态 Tag、载重、承运商(v-if="activeTab === 'carrier'")、关联人员摘要、操作(编辑 / 选择)。

function handleConfirm(row: Vehicle) {
  emit('select', row)
  innerVisible.value = false
}

function handleFormSaved(payload: { mode: 'create' | 'edit'; vehicle: VehicleDetail }) {
  formVisible.value = false
  editingId.value = null
  if (payload.mode === 'create') {
    handleConfirm(payload.vehicle)
    return
  }
  fetchList()
}

样式::deep(.el-dialog__body) { min-height: 560px; };表格 :max-height="560"。

  • Step 3: Tab / 打开重置

handleOpen:keyword 清空、page=1、activeTab 默认 carrier、关表单、fetchList。
Tab @tab-change:page=1 再 fetchList。

  • Step 4: Commit(若用户要求)
git add web-admin/src/components/business/pl-vehicle-library-select-dialog/index.vue
git commit -m "$(cat <<'EOF'
feat(web-admin): add vehicle library select dialog with carrier/self tabs

EOF
)"

Task 3: 接入运输计划表单弹窗

Files:

  • Modify: web-admin/src/components/business/pl-plan-form-dialog/index.vue

  • Step 1: 车牌号旁按钮布局

将原「车牌号」单项改为与地址库相同的行布局:

<el-form-item label="车牌号">
  <div class="pl-plan-vehicle-with-library">
    <el-select
      v-model="formData.vehicleId"
      class="pl-plan-vehicle-with-library__select"
      placeholder="请选择车牌号"
      filterable
      remote
      clearable
      :remote-method="searchVehicles"
      :loading="vehicleSelectLoading || vehicleLoading"
      @focus="onVehicleSelectFocus"
      @change="handleVehicleChange"
    >
      <el-option
        v-for="item in vehicleList"
        :key="item.id"
        :label="item.licensePlate"
        :value="item.id"
      />
    </el-select>
    <el-button
      class="pl-plan-vehicle-with-library__btn btn-ghost"
      @click="vehicleLibraryVisible = true"
    >
      <el-icon><FolderOpened /></el-icon>从车辆库选择
    </el-button>
  </div>
</el-form-item>

<PlVehicleLibrarySelectDialog
  v-model="vehicleLibraryVisible"
  @select="handleVehicleLibrarySelect"
/>

样式(scoped,对照 pl-address-with-library):

.pl-plan-vehicle-with-library {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}
.pl-plan-vehicle-with-library__select {
  flex: 1 1 auto;
  min-width: 0;
}
.pl-plan-vehicle-with-library__btn {
  flex: 0 0 auto;
  white-space: nowrap;
}
  • Step 2: 回填处理
const vehicleLibraryVisible = ref(false)

async function handleVehicleLibrarySelect(vehicle: Vehicle) {
  if (!vehicle?.id) return
  await ensureSelectedVehicleOption(vehicle.id)
  formData.vehicleId = vehicle.id
  await handleVehicleChange(vehicle.id)
  if (vehicle.carrierId) {
    await ensureSelectedCarrierOption(vehicle.carrierId)
  }
}

导入:FolderOpened、PlVehicleLibrarySelectDialog、类型 Vehicle(若尚未导入)。

  • Step 3: Commit(若用户要求)
git add web-admin/src/components/business/pl-plan-form-dialog/index.vue
git commit -m "$(cat <<'EOF'
feat(web-admin): wire vehicle library select into plan form dialog

EOF
)"

Task 4: 验收与图谱

  • Step 1: 手工验收清单(代码已就绪,需在本地浏览器走查)

在 /#/pl/plan-module/plan 打开新增/编辑运输计划:

  1. 车牌旁可见「从车辆库选择」,布局与「从地址库选择」同类
  2. 弹窗 1360 宽、偏高;Tab 切换三方/自有列表正确过滤
  3. 搜索车牌有效;选择 / 链接 / 双击可回填车牌,摘要条与承运商更新
  4. 三方 Tab「新增车辆」:完整表单可保存,成功后自动选中并关闭选车弹窗
  5. 自有 Tab「新增自有车辆」:证书强制与自有管理页一致;成功后回填
  6. 「编辑」保存后列表刷新,计划已选车不被覆盖;选车弹窗仍打开
  7. 遮罩无 blur;按钮均有图标
  • Step 2: 更新前端图谱
./scripts/update-graph.sh frontend

或在仓库根目录:graphify update .(仅 AST)

  • Step 3: 将设计文档状态改为已实现(实现完成后)

docs/superpowers/specs/2026-08-06-pl-plan-vehicle-library-select-design.md 顶部 状态:设计中 → 已实现


Spec 覆盖自检

Spec 要求 Task
车牌旁按钮「从车辆库选择」 Task 3
Tab 三方/自有 + 搜索分页 Task 2
选择/链接/双击回填 + handleVehicleChange Task 2 + 3
新增完整表单(对照管理页) Task 1 + 2
编辑 + 刷新列表不自动选中 Task 1 + 2
新增成功自动选中关弹窗 Task 2
大尺寸 1360 / 列表偏高 Task 1 + 2
不改两管理页 全任务遵守
图谱更新 Task 4