1002 lines
33 KiB
Vue
1002 lines
33 KiB
Vue
<!-- 危化品证上传 -->
|
|
<template>
|
|
<div class="page-container">
|
|
<section class="panel">
|
|
<PlPlanTypeTabs
|
|
v-model="activePlanTypeTab"
|
|
:tabs="PL_PLAN_CERT_TABS"
|
|
@change="handlePlanTypeTabChange"
|
|
/>
|
|
|
|
<!-- 筛选区 -->
|
|
<div class="filter-block">
|
|
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
|
<div class="filter-grid">
|
|
<el-form-item label="计划编号">
|
|
<el-input
|
|
v-model="searchForm.planNo"
|
|
placeholder="搜索计划编号"
|
|
clearable
|
|
@keyup.enter="handleSearch"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="跟进状态">
|
|
<el-select v-model="searchForm.followStatus" placeholder="全部" clearable>
|
|
<el-option
|
|
v-for="opt in PL_PLAN_FOLLOW_STATUS_OPTIONS"
|
|
:key="opt.value"
|
|
:label="opt.label"
|
|
:value="opt.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="客户名称">
|
|
<el-input v-model="searchForm.customerName" placeholder="搜索客户名称" clearable />
|
|
</el-form-item>
|
|
<el-form-item label="计划日期">
|
|
<el-date-picker
|
|
v-model="dateRange"
|
|
type="daterange"
|
|
range-separator="-"
|
|
start-placeholder="开始日期"
|
|
end-placeholder="结束日期"
|
|
value-format="YYYY-MM-DD"
|
|
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" />
|
|
<div class="filter-actions-end">
|
|
<el-button class="btn-ghost" :loading="loading" @click="handleReset">
|
|
<el-icon><RefreshLeft /></el-icon>重置
|
|
</el-button>
|
|
<el-button class="btn-main" type="primary" :loading="loading" @click="handleSearch">
|
|
<el-icon><Search /></el-icon>搜索
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</el-form>
|
|
</div>
|
|
|
|
<div class="list-toolbar">
|
|
<div class="list-toolbar-row pl-plan-list-toolbar-row">
|
|
<ListToolbarDateShortcuts
|
|
variant="pills"
|
|
:active="activeDateShortcut"
|
|
aria-label="计划日期快捷筛选"
|
|
@change="handleDateShortcut"
|
|
/>
|
|
<ListQuickSearchInput
|
|
v-model="quickSearch"
|
|
placeholder="计划编号/供方/需方"
|
|
@search="handleQuickSearch"
|
|
@clear="handleQuickSearchClear"
|
|
/>
|
|
<el-button
|
|
:class="canToolbarMarkCertReady ? 'btn-main' : 'btn-ghost'"
|
|
:type="canToolbarMarkCertReady ? 'primary' : undefined"
|
|
:disabled="!canToolbarMarkCertReady"
|
|
:loading="certReadyLoading"
|
|
@click="handleToolbarMarkCertReady"
|
|
>
|
|
<el-icon><CircleCheck /></el-icon>证书齐备
|
|
</el-button>
|
|
<el-button
|
|
:class="
|
|
selectedPlan && canManageCertReady(selectedPlan) && !isHazardCertReady(selectedPlan)
|
|
? 'btn-ghost'
|
|
: 'btn-main'
|
|
"
|
|
:type="
|
|
selectedPlan && canManageCertReady(selectedPlan) && !isHazardCertReady(selectedPlan)
|
|
? undefined
|
|
: 'primary'
|
|
"
|
|
:disabled="!selectedPlan || !canUploadCert(selectedPlan)"
|
|
@click="handleToolbarUploadCert"
|
|
>
|
|
<el-icon><Upload /></el-icon>上传证书
|
|
</el-button>
|
|
<el-button
|
|
class="btn-ghost"
|
|
:disabled="!canToolbarUnmarkCertReady"
|
|
:loading="certReadyLoading"
|
|
@click="handleToolbarUnmarkCertReady"
|
|
>
|
|
<el-icon><CircleClose /></el-icon>取消齐备
|
|
</el-button>
|
|
|
|
<ListExportDropdown
|
|
:export-fn="planApi.export"
|
|
:get-filter-params="getPlanCertExportFilterParams"
|
|
:has-selection="true"
|
|
:selected-ids="selectedRows.map((r) => r.id)"
|
|
:visible-columns="planCertExportColumns"
|
|
filename="计划证件.xlsx"
|
|
/>
|
|
|
|
<ListToolbarEndActions
|
|
:loading="loading"
|
|
:column-defs="listColumnDefs"
|
|
v-model:visible-columns="visibleColumns"
|
|
v-model:check-all="checkAll"
|
|
:is-indeterminate="isIndeterminate"
|
|
@refresh="fetchData"
|
|
@reset-columns="resetToDefault"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 数据表格 -->
|
|
<div class="list-body list-wrap">
|
|
<ListTable
|
|
table-key="pl-plan-cert"
|
|
class="list-table pl-plan-merge-list-table"
|
|
:data="displayRows"
|
|
v-loading="loading"
|
|
row-key="rowKey"
|
|
:span-method="planListSpanMethod"
|
|
:row-class-name="getPlPlanListRowClassName"
|
|
@selection-change="handleDisplaySelectionChange"
|
|
@row-dblclick="handleDisplayRowDblClick"
|
|
>
|
|
<el-table-column
|
|
type="selection"
|
|
width="55"
|
|
align="center"
|
|
header-align="center"
|
|
:selectable="isPlanFirstRowSelectable"
|
|
/>
|
|
<el-table-column
|
|
type="index"
|
|
label="序号"
|
|
width="68"
|
|
align="center"
|
|
header-align="center"
|
|
:index="getDisplayRowIndex"
|
|
/>
|
|
<el-table-column prop="planNo" label="计划编号" min-width="150" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<el-link type="primary" @click="handleView(row.plan)">{{ row.plan.planNo }}</el-link>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('planDate')"
|
|
prop="planDate"
|
|
label="计划日期"
|
|
min-width="120"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">{{ row.plan.planDate || '—' }}</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('planType')"
|
|
prop="planType"
|
|
label="单据类型"
|
|
min-width="100"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
{{ getPlanTypeLabel(row.plan.planType) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('followStatus')"
|
|
prop="followStatus"
|
|
label="跟进状态"
|
|
min-width="100"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-tag :type="getPlPlanFollowStatusTagType(row.plan.followStatus)" size="small">
|
|
{{ getPlPlanFollowStatusLabel(row.plan.followStatus) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('hazardCertReady')"
|
|
label="证书齐备"
|
|
min-width="100"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-tag :type="getHazardCertReadyTagType(row.plan)" size="small">
|
|
{{ getHazardCertReadyLabel(row.plan) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('planAttachments')"
|
|
label="计划附件"
|
|
min-width="280"
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListCertCell
|
|
:products="row.plan.products"
|
|
hide-product
|
|
clickable
|
|
:clickable-disabled="!canUploadCert(row.plan)"
|
|
:media-active="mediaActive"
|
|
@click="handleUploadCert(row.plan)"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('licensePlate')"
|
|
prop="licensePlate"
|
|
label="车牌号"
|
|
min-width="160"
|
|
show-overflow-tooltip
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
{{ formatPlVehiclePlateDisplay(row.plan.licensePlate, row.plan.trailerNo) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('personnelSummary')"
|
|
prop="personnelSummary"
|
|
label="人员"
|
|
min-width="180"
|
|
show-overflow-tooltip
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">
|
|
{{ normalizePlPersonnelSummaryDisplay(row.plan.personnelSummary) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('followUserName')"
|
|
prop="followUserName"
|
|
label="跟进人"
|
|
min-width="100"
|
|
align="center"
|
|
>
|
|
<template #default="{ row }">{{ row.plan.followUserName || '—' }}</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('stopKind')"
|
|
prop="stopKind"
|
|
label="段次"
|
|
min-width="88"
|
|
align="center"
|
|
class-name="pl-plan-stop-col"
|
|
>
|
|
<template #default="{ row }">
|
|
<span class="pl-plan-stop-kind-text">{{ getStopKindLabel(row) }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('stopCustomer')"
|
|
prop="stopCustomer"
|
|
label="客户"
|
|
min-width="200"
|
|
align="left"
|
|
header-align="left"
|
|
class-name="col-name-wrap pl-plan-stop-col"
|
|
label-class-name="col-name-wrap"
|
|
show-overflow-tooltip
|
|
>
|
|
<template #default="{ row }">
|
|
<span>{{ getStopCustomerName(row) }}</span>
|
|
<el-tag
|
|
v-if="isPlInternalCustomerName(getStopCustomerName(row))"
|
|
type="success"
|
|
size="small"
|
|
class="pl-plan-internal-customer-tag"
|
|
>
|
|
内部
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('stopAddressTime')"
|
|
prop="stopAddressTime"
|
|
label="地址 / 时间"
|
|
min-width="220"
|
|
align="left"
|
|
header-align="left"
|
|
class-name="col-name-wrap pl-plan-stop-col"
|
|
label-class-name="col-name-wrap"
|
|
>
|
|
<template #default="{ row }">
|
|
<div class="pl-plan-stop-address-time">
|
|
<div class="pl-plan-stop-address-time__addr">{{ getStopAddress(row) }}</div>
|
|
<div class="pl-plan-stop-address-time__time">{{ getStopTime(row) }}</div>
|
|
</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="产品名称"
|
|
min-width="160"
|
|
align="left"
|
|
header-align="left"
|
|
class-name="col-name-wrap pl-plan-stop-col"
|
|
label-class-name="col-name-wrap"
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListProductCell
|
|
:stop-products="getStopProducts(row)"
|
|
:plan-products="row.plan.products"
|
|
field="name"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('productSpec')"
|
|
label="型号/规格"
|
|
min-width="96"
|
|
align="center"
|
|
header-align="center"
|
|
class-name="pl-plan-stop-col"
|
|
show-overflow-tooltip
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListProductCell
|
|
:stop-products="getStopProducts(row)"
|
|
:plan-products="row.plan.products"
|
|
field="spec"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('productUnit')"
|
|
label="单位"
|
|
min-width="64"
|
|
align="center"
|
|
header-align="center"
|
|
class-name="pl-plan-stop-col"
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListProductCell
|
|
:stop-products="getStopProducts(row)"
|
|
:plan-products="row.plan.products"
|
|
field="unit"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('productPlanQty')"
|
|
label="计划数量"
|
|
min-width="88"
|
|
align="right"
|
|
header-align="right"
|
|
class-name="pl-plan-stop-col"
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListProductCell
|
|
:stop-products="getStopProducts(row)"
|
|
:plan-products="row.plan.products"
|
|
field="planQty"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('productActualQty')"
|
|
label="实际数量"
|
|
min-width="88"
|
|
align="right"
|
|
header-align="right"
|
|
class-name="pl-plan-stop-col"
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListProductCell
|
|
:stop-kind="row.kind"
|
|
:stop-products="getStopProducts(row)"
|
|
:plan-products="row.plan.products"
|
|
field="actualQty"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('productInboundQty')"
|
|
label="入库数量"
|
|
min-width="88"
|
|
align="right"
|
|
header-align="right"
|
|
class-name="pl-plan-stop-col"
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListProductCell
|
|
:stop-products="getStopProducts(row)"
|
|
:plan-products="row.plan.products"
|
|
field="inboundQty"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="isColumnVisible('productOutboundQty')"
|
|
label="出库数量"
|
|
min-width="88"
|
|
align="right"
|
|
header-align="right"
|
|
class-name="pl-plan-stop-col"
|
|
>
|
|
<template #default="{ row }">
|
|
<PlPlanListProductCell
|
|
:stop-products="getStopProducts(row)"
|
|
:plan-products="row.plan.products"
|
|
field="outboundQty"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
</ListTable>
|
|
|
|
<div class="pagination list-pager-bar">
|
|
<div class="list-pager-selected">
|
|
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
|
</div>
|
|
|
|
<el-pagination
|
|
v-model:current-page="pagination.page"
|
|
v-model:page-size="pagination.pageSize"
|
|
:total="pagination.total"
|
|
:page-sizes="[10, 20, 100, 500]"
|
|
layout="total, sizes, prev, pager, next"
|
|
@size-change="handleSizeChange"
|
|
@current-change="handlePageChange"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<PlPlanViewDialog
|
|
ref="viewDialogRef"
|
|
v-model:visible="viewVisible"
|
|
:plan-id="viewPlanId"
|
|
show-inbound-outbound-qty
|
|
>
|
|
<template v-if="viewingPlan" #hero-actions>
|
|
<el-button
|
|
v-if="canManageCertReady(viewingPlan) && !isHazardCertReady(viewingPlan)"
|
|
class="btn-main"
|
|
type="primary"
|
|
:loading="certReadyLoading"
|
|
@click="handleMarkCertReady(viewingPlan)"
|
|
>
|
|
<el-icon><CircleCheck /></el-icon>证书齐备
|
|
</el-button>
|
|
<el-button
|
|
v-if="canUploadCert(viewingPlan)"
|
|
class="btn-ghost"
|
|
@click="handleUploadCert(viewingPlan)"
|
|
>
|
|
<el-icon><Upload /></el-icon>上传证书
|
|
</el-button>
|
|
<el-button
|
|
v-if="canManageCertReady(viewingPlan) && isHazardCertReady(viewingPlan)"
|
|
class="btn-ghost"
|
|
:loading="certReadyLoading"
|
|
@click="handleUnmarkCertReady(viewingPlan)"
|
|
>
|
|
<el-icon><CircleClose /></el-icon>取消齐备
|
|
</el-button>
|
|
</template>
|
|
</PlPlanViewDialog>
|
|
|
|
<ContractDetailDialog ref="contractDetailRef" hide-product-price />
|
|
|
|
<PlPlanCertUploadDialog
|
|
v-model:visible="certDialogVisible"
|
|
:plan-id="certPlanId"
|
|
@success="handleCertUploadSuccess"
|
|
/>
|
|
</div>
|
|
</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 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 { ContractDetailDialog, contractDetailRef, openContractDetail } = useContractDetailDialog()
|
|
|
|
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 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-v4', listColumnDefs)
|
|
|
|
const selectedRows = ref<Plan[]>([])
|
|
|
|
const pagination = reactive({
|
|
page: 1,
|
|
pageSize: 20,
|
|
total: 0
|
|
})
|
|
|
|
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,
|
|
hazardCertReady:
|
|
searchForm.hazardCertReady === '' ? undefined : Number(searchForm.hazardCertReady)
|
|
}
|
|
}
|
|
|
|
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
|
|
})
|
|
|
|
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
|
|
})
|
|
|
|
function getPlanTypeLabel(type: string) {
|
|
const map: Record<string, string> = { direct: '直销单', inbound: '入库单', outbound: '出库单' }
|
|
return map[type] || type
|
|
}
|
|
|
|
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 handleSearch() {
|
|
pagination.page = 1
|
|
fetchData()
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
function handleReset() {
|
|
activePlanTypeTab.value = 'all'
|
|
searchForm.planNo = ''
|
|
searchForm.planType = ''
|
|
searchForm.followStatus = ''
|
|
searchForm.customerName = ''
|
|
searchForm.certUploadStatus = ''
|
|
searchForm.hazardCertReady = ''
|
|
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)
|
|
}
|
|
|
|
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';
|
|
|
|
.pl-plan-list-toolbar-row {
|
|
flex-wrap: wrap;
|
|
}
|
|
</style>
|