This commit is contained in:
zlwu committed 2026-08-27 19:38:01 +08:00
1 parent 4611dbc2b2
commit ebf006eeff
46 files changed
+36802 -20974

No files matched your search

+485 -372
View File
@@ -31,11 +31,7 @@
</el-select>
</el-form-item>
<el-form-item label="客户名称">
<el-input
v-model="searchForm.customerName"
placeholder="搜索客户名称"
clearable
/>
<el-input v-model="searchForm.customerName" placeholder="搜索客户名称" clearable />
</el-form-item>
<el-form-item label="计划日期">
<el-date-picker
@@ -48,6 +44,16 @@
style="width: 100%"
/>
</el-form-item>
<el-form-item label="证书齐备">
<el-select v-model="searchForm.hazardCertReady" placeholder="全部" clearable>
<el-option
v-for="opt in PL_PLAN_HAZARD_CERT_READY_FILTER_OPTIONS"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
</div>
<div class="filter-actions">
<div class="filter-actions-start" />
@@ -78,25 +84,9 @@
@clear="handleQuickSearchClear"
/>
<el-button
:class="
selectedPlan &&
canManageCertReady(selectedPlan) &&
!isHazardCertReady(selectedPlan)
? 'btn-main'
: 'btn-ghost'
"
:type="
selectedPlan &&
canManageCertReady(selectedPlan) &&
!isHazardCertReady(selectedPlan)
? 'primary'
: undefined
"
:disabled="
!selectedPlan ||
!canManageCertReady(selectedPlan) ||
isHazardCertReady(selectedPlan)
"
:class="canToolbarMarkCertReady ? 'btn-main' : 'btn-ghost'"
:type="canToolbarMarkCertReady ? 'primary' : undefined"
:disabled="!canToolbarMarkCertReady"
:loading="certReadyLoading"
@click="handleToolbarMarkCertReady"
>
@@ -104,16 +94,12 @@
</el-button>
<el-button
:class="
selectedPlan &&
canManageCertReady(selectedPlan) &&
!isHazardCertReady(selectedPlan)
selectedPlan && canManageCertReady(selectedPlan) && !isHazardCertReady(selectedPlan)
? 'btn-ghost'
: 'btn-main'
"
:type="
selectedPlan &&
canManageCertReady(selectedPlan) &&
!isHazardCertReady(selectedPlan)
selectedPlan && canManageCertReady(selectedPlan) && !isHazardCertReady(selectedPlan)
? undefined
: 'primary'
"
@@ -124,11 +110,7 @@
</el-button>
<el-button
class="btn-ghost"
:disabled="
!selectedPlan ||
!canManageCertReady(selectedPlan) ||
!isHazardCertReady(selectedPlan)
"
:disabled="!canToolbarUnmarkCertReady"
:loading="certReadyLoading"
@click="handleToolbarUnmarkCertReady"
>
@@ -335,6 +317,22 @@
</div>
</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('stopContract')"
label="合同"
min-width="180"
align="left"
header-align="left"
class-name="col-name-wrap pl-plan-stop-col"
label-class-name="col-name-wrap"
>
<template #default="{ row }">
<PlPlanContractCell
:lines="getStopContractLines(row)"
@open="openContractDetail"
/>
</template>
</el-table-column>
<el-table-column
v-if="isColumnVisible('productName')"
label="产品名称"
@@ -411,6 +409,7 @@
>
<template #default="{ row }">
<PlPlanListProductCell
:stop-kind="row.kind"
:stop-products="getStopProducts(row)"
:plan-products="row.plan.products"
field="actualQty"
@@ -503,6 +502,8 @@
</template>
</PlPlanViewDialog>
<ContractDetailDialog ref="contractDetailRef" hide-product-price />
<PlPlanCertUploadDialog
v-model:visible="certDialogVisible"
:plan-id="certPlanId"
@@ -512,180 +513,134 @@
</template>
<script setup lang="ts">
import {
CircleCheck,
CircleClose,
RefreshLeft,
Search,
Upload
} from '@element-plus/icons-vue'
import { planApi, type Plan } from '@/api/pl'
import PlPlanCertUploadDialog from '@/components/business/pl-plan-cert-upload-dialog/index.vue'
import PlPlanListCertCell from '@/components/business/pl-plan-list-cert-cell/index.vue'
import PlPlanListProductCell from '@/components/business/pl-plan-list-product-cell/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
import {
buildHazardCertReadyConfirmMessage,
isHazardCertReady,
planHasHazardProducts
} from '@/composables/usePlToxicProductCertCheck'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { usePlPlanMergeListTable } from '@/composables/usePlPlanMergeListTable'
import PlPlanViewDialog from '@/components/business/pl-plan-view-dialog/index.vue'
import PlPlanTypeTabs from '@/components/business/pl-plan-type-tabs/index.vue'
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
import { useListDateShortcut } from '@/composables/useListDateShortcut'
import { useListMediaActive } from '@/composables/useListMediaActive'
import {
formatPlVehiclePlateDisplay,
getPlPlanFollowStatusLabel,
getPlPlanFollowStatusTagType,
normalizePlPersonnelSummaryDisplay,
PL_PLAN_CERT_TABS,
PL_PLAN_FOLLOW_STATUS_OPTIONS,
plPlanCertTabToFilters,
type PlPlanCertTab
} from '@/utils/pl-display'
import { syncViewRowFromFlatList } from '@/utils/view-dialog-refresh'
import {
getPlPlanListRowClassName,
getStopAddress,
getStopCustomerName,
getStopKindLabel,
getStopProducts,
getStopTime
} from '@/utils/pl-plan-list-display-rows'
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
import { refreshMenuBadges } from '@/utils/menu-badge-registry'
import { useMenuStore } from '@/store/modules/menu'
import { CircleCheck, CircleClose, RefreshLeft, Search, Upload } from '@element-plus/icons-vue'
import { planApi, type Plan } from '@/api/pl'
import PlPlanCertUploadDialog from '@/components/business/pl-plan-cert-upload-dialog/index.vue'
import PlPlanContractCell from '@/components/business/pl-plan-contract-cell/index.vue'
import PlPlanListCertCell from '@/components/business/pl-plan-list-cert-cell/index.vue'
import PlPlanListProductCell from '@/components/business/pl-plan-list-product-cell/index.vue'
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
import {
buildBatchHazardCertReadyConfirmMessage,
buildBatchHazardCertUnmarkConfirmMessage,
buildHazardCertReadyConfirmMessage,
isHazardCertReady,
planHasHazardProducts
} from '@/composables/usePlToxicProductCertCheck'
import { useContractDetailDialog } from '@/composables/useContractDetailDialog'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { usePlPlanMergeListTable } from '@/composables/usePlPlanMergeListTable'
import PlPlanViewDialog from '@/components/business/pl-plan-view-dialog/index.vue'
import PlPlanTypeTabs from '@/components/business/pl-plan-type-tabs/index.vue'
import ListToolbarDateShortcuts from '@/components/core/list-toolbar-date-shortcuts/index.vue'
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
import { useListDateShortcut } from '@/composables/useListDateShortcut'
import { useListMediaActive } from '@/composables/useListMediaActive'
import {
formatPlVehiclePlateDisplay,
getPlPlanFollowStatusLabel,
getPlPlanFollowStatusTagType,
normalizePlPersonnelSummaryDisplay,
PL_PLAN_CERT_TABS,
PL_PLAN_FOLLOW_STATUS_OPTIONS,
PL_PLAN_HAZARD_CERT_READY_FILTER_OPTIONS,
plPlanCertTabToFilters,
type PlPlanCertTab
} from '@/utils/pl-display'
import { syncViewRowFromFlatList } from '@/utils/view-dialog-refresh'
import {
getPlPlanListRowClassName,
getStopAddress,
getStopContractLines,
getStopCustomerName,
getStopKindLabel,
getStopProducts,
getStopTime
} from '@/utils/pl-plan-list-display-rows'
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
import { refreshMenuBadges } from '@/utils/menu-badge-registry'
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const menuStore = useMenuStore()
const { ContractDetailDialog, contractDetailRef, openContractDetail } = useContractDetailDialog()
const PLAN_MERGE_COLUMN_PROPS = [
'planDate',
'planType',
'followStatus',
'hazardCertReady',
'planAttachments',
'licensePlate',
'personnelSummary',
'followUserName'
] as const
const PLAN_MERGE_COLUMN_PROPS = [
'planDate',
'planType',
'followStatus',
'hazardCertReady',
'planAttachments',
'licensePlate',
'personnelSummary',
'followUserName'
] as const
const loading = ref(false)
const { mediaActive } = useListMediaActive()
const certReadyLoading = ref(false)
const viewDialogRef = ref<InstanceType<typeof PlPlanViewDialog>>()
const viewVisible = ref(false)
const viewPlanId = ref('')
const certDialogVisible = ref(false)
const certPlanId = ref('')
const planList = ref<Plan[]>([])
const loading = ref(false)
const { mediaActive } = useListMediaActive()
const certReadyLoading = ref(false)
const viewDialogRef = ref<InstanceType<typeof PlPlanViewDialog>>()
const viewVisible = ref(false)
const viewPlanId = ref('')
const certDialogVisible = ref(false)
const certPlanId = ref('')
const planList = ref<Plan[]>([])
const listColumnDefs = [
{ prop: 'planDate', label: '计划日期' },
{ prop: 'planType', label: '单据类型' },
{ prop: 'followStatus', label: '跟进状态' },
{ prop: 'hazardCertReady', label: '证书齐备' },
{ prop: 'planAttachments', label: '计划附件' },
{ prop: 'licensePlate', label: '车牌号' },
{ prop: 'personnelSummary', label: '人员' },
{ prop: 'followUserName', label: '跟进人', defaultVisible: false },
{ prop: 'stopKind', label: '段次' },
{ prop: 'stopCustomer', label: '客户' },
{ prop: 'stopAddressTime', label: '地址 / 时间' },
{ prop: 'productName', label: '产品名称' },
{ prop: 'productSpec', label: '型号/规格' },
{ prop: 'productUnit', label: '单位' },
{ prop: 'productPlanQty', label: '计划数量' },
{ prop: 'productActualQty', label: '实际数量' },
{ prop: 'productInboundQty', label: '入库数量' },
{ prop: 'productOutboundQty', label: '出库数量' }
]
const listColumnDefs = [
{ prop: 'planDate', label: '计划日期' },
{ prop: 'planType', label: '单据类型' },
{ prop: 'followStatus', label: '跟进状态' },
{ prop: 'hazardCertReady', label: '证书齐备' },
{ prop: 'planAttachments', label: '计划附件' },
{ prop: 'licensePlate', label: '车牌号' },
{ prop: 'personnelSummary', label: '人员' },
{ prop: 'followUserName', label: '跟进人', defaultVisible: false },
{ prop: 'stopKind', label: '段次' },
{ prop: 'stopCustomer', label: '客户' },
{ prop: 'stopAddressTime', label: '地址 / 时间' },
{ prop: 'stopContract', label: '合同' },
{ prop: 'productName', label: '产品名称' },
{ prop: 'productSpec', label: '型号/规格' },
{ prop: 'productUnit', label: '单位' },
{ prop: 'productPlanQty', label: '计划数量' },
{ prop: 'productActualQty', label: '实际数量' },
{ prop: 'productInboundQty', label: '入库数量' },
{ prop: 'productOutboundQty', label: '出库数量' }
]
const {
visibleColumns,
checkAll,
isIndeterminate,
isColumnVisible,
resetToDefault
} = usePersistedListColumns('pl-plan-cert-list-columns-v3', listColumnDefs)
const { visibleColumns, checkAll, isIndeterminate, isColumnVisible, resetToDefault } =
usePersistedListColumns('pl-plan-cert-list-columns-v4', listColumnDefs)
const selectedRows = ref<Plan[]>([])
const selectedRows = ref<Plan[]>([])
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
const visiblePlanMergeProps = computed(() =>
PLAN_MERGE_COLUMN_PROPS.filter((prop) => isColumnVisible(prop))
)
const visiblePlanMergeProps = computed(() =>
PLAN_MERGE_COLUMN_PROPS.filter((prop) => isColumnVisible(prop))
)
const {
displayRows,
planListSpanMethod,
getDisplayRowIndex,
isPlanFirstRowSelectable,
handleDisplaySelectionChange,
handleDisplayRowDblClick
} = usePlPlanMergeListTable({
planList,
visiblePlanMergeProps,
selectedRows,
pagination,
onView: (plan) => handleView(plan)
})
function getPlanCertExportFilterParams() {
const keyword = quickSearch.value.trim()
return {
q: keyword || undefined,
planNo: keyword ? undefined : searchForm.planNo || undefined,
planType: searchForm.planType || undefined,
followStatus: searchForm.followStatus || undefined,
customerName: keyword ? undefined : searchForm.customerName || undefined,
planDateFrom: dateRange.value?.[0] || undefined,
planDateTo: dateRange.value?.[1] || undefined,
hasHazardProduct: 1,
certUploadStatus: searchForm.certUploadStatus || undefined
}
}
const planCertExportColumns = computed(() => {
const skip = new Set(['stopKind', 'stopCustomer', 'stopAddressTime'])
const cols = visibleColumns.value.filter((col) => !skip.has(col))
if (!cols.includes('planNo')) cols.unshift('planNo')
return cols
})
const viewingPlan = ref<Plan | null>(null)
const selectedPlan = computed(() => (selectedRows.value.length === 1 ? selectedRows.value[0] : null))
const dateRange = ref<string[]>([])
const quickSearch = ref('')
const activePlanTypeTab = ref<PlPlanCertTab>('all')
const searchForm = reactive({
planNo: '',
planType: '',
followStatus: '',
customerName: '',
certUploadStatus: '' as string
})
function getPlanTypeLabel(type: string) {
const map: Record<string, string> = { direct: '直销单', inbound: '入库单', outbound: '出库单' }
return map[type] || type
}
async function fetchData() {
loading.value = true
try {
const {
displayRows,
planListSpanMethod,
getDisplayRowIndex,
isPlanFirstRowSelectable,
handleDisplaySelectionChange,
handleDisplayRowDblClick
} = usePlPlanMergeListTable({
planList,
visiblePlanMergeProps,
selectedRows,
pagination,
onView: (plan) => handleView(plan)
})
function getPlanCertExportFilterParams() {
const keyword = quickSearch.value.trim()
const res = await planApi.list({
return {
q: keyword || undefined,
planNo: keyword ? undefined : searchForm.planNo || undefined,
planType: searchForm.planType || undefined,
@@ -695,194 +650,352 @@ async function fetchData() {
planDateTo: dateRange.value?.[1] || undefined,
hasHazardProduct: 1,
certUploadStatus: searchForm.certUploadStatus || undefined,
page: pagination.page,
pageSize: pagination.pageSize
})
const data = res.data ?? res
planList.value = data.list || []
pagination.total = data.total || 0
} finally {
loading.value = false
hazardCertReady:
searchForm.hazardCertReady === '' ? undefined : Number(searchForm.hazardCertReady)
}
}
}
function handleSearch() {
pagination.page = 1
fetchData()
}
function handlePlanTypeTabChange(tab: PlPlanCertTab | string) {
const filters = plPlanCertTabToFilters(tab as PlPlanCertTab)
searchForm.planType = filters.planType
searchForm.certUploadStatus = filters.certUploadStatus || ''
pagination.page = 1
fetchData()
}
function handleReset() {
activePlanTypeTab.value = 'all'
searchForm.planNo = ''
searchForm.planType = ''
searchForm.followStatus = ''
searchForm.customerName = ''
searchForm.certUploadStatus = ''
quickSearch.value = ''
resetDateShortcut()
pagination.page = 1
fetchData()
}
function handleQuickSearch() {
searchForm.planNo = ''
searchForm.customerName = ''
pagination.page = 1
fetchData()
}
function handleQuickSearchClear() {
quickSearch.value = ''
handleQuickSearch()
}
const { activeDateShortcut, handleDateShortcut, resetDateShortcut } = useListDateShortcut(
dateRange,
handleSearch
)
function handlePageChange() {
fetchData()
}
function handleSizeChange() {
pagination.page = 1
fetchData()
}
function canUploadCert(row: Plan) {
return row.archiveStatus !== 'archived' && row.followStatus !== 'cancelled'
}
function canManageCertReady(row: Plan) {
return canUploadCert(row) && planHasHazardProducts(row.products)
}
function getHazardCertReadyLabel(row: Plan) {
if (!planHasHazardProducts(row.products)) return '无需'
return isHazardCertReady(row) ? '已齐备' : '待确认'
}
function getHazardCertReadyTagType(row: Plan) {
if (!planHasHazardProducts(row.products)) return 'info'
return isHazardCertReady(row) ? 'success' : 'warning'
}
async function refreshSidebarBadges() {
await refreshMenuBadges(menuStore.menuList)
}
async function handleMarkCertReady(row: Plan) {
await ElMessageBox.confirm(buildHazardCertReadyConfirmMessage(), '证书齐备确认', {
type: 'warning',
confirmButtonText: '确认齐备',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true
const planCertExportColumns = computed(() => {
const skip = new Set(['stopKind', 'stopCustomer', 'stopAddressTime', 'stopContract'])
const cols = visibleColumns.value.filter((col) => !skip.has(col))
if (!cols.includes('planNo')) cols.unshift('planNo')
return cols
})
certReadyLoading.value = true
try {
await planApi.setHazardCertReady(row.id, true)
ElMessage.success('已标记证书齐备')
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
} finally {
certReadyLoading.value = false
}
}
async function handleUnmarkCertReady(row: Plan) {
await ElMessageBox.confirm('确认取消该计划的证书齐备状态?取消后将无法安排运输。', '取消齐备', {
type: 'warning',
confirmButtonText: '确认取消',
cancelButtonText: '返回'
const viewingPlan = ref<Plan | null>(null)
const selectedPlan = computed(() =>
selectedRows.value.length === 1 ? selectedRows.value[0] : null
)
const dateRange = ref<string[]>([])
const quickSearch = ref('')
const activePlanTypeTab = ref<PlPlanCertTab>('all')
const searchForm = reactive({
planNo: '',
planType: '',
followStatus: '',
customerName: '',
certUploadStatus: '' as string,
hazardCertReady: '' as string
})
certReadyLoading.value = true
try {
await planApi.setHazardCertReady(row.id, false)
ElMessage.success('已取消证书齐备')
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
} finally {
certReadyLoading.value = false
function getPlanTypeLabel(type: string) {
const map: Record<string, string> = { direct: '直销单', inbound: '入库单', outbound: '出库单' }
return map[type] || type
}
}
function handleToolbarMarkCertReady() {
if (selectedRows.value.length !== 1) return
handleMarkCertReady(selectedRows.value[0])
}
async function fetchData() {
loading.value = true
try {
const keyword = quickSearch.value.trim()
const res = await planApi.list({
q: keyword || undefined,
planNo: keyword ? undefined : searchForm.planNo || undefined,
planType: searchForm.planType || undefined,
followStatus: searchForm.followStatus || undefined,
customerName: keyword ? undefined : searchForm.customerName || undefined,
planDateFrom: dateRange.value?.[0] || undefined,
planDateTo: dateRange.value?.[1] || undefined,
hasHazardProduct: 1,
certUploadStatus: searchForm.certUploadStatus || undefined,
hazardCertReady:
searchForm.hazardCertReady === '' ? undefined : Number(searchForm.hazardCertReady),
page: pagination.page,
pageSize: pagination.pageSize
})
const data = res.data ?? res
planList.value = data.list || []
pagination.total = data.total || 0
} finally {
loading.value = false
}
}
function handleToolbarUnmarkCertReady() {
if (selectedRows.value.length !== 1) return
handleUnmarkCertReady(selectedRows.value[0])
}
function handleSearch() {
pagination.page = 1
fetchData()
}
function syncViewingPlanFromList() {
syncViewRowFromFlatList(viewingPlan, planList.value)
}
function handlePlanTypeTabChange(tab: PlPlanCertTab | string) {
const filters = plPlanCertTabToFilters(tab as PlPlanCertTab)
searchForm.planType = filters.planType
searchForm.certUploadStatus = filters.certUploadStatus || ''
searchForm.hazardCertReady = filters.hazardCertReady ?? ''
pagination.page = 1
fetchData()
}
async function refreshViewIfOpen() {
if (!viewVisible.value || !viewPlanId.value) return
syncViewingPlanFromList()
await viewDialogRef.value?.reload()
}
async function handleCertUploadSuccess() {
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
}
function handleView(row: Plan) {
viewingPlan.value = row
viewPlanId.value = row.id
viewVisible.value = true
}
function handleUploadCert(row: Plan) {
certPlanId.value = row.id
certDialogVisible.value = true
}
function handleToolbarUploadCert() {
if (selectedRows.value.length !== 1) return
handleUploadCert(selectedRows.value[0])
}
useDbInstantSearch(
() => ({
planNo: searchForm.planNo,
planType: searchForm.planType,
followStatus: searchForm.followStatus,
customerName: searchForm.customerName,
certUploadStatus: searchForm.certUploadStatus,
dateRange: dateRange.value
}),
() => {
function handleReset() {
activePlanTypeTab.value = 'all'
searchForm.planNo = ''
searchForm.planType = ''
searchForm.followStatus = ''
searchForm.customerName = ''
searchForm.certUploadStatus = ''
searchForm.hazardCertReady = ''
quickSearch.value = ''
handleSearch()
resetDateShortcut()
pagination.page = 1
fetchData()
}
)
onMounted(() => {
fetchData()
})
function handleQuickSearch() {
searchForm.planNo = ''
searchForm.customerName = ''
pagination.page = 1
fetchData()
}
function handleQuickSearchClear() {
quickSearch.value = ''
handleQuickSearch()
}
const { activeDateShortcut, handleDateShortcut, resetDateShortcut } = useListDateShortcut(
dateRange,
handleSearch
)
function handlePageChange() {
fetchData()
}
function handleSizeChange() {
pagination.page = 1
fetchData()
}
function canUploadCert(row: Plan) {
return row.archiveStatus !== 'archived' && row.followStatus !== 'cancelled'
}
function canManageCertReady(row: Plan) {
return canUploadCert(row) && planHasHazardProducts(row.products)
}
const certReadyEligibleRows = computed(() =>
selectedRows.value.filter((row) => canManageCertReady(row) && !isHazardCertReady(row))
)
const canToolbarMarkCertReady = computed(() => certReadyEligibleRows.value.length > 0)
const certUnmarkEligibleRows = computed(() =>
selectedRows.value.filter((row) => canManageCertReady(row) && isHazardCertReady(row))
)
const canToolbarUnmarkCertReady = computed(() => certUnmarkEligibleRows.value.length > 0)
function getHazardCertReadyLabel(row: Plan) {
if (!planHasHazardProducts(row.products)) return '无需'
return isHazardCertReady(row) ? '已齐备' : '待确认'
}
function getHazardCertReadyTagType(row: Plan) {
if (!planHasHazardProducts(row.products)) return 'info'
return isHazardCertReady(row) ? 'success' : 'warning'
}
async function refreshSidebarBadges() {
await refreshMenuBadges(menuStore.menuList)
}
async function handleMarkCertReady(row: Plan) {
await ElMessageBox.confirm(buildHazardCertReadyConfirmMessage(), '证书齐备确认', {
type: 'warning',
confirmButtonText: '确认齐备',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true
})
certReadyLoading.value = true
try {
await planApi.setHazardCertReady(row.id, true)
ElMessage.success('已标记证书齐备')
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
} finally {
certReadyLoading.value = false
}
}
async function handleUnmarkCertReady(row: Plan) {
await ElMessageBox.confirm('确认取消该计划的证书齐备状态?取消后将无法安排运输。', '取消齐备', {
type: 'warning',
confirmButtonText: '确认取消',
cancelButtonText: '返回'
})
certReadyLoading.value = true
try {
await planApi.setHazardCertReady(row.id, false)
ElMessage.success('已取消证书齐备')
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
} finally {
certReadyLoading.value = false
}
}
function handleToolbarMarkCertReady() {
void handleBatchMarkCertReady()
}
async function handleBatchMarkCertReady() {
if (!selectedRows.value.length) {
ElMessage.warning('请先勾选运输计划')
return
}
const targets = certReadyEligibleRows.value
const skipped = selectedRows.value.length - targets.length
if (!targets.length) {
ElMessage.warning('所选计划均不可标记证书齐备(已齐备、已归档/作废或无危化品)')
return
}
const confirmMessage =
targets.length === 1
? buildHazardCertReadyConfirmMessage()
: buildBatchHazardCertReadyConfirmMessage(targets.length)
await ElMessageBox.confirm(confirmMessage, '证书齐备确认', {
type: 'warning',
confirmButtonText: '确认齐备',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true
})
certReadyLoading.value = true
let success = 0
let failed = 0
try {
for (const row of targets) {
try {
await planApi.setHazardCertReady(row.id, true)
success += 1
} catch {
failed += 1
}
}
if (failed === 0) {
const skippedHint = skipped > 0 ? `,已跳过 ${skipped} 条不可操作项` : ''
ElMessage.success(`已标记 ${success} 条计划证书齐备${skippedHint}`)
} else {
ElMessage.warning(`成功 ${success} 条,失败 ${failed} 条`)
}
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
} finally {
certReadyLoading.value = false
}
}
function handleToolbarUnmarkCertReady() {
void handleBatchUnmarkCertReady()
}
async function handleBatchUnmarkCertReady() {
if (!selectedRows.value.length) {
ElMessage.warning('请先勾选运输计划')
return
}
const targets = certUnmarkEligibleRows.value
const skipped = selectedRows.value.length - targets.length
if (!targets.length) {
ElMessage.warning('所选计划均不可取消证书齐备(未齐备、已归档/作废或无危化品)')
return
}
await ElMessageBox.confirm(
buildBatchHazardCertUnmarkConfirmMessage(targets.length),
'取消齐备',
{
type: 'warning',
confirmButtonText: '确认取消',
cancelButtonText: '返回'
}
)
certReadyLoading.value = true
let success = 0
let failed = 0
try {
for (const row of targets) {
try {
await planApi.setHazardCertReady(row.id, false)
success += 1
} catch {
failed += 1
}
}
if (failed === 0) {
const skippedHint = skipped > 0 ? `,已跳过 ${skipped} 条不可操作项` : ''
ElMessage.success(`已取消 ${success} 条计划证书齐备${skippedHint}`)
} else {
ElMessage.warning(`成功 ${success} 条,失败 ${failed} 条`)
}
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
} finally {
certReadyLoading.value = false
}
}
function syncViewingPlanFromList() {
syncViewRowFromFlatList(viewingPlan, planList.value)
}
async function refreshViewIfOpen() {
if (!viewVisible.value || !viewPlanId.value) return
syncViewingPlanFromList()
await viewDialogRef.value?.reload()
}
async function handleCertUploadSuccess() {
await fetchData()
await refreshViewIfOpen()
await refreshSidebarBadges()
}
function handleView(row: Plan) {
viewingPlan.value = row
viewPlanId.value = row.id
viewVisible.value = true
}
function handleUploadCert(row: Plan) {
certPlanId.value = row.id
certDialogVisible.value = true
}
function handleToolbarUploadCert() {
if (selectedRows.value.length !== 1) return
handleUploadCert(selectedRows.value[0])
}
useDbInstantSearch(
() => ({
planNo: searchForm.planNo,
planType: searchForm.planType,
followStatus: searchForm.followStatus,
customerName: searchForm.customerName,
certUploadStatus: searchForm.certUploadStatus,
hazardCertReady: searchForm.hazardCertReady,
dateRange: dateRange.value
}),
() => {
quickSearch.value = ''
handleSearch()
}
)
onMounted(() => {
fetchData()
})
</script>
<style scoped lang="scss">
@use '@styles/business/pl-plan-merge-list-table.scss';
@use '@styles/business/pl-plan-merge-list-table.scss';
.pl-plan-list-toolbar-row {
flex-wrap: wrap;
}
.pl-plan-list-toolbar-row {
flex-wrap: wrap;
}
</style>