提交
This commit is contained in:
1 parent
4611dbc2b2
commit
ebf006eeff
46 files changed
+36802
-20974
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user