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>
This commit is contained in:
zlwuandCursor committed 2026-09-02 11:42:23 +08:00
1 parent ae91fbc63e
commit 3c7e3c69c5
4 files changed
+77 -9

No files matched your search

+1
View File
@@ -691,6 +691,7 @@ export interface PlanCreateFullPayload {
followUserId?: string
followUserName?: string
remark?: string
saveMode?: 'draft' | 'formal'
supplierContracts?: PlanContractLink[]
demanderContracts?: PlanContractLink[]
load?: PlanLoad
@@ -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
View File
@@ -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
View File
@@ -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) {