Просмотр исходного кода

feat(web-admin): add plan draft save UI and drafting follow-status filters

Rename pending tab to 待安排/草稿, default-select drafting, and add 暂存 with saveMode.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 месяцев назад
Родитель
Сommit
3c7e3c69c5

+ 1 - 0
web-admin/src/api/pl/index.ts

@@ -691,6 +691,7 @@ export interface PlanCreateFullPayload {
   followUserId?: string
   followUserName?: string
   remark?: string
+  saveMode?: 'draft' | 'formal'
   supplierContracts?: PlanContractLink[]
   demanderContracts?: PlanContractLink[]
   load?: PlanLoad

+ 63 - 2
web-admin/src/components/business/pl-plan-form-dialog/index.vue

@@ -254,6 +254,13 @@
         <el-button @click="innerVisible = false">
           <el-icon><Close /></el-icon>取消
         </el-button>
+        <el-button
+          v-if="showDraftButton"
+          :loading="submitLoading"
+          @click="handleSubmit('draft')"
+        >
+          <el-icon><Document /></el-icon>暂存
+        </el-button>
         <el-button
           v-if="showSaveButton"
           :type="showSaveAndArrangeButton ? 'default' : 'primary'"
@@ -320,6 +327,7 @@
     Check,
     Close,
     Delete,
+    Document,
     DocumentCopy,
     FolderOpened,
     Plus,
@@ -482,6 +490,7 @@
   /** 加载详情 / 重置时禁止计划产品 → 段产品自动对齐 */
   const suppressStopProductSync = ref(false)
   const loadedTransportStatus = ref('pending')
+  const loadedFollowStatus = ref('')
   const loadForm = ref<PlanLoadForm>(createEmptyLoadForm())
   const deliveryForms = ref<PlanDeliveryForm[]>([createEmptyDeliveryForm(1)])
   const loadedPlanSnapshot = ref<Plan | null>(null)
@@ -489,6 +498,12 @@
 
   /** 已安排:禁止仅「保存」,须走保存并安排/调整运输,避免计划与地磅等联动字段脱节 */
   const showSaveButton = computed(() => loadedTransportStatus.value !== 'arranged')
+  const showDraftButton = computed(() => {
+    if (!isEdit.value) return true
+    return (
+      loadedFollowStatus.value === 'drafting' || loadedFollowStatus.value === 'pending_arrange'
+    )
+  })
   const showSaveAndArrangeButton = computed(() => {
     if (!isEdit.value) return true
     if (props.openForArrange && loadedTransportStatus.value === 'arranged') return false
@@ -1577,6 +1592,7 @@
 
   function resetForm() {
     loadedTransportStatus.value = 'pending'
+    loadedFollowStatus.value = ''
     loadedPlanSnapshot.value = null
     vehicleBundle.value = null
     selectedDriverId.value = ''
@@ -1669,6 +1685,7 @@
       })
       applyStopsFromPlan(data)
       loadedTransportStatus.value = data.transportStatus || 'pending'
+      loadedFollowStatus.value = data.followStatus || ''
       loadedPlanSnapshot.value = data
       syncCommittedPartyIds()
 
@@ -1731,6 +1748,7 @@
         products: row.products.map((product) => ({ ...product, planProductId: undefined }))
       }))
       loadedTransportStatus.value = 'pending'
+      loadedFollowStatus.value = ''
       syncCommittedPartyIds()
 
       formData.products = await enrichPlanProductLinesAlias(
@@ -1976,11 +1994,51 @@
     return missing
   }
 
-  async function handleSubmit(mode: 'save' | 'saveAndArrange' | 'rearrange' = 'save') {
+  async function handleSubmit(
+    mode: 'draft' | 'save' | 'saveAndArrange' | 'rearrange' = 'save'
+  ) {
     // 必须在 await validate 之前置位,避免连点并发多次 createFull
     if (submitLoading.value) return
     submitLoading.value = true
     try {
+      if (mode === 'draft') {
+        const companyOk = !!(formData.belongsCompany || '').trim()
+        const dateOk = !!(formData.createDate || '').trim()
+        if (!companyOk || !dateOk) {
+          ElMessage.warning('暂存请至少填写归属公司和创建日期')
+          await basicFormRef.value
+            ?.validateField?.(['belongsCompany', 'createDate'])
+            .catch(() => undefined)
+          return
+        }
+        mirrorPlanProductsFromLoad()
+        const draftPayload = {
+          ...buildPlanCommonPayload({ allowClearTransport: isEdit.value }),
+          saveMode: 'draft' as const,
+          hasFreight: formData.hasFreight
+        }
+        let planId = props.planId || ''
+        if (isEdit.value) {
+          if (!props.planId) return
+          await planApi.update(props.planId, draftPayload)
+          planId = props.planId
+        } else {
+          const res = await planApi.createFull(
+            draftPayload as Parameters<typeof planApi.createFull>[0]
+          )
+          const created = pickBxData<Plan>(res)
+          planId = String(created?.id || '')
+          if (!planId) {
+            ElMessage.error('暂存成功但未返回计划 ID')
+            return
+          }
+        }
+        ElMessage.success('暂存成功')
+        innerVisible.value = false
+        emit('success', { planId, arranged: false })
+        return
+      }
+
       const valid = await basicFormRef.value?.validate().catch(() => false)
       if (!valid || !validateBusinessRules()) return
       if (!validateSingleContractPerStop()) return
@@ -2000,7 +2058,10 @@
       }
 
       mirrorPlanProductsFromLoad()
-      const commonPayload = buildPlanCommonPayload({ allowClearTransport: isEdit.value })
+      const commonPayload = {
+        ...buildPlanCommonPayload({ allowClearTransport: isEdit.value }),
+        saveMode: 'formal' as const
+      }
       let planId = props.planId || ''
       const wasCreate = !isEdit.value
 

+ 6 - 2
web-admin/src/utils/pl-display.ts

@@ -366,7 +366,7 @@ export const PL_PLAN_TYPE_TABS: { label: string; name: PlPlanTypeTab }[] = [
 ]
 
 export const PL_PLAN_LIST_TABS: { label: string; name: PlPlanListTab }[] = [
-  { label: '待安排', name: 'pending_arrange' },
+  { label: '待安排/草稿', name: 'pending_arrange' }, // name 保留;语义含 drafting
   ...PL_PLAN_TYPE_TABS
 ]
 
@@ -384,7 +384,8 @@ export function plPlanListTabToFilters(tab: PlPlanListTab): {
   followStatus?: string
 } {
   if (tab === 'pending_arrange') {
-    return { planType: '', followStatus: 'pending_arrange' }
+    // followStatus 由列表 searchForm 控制(默认 pending_arrange + drafting)
+    return { planType: '' }
   }
   return { planType: plPlanTypeTabToFilter(tab) }
 }
@@ -419,6 +420,7 @@ export function plPlanTypeFilterToTab(planType?: string): PlPlanTypeTab {
 
 /** 运输计划跟进状态选项(筛选) */
 export const PL_PLAN_FOLLOW_STATUS_OPTIONS = [
+  { label: '草稿中', value: 'drafting' },
   { label: '等通知', value: 'wait_notice' },
   { label: '待安排', value: 'pending_arrange' },
   { label: '已安排', value: 'arranged' },
@@ -433,6 +435,7 @@ export const PL_PLAN_DEFAULT_FOLLOW_STATUSES = PL_PLAN_FOLLOW_STATUS_OPTIONS.fil
 
 export function getPlPlanFollowStatusTagType(status?: string) {
   const map: Record<string, string> = {
+    drafting: 'info',
     wait_notice: 'warning',
     pending_arrange: 'danger',
     arranged: 'success',
@@ -447,6 +450,7 @@ export function getPlPlanFollowStatusTagType(status?: string) {
 
 export function getPlPlanFollowStatusLabel(status?: string) {
   const map: Record<string, string> = {
+    drafting: '草稿中',
     wait_notice: '等通知',
     pending_arrange: '待安排',
     arranged: '已安排',

+ 7 - 5
web-admin/src/views/pl/plan/index.vue

@@ -26,7 +26,6 @@
                 placeholder="全部"
                 clearable
                 multiple
-                :disabled="activePlanTypeTab === 'pending_arrange'"
               >
                 <el-option
                   v-for="opt in PL_PLAN_FOLLOW_STATUS_OPTIONS"
@@ -1154,7 +1153,7 @@ tableEl: planTableWrapRef,
   const searchForm = reactive({
     planNo: '',
     planType: '',
-    followStatus: ['pending_arrange'] as string[],
+    followStatus: ['pending_arrange', 'drafting'] as string[],
     customerName: '',
     productName: '',
     licensePlate: ''
@@ -1214,7 +1213,7 @@ tableEl: planTableWrapRef,
     const filters = plPlanListTabToFilters(nextTab)
     searchForm.planType = filters.planType
     if (nextTab === 'pending_arrange') {
-      searchForm.followStatus = ['pending_arrange']
+      searchForm.followStatus = ['pending_arrange', 'drafting']
     } else if (prevTab === 'pending_arrange') {
       searchForm.followStatus = [...PL_PLAN_DEFAULT_FOLLOW_STATUSES]
     }
@@ -1226,7 +1225,7 @@ tableEl: planTableWrapRef,
     activePlanTypeTab.value = 'pending_arrange'
     searchForm.planNo = ''
     searchForm.planType = ''
-    searchForm.followStatus = ['pending_arrange']
+    searchForm.followStatus = ['pending_arrange', 'drafting']
     searchForm.customerName = ''
     searchForm.productName = ''
     searchForm.licensePlate = ''
@@ -1317,7 +1316,10 @@ tableEl: planTableWrapRef,
   }
 
   function canWaitNotice(row: Plan) {
-    return row.followStatus === 'pending_arrange' && row.archiveStatus !== 'archived'
+    return (
+      (row.followStatus === 'pending_arrange' || row.followStatus === 'drafting') &&
+      row.archiveStatus !== 'archived'
+    )
   }
 
   function canCancelWaitNotice(row: Plan) {