1351 lines
43 KiB
Vue
1351 lines
43 KiB
Vue
<!-- 运输计划管理 -->
|
||
<template>
|
||
<div class="page-container">
|
||
<section class="panel">
|
||
<PlPlanTypeTabs
|
||
:model-value="activePlanTypeTab"
|
||
:tabs="PL_PLAN_LIST_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
|
||
:disabled="activePlanTypeTab === 'pending_arrange'"
|
||
>
|
||
<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
|
||
@keyup.enter="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="产品名称">
|
||
<el-input
|
||
v-model="searchForm.productName"
|
||
placeholder="搜索产品名称"
|
||
clearable
|
||
@keyup.enter="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="车牌号">
|
||
<el-input
|
||
v-model="searchForm.licensePlate"
|
||
placeholder="搜索车牌号"
|
||
clearable
|
||
@keyup.enter="handleSearch"
|
||
/>
|
||
</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>
|
||
</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="btn-main" type="primary" @click="handleAdd">
|
||
<el-icon><Plus /></el-icon>新增计划
|
||
</el-button>
|
||
<el-button
|
||
class="btn-ghost"
|
||
:disabled="!selectedPlan"
|
||
@click="handleToolbarCopy"
|
||
>
|
||
<el-icon><CopyDocument /></el-icon>复制
|
||
</el-button>
|
||
<el-button
|
||
class="btn-ghost"
|
||
:disabled="!selectedPlan || !canArrangeTransport(selectedPlan)"
|
||
@click="handleToolbarArrange"
|
||
>
|
||
<el-icon><Van /></el-icon>
|
||
{{ selectedPlan ? arrangeTransportLabel(selectedPlan) : '安排运输' }}
|
||
</el-button>
|
||
<el-button
|
||
class="btn-ghost"
|
||
:disabled="!selectedPlan || !canWaitNotice(selectedPlan)"
|
||
:loading="waitNoticeLoading"
|
||
@click="handleToolbarWaitNotice"
|
||
>
|
||
<el-icon><Bell /></el-icon>等客户通知
|
||
</el-button>
|
||
<el-dropdown trigger="click" @command="handleToolbarPrintSlip">
|
||
<el-button class="btn-ghost">
|
||
<el-icon><Printer /></el-icon>打印单据
|
||
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item
|
||
command="inbound"
|
||
:disabled="!canBatchPrintSlip(selectedRows, 'inbound').ok"
|
||
>
|
||
入库单
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
command="delivery"
|
||
:disabled="!canBatchPrintSlip(selectedRows, 'delivery').ok"
|
||
>
|
||
送(提)货单
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
command="subpack"
|
||
:disabled="!canBatchPrintSlip(selectedRows, 'subpack').ok"
|
||
>
|
||
分装单
|
||
</el-dropdown-item>
|
||
<el-dropdown-item divided command="blank-inbound">空白入库单</el-dropdown-item>
|
||
<el-dropdown-item command="blank-delivery">空白送(提)货单</el-dropdown-item>
|
||
<el-dropdown-item command="blank-subpack">空白分装单</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
<el-button
|
||
class="btn-ghost"
|
||
type="danger"
|
||
:disabled="!selectedPlan || !canVoidPlan(selectedPlan)"
|
||
:loading="voidLoading"
|
||
@click="handleToolbarVoid"
|
||
>
|
||
<el-icon><CircleClose /></el-icon>作废
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
class="pl-plan-paste-create-btn"
|
||
@click="pasteVisible = true"
|
||
>
|
||
<el-icon><DocumentCopy /></el-icon>三方人车快速创建
|
||
</el-button>
|
||
<el-button
|
||
v-if="canForceFollowStatus"
|
||
class="btn-ghost"
|
||
:disabled="!selectedPlan"
|
||
@click="openForceFollowStatusDialog"
|
||
>
|
||
<el-icon><EditPen /></el-icon>强制改状态
|
||
</el-button>
|
||
|
||
<ListExportDropdown
|
||
:export-fn="planApi.export"
|
||
:get-filter-params="getPlanExportFilterParams"
|
||
:has-selection="true"
|
||
:selected-ids="selectedRows.map((r) => r.id)"
|
||
:visible-columns="planExportColumns"
|
||
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">
|
||
<div ref="planTableWrapRef" class="table-scroll bx-list-table-wrap">
|
||
<ListTable table-key="pl-plan"
|
||
class="list-table"
|
||
:data="planList"
|
||
v-loading="loading"
|
||
stripe
|
||
row-key="id"
|
||
@selection-change="handleSelectionChange"
|
||
@row-dblclick="handleView"
|
||
@header-dragend="() => planStatsRowRef?.resync?.()"
|
||
>
|
||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||
<el-table-column
|
||
type="index"
|
||
label="序号"
|
||
min-width="68"
|
||
align="center"
|
||
header-align="center"
|
||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||
/>
|
||
<el-table-column prop="planNo" label="计划编号" min-width="180" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click="handleView(row)">{{ row.planNo }}</el-link>
|
||
</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.followStatus)" size="small">
|
||
{{ getPlPlanFollowStatusLabel(row.followStatus) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('planDate')"
|
||
prop="planDate"
|
||
label="计划日期"
|
||
min-width="120"
|
||
align="center"
|
||
/>
|
||
<el-table-column
|
||
v-if="isColumnVisible('planType')"
|
||
prop="planType"
|
||
label="单据类型"
|
||
min-width="100"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
{{ getPlanTypeLabel(row.planType) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('supplierName')"
|
||
prop="supplierName"
|
||
label="供方"
|
||
min-width="200"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="col-name-wrap"
|
||
label-class-name="col-name-wrap"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
v-if="isColumnVisible('customerName')"
|
||
prop="customerName"
|
||
label="需方"
|
||
min-width="200"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="col-name-wrap"
|
||
label-class-name="col-name-wrap"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
v-if="isColumnVisible('licensePlate')"
|
||
prop="licensePlate"
|
||
label="车牌号"
|
||
min-width="160"
|
||
show-overflow-tooltip
|
||
align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
{{ formatPlVehiclePlateDisplay(row.licensePlate, row.trailerNo) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('productName')"
|
||
label="产品名称"
|
||
min-width="240"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="col-name-wrap"
|
||
label-class-name="col-name-wrap"
|
||
>
|
||
<template #default="{ row }">
|
||
<PlPlanListProductCell :products="row.products" field="name" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('productSpec')"
|
||
label="型号/规格"
|
||
min-width="120"
|
||
align="center"
|
||
header-align="center"
|
||
show-overflow-tooltip
|
||
>
|
||
<template #default="{ row }">
|
||
<PlPlanListProductCell :products="row.products" field="model" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('productPackageSpec')"
|
||
label="包装规格"
|
||
min-width="120"
|
||
align="center"
|
||
header-align="center"
|
||
show-overflow-tooltip
|
||
>
|
||
<template #default="{ row }">
|
||
<PlPlanListProductCell :products="row.products" field="packageSpec" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('productUnit')"
|
||
label="单位"
|
||
min-width="80"
|
||
align="center"
|
||
header-align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
<PlPlanListProductCell :products="row.products" field="unit" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('productPlanQty')"
|
||
label="计划数量"
|
||
min-width="100"
|
||
align="right"
|
||
header-align="right"
|
||
>
|
||
<template #default="{ row }">
|
||
<PlPlanListProductCell :products="row.products" field="planQty" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('productActualQty')"
|
||
label="实际数量"
|
||
min-width="100"
|
||
align="right"
|
||
header-align="right"
|
||
>
|
||
<template #default="{ row }">
|
||
<PlPlanListProductCell :products="row.products" field="actualQty" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('planAttachments')"
|
||
label="计划附件"
|
||
min-width="200"
|
||
align="left"
|
||
header-align="left"
|
||
>
|
||
<template #default="{ row }">
|
||
<PlPlanListCertCell :products="row.products" compact hide-product />
|
||
</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.personnelSummary) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
v-if="isColumnVisible('followUserName')"
|
||
prop="followUserName"
|
||
label="跟进人"
|
||
min-width="100"
|
||
align="center"
|
||
/>
|
||
<el-table-column
|
||
v-if="isColumnVisible('contractSummary')"
|
||
label="合同"
|
||
min-width="220"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="col-name-wrap"
|
||
label-class-name="col-name-wrap"
|
||
>
|
||
<template #default="{ row }">
|
||
<div class="pl-plan-contract-cell">
|
||
<div
|
||
v-for="(line, index) in planContractLines(row)"
|
||
:key="`${line.side}-${line.no}-${index}`"
|
||
class="pl-plan-contract-cell__row"
|
||
>
|
||
<span class="pl-plan-contract-cell__side">{{ line.side }}</span>
|
||
<span
|
||
class="pl-plan-contract-cell__no"
|
||
:class="{ 'is-pending': line.pending }"
|
||
>{{ line.no }}</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</ListTable>
|
||
<ListColumnStatsRow
|
||
ref="planStatsRowRef"
|
||
storage-key="pl-plan-column-stats"
|
||
:columns="planStatsRowColumns"
|
||
:data="planList"
|
||
:table-wrap-el="planTableWrapRef"
|
||
:get-cell-value="getPlanStatCellValue"
|
||
/>
|
||
</div>
|
||
|
||
<div class="pagination list-pager-bar pl-plan-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>
|
||
|
||
<PlPlanFormDialog
|
||
v-model:visible="formDialogVisible"
|
||
:plan-id="editingPlanId"
|
||
:copy-from-plan-id="copyFromPlanId"
|
||
@success="handlePlanMutationSuccess"
|
||
/>
|
||
|
||
<PlPlanViewDialog
|
||
ref="viewDialogRef"
|
||
v-model:visible="viewVisible"
|
||
:plan-id="viewPlanId"
|
||
>
|
||
<template v-if="viewingPlan" #hero-actions>
|
||
<el-button
|
||
v-if="canEditPlan(viewingPlan)"
|
||
class="btn-main"
|
||
type="primary"
|
||
@click="handleEdit(viewingPlan)"
|
||
>
|
||
<el-icon><Edit /></el-icon>编辑
|
||
</el-button>
|
||
<el-button class="btn-ghost" @click="handleCopy(viewingPlan)">
|
||
<el-icon><CopyDocument /></el-icon>复制
|
||
</el-button>
|
||
<el-button
|
||
v-if="canArrangeTransport(viewingPlan)"
|
||
class="btn-ghost"
|
||
@click="handleArrange(viewingPlan)"
|
||
>
|
||
<el-icon><Van /></el-icon>{{ arrangeTransportLabel(viewingPlan) }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="canWaitNotice(viewingPlan)"
|
||
class="btn-ghost"
|
||
:loading="waitNoticeLoading"
|
||
@click="handleWaitNotice(viewingPlan)"
|
||
>
|
||
<el-icon><Bell /></el-icon>等客户通知
|
||
</el-button>
|
||
<el-button
|
||
v-if="canCancelWaitNotice(viewingPlan)"
|
||
class="btn-ghost"
|
||
:loading="waitNoticeLoading"
|
||
@click="handleCancelWaitNotice(viewingPlan)"
|
||
>
|
||
<el-icon><RefreshLeft /></el-icon>取消等通知
|
||
</el-button>
|
||
<el-dropdown trigger="click" @command="handleViewPrintSlipCommand">
|
||
<el-button class="btn-ghost">
|
||
<el-icon><Printer /></el-icon>打印单据
|
||
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item
|
||
command="inbound"
|
||
:disabled="!canPrintSlipCommand(viewingPlan?.planType, 'inbound')"
|
||
>
|
||
入库单
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
command="delivery"
|
||
:disabled="!canPrintSlipCommand(viewingPlan?.planType, 'delivery')"
|
||
>
|
||
送(提)货单
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
command="subpack"
|
||
:disabled="!canPrintSlipCommand(viewingPlan?.planType, 'subpack')"
|
||
>
|
||
分装单
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
<el-button
|
||
v-if="canVoidPlan(viewingPlan)"
|
||
class="btn-ghost"
|
||
type="danger"
|
||
:loading="voidLoading"
|
||
@click="handleVoid(viewingPlan)"
|
||
>
|
||
<el-icon><CircleClose /></el-icon>作废
|
||
</el-button>
|
||
</template>
|
||
</PlPlanViewDialog>
|
||
|
||
<PlPlanArrangeDialog
|
||
v-model:visible="arrangeDialogVisible"
|
||
:plan-id="arrangePlanId"
|
||
@success="handlePlanMutationSuccess"
|
||
/>
|
||
|
||
<PlInboundSlipPrintDialog ref="inboundSlipPrintRef" />
|
||
<PlDeliverySlipPrintDialog ref="deliverySlipPrintRef" />
|
||
<PlSubpackSlipPrintDialog ref="subpackSlipPrintRef" />
|
||
|
||
<PlPasteRecognizeDialog v-model:visible="pasteVisible" mode="vehicle-bundle" />
|
||
|
||
<el-dialog
|
||
v-model="forceFollowDialogVisible"
|
||
title="强制修改跟进状态"
|
||
width="640px"
|
||
align-center
|
||
class="dialog-form-layout"
|
||
modal-class="order-module-dialog-mask"
|
||
destroy-on-close
|
||
@closed="resetForceFollowDialog"
|
||
>
|
||
<el-form label-position="top" class="form-grid-2">
|
||
<el-form-item label="计划编号" style="margin-bottom: 0">
|
||
<span>{{ selectedPlan?.planNo || '—' }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="单据类型" style="margin-bottom: 0">
|
||
<span>{{ getPlanTypeLabel(selectedPlan?.planType) }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="供方" style="margin-bottom: 0">
|
||
<span>{{ selectedPlan?.supplierName || '—' }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="需方" style="margin-bottom: 0">
|
||
<span>{{ selectedPlan?.customerName || '—' }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="产品信息" class="form-col-span-2" style="margin-bottom: 0">
|
||
<PlPlanListProductCell :products="selectedPlan?.products || []" />
|
||
</el-form-item>
|
||
<el-form-item label="当前跟进状态" style="margin-bottom: 0">
|
||
<el-tag
|
||
v-if="selectedPlan"
|
||
:type="getPlPlanFollowStatusTagType(selectedPlan.followStatus)"
|
||
size="small"
|
||
>
|
||
{{ getPlPlanFollowStatusLabel(selectedPlan.followStatus) }}
|
||
</el-tag>
|
||
<span v-else>—</span>
|
||
</el-form-item>
|
||
<el-form-item label="目标跟进状态" required style="margin-bottom: 0">
|
||
<el-select v-model="forceFollowStatusValue" placeholder="请选择" style="width: 100%">
|
||
<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>
|
||
<p class="form-tip form-col-span-2">仅修改跟进状态字段,不会联动作废或运输安排。</p>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="forceFollowDialogVisible = false">
|
||
<el-icon><Close /></el-icon>取消
|
||
</el-button>
|
||
<el-button type="primary" :loading="forceFollowLoading" @click="submitForceFollowStatus">
|
||
<el-icon><Check /></el-icon>确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {
|
||
ArrowDown,
|
||
Bell,
|
||
Check,
|
||
CircleClose,
|
||
Close,
|
||
CopyDocument,
|
||
DocumentCopy,
|
||
Edit,
|
||
EditPen,
|
||
Plus,
|
||
Printer,
|
||
RefreshLeft,
|
||
Search,
|
||
Van
|
||
} from '@element-plus/icons-vue'
|
||
import { fetchGetMenuCodes } from '@/api/auth'
|
||
import { planApi, type Plan } from '@/api/pl'
|
||
import PlInboundSlipPrintDialog from '@/components/business/pl-inbound-slip-print-dialog/index.vue'
|
||
import PlDeliverySlipPrintDialog from '@/components/business/pl-delivery-slip-print-dialog/index.vue'
|
||
import PlSubpackSlipPrintDialog from '@/components/business/pl-subpack-slip-print-dialog/index.vue'
|
||
import PlPlanArrangeDialog from '@/components/business/pl-plan-arrange-dialog/index.vue'
|
||
import PlPasteRecognizeDialog from '@/components/business/pl-paste-recognize-dialog/index.vue'
|
||
import PlPlanFormDialog from '@/components/business/pl-plan-form-dialog/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 ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
|
||
import type { StatsRowColumn } from '@/utils/list-column-stats'
|
||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||
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 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 { canBatchPrintSlip } from '@/utils/pl-batch-print'
|
||
import { planContractLines } from '@/utils/pl-plan-contract-display'
|
||
import {
|
||
canPrintSlipCommand,
|
||
formatPlVehiclePlateDisplay,
|
||
getPlPlanFollowStatusLabel,
|
||
getPlPlanFollowStatusTagType,
|
||
normalizePlPersonnelSummaryDisplay,
|
||
PL_PLAN_FOLLOW_STATUS_OPTIONS,
|
||
PL_PLAN_LIST_TABS,
|
||
plPlanListTabToFilters,
|
||
type PlPlanListTab,
|
||
type PlPrintSlipCommand
|
||
} from '@/utils/pl-display'
|
||
import { syncViewRowFromFlatList } from '@/utils/view-dialog-refresh'
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
|
||
const loading = ref(false)
|
||
const voidLoading = ref(false)
|
||
const waitNoticeLoading = ref(false)
|
||
const canForceFollowStatus = ref(false)
|
||
const forceFollowDialogVisible = ref(false)
|
||
const forceFollowLoading = ref(false)
|
||
const forceFollowStatusValue = ref('')
|
||
const formDialogVisible = ref(false)
|
||
const pasteVisible = ref(false)
|
||
const editingPlanId = ref('')
|
||
const copyFromPlanId = ref('')
|
||
const viewVisible = ref(false)
|
||
const viewPlanId = ref('')
|
||
const viewDialogRef = ref<InstanceType<typeof PlPlanViewDialog>>()
|
||
const arrangeDialogVisible = ref(false)
|
||
const arrangePlanId = ref('')
|
||
const inboundSlipPrintRef = ref<InstanceType<typeof PlInboundSlipPrintDialog>>()
|
||
const deliverySlipPrintRef = ref<InstanceType<typeof PlDeliverySlipPrintDialog>>()
|
||
const subpackSlipPrintRef = ref<InstanceType<typeof PlSubpackSlipPrintDialog>>()
|
||
const planList = ref<Plan[]>([])
|
||
|
||
const listColumnDefs = [
|
||
{ prop: 'followStatus', label: '跟进状态' },
|
||
{ prop: 'planDate', label: '计划日期' },
|
||
{ prop: 'planType', label: '单据类型' },
|
||
{ prop: 'supplierName', label: '供方' },
|
||
{ prop: 'customerName', label: '需方' },
|
||
{ prop: 'licensePlate', label: '车牌号' },
|
||
{ prop: 'productName', label: '产品名称' },
|
||
{ prop: 'productSpec', label: '型号/规格' },
|
||
{ prop: 'productPackageSpec', label: '包装规格' },
|
||
{ prop: 'productUnit', label: '单位' },
|
||
{ prop: 'productPlanQty', label: '计划数量' },
|
||
{ prop: 'productActualQty', label: '实际数量' },
|
||
{ prop: 'planAttachments', label: '计划附件' },
|
||
{ prop: 'personnelSummary', label: '人员' },
|
||
{ prop: 'followUserName', label: '跟进人', defaultVisible: false },
|
||
{ prop: 'contractSummary', label: '合同' }
|
||
]
|
||
|
||
const {
|
||
visibleColumns,
|
||
checkAll,
|
||
isIndeterminate,
|
||
isColumnVisible,
|
||
resetToDefault
|
||
} = usePersistedListColumns('pl-plan-list-columns-v2', listColumnDefs)
|
||
|
||
const planTableWrapRef = ref<HTMLElement | null>(null)
|
||
const planStatsRowRef = ref<{ resync?: () => void } | null>(null)
|
||
|
||
const planStatsRowColumns = computed((): StatsRowColumn[] => {
|
||
const cols: StatsRowColumn[] = [
|
||
{ key: '__selection', interactive: false },
|
||
{ key: '__index', interactive: false },
|
||
{ key: 'planNo', interactive: true, prop: 'planNo' }
|
||
]
|
||
for (const def of listColumnDefs) {
|
||
if (!isColumnVisible(def.prop)) continue
|
||
cols.push({
|
||
key: def.prop,
|
||
interactive: true,
|
||
prop: def.prop
|
||
})
|
||
}
|
||
return cols
|
||
})
|
||
|
||
function getPlanStatCellValue(row: Plan, prop: string): unknown {
|
||
if (prop === 'followStatus') return getPlPlanFollowStatusLabel(row.followStatus)
|
||
if (prop === 'planType') return getPlanTypeLabel(row.planType)
|
||
if (prop === 'contractSummary') {
|
||
return planContractLines(row)
|
||
.map((l) => `${l.side}${l.no}`)
|
||
.join(' ')
|
||
}
|
||
if (prop === 'productPlanQty') {
|
||
return (row.products || []).reduce((sum, p) => sum + (Number(p.quantity) || 0), 0)
|
||
}
|
||
if (prop === 'productActualQty') {
|
||
return (row.products || []).reduce((sum, p) => sum + (Number(p.actualQty) || 0), 0)
|
||
}
|
||
if (prop === 'productName') {
|
||
return (row.products || []).map((p) => p.productName || '').filter(Boolean).join('\n')
|
||
}
|
||
if (prop === 'productSpec') {
|
||
return (row.products || []).map((p) => p.model || p.spec || '').filter(Boolean).join('\n')
|
||
}
|
||
if (prop === 'productPackageSpec') {
|
||
return (row.products || []).map((p) => p.packageSpec || '').filter(Boolean).join('\n')
|
||
}
|
||
if (prop === 'productUnit') {
|
||
return (row.products || []).map((p) => p.unit || '').filter(Boolean).join('\n')
|
||
}
|
||
if (prop === 'planAttachments') {
|
||
return ''
|
||
}
|
||
if (prop === 'licensePlate') {
|
||
return formatPlVehiclePlateDisplay(row.licensePlate, row.trailerNo)
|
||
}
|
||
if (prop === 'personnelSummary') {
|
||
return normalizePlPersonnelSummaryDisplay(row.personnelSummary)
|
||
}
|
||
return (row as any)?.[prop]
|
||
}
|
||
|
||
const selectedRows = ref<Plan[]>([])
|
||
function getPlanExportFilterParams() {
|
||
const keyword = quickSearch.value.trim()
|
||
return {
|
||
q: keyword || undefined,
|
||
planNo: keyword ? undefined : searchForm.planNo || undefined,
|
||
planType: tabFilters.planType || undefined,
|
||
followStatus: tabFilters.followStatus || searchForm.followStatus || undefined,
|
||
customerName: keyword ? undefined : searchForm.customerName || undefined,
|
||
productName: keyword ? undefined : searchForm.productName || undefined,
|
||
licensePlate: keyword ? undefined : searchForm.licensePlate || undefined,
|
||
planDateFrom: dateRange.value?.[0] || undefined,
|
||
planDateTo: dateRange.value?.[1] || undefined
|
||
}
|
||
}
|
||
|
||
const planExportColumns = computed(() => {
|
||
const cols = [...visibleColumns.value]
|
||
if (!cols.includes('planNo')) cols.unshift('planNo')
|
||
return cols
|
||
})
|
||
|
||
const viewingPlan = ref<Plan | null>(null)
|
||
|
||
function syncViewingPlanFromList() {
|
||
syncViewRowFromFlatList(viewingPlan, planList.value)
|
||
}
|
||
|
||
async function refreshViewIfOpen() {
|
||
if (!viewVisible.value || !viewPlanId.value) return
|
||
syncViewingPlanFromList()
|
||
await viewDialogRef.value?.reload()
|
||
}
|
||
|
||
function getPlanArrangePrerequisiteGaps(plan: Plan): string[] {
|
||
const missing: string[] = []
|
||
if (!String(plan.createDate || '').trim()) missing.push('创建日期')
|
||
if (!String(plan.planDate || '').trim()) missing.push('计划日期')
|
||
if (!String(plan.belongsCompany || '').trim()) missing.push('归属公司')
|
||
if (!String(plan.planType || '').trim()) missing.push('单据类型')
|
||
if (!String(plan.followUserId || '').trim() && !String(plan.followUserName || '').trim()) {
|
||
missing.push('跟进人')
|
||
}
|
||
if (!String(plan.supplierId || '').trim() || !String(plan.supplierName || '').trim()) {
|
||
missing.push('供方客户')
|
||
}
|
||
if (!String(plan.customerId || '').trim() || !String(plan.customerName || '').trim()) {
|
||
missing.push('需方客户')
|
||
}
|
||
return missing
|
||
}
|
||
|
||
/** 安排运输前校验必填项;缺失则提示并打开编辑弹窗 */
|
||
async function ensurePlanReadyForArrange(plan: Plan): Promise<boolean> {
|
||
const missing = getPlanArrangePrerequisiteGaps(plan)
|
||
if (!missing.length) return true
|
||
|
||
try {
|
||
await ElMessageBox.alert(
|
||
`当前运输计划缺少:${missing.join('、')}。请先编辑完善后再安排运输。`,
|
||
'请先编辑运输计划',
|
||
{ type: 'warning', confirmButtonText: '去编辑' }
|
||
)
|
||
} catch {
|
||
// 关闭弹窗仍跳转编辑
|
||
}
|
||
|
||
if (viewVisible.value && viewingPlan.value?.id === plan.id) {
|
||
viewVisible.value = false
|
||
}
|
||
handleEdit(plan)
|
||
return false
|
||
}
|
||
|
||
async function handlePlanMutationSuccess(payload?: { planId?: string; openArrange?: boolean }) {
|
||
await fetchData()
|
||
await refreshViewIfOpen()
|
||
if (payload?.openArrange && payload.planId) {
|
||
let plan = planList.value.find(item => item.id === payload.planId)
|
||
if (!plan) {
|
||
try {
|
||
const res = await planApi.get(payload.planId)
|
||
plan = (res.data ?? res) as Plan
|
||
} catch {
|
||
ElMessage.error('获取运输计划失败,无法安排运输')
|
||
return
|
||
}
|
||
}
|
||
if (!(await ensurePlanReadyForArrange(plan))) return
|
||
arrangePlanId.value = payload.planId
|
||
arrangeDialogVisible.value = true
|
||
}
|
||
}
|
||
|
||
const selectedPlan = computed(() => (selectedRows.value.length === 1 ? selectedRows.value[0] : null))
|
||
const dateRange = ref<string[]>([])
|
||
const quickSearch = ref('')
|
||
const activePlanTypeTab = ref<PlPlanListTab>('all')
|
||
|
||
const searchForm = reactive({
|
||
planNo: '',
|
||
planType: '',
|
||
followStatus: '',
|
||
customerName: '',
|
||
productName: '',
|
||
licensePlate: ''
|
||
})
|
||
|
||
const pagination = reactive({
|
||
page: 1,
|
||
pageSize: 20,
|
||
total: 0
|
||
})
|
||
|
||
function getPlanTypeLabel(type?: string | null) {
|
||
if (!type) return '—'
|
||
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 tabFilters = plPlanListTabToFilters(activePlanTypeTab.value)
|
||
const res = await planApi.list({
|
||
q: keyword || undefined,
|
||
planNo: keyword ? undefined : searchForm.planNo || undefined,
|
||
planType: tabFilters.planType || undefined,
|
||
followStatus: tabFilters.followStatus || searchForm.followStatus || undefined,
|
||
customerName: keyword ? undefined : searchForm.customerName || undefined,
|
||
productName: keyword ? undefined : searchForm.productName || undefined,
|
||
licensePlate: keyword ? undefined : searchForm.licensePlate || undefined,
|
||
planDateFrom: dateRange.value?.[0] || undefined,
|
||
planDateTo: dateRange.value?.[1] || 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
|
||
}
|
||
}
|
||
|
||
function handleSearch() {
|
||
pagination.page = 1
|
||
fetchData()
|
||
}
|
||
|
||
function handlePlanTypeTabChange(tab: PlPlanListTab | string) {
|
||
const nextTab = tab as PlPlanListTab
|
||
const prevTab = activePlanTypeTab.value
|
||
activePlanTypeTab.value = nextTab
|
||
const filters = plPlanListTabToFilters(nextTab)
|
||
searchForm.planType = filters.planType
|
||
if (nextTab === 'pending_arrange') {
|
||
searchForm.followStatus = 'pending_arrange'
|
||
} else if (prevTab === 'pending_arrange') {
|
||
searchForm.followStatus = ''
|
||
}
|
||
pagination.page = 1
|
||
fetchData()
|
||
}
|
||
|
||
function handleReset() {
|
||
activePlanTypeTab.value = 'all'
|
||
searchForm.planNo = ''
|
||
searchForm.planType = ''
|
||
searchForm.followStatus = ''
|
||
searchForm.customerName = ''
|
||
searchForm.productName = ''
|
||
searchForm.licensePlate = ''
|
||
quickSearch.value = ''
|
||
resetDateShortcut()
|
||
pagination.page = 1
|
||
fetchData()
|
||
}
|
||
|
||
function handleQuickSearch() {
|
||
searchForm.planNo = ''
|
||
searchForm.customerName = ''
|
||
searchForm.productName = ''
|
||
searchForm.licensePlate = ''
|
||
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 handleAdd() {
|
||
editingPlanId.value = ''
|
||
copyFromPlanId.value = ''
|
||
formDialogVisible.value = true
|
||
}
|
||
|
||
function handleSelectionChange(selection: Plan[]) {
|
||
selectedRows.value = selection
|
||
}
|
||
|
||
function handleView(row: Plan) {
|
||
viewingPlan.value = row
|
||
viewPlanId.value = row.id
|
||
viewVisible.value = true
|
||
}
|
||
|
||
function handleEdit(row: Plan) {
|
||
editingPlanId.value = row.id
|
||
copyFromPlanId.value = ''
|
||
formDialogVisible.value = true
|
||
}
|
||
|
||
function handleCopy(row: Plan) {
|
||
editingPlanId.value = ''
|
||
copyFromPlanId.value = row.id
|
||
formDialogVisible.value = true
|
||
}
|
||
|
||
function handleToolbarCopy() {
|
||
if (!selectedPlan.value) return
|
||
handleCopy(selectedPlan.value)
|
||
}
|
||
|
||
function canEditPlan(row: Plan) {
|
||
return row.archiveStatus !== 'archived' && row.followStatus !== 'cancelled'
|
||
}
|
||
|
||
function canArrangeTransport(row: Plan) {
|
||
return (
|
||
(row.transportStatus === 'pending' || row.transportStatus === 'arranged') &&
|
||
row.archiveStatus !== 'archived' &&
|
||
row.followStatus !== 'cancelled'
|
||
)
|
||
}
|
||
|
||
function arrangeTransportLabel(row: Plan) {
|
||
return row.transportStatus === 'arranged' ? '调整运输' : '安排运输'
|
||
}
|
||
|
||
function canVoidPlan(row: Plan) {
|
||
return row.archiveStatus !== 'archived' && row.followStatus !== 'cancelled'
|
||
}
|
||
|
||
function canWaitNotice(row: Plan) {
|
||
return (
|
||
row.followStatus === 'pending_arrange' &&
|
||
row.archiveStatus !== 'archived'
|
||
)
|
||
}
|
||
|
||
function canCancelWaitNotice(row: Plan) {
|
||
return row.followStatus === 'wait_notice' && row.archiveStatus !== 'archived'
|
||
}
|
||
|
||
function syncViewingPlan(updated: Plan) {
|
||
if (viewingPlan.value?.id === updated.id) {
|
||
viewingPlan.value = { ...viewingPlan.value, ...updated }
|
||
}
|
||
}
|
||
|
||
async function handleWaitNotice(row: Plan) {
|
||
waitNoticeLoading.value = true
|
||
try {
|
||
const res = await planApi.waitNotice(row.id)
|
||
const data = (res as any)?.data ?? res
|
||
ElMessage.success('已标记为等客户通知')
|
||
if (data?.id) syncViewingPlan(data as Plan)
|
||
fetchData()
|
||
} catch (e) {
|
||
const message = e instanceof Error ? e.message : '操作失败'
|
||
ElMessage.error(message)
|
||
} finally {
|
||
waitNoticeLoading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleCancelWaitNotice(row: Plan) {
|
||
waitNoticeLoading.value = true
|
||
try {
|
||
const res = await planApi.cancelWaitNotice(row.id)
|
||
const data = (res as any)?.data ?? res
|
||
ElMessage.success('已取消等通知,恢复为待安排')
|
||
if (data?.id) syncViewingPlan(data as Plan)
|
||
fetchData()
|
||
} catch (e) {
|
||
const message = e instanceof Error ? e.message : '操作失败'
|
||
ElMessage.error(message)
|
||
} finally {
|
||
waitNoticeLoading.value = false
|
||
}
|
||
}
|
||
|
||
function handleToolbarWaitNotice() {
|
||
if (!selectedPlan.value) return
|
||
handleWaitNotice(selectedPlan.value)
|
||
}
|
||
|
||
async function handleVoid(row: Plan) {
|
||
const hint =
|
||
row.transportStatus === 'arranged'
|
||
? '作废后将取消运输安排、作废关联入库/出库单与地磅出入库单,现场链接将失效。是否继续?'
|
||
: '确认作废该运输计划吗?作废后将无法恢复。'
|
||
await ElMessageBox.confirm(hint, '作废运输计划', { type: 'warning' })
|
||
voidLoading.value = true
|
||
try {
|
||
await planApi.cancel(row.id)
|
||
ElMessage.success('作废成功')
|
||
fetchData()
|
||
} catch (e) {
|
||
const message = e instanceof Error ? e.message : '作废失败'
|
||
ElMessage.error(message)
|
||
} finally {
|
||
voidLoading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleArrange(row: Plan) {
|
||
if (!(await ensurePlanReadyForArrange(row))) return
|
||
arrangePlanId.value = row.id
|
||
arrangeDialogVisible.value = true
|
||
}
|
||
|
||
function handlePrintSlip(command: PlPrintSlipCommand, row: Plan) {
|
||
if (!canPrintSlipCommand(row.planType, command)) {
|
||
ElMessage.warning('当前单据类型不支持打印该单据')
|
||
return
|
||
}
|
||
if (command === 'inbound') {
|
||
inboundSlipPrintRef.value?.open(row.id)
|
||
return
|
||
}
|
||
if (command === 'delivery') {
|
||
deliverySlipPrintRef.value?.open(row.id)
|
||
return
|
||
}
|
||
subpackSlipPrintRef.value?.open(row.id)
|
||
}
|
||
|
||
function handleViewPrintSlipCommand(command: string) {
|
||
if (!viewingPlan.value) return
|
||
handlePrintSlip(command as PlPrintSlipCommand, viewingPlan.value)
|
||
}
|
||
|
||
function handleToolbarPrintSlip(command: string) {
|
||
if (command === 'blank-inbound') {
|
||
inboundSlipPrintRef.value?.openBlank()
|
||
return
|
||
}
|
||
if (command === 'blank-delivery') {
|
||
deliverySlipPrintRef.value?.openBlank()
|
||
return
|
||
}
|
||
if (command === 'blank-subpack') {
|
||
subpackSlipPrintRef.value?.openBlank()
|
||
return
|
||
}
|
||
|
||
const cmd = command as PlPrintSlipCommand
|
||
const plans = selectedRows.value
|
||
const check = canBatchPrintSlip(plans, cmd)
|
||
if (!check.ok) {
|
||
ElMessage.warning(check.reason || '无法打印')
|
||
return
|
||
}
|
||
const ids = plans.map((p) => p.id)
|
||
if (cmd === 'inbound') {
|
||
inboundSlipPrintRef.value?.open(ids)
|
||
return
|
||
}
|
||
if (cmd === 'delivery') {
|
||
deliverySlipPrintRef.value?.open(ids)
|
||
return
|
||
}
|
||
subpackSlipPrintRef.value?.open(ids)
|
||
}
|
||
|
||
async function handleToolbarArrange() {
|
||
if (selectedRows.value.length !== 1) return
|
||
await handleArrange(selectedRows.value[0])
|
||
}
|
||
|
||
function handleToolbarVoid() {
|
||
if (selectedRows.value.length !== 1) return
|
||
handleVoid(selectedRows.value[0])
|
||
}
|
||
|
||
function openForceFollowStatusDialog() {
|
||
if (!selectedPlan.value) return
|
||
forceFollowStatusValue.value = selectedPlan.value.followStatus || ''
|
||
forceFollowDialogVisible.value = true
|
||
}
|
||
|
||
function resetForceFollowDialog() {
|
||
forceFollowStatusValue.value = ''
|
||
}
|
||
|
||
async function submitForceFollowStatus() {
|
||
if (!selectedPlan.value) return
|
||
const next = (forceFollowStatusValue.value || '').trim()
|
||
if (!next) {
|
||
ElMessage.warning('请选择目标跟进状态')
|
||
return
|
||
}
|
||
if (next === selectedPlan.value.followStatus) {
|
||
ElMessage.warning('目标状态与当前状态相同')
|
||
return
|
||
}
|
||
forceFollowLoading.value = true
|
||
try {
|
||
await planApi.forceFollowStatus(selectedPlan.value.id, next)
|
||
ElMessage.success('跟进状态已更新')
|
||
forceFollowDialogVisible.value = false
|
||
fetchData()
|
||
} catch (e) {
|
||
const message = e instanceof Error ? e.message : '修改失败'
|
||
ElMessage.error(message)
|
||
} finally {
|
||
forceFollowLoading.value = false
|
||
}
|
||
}
|
||
|
||
async function loadForceFollowPermission() {
|
||
try {
|
||
const perm = await fetchGetMenuCodes()
|
||
canForceFollowStatus.value =
|
||
!!perm?.all ||
|
||
!!perm?.isSuperAdmin ||
|
||
(perm?.menuCodes || []).includes('pl:plan:force-follow-status')
|
||
} catch {
|
||
canForceFollowStatus.value = false
|
||
}
|
||
}
|
||
|
||
useDbInstantSearch(
|
||
() => ({
|
||
planNo: searchForm.planNo,
|
||
planType: searchForm.planType,
|
||
followStatus: searchForm.followStatus,
|
||
customerName: searchForm.customerName,
|
||
productName: searchForm.productName,
|
||
licensePlate: searchForm.licensePlate,
|
||
dateRange: dateRange.value
|
||
}),
|
||
() => {
|
||
quickSearch.value = ''
|
||
handleSearch()
|
||
}
|
||
)
|
||
|
||
onMounted(() => {
|
||
loadForceFollowPermission()
|
||
fetchData()
|
||
openArrangeFromQuery()
|
||
})
|
||
|
||
async function openArrangeFromQuery() {
|
||
const arrangeId = route.query.arrange as string | undefined
|
||
if (!arrangeId) return
|
||
router.replace({ path: '/pl/plan' })
|
||
try {
|
||
const res = await planApi.get(arrangeId)
|
||
const plan = (res.data ?? res) as Plan
|
||
await handleArrange(plan)
|
||
} catch {
|
||
ElMessage.error('获取运输计划失败,无法安排运输')
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.bx-list-table-wrap {
|
||
min-width: 0;
|
||
}
|
||
|
||
.pl-plan-list-toolbar-row {
|
||
flex-wrap: nowrap;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
|
||
> .list-toolbar-date-shortcuts,
|
||
> .quick-search-input,
|
||
> .quick-search-group,
|
||
> .el-button,
|
||
> .el-dropdown {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 工具栏全局把按钮刷成描边,需单独盖住才能显示危险实心红 */
|
||
> .el-button.pl-plan-paste-create-btn {
|
||
color: #fff !important;
|
||
background: var(--danger, #dc2626) !important;
|
||
border-color: var(--danger, #dc2626) !important;
|
||
|
||
&:hover,
|
||
&:focus {
|
||
color: #fff !important;
|
||
background: #b91c1c !important;
|
||
border-color: #b91c1c !important;
|
||
}
|
||
}
|
||
}
|
||
|
||
.pl-plan-list-pager-bar {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.list-pager-selected {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
min-height: 32px;
|
||
margin-right: auto;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--mint-text-muted, #64748b);
|
||
|
||
.sel-num {
|
||
margin: 0 2px;
|
||
color: var(--mint-primary-dark, #059669);
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
|
||
.pl-plan-contract-cell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
width: 100%;
|
||
text-align: left;
|
||
}
|
||
|
||
.pl-plan-contract-cell__row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 6px;
|
||
width: 100%;
|
||
min-width: 0;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
color: var(--text, #1e293b);
|
||
}
|
||
|
||
.pl-plan-contract-cell__side {
|
||
flex-shrink: 0;
|
||
font-weight: 600;
|
||
color: var(--mint-text-muted, #64748b);
|
||
}
|
||
|
||
.pl-plan-contract-cell__no {
|
||
min-width: 0;
|
||
white-space: normal;
|
||
word-break: break-word;
|
||
overflow-wrap: anywhere;
|
||
|
||
&.is-pending {
|
||
font-weight: 600;
|
||
color: #d97706;
|
||
}
|
||
}
|
||
</style>
|