9660 lines
341 KiB
Vue
9660 lines
341 KiB
Vue
<template>
|
||
<basic-container class="business-crud-page">
|
||
<avue-crud
|
||
:option="option"
|
||
:table-loading="loading"
|
||
:data="data"
|
||
v-model:page="page"
|
||
v-model="form"
|
||
ref="crud"
|
||
:permission="permissionList"
|
||
:before-open="beforeOpen"
|
||
@row-save="rowSave"
|
||
@row-update="rowUpdate"
|
||
@row-del="rowDel"
|
||
@search-change="searchChange"
|
||
@search-reset="searchReset"
|
||
@selection-change="selectionChange"
|
||
@current-change="currentChange"
|
||
@size-change="sizeChange"
|
||
@refresh-change="refreshChange"
|
||
@on-load="onLoad"
|
||
>
|
||
<template #menu-left>
|
||
<el-button
|
||
type="primary"
|
||
:icon="actionButtonLikeSearch ? undefined : 'el-icon-plus'"
|
||
:plain="!actionButtonLikeSearch"
|
||
v-if="canCreate"
|
||
@click="$refs.crud.rowAdd()"
|
||
>{{ config.createText || '新增' }}
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-upload"
|
||
plain
|
||
v-if="
|
||
(config.importUrl || config.enableTransportPlanImport) &&
|
||
hasPermission(`${config.permission}_import`) &&
|
||
!readonlyScope
|
||
"
|
||
@click="handleImport"
|
||
>{{ config.importText || '批量导入' }}
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-download"
|
||
plain
|
||
v-if="config.templateUrl && hasPermission(`${config.permission}_template`)"
|
||
@click="handleTemplate"
|
||
>下载模板
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-download"
|
||
plain
|
||
v-if="config.exportUrl && hasPermission(`${config.permission}_export`)"
|
||
@click="handleExport"
|
||
>批量导出
|
||
</el-button>
|
||
<el-button
|
||
type="success"
|
||
icon="el-icon-check"
|
||
plain
|
||
v-if="canBatchComplete"
|
||
@click="handleBatchComplete"
|
||
>批量完成
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:plain="!actionButtonLikeSearch"
|
||
v-if="canRoadLoading"
|
||
@click="handleRoadLoading"
|
||
>{{ config.roadLoadingText || '公路配载' }}
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
icon="el-icon-delete"
|
||
plain
|
||
v-if="
|
||
config.batchDelete !== false &&
|
||
hasPermission(`${config.permission}_delete`) &&
|
||
!readonlyScope
|
||
"
|
||
@click="handleDelete"
|
||
>批量删除
|
||
</el-button>
|
||
</template>
|
||
|
||
<template #status="{ row }">
|
||
<el-tag :type="statusTagType(row.status)">
|
||
{{ displayStatus(row, 'status') }}
|
||
</el-tag>
|
||
</template>
|
||
|
||
<template #businessStatus="{ row }">
|
||
<el-tag :type="statusTagType(row.businessStatus)">
|
||
{{ displayStatus(row, 'businessStatus') }}
|
||
</el-tag>
|
||
</template>
|
||
|
||
<template #approvalStatus="{ row }">
|
||
<el-tag :type="statusTagType(row.approvalStatus)">
|
||
{{ displayStatus(row, 'approvalStatus') }}
|
||
</el-tag>
|
||
</template>
|
||
|
||
<template #basicInfoTitle-form>
|
||
<div class="dialog-section-title">基本信息</div>
|
||
</template>
|
||
|
||
<template #templateInfoTitle-form>
|
||
<div class="dialog-section-title">模板信息</div>
|
||
</template>
|
||
|
||
<template #contractCategory-label>合同类型</template>
|
||
|
||
<template #projectName-form>
|
||
<el-select
|
||
v-model="selectedProjectId"
|
||
class="business-crud-page__field"
|
||
placeholder="请选择项目名称"
|
||
filterable
|
||
clearable
|
||
:loading="projectLoading"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadProjectOptions()"
|
||
@change="handleProjectChange"
|
||
>
|
||
<el-option
|
||
v-for="item in projectOptions"
|
||
:key="item.id"
|
||
:label="item.projectName"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
|
||
<template #contractName-form>
|
||
<el-select
|
||
v-if="contractSelectEnabled"
|
||
v-model="form.contractId"
|
||
class="business-crud-page__field"
|
||
placeholder="请选择客户合同"
|
||
filterable
|
||
clearable
|
||
:loading="contractLoading"
|
||
:disabled="dialogReadonly || !form.projectId"
|
||
@visible-change="visible => visible && loadContractOptionsForProject()"
|
||
@change="handleContractChange"
|
||
>
|
||
<el-option
|
||
v-for="item in contractOptions"
|
||
:key="item.id"
|
||
:label="item.contractName"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
<el-input
|
||
v-else
|
||
v-model="form.contractName"
|
||
class="business-crud-page__field"
|
||
placeholder="请输入合同名称"
|
||
clearable
|
||
maxlength="100"
|
||
show-word-limit
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
|
||
<template #planName-form>
|
||
<el-select
|
||
v-if="shippingPlanSelectEnabled"
|
||
v-model="form.planName"
|
||
class="business-crud-page__field"
|
||
placeholder="请选择发货计划"
|
||
filterable
|
||
clearable
|
||
:loading="shippingPlanLoading"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadShippingPlanOptions()"
|
||
@change="handleShippingPlanChange"
|
||
>
|
||
<el-option
|
||
v-for="item in shippingPlanOptions"
|
||
:key="item.id || item.planName"
|
||
:label="item.planName"
|
||
:value="item.planName"
|
||
/>
|
||
</el-select>
|
||
<el-input
|
||
v-else
|
||
v-model="form.planName"
|
||
class="business-crud-page__field"
|
||
placeholder="请输入计划名称"
|
||
clearable
|
||
maxlength="50"
|
||
show-word-limit
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
|
||
<template #shippingInfoTitle-form>
|
||
<div
|
||
class="dialog-section-title dialog-section-title--action business-crud-page__shipping-title"
|
||
>
|
||
<span>收发货信息</span>
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportRouteDialog"
|
||
>
|
||
选择线路
|
||
</el-link>
|
||
</div>
|
||
</template>
|
||
|
||
<template #departureAddress-form>
|
||
<div class="business-crud-page__route-address-row">
|
||
<el-input
|
||
v-model="form.departureName"
|
||
readonly
|
||
:placeholder="transportStationNamePlaceholder"
|
||
:suffix-icon="transportStationMode ? Search : undefined"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportPrimaryAddressPicker('departure')"
|
||
/>
|
||
<el-input
|
||
v-model="form.departureAddress"
|
||
readonly
|
||
:placeholder="transportAddressDetailPlaceholder"
|
||
:suffix-icon="transportStationMode ? undefined : Location"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportSecondaryAddressPicker('departure')"
|
||
/>
|
||
<el-tooltip content="选择常用地址" placement="top">
|
||
<el-button
|
||
type="primary"
|
||
:icon="List"
|
||
link
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportAddressDialog('departure')"
|
||
/>
|
||
</el-tooltip>
|
||
<span class="business-crud-page__route-label">联系人</span>
|
||
<el-input
|
||
v-model="form.departureContact"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
<span class="business-crud-page__route-label">联系方式</span>
|
||
<el-input v-model="form.departurePhone" placeholder="请输入" :disabled="dialogReadonly" />
|
||
</div>
|
||
</template>
|
||
|
||
<template #arrivalAddress-form>
|
||
<div class="business-crud-page__route-address-row">
|
||
<el-input
|
||
v-model="form.arrivalName"
|
||
readonly
|
||
:placeholder="transportStationNamePlaceholder"
|
||
:suffix-icon="transportStationMode ? Search : undefined"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportPrimaryAddressPicker('arrival')"
|
||
/>
|
||
<el-input
|
||
v-model="form.arrivalAddress"
|
||
readonly
|
||
:placeholder="transportAddressDetailPlaceholder"
|
||
:suffix-icon="transportStationMode ? undefined : Location"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportSecondaryAddressPicker('arrival')"
|
||
/>
|
||
<el-tooltip content="选择常用地址" placement="top">
|
||
<el-button
|
||
type="primary"
|
||
:icon="List"
|
||
link
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportAddressDialog('arrival')"
|
||
/>
|
||
</el-tooltip>
|
||
<span class="business-crud-page__route-label">联系人</span>
|
||
<el-input v-model="form.arrivalContact" placeholder="请输入" :disabled="dialogReadonly" />
|
||
<span class="business-crud-page__route-label">联系方式</span>
|
||
<el-input v-model="form.arrivalPhone" placeholder="请输入" :disabled="dialogReadonly" />
|
||
</div>
|
||
</template>
|
||
|
||
<template #taskInfoTitle-form>
|
||
<div class="dialog-section-title">任务信息</div>
|
||
</template>
|
||
|
||
<template #taskInfoForm-form>
|
||
<div class="business-crud-page__task">
|
||
<div class="business-crud-page__task-toolbar">
|
||
<el-segmented
|
||
v-model="form.taskEntryMode"
|
||
:options="taskEntryModeOptions"
|
||
:disabled="dialogReadonly"
|
||
@change="handleTaskEntryModeChange"
|
||
/>
|
||
</div>
|
||
<div v-if="isTaskFullMode" class="business-crud-page__task-full">
|
||
<div class="business-crud-page__subsection">
|
||
<div
|
||
class="dialog-section-title dialog-section-title--action business-crud-page__goods-title"
|
||
>
|
||
<span>货物信息</span>
|
||
<div v-if="!dialogReadonly" class="business-crud-page__section-actions">
|
||
<el-link type="primary" @click="cargoImportBox = true">导入货物</el-link>
|
||
<el-link type="primary" @click="openCommonCargoDialog">常用货物</el-link>
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__cargo-wrap">
|
||
<el-table
|
||
:data="transportCargoRows"
|
||
border
|
||
class="business-crud-page__cargo-table business-crud-page__cargo-table--task"
|
||
empty-text="暂无货物信息"
|
||
>
|
||
<el-table-column
|
||
type="index"
|
||
label="序号"
|
||
width="70"
|
||
align="center"
|
||
fixed="left"
|
||
/>
|
||
<el-table-column min-width="240" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">货物名称</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-model="row.cargoName"
|
||
placeholder="请输入"
|
||
filterable
|
||
allow-create
|
||
default-first-option
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
:loading="isTaskCargoOptionsLoading(row.cargoTypePath)"
|
||
@visible-change="
|
||
visible => visible && loadTaskCargoOptionsByPath(row.cargoTypePath)
|
||
"
|
||
@change="value => handleTaskCargoRowNameChange(row, value)"
|
||
>
|
||
<el-option
|
||
v-for="item in getTaskCargoRowOptions(row)"
|
||
:key="item.id || item.cargoCode || item.cargoName"
|
||
:label="formatBillingCargoTypeLabel(item)"
|
||
:value="formatBillingCargoTypeLabel(item)"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column min-width="220" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">货物类型</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-cascader
|
||
v-model="row.cargoTypePath"
|
||
:options="transportCargoTypeOptions"
|
||
:props="transportCargoTypeCascaderProps"
|
||
placeholder="从货物类型获取"
|
||
clearable
|
||
filterable
|
||
:disabled="dialogReadonly"
|
||
:filter-method="filterBillingCargoType"
|
||
@visible-change="visible => visible && ensureBillingCargoTypeOptions()"
|
||
@change="value => handleTransportCargoTypeChange(row, value)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="包装" min-width="130" align="center">
|
||
<template #default="{ row }">
|
||
<el-select v-model="row.packageType" clearable :disabled="dialogReadonly">
|
||
<el-option
|
||
v-for="item in packageOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column min-width="130" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">数量</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.quantity"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTransportCargoQuantityInput(row, value)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column min-width="140" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">数量单位</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-model="row.quantityUnit"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskQuantityUnitOptions()"
|
||
>
|
||
<el-option
|
||
v-for="item in taskQuantityUnitOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="品牌" min-width="130" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.brand"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="规格" min-width="130" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.specification"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="型号" min-width="130" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.model"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="物料编码" min-width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.materialCode"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备编码" min-width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.deviceCode"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" min-width="180" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.remark"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||
<template #default="{ $index }">
|
||
<div class="business-crud-page__cargo-actions">
|
||
<el-link
|
||
v-if="!dialogReadonly && $index === transportCargoRows.length - 1"
|
||
type="primary"
|
||
@click="addTransportCargoRow($index)"
|
||
>
|
||
新增
|
||
</el-link>
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
@click="removeTransportCargoRow($index)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
<el-form
|
||
:model="form"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="business-crud-page__task-form business-crud-page__task-form--full"
|
||
>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item
|
||
label="承运类型"
|
||
required
|
||
class="business-crud-page__task-carrier-type business-crud-page__task-span-4"
|
||
>
|
||
<el-radio-group
|
||
v-model="form.carrierType"
|
||
:disabled="dialogReadonly"
|
||
@change="handleTaskCarrierTypeChange"
|
||
>
|
||
<el-radio-button
|
||
v-for="item in taskCarrierTypes"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</div>
|
||
<div
|
||
v-if="isCarrierMode"
|
||
class="business-crud-page__subsection business-crud-page__carrier-section"
|
||
>
|
||
<div class="business-crud-page__subsection-head">
|
||
<span>承运信息</span>
|
||
</div>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item label="承运商" required class="business-crud-page__task-span-1">
|
||
<el-select
|
||
v-model="form.carrierName"
|
||
placeholder="请输入"
|
||
filterable
|
||
clearable
|
||
:suffix-icon="Search"
|
||
:loading="taskCarrierLoading"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskCarrierOptions()"
|
||
@change="handleTaskCarrierChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskCarrierOptions"
|
||
:key="
|
||
item.id ||
|
||
item.customerName ||
|
||
item.carrierName ||
|
||
item.fullName ||
|
||
item.name ||
|
||
item.customerCode
|
||
"
|
||
:label="item.customerName || item.carrierName || item.fullName || item.name"
|
||
:value="item.customerName || item.carrierName || item.fullName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="司机" class="business-crud-page__task-span-1">
|
||
<el-select
|
||
v-model="form.driverName"
|
||
placeholder="请输入"
|
||
filterable
|
||
clearable
|
||
allow-create
|
||
default-first-option
|
||
:suffix-icon="Search"
|
||
:loading="taskDriverLoading"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskDriverOptions()"
|
||
@change="handleTaskDriverChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskDriverOptions"
|
||
:key="item.id || item.driverName || item.mobile"
|
||
:label="item.driverName || item.name"
|
||
:value="item.driverName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="手机号" class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.driverPhone"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="车牌号" required class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.vehicleNo"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item label="里程(km)" class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.mileage"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTaskNumberInput('mileage', value)"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计发货日期" class="business-crud-page__task-span-1">
|
||
<el-date-picker
|
||
v-model="form.estimatedStartTime"
|
||
type="date"
|
||
placeholder="默认填充当天"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计完成日期" class="business-crud-page__task-span-1">
|
||
<el-date-picker
|
||
v-model="form.estimatedEndTime"
|
||
type="date"
|
||
placeholder="请输入"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item
|
||
label="备注"
|
||
class="business-crud-page__task-note business-crud-page__task-span-4"
|
||
>
|
||
<el-input
|
||
v-model="form.taskRemark"
|
||
placeholder="请输入,200字以内"
|
||
clearable
|
||
maxlength="200"
|
||
show-word-limit
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
<template v-else>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item label="司机" class="business-crud-page__task-span-1">
|
||
<el-select
|
||
v-model="form.driverName"
|
||
placeholder="请输入"
|
||
filterable
|
||
clearable
|
||
allow-create
|
||
default-first-option
|
||
:suffix-icon="Search"
|
||
:loading="taskDriverLoading"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskDriverOptions()"
|
||
@change="handleTaskDriverChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskDriverOptions"
|
||
:key="item.id || item.driverName || item.mobile"
|
||
:label="item.driverName || item.name"
|
||
:value="item.driverName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="司机手机号" required class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.driverPhone"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="车牌号" required class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.vehicleNo"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="挂车车牌号" class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.trailerVehicleNo"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item label="押运人" class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.escortName"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="押运人手机号" class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.escortPhone"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="里程(km)" class="business-crud-page__task-span-1">
|
||
<el-input
|
||
v-model="form.mileage"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTaskNumberInput('mileage', value)"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item label="预计发货日期" class="business-crud-page__task-span-1">
|
||
<el-date-picker
|
||
v-model="form.estimatedStartTime"
|
||
type="date"
|
||
placeholder="默认填充当天"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计完成日期" class="business-crud-page__task-span-1">
|
||
<el-date-picker
|
||
v-model="form.estimatedEndTime"
|
||
type="date"
|
||
placeholder="请输入"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="business-crud-page__task-row">
|
||
<el-form-item
|
||
label="备注"
|
||
class="business-crud-page__task-note business-crud-page__task-span-4"
|
||
>
|
||
<el-input
|
||
v-model="form.taskRemark"
|
||
placeholder="请输入,200字以内"
|
||
clearable
|
||
maxlength="200"
|
||
show-word-limit
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
</template>
|
||
</el-form>
|
||
<div class="business-crud-page__subsection">
|
||
<div class="business-crud-page__subsection-head">
|
||
<span>运费信息</span>
|
||
</div>
|
||
<el-form
|
||
:model="form"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="business-crud-page__freight-form"
|
||
>
|
||
<el-form-item label="单价">
|
||
<el-input
|
||
v-model="form.unitPrice"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTaskNumberInput('unitPrice', value)"
|
||
>
|
||
<template #append>
|
||
<el-select
|
||
v-model="form.priceUnit"
|
||
class="business-crud-page__append-select"
|
||
placeholder="元/吨"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||
>
|
||
<el-option
|
||
v-for="item in taskFeeUnitOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="数量合计">
|
||
<el-input
|
||
:model-value="taskCargoQuantityTotal"
|
||
disabled
|
||
placeholder="自动汇总货物数量"
|
||
>
|
||
<template #append>
|
||
<el-select
|
||
v-model="form.quantityUnit"
|
||
class="business-crud-page__append-select"
|
||
disabled
|
||
placeholder="吨"
|
||
>
|
||
<el-option
|
||
v-for="item in taskQuantityUnitOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="运费">
|
||
<el-input :model-value="taskFreightAmount" disabled placeholder="自动计算">
|
||
<template #append>元</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="其他运费合计">
|
||
<el-input
|
||
v-model="form.otherFeeTotal"
|
||
placeholder="请输入"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTaskNumberInput('otherFeeTotal', value)"
|
||
>
|
||
<template #append>元</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
<el-form
|
||
v-else
|
||
:model="form"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="business-crud-page__task-form"
|
||
>
|
||
<el-form-item
|
||
label="承运类型"
|
||
required
|
||
class="business-crud-page__task-carrier-type business-crud-page__task-span-4"
|
||
>
|
||
<el-radio-group
|
||
v-model="form.carrierType"
|
||
:disabled="dialogReadonly"
|
||
@change="handleTaskCarrierTypeChange"
|
||
>
|
||
<el-radio-button
|
||
v-for="item in taskCarrierTypes"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item v-if="isCarrierMode" label="承运商" required>
|
||
<el-select
|
||
v-model="form.carrierName"
|
||
placeholder="请选择承运商"
|
||
filterable
|
||
clearable
|
||
:loading="taskCarrierLoading"
|
||
:disabled="dialogReadonly || form.carrierType !== '承运商'"
|
||
@visible-change="visible => visible && loadTaskCarrierOptions()"
|
||
@change="handleTaskCarrierChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskCarrierOptions"
|
||
:key="
|
||
item.id ||
|
||
item.customerName ||
|
||
item.carrierName ||
|
||
item.fullName ||
|
||
item.name ||
|
||
item.customerCode
|
||
"
|
||
:label="item.customerName || item.carrierName || item.fullName || item.name"
|
||
:value="item.customerName || item.carrierName || item.fullName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="司机">
|
||
<el-select
|
||
v-model="form.driverName"
|
||
placeholder="请选择或输入司机"
|
||
filterable
|
||
clearable
|
||
allow-create
|
||
default-first-option
|
||
:loading="taskDriverLoading"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskDriverOptions()"
|
||
@change="handleTaskDriverChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskDriverOptions"
|
||
:key="item.id || item.driverName || item.mobile"
|
||
:label="item.driverName || item.name"
|
||
:value="item.driverName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="手机号" :required="form.carrierType === '自运'">
|
||
<el-input
|
||
v-model="form.driverPhone"
|
||
placeholder="请输入手机号"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="车牌号" required>
|
||
<el-input
|
||
v-model="form.vehicleNo"
|
||
placeholder="请输入车牌号"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="货物类型" required>
|
||
<el-cascader
|
||
v-model="form.cargoTypePath"
|
||
:options="transportCargoTypeOptions"
|
||
:props="transportCargoTypeCascaderProps"
|
||
placeholder="请选择到二级"
|
||
clearable
|
||
filterable
|
||
:disabled="dialogReadonly"
|
||
:filter-method="filterBillingCargoType"
|
||
@visible-change="visible => visible && ensureBillingCargoTypeOptions()"
|
||
@change="handleTaskCargoTypeChange"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="货物名称">
|
||
<el-select
|
||
v-model="form.cargoName"
|
||
placeholder="请选择货物"
|
||
filterable
|
||
clearable
|
||
:disabled="dialogReadonly || !form.cargoType"
|
||
:loading="taskCargoLoading"
|
||
@visible-change="visible => visible && loadTaskCargoOptionsForForm()"
|
||
@change="handleTaskCargoChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskCargoOptions"
|
||
:key="item.id || item.cargoCode || item.cargoName"
|
||
:label="formatBillingCargoTypeLabel(item)"
|
||
:value="formatBillingCargoTypeLabel(item)"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="规格">
|
||
<el-input
|
||
v-model="form.specification"
|
||
placeholder="选择货物自动回填"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="型号">
|
||
<el-input
|
||
v-model="form.model"
|
||
placeholder="选择货物自动回填"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="数量">
|
||
<el-input
|
||
v-model="form.quantity"
|
||
placeholder="请输入数量"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTaskNumberInput('quantity', value)"
|
||
>
|
||
<template #suffix>
|
||
<el-select
|
||
v-model="form.quantityUnit"
|
||
class="business-crud-page__suffix-select"
|
||
placeholder="单位"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskQuantityUnitOptions()"
|
||
>
|
||
<el-option
|
||
v-for="item in taskQuantityUnitOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="里程(公里)">
|
||
<el-input
|
||
v-model="form.mileage"
|
||
placeholder="请输入里程"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTaskNumberInput('mileage', value)"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计发货日期">
|
||
<el-date-picker
|
||
v-model="form.estimatedStartTime"
|
||
type="date"
|
||
placeholder="请选择预计发货日期"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计完成日期">
|
||
<el-date-picker
|
||
v-model="form.estimatedEndTime"
|
||
type="date"
|
||
placeholder="请选择预计完成日期"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="单价">
|
||
<el-input
|
||
v-model="form.unitPrice"
|
||
placeholder="请输入单价"
|
||
clearable
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTaskNumberInput('unitPrice', value)"
|
||
>
|
||
<template #suffix>
|
||
<el-select
|
||
v-model="form.priceUnit"
|
||
class="business-crud-page__suffix-select business-crud-page__suffix-select--wide"
|
||
placeholder="单位"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||
>
|
||
<el-option
|
||
v-for="item in taskFeeUnitOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="备注" class="business-crud-page__task-remark">
|
||
<el-input
|
||
v-model="form.taskRemark"
|
||
type="textarea"
|
||
:rows="2"
|
||
maxlength="200"
|
||
show-word-limit
|
||
placeholder="请输入备注,200字以内"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</template>
|
||
|
||
<template #goodsInfoTitle-form>
|
||
<div
|
||
v-if="config.enableTransportPlanForm"
|
||
class="dialog-section-title dialog-section-title--action business-crud-page__goods-title"
|
||
>
|
||
<span>货物信息</span>
|
||
<div v-if="!dialogReadonly" class="business-crud-page__section-actions">
|
||
<el-link type="primary" @click="cargoImportBox = true">导入货物</el-link>
|
||
<el-link type="primary" @click="openCommonCargoDialog">常用货物</el-link>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template #goodsJson-form>
|
||
<div v-if="config.enableTransportPlanForm" class="business-crud-page__cargo-wrap">
|
||
<el-table
|
||
:data="transportCargoRows"
|
||
border
|
||
class="business-crud-page__cargo-table"
|
||
empty-text="暂无货物信息"
|
||
>
|
||
<el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
|
||
<el-table-column label="货物名称" min-width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.cargoName" placeholder="请输入" :disabled="dialogReadonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column min-width="220" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">货物类型</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-cascader
|
||
v-model="row.cargoTypePath"
|
||
:options="transportCargoTypeOptions"
|
||
:props="transportCargoTypeCascaderProps"
|
||
:placeholder="row.cargoType || '请选择'"
|
||
clearable
|
||
filterable
|
||
:disabled="dialogReadonly"
|
||
:filter-method="filterBillingCargoType"
|
||
@visible-change="visible => visible && ensureBillingCargoTypeOptions()"
|
||
@change="value => handleTransportCargoTypeChange(row, value)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="数量" min-width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.quantity"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleTransportCargoQuantityInput(row, value)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="数量单位" min-width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-select v-model="row.quantityUnit" clearable :disabled="dialogReadonly">
|
||
<el-option
|
||
v-for="item in quantityUnitOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="包装" min-width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-select v-model="row.packageType" clearable :disabled="dialogReadonly">
|
||
<el-option
|
||
v-for="item in packageOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="品牌" min-width="130" align="center">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.brand" placeholder="请输入" :disabled="dialogReadonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="规格" min-width="130" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.specification"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="型号" min-width="130" align="center">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.model" placeholder="请输入" :disabled="dialogReadonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="物料编码" min-width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.materialCode"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备编码" min-width="150" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.deviceCode"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" min-width="180" align="center">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.remark" placeholder="请输入" :disabled="dialogReadonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||
<template #default="{ $index }">
|
||
<div class="business-crud-page__cargo-actions">
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
@click="addTransportCargoRow($index)"
|
||
>
|
||
新增
|
||
</el-link>
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
@click="removeTransportCargoRow($index)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="business-crud-page__cargo-total">
|
||
运输数量合计:{{ transportCargoQuantityTotal }}
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template #organizationName-form>
|
||
<el-cascader
|
||
v-model="selectedOrganizationId"
|
||
class="business-crud-page__field"
|
||
placeholder="请选择所属组织"
|
||
:options="organizationOptions"
|
||
:props="organizationCascaderProps"
|
||
clearable
|
||
filterable
|
||
:show-all-levels="false"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadOrganizationOptions()"
|
||
@change="handleOrganizationChange"
|
||
/>
|
||
</template>
|
||
|
||
<template #applyLimit-form>
|
||
<el-input
|
||
v-model="form.applyLimit"
|
||
class="business-crud-page__field"
|
||
placeholder="请输入申请临时额度"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleAmountInput('applyLimit', value)"
|
||
/>
|
||
</template>
|
||
|
||
<template #contractPeriod-form>
|
||
<div class="business-crud-page__date-range">
|
||
<el-date-picker
|
||
v-model="form.startDate"
|
||
class="business-crud-page__date"
|
||
type="date"
|
||
placeholder="YYYY-MM-DD"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
@change="handleContractDateChange"
|
||
/>
|
||
<span class="business-crud-page__date-separator">至</span>
|
||
<el-date-picker
|
||
v-model="form.endDate"
|
||
class="business-crud-page__date"
|
||
type="date"
|
||
placeholder="YYYY-MM-DD"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
@change="handleContractDateChange"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<template #copyCount-form>
|
||
<div class="business-crud-page__inline-number">
|
||
<el-input
|
||
v-model="form.copyCount"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleIntegerInput('copyCount', value)"
|
||
/>
|
||
<span>份</span>
|
||
</div>
|
||
</template>
|
||
|
||
<template #paymentDays-form>
|
||
<div class="business-crud-page__inline-number">
|
||
<el-input
|
||
v-model="form.paymentDays"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleIntegerInput('paymentDays', value)"
|
||
/>
|
||
<span>天</span>
|
||
</div>
|
||
</template>
|
||
|
||
<template #contractFileTitle-form>
|
||
<div class="dialog-section-title">合同文件</div>
|
||
</template>
|
||
|
||
<template #contractFileJson-form>
|
||
<div class="business-crud-page__attachment">
|
||
<div class="business-crud-page__attachment-head">
|
||
<vehicle-attachment-upload
|
||
v-model="contractFileRows"
|
||
:readonly="dialogReadonly"
|
||
:file-types="attachmentFileTypes"
|
||
:max-size="500"
|
||
:show-tip="false"
|
||
:show-file-list="false"
|
||
button-text="上传附件"
|
||
@change="handleContractFileChange"
|
||
/>
|
||
</div>
|
||
<el-table :data="contractFileRows" border class="business-crud-page__attachment-table">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column label="文件名" min-width="240" align="center" show-overflow-tooltip>
|
||
<template #default="{ row }">{{ row.originalName || row.name }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="文件大小" width="120" align="center">
|
||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
|
||
<el-table-column label="操作" width="120" align="center">
|
||
<template #default="{ row, $index }">
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
@click="removeContractFile($index)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
<el-link v-else type="primary" @click="downloadAttachment(row)">
|
||
下载
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</template>
|
||
|
||
<template #billingInfoTitle-form>
|
||
<div class="dialog-section-title">计费信息</div>
|
||
</template>
|
||
|
||
<template #billingPlanJson-form>
|
||
<div class="business-crud-page__billing">
|
||
<div class="business-crud-page__billing-head">
|
||
<div class="business-crud-page__billing-switch">
|
||
<span>自动生成费用明细</span>
|
||
<el-radio-group v-model="form.billingEnabled" :disabled="dialogReadonly">
|
||
<el-radio :label="1">开启</el-radio>
|
||
<el-radio :label="0">关闭</el-radio>
|
||
</el-radio-group>
|
||
</div>
|
||
<el-button v-if="!dialogReadonly" type="primary" @click="openBillingPlan()">
|
||
添加
|
||
</el-button>
|
||
</div>
|
||
<el-table :data="billingPlanRows" border class="business-crud-page__billing-table">
|
||
<el-table-column type="index" label="序号" width="80" align="center" />
|
||
<el-table-column prop="planName" label="方案名称" min-width="220" align="center" />
|
||
<el-table-column label="默认方案" width="140" align="center">
|
||
<template #default="{ row }">{{ row.defaultPlan ? '是' : '' }}</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="remark"
|
||
label="备注"
|
||
min-width="260"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column label="操作" width="180" align="center">
|
||
<template #default="{ row, $index }">
|
||
<el-link type="primary" @click="openBillingPlan(row, $index)">
|
||
编辑
|
||
</el-link>
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
@click="removeBillingPlan($index)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</template>
|
||
|
||
<template #settlementRuleTitle-form>
|
||
<div class="dialog-section-title">结算生成规则</div>
|
||
</template>
|
||
|
||
<template #settlementRuleJson-form>
|
||
<div class="business-crud-page__settlement-rule">
|
||
<div class="business-crud-page__settlement-switch">
|
||
<span>自动生成结算单</span>
|
||
<el-radio-group v-model="settlementRuleForm.autoGenerate" :disabled="dialogReadonly">
|
||
<el-radio :label="1">开启</el-radio>
|
||
<el-radio :label="0">关闭</el-radio>
|
||
</el-radio-group>
|
||
</div>
|
||
<el-form
|
||
:model="settlementRuleForm"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="business-crud-page__module-form business-crud-page__module-form--three"
|
||
>
|
||
<el-form-item label="账单起始日期" required>
|
||
<el-date-picker
|
||
v-model="settlementRuleForm.billStartDate"
|
||
type="date"
|
||
placeholder="请选择账单起始日期"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="结算类型" required>
|
||
<el-select
|
||
v-model="settlementRuleForm.settlementType"
|
||
placeholder="请选择结算类型"
|
||
:disabled="dialogReadonly"
|
||
@change="handleSettlementTypeChange"
|
||
>
|
||
<el-option
|
||
v-for="item in settlementTypeOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item v-if="showSettlementBillCycleType" label="账单周期类型" required>
|
||
<el-select
|
||
v-model="settlementRuleForm.billCycleType"
|
||
placeholder="请选择账单周期类型"
|
||
:disabled="dialogReadonly"
|
||
@change="handleSettlementBillCycleTypeChange"
|
||
>
|
||
<el-option
|
||
v-for="item in billCycleTypeOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item v-if="showSettlementBillCutoffDay" label="账单截单日">
|
||
<el-select
|
||
v-model="settlementRuleForm.billCutoffDay"
|
||
placeholder="请选择账单截单日"
|
||
:disabled="dialogReadonly"
|
||
>
|
||
<el-option
|
||
v-for="item in billCutoffDayOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item v-if="showSettlementCycleDays" label="周期天数" required>
|
||
<el-select
|
||
v-model="settlementRuleForm.cycleDays"
|
||
placeholder="请选择周期天数"
|
||
:disabled="dialogReadonly"
|
||
>
|
||
<el-option
|
||
v-for="item in cycleDayOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</template>
|
||
|
||
<template #reconciliationTitle-form>
|
||
<div class="dialog-section-title">对账配置</div>
|
||
</template>
|
||
|
||
<template #reconciliationJson-form>
|
||
<el-form
|
||
:model="reconciliationForm"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="business-crud-page__module-form business-crud-page__module-form--two"
|
||
>
|
||
<el-form-item label="是否跳过对账" required>
|
||
<el-select
|
||
v-model="reconciliationForm.skipReconciliation"
|
||
placeholder="请选择是否跳过对账"
|
||
:disabled="dialogReadonly"
|
||
>
|
||
<el-option label="是" :value="1" />
|
||
<el-option label="否" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="对账模式" required>
|
||
<el-select
|
||
v-model="reconciliationForm.reconciliationMode"
|
||
placeholder="请选择对账模式"
|
||
:disabled="dialogReadonly"
|
||
>
|
||
<el-option
|
||
v-for="item in reconciliationModeOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
</template>
|
||
|
||
<template #attachmentTitle-form>
|
||
<div class="dialog-section-title">{{ config.attachmentTitle || '其它附件' }}</div>
|
||
</template>
|
||
|
||
<template #attachmentsJson-form>
|
||
<div class="business-crud-page__attachment">
|
||
<div class="business-crud-page__attachment-head">
|
||
<vehicle-attachment-upload
|
||
v-model="attachmentRows"
|
||
:readonly="dialogReadonly"
|
||
:file-types="attachmentFileTypes"
|
||
:max-size="500"
|
||
:show-tip="false"
|
||
:show-file-list="false"
|
||
button-text="上传附件"
|
||
@change="handleAttachmentChange"
|
||
/>
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!attachmentRows.length"
|
||
@click="handleBatchDownload"
|
||
>
|
||
批量下载
|
||
</el-button>
|
||
</div>
|
||
<el-table :data="attachmentRows" border class="business-crud-page__attachment-table">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column label="文件名" min-width="200" align="center" show-overflow-tooltip>
|
||
<template #default="{ row }">{{ row.originalName || row.name }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="附件描述" min-width="220" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.description"
|
||
placeholder="请输入附件描述"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="文件大小" width="110" align="center">
|
||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="160" align="center" />
|
||
<el-table-column label="操作" width="100" align="center">
|
||
<template #default="{ row, $index }">
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
@click="removeAttachment($index)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
<el-link v-else type="primary" @click="downloadAttachment(row)">
|
||
下载
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</template>
|
||
|
||
<template #changeRecordTitle-form>
|
||
<div class="dialog-section-title">变更记录</div>
|
||
</template>
|
||
|
||
<template #changeRecordJson-form>
|
||
<el-table :data="changeRecordRows" border class="business-crud-page__change-table">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column prop="changeDate" label="变更日期" min-width="150" align="center" />
|
||
<el-table-column prop="handlerUserName" label="经办人" min-width="140" align="center" />
|
||
<el-table-column prop="changeType" label="变更类型" min-width="160" align="center" />
|
||
<el-table-column
|
||
prop="changeReason"
|
||
label="变更原因"
|
||
min-width="240"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="statusName" label="状态" min-width="140" align="center" />
|
||
<el-table-column label="操作" width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click="handleChangeRecordFlow(row)"> 流程 </el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</template>
|
||
|
||
<template #menu-form-before>
|
||
<el-button
|
||
v-if="config.enableDraftSave && !dialogReadonly"
|
||
type="primary"
|
||
plain
|
||
:loading="draftSaveLoading"
|
||
@click="handleDraftSave"
|
||
>
|
||
保存
|
||
</el-button>
|
||
</template>
|
||
|
||
<template #menu-form>
|
||
<div v-if="showTransportPlanCreateActions" class="business-crud-page__create-actions">
|
||
<el-button :disabled="Boolean(transportPlanCreateActionLoading)" @click="closeCrudDialog">
|
||
关闭
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
plain
|
||
:loading="transportPlanCreateActionLoading === 'draft'"
|
||
:disabled="Boolean(transportPlanCreateActionLoading)"
|
||
@click="handleTransportPlanCreateAction('draft')"
|
||
>
|
||
暂存
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="transportPlanCreateActionLoading === 'create'"
|
||
:disabled="Boolean(transportPlanCreateActionLoading)"
|
||
@click="handleTransportPlanCreateAction('create')"
|
||
>
|
||
确认创建
|
||
</el-button>
|
||
</div>
|
||
<div v-if="showContractCreateActions" class="business-crud-page__contract-actions">
|
||
<el-button
|
||
type="primary"
|
||
plain
|
||
:loading="contractCreateActionLoading === 'draft'"
|
||
:disabled="Boolean(contractCreateActionLoading)"
|
||
@click="handleContractCreateAction('draft')"
|
||
>
|
||
保存
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="contractCreateActionLoading === 'temporary'"
|
||
:disabled="Boolean(contractCreateActionLoading)"
|
||
@click="handleContractCreateAction('temporary')"
|
||
>
|
||
提交临时合同
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="contractCreateActionLoading === 'formal'"
|
||
:disabled="Boolean(contractCreateActionLoading)"
|
||
@click="handleContractCreateAction('formal')"
|
||
>
|
||
提交正式合同
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
|
||
<template #contractStage="{ row }">
|
||
<el-tag :type="stageTagType(row.contractStage)">
|
||
{{ displayStatus(row, 'contractStage') }}
|
||
</el-tag>
|
||
</template>
|
||
|
||
<template #menu="{ row, index }">
|
||
<template v-if="config.customMenuActions">
|
||
<el-link
|
||
v-for="action in menuActions(row)"
|
||
:key="action.key"
|
||
:type="action.type || 'primary'"
|
||
@click="handleMenuAction(action, row, index)"
|
||
>
|
||
{{ action.label }}
|
||
</el-link>
|
||
</template>
|
||
<template v-else>
|
||
<el-link type="primary" v-if="showDetailButton(row)" @click="openDetail(row)">详情</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="hasPermission(`${config.permission}_view`) && !showDetailButton(row)"
|
||
@click="$refs.crud.rowView(row, index)"
|
||
>
|
||
查看
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="canEdit(row)"
|
||
@click="$refs.crud.rowEdit(row, index)"
|
||
>
|
||
编辑
|
||
</el-link>
|
||
<el-link type="primary" v-if="canCopy(row)" @click="handleCopy(row)">
|
||
复制
|
||
</el-link>
|
||
<el-link type="primary" v-if="canEnable(row)" @click="handleAction('enable', row)">
|
||
启用
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="canDisable(row)"
|
||
@click="handleAction('disable', row)"
|
||
>
|
||
停用
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="canReassign(row)"
|
||
@click="handleAction('reassign', row)"
|
||
>
|
||
重新派单
|
||
</el-link>
|
||
<el-link type="primary" v-if="canCancel(row)" @click="handleAction('cancel', row)">
|
||
取消
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="canComplete(row)"
|
||
@click="handleAction('complete', row)"
|
||
>
|
||
完成
|
||
</el-link>
|
||
<el-link
|
||
v-for="operation in customOperations(row)"
|
||
:key="operation.action"
|
||
:type="operation.type || 'primary'"
|
||
@click="handleCustomOperation(operation, row)"
|
||
>
|
||
{{ operation.label }}
|
||
</el-link>
|
||
<el-link type="primary" v-if="canDelete(row)" @click="rowDel(row)">
|
||
删除
|
||
</el-link>
|
||
</template>
|
||
</template>
|
||
</avue-crud>
|
||
|
||
<empty-pagination
|
||
:page="page"
|
||
@size-change="sizeChange"
|
||
@current-change="currentChange"
|
||
@load="onLoad(page, query)"
|
||
/>
|
||
|
||
<el-dialog
|
||
v-model="detailBox"
|
||
:title="`${config.title}详情`"
|
||
append-to-body
|
||
width="96%"
|
||
class="business-crud-page__detail-dialog"
|
||
>
|
||
<div v-loading="detailLoading">
|
||
<section
|
||
v-for="section in detailSections"
|
||
:key="section.title"
|
||
class="business-crud-page__detail-section"
|
||
>
|
||
<div class="dialog-section-title">{{ section.title }}</div>
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item
|
||
v-for="field in section.fields"
|
||
:key="field[0]"
|
||
:label="field[1]"
|
||
:span="field[2] || 1"
|
||
>
|
||
{{ formatDetailValue(detailRow, field[0]) }}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</section>
|
||
<section v-if="config.enableTransportPlanForm" class="business-crud-page__detail-section">
|
||
<div class="dialog-section-title">货物信息</div>
|
||
<el-table :data="transportCargoRows" border empty-text="暂无货物信息">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column prop="cargoName" label="货物名称" min-width="180" />
|
||
<el-table-column prop="cargoType" label="货物类型" min-width="150" />
|
||
<el-table-column prop="quantity" label="数量" min-width="100" />
|
||
<el-table-column prop="quantityUnit" label="数量单位" min-width="110" />
|
||
<el-table-column prop="packageType" label="包装" min-width="110" />
|
||
<el-table-column prop="brand" label="品牌" min-width="120" />
|
||
<el-table-column prop="specification" label="规格" min-width="120" />
|
||
<el-table-column prop="model" label="型号" min-width="120" />
|
||
<el-table-column prop="materialCode" label="物料编码" min-width="140" />
|
||
<el-table-column prop="deviceCode" label="设备编码" min-width="140" />
|
||
<el-table-column prop="remark" label="备注" min-width="160" />
|
||
</el-table>
|
||
</section>
|
||
<section v-if="config.enableAttachmentTable" class="business-crud-page__detail-section">
|
||
<div class="dialog-section-title">{{ config.attachmentTitle || '附件' }}</div>
|
||
<el-table :data="attachmentRows" border empty-text="暂无附件">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column label="文件名" min-width="260">
|
||
<template #default="{ row }">{{ row.originalName || row.name || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="description" label="附件描述" min-width="220" />
|
||
<el-table-column label="文件大小" width="110">
|
||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="180" />
|
||
</el-table>
|
||
</section>
|
||
</div>
|
||
<template #footer>
|
||
<el-button type="primary" @click="detailBox = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
v-if="config.enableTransportPlanImport"
|
||
v-model="transportPlanImportBox"
|
||
:title="config.transportPlanImportTitle || `导入${config.title}`"
|
||
append-to-body
|
||
width="96%"
|
||
class="business-crud-page__transport-plan-import-dialog"
|
||
@closed="resetTransportPlanImport"
|
||
>
|
||
<el-form
|
||
ref="transportPlanImportFormRef"
|
||
:model="transportPlanImportForm"
|
||
:rules="transportPlanImportRules"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="business-crud-page__transport-plan-import-form"
|
||
>
|
||
<el-row :gutter="24">
|
||
<el-col :span="8">
|
||
<el-form-item label="项目" prop="projectId" required>
|
||
<el-select
|
||
v-model="transportPlanImportForm.projectId"
|
||
placeholder="模糊查询后选择"
|
||
filterable
|
||
clearable
|
||
:loading="projectLoading"
|
||
@visible-change="visible => visible && loadProjectOptions()"
|
||
@change="handleTransportPlanImportProjectChange"
|
||
>
|
||
<el-option
|
||
v-for="item in projectOptions"
|
||
:key="item.id"
|
||
:label="item.projectName"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="客户" prop="customerName" required>
|
||
<el-input
|
||
v-model="transportPlanImportForm.customerName"
|
||
disabled
|
||
placeholder="选择项目后带出"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="客户合同" prop="contractId" required>
|
||
<el-select
|
||
v-model="transportPlanImportForm.contractId"
|
||
placeholder="选择项目后选择"
|
||
filterable
|
||
clearable
|
||
:loading="transportPlanImportContractLoading"
|
||
:disabled="!transportPlanImportForm.projectId"
|
||
@change="handleTransportPlanImportContractChange"
|
||
>
|
||
<el-option
|
||
v-for="item in transportPlanImportContractOptions"
|
||
:key="item.id"
|
||
:label="item.contractName"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-form-item
|
||
label="计划明细表"
|
||
prop="file"
|
||
required
|
||
class="business-crud-page__import-file-item"
|
||
>
|
||
<el-upload
|
||
action="#"
|
||
accept=".xls,.xlsx"
|
||
:auto-upload="false"
|
||
:limit="1"
|
||
:file-list="transportPlanImportFiles"
|
||
:on-change="handleTransportPlanImportFileChange"
|
||
:on-remove="handleTransportPlanImportFileRemove"
|
||
>
|
||
<el-button type="primary">添加附件</el-button>
|
||
</el-upload>
|
||
<span class="business-crud-page__import-file-tip"
|
||
>请上传计划明细表,仅支持 Excel 格式</span
|
||
>
|
||
<el-link type="primary" @click="handleTransportPlanTemplate">下载模板</el-link>
|
||
</el-form-item>
|
||
</el-form>
|
||
<section v-if="transportPlanImportRows.length" class="business-crud-page__import-preview">
|
||
<div class="business-crud-page__import-preview-head">
|
||
<div class="dialog-section-title">数据明细</div>
|
||
<el-button
|
||
type="danger"
|
||
plain
|
||
:disabled="!transportPlanImportSelection.length"
|
||
@click="removeSelectedTransportPlanImportRows"
|
||
>
|
||
批量删除
|
||
</el-button>
|
||
</div>
|
||
<el-tabs v-model="transportPlanImportTab" class="business-crud-page__import-preview-tabs">
|
||
<el-tab-pane :label="`计划单数(${transportPlanImportRows.length})`" name="all" />
|
||
<el-tab-pane
|
||
:label="`疑似重复(${transportPlanImportDuplicateCount})`"
|
||
name="duplicate"
|
||
/>
|
||
</el-tabs>
|
||
<div class="business-crud-page__import-preview-table">
|
||
<el-table
|
||
:data="transportPlanImportVisibleRows"
|
||
border
|
||
row-key="_key"
|
||
empty-text="暂无明细数据"
|
||
@selection-change="handleTransportPlanImportSelectionChange"
|
||
>
|
||
<el-table-column type="selection" width="52" fixed="left" />
|
||
<el-table-column type="index" label="序号" width="70" fixed="left" />
|
||
<el-table-column label="计划单号" min-width="150">
|
||
<template #default>待生成</template>
|
||
</el-table-column>
|
||
<el-table-column label="计划名称" min-width="180">
|
||
<template #default="{ row }">
|
||
<el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.planName" />
|
||
<span v-else>{{ row.planName || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="运输类型" min-width="160">
|
||
<template #default="{ row }">
|
||
<el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.transportType" />
|
||
<span v-else>{{ row.transportType || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="货物名称" min-width="160">
|
||
<template #default="{ row }">
|
||
<el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.cargoName" />
|
||
<span v-else>{{ row.cargoName || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="货物类型" min-width="160">
|
||
<template #default="{ row }">
|
||
<el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.cargoType" />
|
||
<span v-else>{{ row.cargoType || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="数量" min-width="120">
|
||
<template #default="{ row }">
|
||
<el-input v-if="isEditingTransportPlanImportRow(row)" v-model="row.quantity" />
|
||
<span v-else>{{ row.quantity || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="计量单位" min-width="130">
|
||
<template #default="{ row }">
|
||
<el-select v-if="isEditingTransportPlanImportRow(row)" v-model="row.quantityUnit">
|
||
<el-option
|
||
v-for="item in quantityUnitOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
<span v-else>{{ row.quantityUnit || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="发货地址" min-width="260">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="isEditingTransportPlanImportRow(row)"
|
||
v-model="row.departureAddress"
|
||
/>
|
||
<span v-else>{{ row.departureAddress || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="150" fixed="right">
|
||
<template #default="{ row }">
|
||
<template v-if="isEditingTransportPlanImportRow(row)">
|
||
<el-link type="primary" @click="saveTransportPlanImportRow(row)">保存</el-link>
|
||
<el-link type="primary" @click="cancelTransportPlanImportRow(row)">取消</el-link>
|
||
</template>
|
||
<template v-else>
|
||
<el-link type="primary" @click="editTransportPlanImportRow(row)">编辑</el-link>
|
||
<el-link type="danger" @click="removeTransportPlanImportRow(row)">删除</el-link>
|
||
</template>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</section>
|
||
<template #footer>
|
||
<el-button type="primary" :loading="transportPlanImportLoading" @click="submitTransportPlanImport">提交</el-button>
|
||
<el-button @click="transportPlanImportBox = false">返回</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<waybill-import-dialog v-if="config.enableWaybillImport" v-model="waybillImportBox" />
|
||
|
||
<flow-design
|
||
v-if="website.design.designMode"
|
||
is-dialog
|
||
v-model:is-display="flowBox"
|
||
:process-instance-id="processInstanceId"
|
||
/>
|
||
<el-dialog v-else title="流程图" append-to-body v-model="flowBox" :fullscreen="true">
|
||
<iframe
|
||
:src="flowUrl"
|
||
width="100%"
|
||
height="700"
|
||
title="流程图"
|
||
frameBorder="no"
|
||
border="0"
|
||
marginWidth="0"
|
||
marginHeight="0"
|
||
scrolling="no"
|
||
allowTransparency="yes"
|
||
/>
|
||
<template #footer>
|
||
<el-button @click="flowBox = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:title="dispatchDialogTitle"
|
||
append-to-body
|
||
v-model="dispatchBox"
|
||
width="calc(100vw - 32px)"
|
||
top="16px"
|
||
class="business-crud-page__dispatch-dialog"
|
||
@closed="resetDispatchDialog"
|
||
>
|
||
<div v-loading="dispatchLoading" class="business-crud-page__dispatch-shell">
|
||
<div class="business-crud-page__dispatch-top">
|
||
<div class="business-crud-page__dispatch-heading">
|
||
<div class="business-crud-page__dispatch-heading-title">
|
||
<span class="business-crud-page__dispatch-heading-name">
|
||
{{ dispatchDialogTitle }}
|
||
</span>
|
||
<span class="business-crud-page__dispatch-heading-split">|</span>
|
||
<span class="business-crud-page__dispatch-heading-no">
|
||
{{ dispatchDialogNo || '-' }}
|
||
</span>
|
||
<el-tag :type="statusTagType(dispatchRow.businessStatus)" effect="light">
|
||
{{ dispatchDialogStatusText }}
|
||
</el-tag>
|
||
<el-tag type="primary" effect="light">
|
||
{{ dispatchRow.transportType || '公路整车' }}
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__dispatch-summary">
|
||
<div class="business-crud-page__dispatch-summary-left">
|
||
<div class="business-crud-page__dispatch-summary-grid">
|
||
<div class="business-crud-page__dispatch-summary-item">
|
||
<div class="business-crud-page__dispatch-summary-label">客户</div>
|
||
<div class="business-crud-page__dispatch-summary-value">
|
||
{{ dispatchRow.customerName || '-' }}
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__dispatch-summary-item">
|
||
<div class="business-crud-page__dispatch-summary-label">项目</div>
|
||
<div class="business-crud-page__dispatch-summary-value">
|
||
{{ dispatchRow.projectName || '-' }}
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__dispatch-summary-item">
|
||
<div class="business-crud-page__dispatch-summary-label">合同编号</div>
|
||
<div class="business-crud-page__dispatch-summary-value">
|
||
{{ dispatchRow.contractName || dispatchRow.contractNo || '-' }}
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__dispatch-summary-item">
|
||
<div class="business-crud-page__dispatch-summary-label">货物信息</div>
|
||
<div class="business-crud-page__dispatch-summary-value">
|
||
{{ dispatchPlanCargoInfo || '-' }}
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__dispatch-summary-item">
|
||
<div class="business-crud-page__dispatch-summary-label">计划执行时间</div>
|
||
<div class="business-crud-page__dispatch-summary-value">
|
||
{{
|
||
[dispatchRow.planStartDate, dispatchRow.planEndDate]
|
||
.filter(Boolean)
|
||
.join('-') || '-'
|
||
}}
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="business-crud-page__dispatch-summary-item business-crud-page__dispatch-summary-item--attachments"
|
||
>
|
||
<div class="business-crud-page__dispatch-summary-label">附件</div>
|
||
<div class="business-crud-page__dispatch-summary-value">
|
||
<el-link
|
||
v-for="(item, index) in dispatchAttachmentRows"
|
||
:key="dispatchAttachmentLabel(item) + index"
|
||
type="primary"
|
||
:href="dispatchAttachmentUrl(item) || undefined"
|
||
:underline="false"
|
||
target="_blank"
|
||
class="business-crud-page__dispatch-attachment-link"
|
||
>
|
||
{{ dispatchAttachmentLabel(item) }}
|
||
</el-link>
|
||
<span v-if="!dispatchAttachmentRows.length">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__dispatch-route">
|
||
<div class="business-crud-page__dispatch-route-item">
|
||
<span class="business-crud-page__dispatch-route-badge is-start">起</span>
|
||
<div class="business-crud-page__dispatch-route-body">
|
||
<div class="business-crud-page__dispatch-route-title">
|
||
{{ parseDispatchRoutePoint(dispatchRow, 'start').name || '-' }}
|
||
</div>
|
||
<div class="business-crud-page__dispatch-route-address">
|
||
{{ parseDispatchRoutePoint(dispatchRow, 'start').address || '-' }}
|
||
</div>
|
||
<div class="business-crud-page__dispatch-route-contact">
|
||
{{ parseDispatchRoutePoint(dispatchRow, 'start').contact || '-' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="business-crud-page__dispatch-route-line" />
|
||
<div class="business-crud-page__dispatch-route-item">
|
||
<span class="business-crud-page__dispatch-route-badge is-end">终</span>
|
||
<div class="business-crud-page__dispatch-route-body">
|
||
<div class="business-crud-page__dispatch-route-title">
|
||
{{ parseDispatchRoutePoint(dispatchRow, 'end').name || '-' }}
|
||
</div>
|
||
<div class="business-crud-page__dispatch-route-address">
|
||
{{ parseDispatchRoutePoint(dispatchRow, 'end').address || '-' }}
|
||
</div>
|
||
<div class="business-crud-page__dispatch-route-contact">
|
||
{{ parseDispatchRoutePoint(dispatchRow, 'end').contact || '-' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="business-crud-page__dispatch-list">
|
||
<div class="business-crud-page__dispatch-list-head">
|
||
<div class="business-crud-page__dispatch-list-title">待调度列表</div>
|
||
<el-button
|
||
type="primary"
|
||
:disabled="dispatchSummaryRemainingQuantity <= 0"
|
||
@click="handleDispatchAddRow"
|
||
>
|
||
新增
|
||
</el-button>
|
||
<div class="business-crud-page__dispatch-list-summary">
|
||
{{ dispatchSummaryText }}
|
||
</div>
|
||
</div>
|
||
<el-table
|
||
:data="dispatchRows"
|
||
border
|
||
height="260"
|
||
row-key="_key"
|
||
class="business-crud-page__dispatch-table"
|
||
>
|
||
<el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
|
||
<el-table-column
|
||
prop="transportType"
|
||
label="运输方式"
|
||
min-width="180"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="carrierType"
|
||
label="承运类型"
|
||
min-width="150"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="carrierName"
|
||
label="承运商"
|
||
min-width="150"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="driverName"
|
||
label="司机(联系方式)"
|
||
min-width="220"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
>
|
||
<template #default="{ row }">
|
||
<div>
|
||
<div>{{ row.driverName || '-' }}</div>
|
||
<div>{{ row.driverPhone || '-' }}</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="vehicleNo"
|
||
label="车牌号/航班号/船号/班列号"
|
||
min-width="200"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="cargoType"
|
||
label="货物类型"
|
||
min-width="170"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="cargoInfo"
|
||
label="货物信息"
|
||
min-width="280"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="departureAddress"
|
||
label="发货地址"
|
||
min-width="220"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="arrivalAddress"
|
||
label="收货地址"
|
||
min-width="220"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="dispatchUserName"
|
||
label="调度人"
|
||
min-width="140"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="dispatchTime"
|
||
label="调度时间"
|
||
min-width="160"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||
<template #default="{ row, $index }">
|
||
<div class="business-crud-page__dispatch-actions">
|
||
<el-link type="primary" @click="handleDispatchEditRow(row, $index)">编辑</el-link>
|
||
<el-link type="danger" @click="removeDispatchRow($index)">删除</el-link>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<div class="business-crud-page__dispatch-footer">
|
||
<el-button @click="dispatchBox = false">关闭</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="dispatchSaving === 'submit'"
|
||
@click="handleDispatchSubmit('submit')"
|
||
>
|
||
确认生成
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:title="dispatchItemDialogTitle"
|
||
append-to-body
|
||
v-model="dispatchItemBox"
|
||
width="96%"
|
||
top="8vh"
|
||
class="business-crud-page__dispatch-item-dialog"
|
||
@closed="resetDispatchItemDialog"
|
||
>
|
||
<el-form
|
||
:model="dispatchItemForm"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="business-crud-page__dispatch-item-form"
|
||
>
|
||
<div class="dialog-section-title">收发货信息</div>
|
||
<div class="business-crud-page__dispatch-shipping-form">
|
||
<el-form-item label="发货地址" required>
|
||
<div class="business-crud-page__route-address-row">
|
||
<el-input
|
||
v-model="dispatchItemForm.departureName"
|
||
:placeholder="dispatchStationNamePlaceholder"
|
||
:suffix-icon="dispatchStationMode ? Search : undefined"
|
||
/>
|
||
<el-input
|
||
v-model="dispatchItemForm.departureAddress"
|
||
:placeholder="dispatchAddressDetailPlaceholder"
|
||
:suffix-icon="dispatchStationMode ? undefined : Location"
|
||
/>
|
||
<span class="business-crud-page__route-label">联系人</span>
|
||
<el-input v-model="dispatchItemForm.departureContact" placeholder="请输入" />
|
||
<span class="business-crud-page__route-label">联系方式</span>
|
||
<el-input v-model="dispatchItemForm.departurePhone" placeholder="请输入" />
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="收货地址" required>
|
||
<div class="business-crud-page__route-address-row">
|
||
<el-input
|
||
v-model="dispatchItemForm.arrivalName"
|
||
:placeholder="dispatchStationNamePlaceholder"
|
||
:suffix-icon="dispatchStationMode ? Search : undefined"
|
||
/>
|
||
<el-input
|
||
v-model="dispatchItemForm.arrivalAddress"
|
||
:placeholder="dispatchAddressDetailPlaceholder"
|
||
:suffix-icon="dispatchStationMode ? undefined : Location"
|
||
/>
|
||
<span class="business-crud-page__route-label">联系人</span>
|
||
<el-input v-model="dispatchItemForm.arrivalContact" placeholder="请输入" />
|
||
<span class="business-crud-page__route-label">联系方式</span>
|
||
<el-input v-model="dispatchItemForm.arrivalPhone" placeholder="请输入" />
|
||
</div>
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<div
|
||
class="dialog-section-title dialog-section-title--action"
|
||
style="display: flex; width: 100%; justify-content: space-between"
|
||
>
|
||
<span class="business-crud-page__dispatch-task-title">任务信息</span>
|
||
<el-segmented
|
||
v-model="dispatchItemForm.taskEntryMode"
|
||
:options="taskEntryModeOptions"
|
||
class="business-crud-page__dispatch-entry-mode"
|
||
/>
|
||
</div>
|
||
|
||
<template v-if="dispatchItemForm.taskEntryMode === 'full'">
|
||
<div class="dialog-section-title">货物信息</div>
|
||
<div class="business-crud-page__dispatch-item-grid">
|
||
<el-form-item label="货物名称" required>
|
||
<el-input v-model="dispatchItemForm.cargoName" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="货物类型" required>
|
||
<el-input v-model="dispatchItemForm.cargoType" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="数量" required>
|
||
<el-input v-model="dispatchItemForm.quantity" placeholder="请输入">
|
||
<template #append>
|
||
<el-select
|
||
v-model="dispatchItemForm.quantityUnit"
|
||
class="business-crud-page__dispatch-unit-select"
|
||
>
|
||
<el-option
|
||
v-for="item in quantityUnitOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="包装">
|
||
<el-select v-model="dispatchItemForm.packageType" clearable placeholder="请选择">
|
||
<el-option
|
||
v-for="item in packageOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="规格">
|
||
<el-input v-model="dispatchItemForm.specification" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="型号">
|
||
<el-input v-model="dispatchItemForm.model" placeholder="请输入" />
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<div class="dialog-section-title">费用信息</div>
|
||
<div class="business-crud-page__dispatch-item-grid">
|
||
<el-form-item label="单价">
|
||
<el-input v-model="dispatchItemForm.unitPrice" placeholder="请输入">
|
||
<template #append>
|
||
<el-select
|
||
v-model="dispatchItemForm.priceUnit"
|
||
class="business-crud-page__dispatch-unit-select"
|
||
placeholder="元/吨"
|
||
:loading="taskFeeUnitLoading"
|
||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||
>
|
||
<el-option
|
||
v-if="!taskFeeUnitOptions.some(item => item.value === '元/吨')"
|
||
label="元/吨"
|
||
value="元/吨"
|
||
/>
|
||
<el-option
|
||
v-for="item in taskFeeUnitOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="其他费用合计">
|
||
<el-input v-model="dispatchItemForm.otherFeeTotal" placeholder="请输入" />
|
||
</el-form-item>
|
||
</div>
|
||
</template>
|
||
<template v-else>
|
||
<div
|
||
class="business-crud-page__dispatch-item-grid business-crud-page__dispatch-item-grid--compact"
|
||
>
|
||
<el-form-item
|
||
label="承运类型"
|
||
required
|
||
class="business-crud-page__dispatch-item-carrier-type"
|
||
>
|
||
<el-radio-group
|
||
v-model="dispatchItemForm.carrierType"
|
||
@change="handleDispatchCarrierTypeChange"
|
||
>
|
||
<el-radio-button v-for="item in taskCarrierTypes" :key="item" :label="item" />
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item v-if="dispatchItemForm.carrierType === '承运商'" label="承运商" required>
|
||
<el-select
|
||
v-model="dispatchItemForm.carrierName"
|
||
placeholder="请输入"
|
||
filterable
|
||
clearable
|
||
:suffix-icon="Search"
|
||
:loading="taskCarrierLoading"
|
||
@visible-change="visible => visible && loadDispatchCarrierOptions()"
|
||
@change="handleDispatchCarrierChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskCarrierOptions"
|
||
:key="
|
||
item.id || item.customerName || item.carrierName || item.fullName || item.name
|
||
"
|
||
:label="item.customerName || item.carrierName || item.fullName || item.name"
|
||
:value="item.customerName || item.carrierName || item.fullName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="司机" :required="dispatchItemForm.carrierType !== '承运商'">
|
||
<el-select
|
||
v-model="dispatchItemForm.driverName"
|
||
placeholder="请输入"
|
||
filterable
|
||
clearable
|
||
allow-create
|
||
default-first-option
|
||
:suffix-icon="Search"
|
||
:loading="taskDriverLoading"
|
||
@visible-change="visible => visible && loadDispatchDriverOptions()"
|
||
@change="handleDispatchDriverChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskDriverOptions"
|
||
:key="item.id || item.driverName || item.mobile"
|
||
:label="item.driverName || item.name"
|
||
:value="item.driverName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item
|
||
:label="dispatchItemForm.carrierType === '承运商' ? '手机号' : '司机手机号'"
|
||
:required="dispatchItemForm.carrierType !== '承运商'"
|
||
>
|
||
<el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="车牌号" required>
|
||
<el-input v-model="dispatchItemForm.vehicleNo" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType !== '承运商'"
|
||
label="挂车车牌号"
|
||
required
|
||
>
|
||
<el-input v-model="dispatchItemForm.trailerVehicleNo" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item v-if="dispatchItemForm.carrierType !== '承运商'" label="押运人" required>
|
||
<el-input v-model="dispatchItemForm.escortName" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType !== '承运商'"
|
||
label="押运人手机号"
|
||
required
|
||
>
|
||
<el-input v-model="dispatchItemForm.escortPhone" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="货物类型" required>
|
||
<el-input v-model="dispatchItemForm.cargoType" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="货物名称" required>
|
||
<el-input v-model="dispatchItemForm.cargoName" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="规格">
|
||
<el-input v-model="dispatchItemForm.specification" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="型号">
|
||
<el-input v-model="dispatchItemForm.model" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="数量" required>
|
||
<el-input v-model="dispatchItemForm.quantity" placeholder="请输入">
|
||
<template #append>
|
||
<el-select
|
||
v-model="dispatchItemForm.quantityUnit"
|
||
class="business-crud-page__dispatch-unit-select"
|
||
>
|
||
<el-option
|
||
v-for="item in quantityUnitOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="里程(公里)" :required="dispatchItemForm.carrierType !== '承运商'">
|
||
<el-input v-model="dispatchItemForm.mileage" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="预计发货日期">
|
||
<el-date-picker
|
||
v-model="dispatchItemForm.estimatedStartTime"
|
||
type="date"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计完成日期">
|
||
<el-date-picker
|
||
v-model="dispatchItemForm.estimatedEndTime"
|
||
type="date"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="单价">
|
||
<el-input v-model="dispatchItemForm.unitPrice" placeholder="请输入">
|
||
<template #append>
|
||
<el-select
|
||
v-model="dispatchItemForm.priceUnit"
|
||
class="business-crud-page__dispatch-unit-select"
|
||
placeholder="元/吨"
|
||
:loading="taskFeeUnitLoading"
|
||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||
>
|
||
<el-option
|
||
v-if="!taskFeeUnitOptions.some(item => item.value === '元/吨')"
|
||
label="元/吨"
|
||
value="元/吨"
|
||
/>
|
||
<el-option
|
||
v-for="item in taskFeeUnitOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="备注" class="business-crud-page__dispatch-item-span-3">
|
||
<el-input
|
||
v-model="dispatchItemForm.remark"
|
||
maxlength="200"
|
||
show-word-limit
|
||
placeholder="请输入,200字以内"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
</template>
|
||
|
||
<div
|
||
v-if="dispatchItemForm.taskEntryMode === 'full'"
|
||
class="dialog-section-title business-crud-page__dispatch-carrier-title"
|
||
style="display: flex; width: 100%; margin-top: 16px"
|
||
>
|
||
承运信息
|
||
</div>
|
||
<div
|
||
v-if="dispatchItemForm.taskEntryMode === 'full'"
|
||
class="business-crud-page__dispatch-item-grid"
|
||
>
|
||
<el-form-item label="承运类型" required class="business-crud-page__dispatch-item-span-2">
|
||
<el-radio-group
|
||
v-model="dispatchItemForm.carrierType"
|
||
@change="handleDispatchCarrierTypeChange"
|
||
>
|
||
<el-radio-button v-for="item in taskCarrierTypes" :key="item" :label="item" />
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item v-if="dispatchItemForm.carrierType === '承运商'" label="承运商" required>
|
||
<el-select
|
||
v-model="dispatchItemForm.carrierName"
|
||
placeholder="请输入"
|
||
filterable
|
||
clearable
|
||
:suffix-icon="Search"
|
||
:loading="taskCarrierLoading"
|
||
@visible-change="visible => visible && loadDispatchCarrierOptions()"
|
||
@change="handleDispatchCarrierChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskCarrierOptions"
|
||
:key="
|
||
item.id || item.customerName || item.carrierName || item.fullName || item.name
|
||
"
|
||
:label="item.customerName || item.carrierName || item.fullName || item.name"
|
||
:value="item.customerName || item.carrierName || item.fullName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType"
|
||
label="司机"
|
||
:required="dispatchItemForm.carrierType !== '承运商'"
|
||
>
|
||
<el-select
|
||
v-model="dispatchItemForm.driverName"
|
||
placeholder="请输入"
|
||
filterable
|
||
clearable
|
||
allow-create
|
||
default-first-option
|
||
:suffix-icon="Search"
|
||
:loading="taskDriverLoading"
|
||
@visible-change="visible => visible && loadDispatchDriverOptions()"
|
||
@change="handleDispatchDriverChange"
|
||
>
|
||
<el-option
|
||
v-for="item in taskDriverOptions"
|
||
:key="item.id || item.driverName || item.mobile"
|
||
:label="item.driverName || item.name"
|
||
:value="item.driverName || item.name"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType"
|
||
:label="dispatchItemForm.carrierType === '承运商' ? '手机号' : '司机手机号'"
|
||
:required="dispatchItemForm.carrierType !== '承运商'"
|
||
>
|
||
<el-input v-model="dispatchItemForm.driverPhone" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType"
|
||
label="车牌号/航班号/船号/班列号"
|
||
required
|
||
>
|
||
<el-input v-model="dispatchItemForm.vehicleNo" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
|
||
label="挂车车牌号"
|
||
required
|
||
>
|
||
<el-input v-model="dispatchItemForm.trailerVehicleNo" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
|
||
label="押运人"
|
||
required
|
||
>
|
||
<el-input v-model="dispatchItemForm.escortName" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
|
||
label="押运人手机号"
|
||
required
|
||
>
|
||
<el-input v-model="dispatchItemForm.escortPhone" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item
|
||
v-if="dispatchItemForm.carrierType && dispatchItemForm.carrierType !== '承运商'"
|
||
label="里程(km)"
|
||
required
|
||
>
|
||
<el-input v-model="dispatchItemForm.mileage" placeholder="请输入" />
|
||
</el-form-item>
|
||
<el-form-item label="预计发货日期">
|
||
<el-date-picker
|
||
v-model="dispatchItemForm.estimatedStartTime"
|
||
type="date"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="预计完成日期">
|
||
<el-date-picker
|
||
v-model="dispatchItemForm.estimatedEndTime"
|
||
type="date"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="备注" class="business-crud-page__dispatch-item-span-2">
|
||
<el-input
|
||
v-model="dispatchItemForm.remark"
|
||
type="textarea"
|
||
:rows="2"
|
||
maxlength="200"
|
||
show-word-limit
|
||
placeholder="请输入,200字以内"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<template v-if="dispatchItemForm.taskEntryMode === 'full'">
|
||
<div class="dialog-section-title">附件</div>
|
||
<div class="business-crud-page__attachment">
|
||
<div class="business-crud-page__attachment-head">
|
||
<vehicle-attachment-upload
|
||
v-model="dispatchItemAttachmentRows"
|
||
:file-types="attachmentFileTypes"
|
||
:max-size="500"
|
||
:show-tip="false"
|
||
:show-file-list="false"
|
||
button-text="上传附件"
|
||
@change="handleDispatchItemAttachmentChange"
|
||
/>
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!dispatchItemAttachmentRows.length"
|
||
@click="handleDispatchItemBatchDownload"
|
||
>
|
||
批量下载
|
||
</el-button>
|
||
</div>
|
||
<el-table
|
||
:data="dispatchItemAttachmentRows"
|
||
border
|
||
class="business-crud-page__attachment-table"
|
||
>
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column label="文件名" min-width="200" align="center" show-overflow-tooltip>
|
||
<template #default="{ row }">{{ row.originalName || row.name }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="附件描述" min-width="220" align="center">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.description" placeholder="请输入附件描述" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="文件大小" width="110" align="center">
|
||
<template #default="{ row }">{{ formatFileSize(row.size) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="160" align="center" />
|
||
<el-table-column label="操作" width="100" align="center">
|
||
<template #default="{ $index }">
|
||
<el-link type="danger" @click="removeDispatchItemAttachment($index)">删除</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</template>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="dispatchItemBox = false">取消</el-button>
|
||
<el-button type="primary" @click="saveDispatchItem">提交</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:title="billingPlanMode === 'edit' ? '编辑计费方案' : '添加计费方案'"
|
||
append-to-body
|
||
v-model="billingPlanBox"
|
||
width="96%"
|
||
class="business-crud-page__billing-dialog"
|
||
>
|
||
<el-form
|
||
ref="billingPlanFormRef"
|
||
:model="billingPlanForm"
|
||
:rules="billingPlanRules"
|
||
label-position="right"
|
||
label-width="auto"
|
||
>
|
||
<div class="business-crud-page__billing-form-row">
|
||
<el-form-item label="方案名称" prop="planName" required>
|
||
<el-input
|
||
v-model="billingPlanForm.planName"
|
||
maxlength="100"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="" class="business-crud-page__billing-default">
|
||
<el-checkbox v-model="billingPlanForm.defaultPlan" :disabled="dialogReadonly">
|
||
默认方案
|
||
</el-checkbox>
|
||
</el-form-item>
|
||
</div>
|
||
<el-form-item label="备注">
|
||
<el-input
|
||
v-model="billingPlanForm.remark"
|
||
maxlength="100"
|
||
show-word-limit
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<div class="business-crud-page__billing-rule-head">
|
||
<el-link v-if="!dialogReadonly" type="primary" @click="addBillingRule">
|
||
+添加规则
|
||
</el-link>
|
||
</div>
|
||
<div class="business-crud-page__billing-rule-wrap">
|
||
<el-table
|
||
:data="billingPlanForm.rules"
|
||
border
|
||
class="business-crud-page__billing-rule-table"
|
||
>
|
||
<el-table-column type="index" label="序号" width="70" align="center" fixed="left" />
|
||
<el-table-column width="180" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">费用类型</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-model="row.feeType"
|
||
class="business-crud-page__billing-field"
|
||
clearable
|
||
filterable
|
||
:disabled="dialogReadonly"
|
||
:loading="billingFeeCategoryLoading"
|
||
placeholder="请选择"
|
||
@change="value => handleBillingFeeTypeChange(row, value)"
|
||
>
|
||
<el-option
|
||
v-for="item in billingFeeCategoryOptions"
|
||
:key="item.id || item.dictKey"
|
||
:label="item.dictValue"
|
||
:value="item.dictKey"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column width="210" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">费用项</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-model="row.feeItem"
|
||
class="business-crud-page__billing-field"
|
||
clearable
|
||
filterable
|
||
:disabled="dialogReadonly || !row.feeType"
|
||
:loading="billingFeeItemLoadingMap[resolveBillingFeeType(row.feeType)]"
|
||
:placeholder="row.feeType ? '请选择' : '请先选择费用类型'"
|
||
>
|
||
<el-option
|
||
v-for="item in getBillingFeeItemOptions(row.feeType)"
|
||
:key="item.id || item.name || item.englishName"
|
||
:label="item.name || item.englishName"
|
||
:value="item.name || item.englishName"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column width="170" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">计费要素</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-model="row.billingElement"
|
||
:disabled="dialogReadonly"
|
||
placeholder="请选择"
|
||
@change="handleBillingElementChange(row)"
|
||
>
|
||
<el-option
|
||
v-for="item in billingElementOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column width="180" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">计费类型</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-model="row.billingType"
|
||
:disabled="dialogReadonly || !row.billingElement"
|
||
:placeholder="row.billingElement ? '请选择' : '请先选择计费要素'"
|
||
@change="handleBillingTypeChange(row)"
|
||
>
|
||
<el-option
|
||
v-for="item in getBillingTypeOptions(row)"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column width="150" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">计费单位</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-model="row.billingUnit"
|
||
clearable
|
||
filterable
|
||
:disabled="dialogReadonly"
|
||
:loading="billingUnitLoading"
|
||
placeholder="请选择"
|
||
>
|
||
<el-option
|
||
v-for="item in billingUnitOptions"
|
||
:key="item.id || item.dictKey || item.dictValue"
|
||
:label="item.dictValue"
|
||
:value="item.dictValue"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column width="140" align="center">
|
||
<template #header>
|
||
<span class="business-crud-page__required-column">单价(元)</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.unitPrice"
|
||
:disabled="dialogReadonly"
|
||
placeholder="请输入"
|
||
@input="value => handleBillingDecimalInput(row, 'unitPrice', value)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="计费要素下限" width="260" align="center">
|
||
<template #default="{ row }">
|
||
<div class="business-crud-page__limit-list">
|
||
<el-input
|
||
v-for="(item, rangeIndex) in getBillingLimitRanges(row)"
|
||
:key="rangeIndex"
|
||
v-model="item.lowerLimit"
|
||
:disabled="!canEditBillingLimit(row)"
|
||
:placeholder="canEditBillingLimit(row) ? '请输入下限' : '无需配置'"
|
||
@input="value => handleBillingLimitInput(row, rangeIndex, 'lowerLimit', value)"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="计费要素上限" width="260" align="center">
|
||
<template #default="{ row }">
|
||
<div class="business-crud-page__limit-list">
|
||
<div
|
||
v-for="(item, rangeIndex) in getBillingLimitRanges(row)"
|
||
:key="rangeIndex"
|
||
class="business-crud-page__limit-row"
|
||
>
|
||
<el-input
|
||
v-model="item.upperLimit"
|
||
:disabled="!canEditBillingLimit(row)"
|
||
:placeholder="canEditBillingLimit(row) ? '请输入上限' : '无需配置'"
|
||
@input="value => handleBillingLimitInput(row, rangeIndex, 'upperLimit', value)"
|
||
/>
|
||
<el-link
|
||
v-if="canEditBillingLimit(row)"
|
||
type="primary"
|
||
@click="addBillingLimitRange(row, rangeIndex)"
|
||
>
|
||
添加
|
||
</el-link>
|
||
<el-button
|
||
v-if="canEditBillingLimit(row) && getBillingLimitRanges(row).length > 1"
|
||
type="primary"
|
||
text
|
||
@click="removeBillingLimitRange(row, rangeIndex)"
|
||
>
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="保底计费重量" width="240" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.minimumBillingWeight"
|
||
:disabled="!canEditMinimumBillingWeight(row)"
|
||
:placeholder="
|
||
canEditMinimumBillingWeight(row)
|
||
? '实际计量值小于保底数值时按保底计费'
|
||
: '仅按重量、按吨·公里可填写'
|
||
"
|
||
@input="value => handleBillingDecimalInput(row, 'minimumBillingWeight', value)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" width="180" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.remark"
|
||
maxlength="100"
|
||
show-word-limit
|
||
:disabled="dialogReadonly"
|
||
placeholder="请输入"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="230" align="center" fixed="right">
|
||
<template #default="{ row, $index }">
|
||
<el-link type="primary" @click="openBillingMatch(row, $index)">
|
||
设置匹配条件
|
||
</el-link>
|
||
<el-link v-if="!dialogReadonly" type="primary" @click="copyBillingRule(row)">
|
||
复制
|
||
</el-link>
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
type="primary"
|
||
@click="removeBillingRule($index)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<div class="business-crud-page__dialog-footer">
|
||
<el-button type="primary" :disabled="dialogReadonly" @click="submitBillingPlan">
|
||
提交
|
||
</el-button>
|
||
<el-button @click="billingPlanBox = false">取消</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog title="设置匹配条件" append-to-body v-model="billingMatchBox" width="720px">
|
||
<el-form
|
||
:model="billingMatchForm"
|
||
label-position="right"
|
||
label-width="90px"
|
||
class="business-crud-page__match-form"
|
||
>
|
||
<el-row :gutter="48">
|
||
<el-col :span="12">
|
||
<el-form-item label="起运地">
|
||
<el-cascader
|
||
v-model="billingMatchForm.originPath"
|
||
:options="billingMatchRegionOptions"
|
||
:props="billingMatchRegionCascaderProps"
|
||
:placeholder="billingMatchForm.origin || '请选择起运地'"
|
||
:loading="billingMatchRegionLoading"
|
||
:disabled="dialogReadonly"
|
||
clearable
|
||
filterable
|
||
@visible-change="visible => visible && ensureBillingMatchRegionOptions()"
|
||
@change="value => handleBillingMatchRegionChange('origin', value)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="目的地">
|
||
<el-cascader
|
||
v-model="billingMatchForm.destinationPath"
|
||
:options="billingMatchRegionOptions"
|
||
:props="billingMatchRegionCascaderProps"
|
||
:placeholder="billingMatchForm.destination || '请选择目的地'"
|
||
:loading="billingMatchRegionLoading"
|
||
:disabled="dialogReadonly"
|
||
clearable
|
||
filterable
|
||
@visible-change="visible => visible && ensureBillingMatchRegionOptions()"
|
||
@change="value => handleBillingMatchRegionChange('destination', value)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="运输方式">
|
||
<el-select
|
||
v-model="billingMatchForm.transportMode"
|
||
:disabled="dialogReadonly"
|
||
clearable
|
||
>
|
||
<el-option label="公路" value="公路" />
|
||
<el-option label="铁路" value="铁路" />
|
||
<el-option label="水路" value="水路" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="货物类型">
|
||
<el-cascader
|
||
v-model="billingMatchForm.cargoTypePath"
|
||
:options="billingCargoTypeOptions"
|
||
:props="billingCargoTypeCascaderProps"
|
||
:placeholder="billingMatchForm.cargoType || '请选择货物类型'"
|
||
:loading="billingCargoTypeLoading"
|
||
:disabled="dialogReadonly"
|
||
clearable
|
||
filterable
|
||
:filter-method="filterBillingCargoType"
|
||
@visible-change="visible => visible && ensureBillingCargoTypeOptions()"
|
||
@change="handleBillingMatchCargoTypeChange"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="billingMatchBox = false">取消</el-button>
|
||
<el-button type="primary" :disabled="dialogReadonly" @click="saveBillingMatch">
|
||
保存
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="选择线路"
|
||
append-to-body
|
||
v-model="transportRouteBox"
|
||
width="1280px"
|
||
@opened="loadTransportRouteList"
|
||
>
|
||
<div class="business-crud-page__dialog-search">
|
||
<el-form :model="transportRouteQuery" label-position="right" label-width="86px">
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="线路名称">
|
||
<el-input v-model="transportRouteQuery.routeName" clearable placeholder="请输入" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="发货地址">
|
||
<el-input
|
||
v-model="transportRouteQuery.departureAddress"
|
||
clearable
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="收货地址">
|
||
<el-input
|
||
v-model="transportRouteQuery.arrivalAddress"
|
||
clearable
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6" class="business-crud-page__dialog-search-actions">
|
||
<el-button type="primary" @click="handleTransportRouteSearch">查询</el-button>
|
||
<el-button @click="handleTransportRouteReset">重置</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</div>
|
||
<el-table border height="320" :data="transportRouteRows" v-loading="transportRouteLoading">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column prop="routeName" label="线路名称" min-width="150" />
|
||
<el-table-column prop="routeCode" label="线路编号" min-width="140" />
|
||
<el-table-column prop="departureName" label="发货地" min-width="140" />
|
||
<el-table-column
|
||
prop="departureAddress"
|
||
label="发货地址"
|
||
min-width="220"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="departureContact" label="发货联系人" min-width="120" />
|
||
<el-table-column prop="departurePhone" label="发货联系方式" min-width="140" />
|
||
<el-table-column prop="arrivalName" label="收货地" min-width="140" />
|
||
<el-table-column
|
||
prop="arrivalAddress"
|
||
label="收货地址"
|
||
min-width="220"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="arrivalContact" label="收货联系人" min-width="120" />
|
||
<el-table-column prop="arrivalPhone" label="收货联系方式" min-width="140" />
|
||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="deptName" label="组织" min-width="150" show-overflow-tooltip />
|
||
<el-table-column
|
||
prop="updateUserName"
|
||
label="更新人"
|
||
min-width="120"
|
||
:formatter="formatTransportRouteUpdateUserName"
|
||
/>
|
||
<el-table-column prop="updateTime" label="更新时间" min-width="160" />
|
||
<el-table-column prop="createTime" label="创建时间" min-width="160" />
|
||
<el-table-column label="操作" width="110" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click.stop="selectTransportRouteRow(row)">
|
||
选择
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="business-crud-page__dialog-pagination">
|
||
<el-pagination
|
||
v-model:current-page="transportRoutePage.currentPage"
|
||
v-model:page-size="transportRoutePage.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, prev, pager, next, sizes"
|
||
:total="transportRoutePage.total"
|
||
@size-change="handleTransportRouteSizeChange"
|
||
@current-change="handleTransportRouteCurrentChange"
|
||
/>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="transportRouteBox = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="选择常用地址"
|
||
append-to-body
|
||
v-model="transportAddressBox"
|
||
width="1280px"
|
||
@opened="loadTransportAddressList"
|
||
>
|
||
<div class="business-crud-page__dialog-search">
|
||
<el-form :model="transportAddressQuery" label-position="right" label-width="86px">
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="地址名称">
|
||
<el-input
|
||
v-model="transportAddressQuery.addressName"
|
||
clearable
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="地址类型">
|
||
<el-select
|
||
v-model="transportAddressQuery.addressType"
|
||
clearable
|
||
filterable
|
||
placeholder="请选择"
|
||
>
|
||
<el-option
|
||
v-for="item in addressTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="详细地址">
|
||
<el-input
|
||
v-model="transportAddressQuery.detailAddress"
|
||
clearable
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6" class="business-crud-page__dialog-search-actions">
|
||
<el-button type="primary" @click="handleTransportAddressSearch">查询</el-button>
|
||
<el-button @click="handleTransportAddressReset">重置</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</div>
|
||
<el-table
|
||
border
|
||
height="320"
|
||
:data="transportAddressRows"
|
||
v-loading="transportAddressLoading"
|
||
>
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column prop="addressName" label="地址名称" min-width="150" />
|
||
<el-table-column prop="addressCode" label="地址编号" min-width="130" />
|
||
<el-table-column prop="addressType" label="类型" min-width="120" />
|
||
<el-table-column
|
||
prop="siteCodeDisplay"
|
||
label="站点编码"
|
||
min-width="120"
|
||
:formatter="formatTransportAddressSiteCode"
|
||
/>
|
||
<el-table-column
|
||
prop="detailAddress"
|
||
label="详细地址"
|
||
min-width="260"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="longitude" label="经度" min-width="120" />
|
||
<el-table-column prop="latitude" label="纬度" min-width="120" />
|
||
<el-table-column prop="regionName" label="行政区划" min-width="150" show-overflow-tooltip />
|
||
<el-table-column prop="contactName" label="联系人" min-width="120" />
|
||
<el-table-column prop="contactPhone" label="联系方式" min-width="140" />
|
||
<el-table-column prop="deptName" label="组织" min-width="150" show-overflow-tooltip />
|
||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="updateTime" label="更新时间" min-width="160" />
|
||
<el-table-column prop="createTime" label="创建时间" min-width="160" />
|
||
<el-table-column label="操作" width="110" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click.stop="selectTransportAddressRow(row)">
|
||
选择
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="business-crud-page__dialog-pagination">
|
||
<el-pagination
|
||
v-model:current-page="transportAddressPage.currentPage"
|
||
v-model:page-size="transportAddressPage.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, prev, pager, next, sizes"
|
||
:total="transportAddressPage.total"
|
||
@size-change="handleTransportAddressSizeChange"
|
||
@current-change="handleTransportAddressCurrentChange"
|
||
/>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="transportAddressBox = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="选择站点"
|
||
append-to-body
|
||
v-model="transportStationBox"
|
||
width="1180px"
|
||
@opened="loadTransportStationList"
|
||
>
|
||
<div class="business-crud-page__dialog-search">
|
||
<el-form :model="transportStationQuery" label-position="right" label-width="86px">
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="站点名称">
|
||
<el-input v-model="transportStationQuery.name" clearable placeholder="请输入" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="站点类型">
|
||
<el-input :model-value="transportStationTypeLabel" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="详细地址">
|
||
<el-input
|
||
v-model="transportStationQuery.detailAddress"
|
||
clearable
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6" class="business-crud-page__dialog-search-actions">
|
||
<el-button type="primary" @click="handleTransportStationSearch">查询</el-button>
|
||
<el-button @click="handleTransportStationReset">重置</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</div>
|
||
<el-table
|
||
border
|
||
height="320"
|
||
:data="transportStationRows"
|
||
v-loading="transportStationLoading"
|
||
>
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column prop="stationName" label="站点名" min-width="160" />
|
||
<el-table-column prop="stationType" label="类型" min-width="120" />
|
||
<el-table-column prop="stationCode" label="站点编码" min-width="130" />
|
||
<el-table-column
|
||
prop="detailAddress"
|
||
label="详细地址"
|
||
min-width="260"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="longitude" label="经度" min-width="120" />
|
||
<el-table-column prop="latitude" label="纬度" min-width="120" />
|
||
<el-table-column prop="regionName" label="行政区划" min-width="160" show-overflow-tooltip />
|
||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="updateTime" label="更新时间" min-width="160" />
|
||
<el-table-column prop="createTime" label="创建时间" min-width="160" />
|
||
<el-table-column label="操作" width="110" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click.stop="selectTransportStationRow(row)">
|
||
选择
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="business-crud-page__dialog-pagination">
|
||
<el-pagination
|
||
v-model:current-page="transportStationPage.currentPage"
|
||
v-model:page-size="transportStationPage.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, prev, pager, next, sizes"
|
||
:total="transportStationPage.total"
|
||
@size-change="handleTransportStationSizeChange"
|
||
@current-change="handleTransportStationCurrentChange"
|
||
/>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="transportStationBox = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="地图选择地址"
|
||
append-to-body
|
||
v-model="transportMapBox"
|
||
width="920px"
|
||
@opened="initTransportAmap"
|
||
>
|
||
<div class="business-crud-page__map-toolbar">
|
||
<el-input
|
||
v-model="transportMapKeyword"
|
||
clearable
|
||
placeholder="输入地址关键词"
|
||
@keyup.enter="searchTransportMapKeyword"
|
||
/>
|
||
<el-button
|
||
type="primary"
|
||
:icon="Search"
|
||
:loading="transportMapLoading"
|
||
@click="searchTransportMapKeyword"
|
||
>
|
||
搜索
|
||
</el-button>
|
||
</div>
|
||
<div ref="transportAmap" class="business-crud-page__map"></div>
|
||
<div class="business-crud-page__map-info">
|
||
<span>{{ transportMapStatus }}</span>
|
||
<span v-if="transportMapSelected.regionName"
|
||
>行政区划:{{ transportMapSelected.regionName }}</span
|
||
>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="transportMapBox = false">取消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!transportMapSelected.longitude"
|
||
@click="confirmTransportMapPick"
|
||
>
|
||
确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="选择常用货物"
|
||
append-to-body
|
||
v-model="commonCargoBox"
|
||
width="1280px"
|
||
@opened="loadCommonCargoList"
|
||
>
|
||
<div class="business-crud-page__dialog-search">
|
||
<el-form :model="commonCargoQuery" label-position="right" label-width="100px">
|
||
<el-row :gutter="16">
|
||
<el-col :span="6">
|
||
<el-form-item label="货物名称">
|
||
<el-input v-model="commonCargoQuery.cargoName" clearable placeholder="请输入" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="货物类型">
|
||
<el-cascader
|
||
v-model="commonCargoQuery.cargoTypePath"
|
||
:options="billingCargoTypeOptions"
|
||
:props="billingCargoTypeCascaderProps"
|
||
placeholder="请选择"
|
||
clearable
|
||
filterable
|
||
:filter-method="filterBillingCargoType"
|
||
@visible-change="visible => visible && ensureBillingCargoTypeOptions()"
|
||
@change="handleCommonCargoTypeChange"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="规格型号">
|
||
<el-input
|
||
v-model="commonCargoQuery.specificationModel"
|
||
clearable
|
||
placeholder="请输入"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6" class="business-crud-page__dialog-search-actions">
|
||
<el-button type="primary" @click="handleCommonCargoSearch">查询</el-button>
|
||
<el-button @click="handleCommonCargoReset">重置</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</div>
|
||
<el-table border height="320" :data="commonCargoRows" v-loading="commonCargoLoading">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column prop="cargoName" label="货物名称" min-width="150" />
|
||
<el-table-column prop="cargoCode" label="货物编号" min-width="130" />
|
||
<el-table-column prop="firstCargoTypeName" label="一级货物类型" min-width="150" />
|
||
<el-table-column prop="secondCargoTypeName" label="二级货物类型" min-width="150" />
|
||
<el-table-column prop="secondCargoTypeCode" label="二级货物编码" min-width="140" />
|
||
<el-table-column prop="packageType" label="包装" min-width="100" />
|
||
<el-table-column prop="brand" label="品牌" min-width="120" />
|
||
<el-table-column prop="specification" label="规格" min-width="120" />
|
||
<el-table-column prop="model" label="型号" min-width="120" />
|
||
<el-table-column prop="cargoValue" label="单价" min-width="120" />
|
||
<el-table-column prop="priceUnit" label="计价单位" min-width="120" />
|
||
<el-table-column
|
||
prop="cargoSize"
|
||
label="尺寸"
|
||
min-width="120"
|
||
:formatter="formatCommonCargoSize"
|
||
/>
|
||
<el-table-column
|
||
prop="descriptionOne"
|
||
label="其他说明1"
|
||
min-width="160"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column
|
||
prop="descriptionTwo"
|
||
label="其他说明2"
|
||
min-width="160"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="deptName" label="组织" min-width="150" show-overflow-tooltip />
|
||
<el-table-column prop="updateTime" label="更新时间" min-width="160" />
|
||
<el-table-column prop="createTime" label="创建时间" min-width="160" />
|
||
<el-table-column label="操作" width="110" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click.stop="selectCommonCargoRow(row)">
|
||
选择
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="business-crud-page__dialog-pagination">
|
||
<el-pagination
|
||
v-model:current-page="commonCargoPage.currentPage"
|
||
v-model:page-size="commonCargoPage.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, prev, pager, next, sizes"
|
||
:total="commonCargoPage.total"
|
||
@size-change="handleCommonCargoSizeChange"
|
||
@current-change="handleCommonCargoCurrentChange"
|
||
/>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="commonCargoBox = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog title="导入货物" append-to-body v-model="cargoImportBox" width="560px">
|
||
<el-upload
|
||
drag
|
||
action="#"
|
||
accept=".xls,.xlsx"
|
||
:auto-upload="false"
|
||
:show-file-list="false"
|
||
:disabled="cargoImportLoading"
|
||
:on-change="handleCargoImportFileChange"
|
||
>
|
||
<i class="el-icon-upload"></i>
|
||
<div class="el-upload__text">将 Excel 文件拖到此处,或点击上传</div>
|
||
<template #tip>
|
||
<div class="el-upload__tip">
|
||
按货物信息表头读取:货物名称、货物类型、数量、数量单位、包装、品牌、规格、型号、物料编码、设备编码、备注
|
||
</div>
|
||
</template>
|
||
</el-upload>
|
||
<template #footer>
|
||
<el-button @click="cargoImportBox = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog :title="`${config.title}导入`" append-to-body v-model="excelBox" width="555px">
|
||
<avue-form :option="excelOptionConfig" v-model="excelForm">
|
||
<template #excelTemplate>
|
||
<el-button type="primary" @click="handleTemplate">
|
||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||
</el-button>
|
||
</template>
|
||
</avue-form>
|
||
</el-dialog>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<script>
|
||
import { exportBlob } from '@/api/common';
|
||
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
||
import { getList as getAirportMasterList } from '@/api/base/airport-master';
|
||
import { getList as getCargoTypeList } from '@/api/base/cargo-type';
|
||
import { getList as getFeeItemList } from '@/api/base/fee-item';
|
||
import { getList as getPortTerminalList } from '@/api/base/port-terminal';
|
||
import { getList as getRailwayStationList } from '@/api/base/railway-station';
|
||
import { getLazyTree as getRegionLazyTree } from '@/api/base/region';
|
||
import { getList as getCommonCargoList } from '@/api/business/common-cargo';
|
||
import { getList as getContractList } from '@/api/business/contract-manage';
|
||
import { getList as getProjectList } from '@/api/business/project-apply';
|
||
import { getList as getCommonRouteList } from '@/api/business/common-route';
|
||
import { getList as getTransportPlanList } from '@/api/business/transport-plan';
|
||
import { getList as getCarrierCustomerList } from '@/api/vehicle/customer-archive';
|
||
import { getList as getDriverList } from '@/api/transportCapacity/driver';
|
||
import { getDictionary } from '@/api/system/dictbiz';
|
||
import { getDeptTree } from '@/api/system/dept';
|
||
import { addressTypeOptions } from '@/option/base/common-address';
|
||
import { packageOptions } from '@/option/business/common';
|
||
import { formatUpdateUserName } from '@/utils/audit';
|
||
import { getToken } from '@/utils/auth';
|
||
import { openImportDialog } from '@/utils/import-excel';
|
||
import { applyTableMenuWidth } from '@/utils/table-menu';
|
||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||
import { List, Location, Search } from '@element-plus/icons-vue';
|
||
import { mapGetters } from 'vuex';
|
||
import NProgress from 'nprogress';
|
||
import 'nprogress/nprogress.css';
|
||
import WaybillImportDialog from './waybill-import-dialog.vue';
|
||
|
||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||
let amapLoader;
|
||
|
||
const estimateMenuButtonCount = config => {
|
||
const rowActions = (config.actions || []).filter(action => action !== 'batchComplete');
|
||
return 3 + rowActions.length + (config.operations || []).length;
|
||
};
|
||
|
||
const extractRecords = res => {
|
||
const data = res?.data?.data || res?.data || res;
|
||
if (Array.isArray(data)) return data;
|
||
if (Array.isArray(data?.records)) return data.records;
|
||
if (Array.isArray(data?.data)) return data.data;
|
||
if (Array.isArray(data?.data?.records)) return data.data.records;
|
||
return [];
|
||
};
|
||
|
||
const transportPlanImportColumns = [
|
||
['计划名称', 'planName'],
|
||
['运输方式', 'transportType'],
|
||
['计划开始日期', 'planStartDate'],
|
||
['计划结束日期', 'planEndDate'],
|
||
['货物名称', 'cargoName'],
|
||
['货物类型', 'cargoType'],
|
||
['数量', 'quantity'],
|
||
['数量单位', 'quantityUnit'],
|
||
['包装', 'packageType'],
|
||
['品牌', 'brand'],
|
||
['规格', 'specification'],
|
||
['型号', 'model'],
|
||
['物料编码', 'materialCode'],
|
||
['设备编码', 'deviceCode'],
|
||
['发货地址', 'departureAddress'],
|
||
['发货联系人', 'departureContact'],
|
||
['发货联系方式', 'departurePhone'],
|
||
['收货地址', 'arrivalAddress'],
|
||
['收货联系人', 'arrivalContact'],
|
||
['收货联系方式', 'arrivalPhone'],
|
||
['备注', 'remark'],
|
||
];
|
||
|
||
const defaultBillingRule = () => ({
|
||
feeType: '',
|
||
feeItem: '',
|
||
billingElement: '',
|
||
billingType: '',
|
||
billingUnit: '',
|
||
unitPrice: '',
|
||
lowerLimit: '',
|
||
upperLimit: '',
|
||
limitRanges: [{ lowerLimit: '', upperLimit: '' }],
|
||
minimumBillingWeight: '',
|
||
remark: '',
|
||
matchCondition: {
|
||
origin: '',
|
||
originCode: '',
|
||
originPath: [],
|
||
destination: '',
|
||
destinationCode: '',
|
||
destinationPath: [],
|
||
transportMode: '',
|
||
cargoType: '',
|
||
cargoTypeCode: '',
|
||
cargoTypePath: [],
|
||
},
|
||
});
|
||
|
||
const defaultBillingPlan = () => ({
|
||
planName: '',
|
||
defaultPlan: false,
|
||
remark: '',
|
||
rules: [defaultBillingRule()],
|
||
});
|
||
|
||
const defaultSettlementRule = () => ({
|
||
autoGenerate: 1,
|
||
billStartDate: '',
|
||
settlementType: '月结',
|
||
billCycleType: '固定截单日',
|
||
billCutoffDay: 25,
|
||
cycleDays: '',
|
||
});
|
||
|
||
const defaultReconciliation = () => ({
|
||
skipReconciliation: 1,
|
||
reconciliationMode: '明细逐行核对',
|
||
});
|
||
|
||
const defaultTransportCargo = () => ({
|
||
cargoName: '',
|
||
cargoType: '',
|
||
cargoTypeCode: '',
|
||
cargoTypePath: [],
|
||
quantity: '',
|
||
quantityUnit: '',
|
||
packageType: '',
|
||
brand: '',
|
||
specification: '',
|
||
model: '',
|
||
materialCode: '',
|
||
deviceCode: '',
|
||
remark: '',
|
||
});
|
||
|
||
const defaultDispatchRow = () => ({
|
||
taskEntryMode: 'simple',
|
||
transportType: '',
|
||
carrierType: '承运商',
|
||
carrierId: '',
|
||
carrierName: '',
|
||
driverId: '',
|
||
driverName: '',
|
||
driverPhone: '',
|
||
vehicleNo: '',
|
||
trailerVehicleNo: '',
|
||
escortName: '',
|
||
escortPhone: '',
|
||
mileage: '',
|
||
cargoType: '',
|
||
cargoName: '',
|
||
cargoInfo: '',
|
||
quantity: '',
|
||
quantityUnit: '吨',
|
||
specification: '',
|
||
model: '',
|
||
packageType: '',
|
||
brand: '',
|
||
materialCode: '',
|
||
unitPrice: '',
|
||
priceUnit: '元/吨',
|
||
otherFeeTotal: '',
|
||
estimatedStartTime: '',
|
||
estimatedEndTime: '',
|
||
departureAddress: '',
|
||
departureName: '',
|
||
departureContact: '',
|
||
departurePhone: '',
|
||
arrivalAddress: '',
|
||
arrivalName: '',
|
||
arrivalContact: '',
|
||
arrivalPhone: '',
|
||
dispatchUserName: '',
|
||
dispatchTime: '',
|
||
attachmentsJson: '',
|
||
remark: '',
|
||
});
|
||
|
||
const taskCarrierTypes = ['承运商', '自运', '网货平台'];
|
||
|
||
export default {
|
||
components: { WaybillImportDialog },
|
||
name: 'BusinessCrudPage',
|
||
props: {
|
||
api: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
config: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
crudOption: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
crudExcelOption: {
|
||
type: Object,
|
||
default: () => ({ column: [] }),
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
List,
|
||
Location,
|
||
Search,
|
||
form: {},
|
||
suppressTransportTypeClear: false,
|
||
query: {},
|
||
loading: true,
|
||
data: [],
|
||
allDept: 0,
|
||
dialogReadonly: false,
|
||
detailBox: false,
|
||
detailLoading: false,
|
||
detailRow: {},
|
||
waybillImportBox: false,
|
||
transportPlanImportBox: false,
|
||
transportPlanImportLoading: false,
|
||
transportPlanImportFiles: [],
|
||
transportPlanImportSourceFile: null,
|
||
transportPlanImportRows: [],
|
||
transportPlanImportSelection: [],
|
||
transportPlanImportTab: 'all',
|
||
transportPlanImportEditingKey: '',
|
||
transportPlanImportEditSnapshot: null,
|
||
transportPlanImportContractOptions: [],
|
||
transportPlanImportContractLoading: false,
|
||
transportPlanImportForm: {
|
||
projectId: '',
|
||
projectName: '',
|
||
customerName: '',
|
||
contractId: '',
|
||
contractName: '',
|
||
file: null,
|
||
},
|
||
transportPlanImportRules: {
|
||
projectId: [{ required: true, message: '请选择项目', trigger: 'change' }],
|
||
customerName: [{ required: true, message: '请选择项目后带出客户', trigger: 'change' }],
|
||
contractId: [{ required: true, message: '请选择项目后带出客户合同', trigger: 'change' }],
|
||
file: [{ required: true, message: '请上传计划明细表', trigger: 'change' }],
|
||
},
|
||
draftSaveLoading: false,
|
||
crudDialogType: '',
|
||
defaultDialogButtons: null,
|
||
contractCreateActionLoading: '',
|
||
transportPlanCreateActionLoading: '',
|
||
option: this.cloneOption(this.crudOption, estimateMenuButtonCount(this.config)),
|
||
selectedProjectId: '',
|
||
projectOptions: [],
|
||
projectLoading: false,
|
||
contractOptions: [],
|
||
contractLoading: false,
|
||
shippingPlanOptions: [],
|
||
shippingPlanLoading: false,
|
||
selectedOrganizationId: '',
|
||
organizationOptions: [],
|
||
organizationFlatOptions: [],
|
||
organizationLoading: false,
|
||
addressTypeOptions,
|
||
flowBox: false,
|
||
flowUrl: '',
|
||
processInstanceId: '',
|
||
dispatchBox: false,
|
||
dispatchLoading: false,
|
||
dispatchRow: {},
|
||
dispatchRows: [],
|
||
dispatchSaving: '',
|
||
dispatchItemBox: false,
|
||
dispatchItemIndex: -1,
|
||
dispatchItemForm: defaultDispatchRow(),
|
||
dispatchItemAttachmentRows: [],
|
||
attachmentRows: [],
|
||
contractFileRows: [],
|
||
transportCargoRows: [],
|
||
transportRouteBox: false,
|
||
transportRouteLoading: false,
|
||
transportRouteQuery: {},
|
||
transportRouteRows: [],
|
||
transportRouteSelected: null,
|
||
transportRoutePage: {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
transportAddressBox: false,
|
||
transportAddressTarget: '',
|
||
transportAddressLoading: false,
|
||
transportAddressQuery: {},
|
||
transportAddressRows: [],
|
||
transportAddressSelected: null,
|
||
transportAddressPage: {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
transportStationBox: false,
|
||
transportStationTarget: '',
|
||
transportStationLoading: false,
|
||
transportStationQuery: {},
|
||
transportStationRows: [],
|
||
transportStationPage: {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
transportMapBox: false,
|
||
transportMapTarget: '',
|
||
transportMapLoading: false,
|
||
transportMapKeyword: '',
|
||
transportMapStatus: '可搜索地址或点击地图选点',
|
||
transportMapSelected: {},
|
||
transportAmap: null,
|
||
transportAmapMarker: null,
|
||
transportAmapGeocoder: null,
|
||
commonCargoBox: false,
|
||
commonCargoLoading: false,
|
||
commonCargoQuery: {},
|
||
commonCargoRows: [],
|
||
commonCargoSelected: [],
|
||
commonCargoPage: {
|
||
pageSize: 10,
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
taskCarrierTypes,
|
||
taskCarrierOptions: [],
|
||
taskCarrierLoading: false,
|
||
taskDriverOptions: [],
|
||
taskDriverLoading: false,
|
||
taskCargoOptionsMap: {},
|
||
taskCargoLoadingMap: {},
|
||
taskQuantityUnitOptions: [],
|
||
taskQuantityUnitLoading: false,
|
||
taskFeeUnitOptions: [],
|
||
taskFeeUnitLoading: false,
|
||
cargoImportBox: false,
|
||
cargoImportLoading: false,
|
||
billingPlanRows: [],
|
||
settlementRuleForm: defaultSettlementRule(),
|
||
reconciliationForm: defaultReconciliation(),
|
||
changeRecordRows: [],
|
||
billingPlanBox: false,
|
||
billingPlanMode: 'add',
|
||
billingPlanIndex: -1,
|
||
billingPlanForm: defaultBillingPlan(),
|
||
billingFeeCategoryOptions: [],
|
||
billingFeeCategoryLoading: false,
|
||
billingFeeCategoryRequest: null,
|
||
billingFeeItemOptionsMap: {},
|
||
billingFeeItemLoadingMap: {},
|
||
billingFeeItemRequestMap: {},
|
||
billingUnitOptions: [],
|
||
billingUnitLoading: false,
|
||
billingUnitRequest: null,
|
||
billingMatchBox: false,
|
||
billingMatchRuleIndex: -1,
|
||
billingMatchForm: defaultBillingRule().matchCondition,
|
||
billingMatchRegionOptions: [],
|
||
billingMatchRegionLoading: false,
|
||
billingMatchRegionRequest: null,
|
||
billingCargoTypeOptions: [],
|
||
billingCargoTypeFlatOptions: [],
|
||
billingCargoTypeLoading: false,
|
||
billingCargoTypeRequest: null,
|
||
billingPlanRules: {
|
||
planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }],
|
||
},
|
||
billingElementOptions: [
|
||
'按重量',
|
||
'按体积',
|
||
'按车辆',
|
||
'按里程',
|
||
'按吨·公里',
|
||
'固定金额(整单一口价)',
|
||
'按数量',
|
||
],
|
||
billingElementTypeMap: {
|
||
按重量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||
按体积: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||
按车辆: ['固定单价'],
|
||
按里程: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||
'按吨·公里': ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||
'固定金额(整单一口价)': ['固定一口价'],
|
||
按数量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
|
||
},
|
||
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
|
||
billCycleTypeOptions: ['固定截单日', '自然月'],
|
||
reconciliationModeOptions: ['明细逐行核对', '按账单汇总核对', '跳过自动核对'],
|
||
packageOptions,
|
||
quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'],
|
||
attachmentFileTypes: [
|
||
'pdf',
|
||
'bmp',
|
||
'jpeg',
|
||
'png',
|
||
'jpg',
|
||
'doc',
|
||
'docx',
|
||
'ppt',
|
||
'pptx',
|
||
'xlsx',
|
||
'xls',
|
||
'eml',
|
||
'msg',
|
||
'zip',
|
||
],
|
||
excelBox: false,
|
||
excelForm: {},
|
||
excelOptionConfig: this.cloneOption(this.crudExcelOption),
|
||
page: {
|
||
pageSize: 10,
|
||
pageSizes: [10, 20, 50, 100],
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
selectionList: [],
|
||
};
|
||
},
|
||
created() {
|
||
if (this.config.enableProjectSelect) {
|
||
this.loadProjectOptions();
|
||
}
|
||
if (this.config.enableOrganizationSelect) {
|
||
this.loadOrganizationOptions();
|
||
}
|
||
if (this.config.enableBillingPlan) {
|
||
this.ensureBillingFeeCategoryOptions();
|
||
this.ensureBillingUnitOptions();
|
||
}
|
||
if (this.config.enableTaskInfoForm) {
|
||
this.loadTaskQuantityUnitOptions();
|
||
this.loadTaskFeeUnitOptions();
|
||
this.ensureBillingCargoTypeOptions();
|
||
}
|
||
this.consumeTemplateCreatePayload();
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
permissionList() {
|
||
return {
|
||
addBtn: this.canCreate,
|
||
};
|
||
},
|
||
detailSections() {
|
||
return this.config.detailSections || [];
|
||
},
|
||
transportPlanImportDuplicateCount() {
|
||
return this.transportPlanImportRows.filter(row => row._duplicate).length;
|
||
},
|
||
transportPlanImportVisibleRows() {
|
||
return this.transportPlanImportTab === 'duplicate'
|
||
? this.transportPlanImportRows.filter(row => row._duplicate)
|
||
: this.transportPlanImportRows;
|
||
},
|
||
actionButtonLikeSearch() {
|
||
return this.config.actionButtonLikeSearch === true;
|
||
},
|
||
contractSelectEnabled() {
|
||
return this.config.enableContractSelect || this.config.enableTransportPlanForm;
|
||
},
|
||
shippingPlanSelectEnabled() {
|
||
return this.config.enableShippingPlanSelect === true;
|
||
},
|
||
shippingInfoFormEnabled() {
|
||
return this.config.enableShippingInfoForm || this.config.enableTransportPlanForm;
|
||
},
|
||
taskInfoFormEnabled() {
|
||
return this.config.enableTaskInfoForm === true;
|
||
},
|
||
taskEntryModeOptions() {
|
||
return [
|
||
{ label: '精简录入', value: 'simple' },
|
||
{ label: '完整录入', value: 'full' },
|
||
];
|
||
},
|
||
isTaskFullMode() {
|
||
return this.form.taskEntryMode === 'full';
|
||
},
|
||
isCarrierMode() {
|
||
return this.form.carrierType === '承运商';
|
||
},
|
||
taskCargoOptions() {
|
||
return this.getTaskCargoOptionsByPath(this.form.cargoTypePath);
|
||
},
|
||
taskCargoLoading() {
|
||
return this.isTaskCargoOptionsLoading(this.form.cargoTypePath);
|
||
},
|
||
taskCargoQuantityTotal() {
|
||
const total = this.transportCargoRows.reduce((sum, row) => {
|
||
const value = Number(row.quantity);
|
||
return Number.isFinite(value) ? sum + value : sum;
|
||
}, 0);
|
||
if (!total) return '';
|
||
return Number.isInteger(total) ? String(total) : String(Number(total.toFixed(3)));
|
||
},
|
||
dispatchPlanGoodsRows() {
|
||
return this.parseJsonArray(this.dispatchRow.goodsJson);
|
||
},
|
||
dispatchPlanCargoInfo() {
|
||
const cargoInfo = this.dispatchPlanGoodsRows
|
||
.map(row => this.formatDispatchCargoInfo({}, row))
|
||
.filter(Boolean)
|
||
.join('; ');
|
||
return cargoInfo || this.dispatchRow.goodsInfo || '';
|
||
},
|
||
dispatchSummaryTotalQuantity() {
|
||
const goodsQuantity = this.dispatchPlanGoodsRows.reduce(
|
||
(total, row) =>
|
||
total +
|
||
this.parseDispatchQuantity(row.quantity || row.cargoQuantity || row.goodsQuantity),
|
||
0
|
||
);
|
||
if (goodsQuantity > 0) return goodsQuantity;
|
||
return this.parseDispatchQuantity(
|
||
this.dispatchRow.totalQuantity ||
|
||
this.dispatchRow.totalCargoQuantity ||
|
||
this.dispatchRow.quantity
|
||
);
|
||
},
|
||
dispatchSummaryAssignedQuantity() {
|
||
return this.dispatchRows.reduce(
|
||
(total, row) => total + this.parseDispatchQuantity(row.quantity),
|
||
0
|
||
);
|
||
},
|
||
dispatchSummaryRemainingQuantity() {
|
||
const total = this.dispatchSummaryTotalQuantity;
|
||
const assigned = this.dispatchSummaryAssignedQuantity;
|
||
const remain = total - assigned;
|
||
return remain > 0 ? remain : 0;
|
||
},
|
||
dispatchSummaryUnit() {
|
||
const unitRow = this.dispatchPlanGoodsRows.find(
|
||
row => row.quantityUnit || row.goodsQuantityUnit || row.unit
|
||
);
|
||
return (
|
||
unitRow?.quantityUnit ||
|
||
unitRow?.goodsQuantityUnit ||
|
||
unitRow?.unit ||
|
||
this.dispatchRow.quantityUnit ||
|
||
this.dispatchRows.find(row => row.quantityUnit)?.quantityUnit ||
|
||
'吨'
|
||
);
|
||
},
|
||
dispatchSummaryText() {
|
||
const total = this.formatDispatchQuantity(this.dispatchSummaryTotalQuantity);
|
||
const assigned = this.formatDispatchQuantity(this.dispatchSummaryAssignedQuantity);
|
||
const remain = this.formatDispatchQuantity(this.dispatchSummaryRemainingQuantity);
|
||
return `共${total}${this.dispatchSummaryUnit} | 已调度 ${assigned}${this.dispatchSummaryUnit},剩余${remain}${this.dispatchSummaryUnit}`;
|
||
},
|
||
dispatchAttachmentRows() {
|
||
return this.parseJsonArray(this.dispatchRow.attachmentsJson);
|
||
},
|
||
dispatchRouteStart() {
|
||
const address = this.dispatchRow.departureAddress || this.dispatchRow.departureName || '';
|
||
return {
|
||
label: this.dispatchRow.departureName || '起',
|
||
address,
|
||
detail: this.dispatchRow.departureDetailAddress || address,
|
||
contact: [this.dispatchRow.departureContact, this.dispatchRow.departurePhone]
|
||
.filter(Boolean)
|
||
.join(' - '),
|
||
};
|
||
},
|
||
dispatchRouteEnd() {
|
||
const address = this.dispatchRow.arrivalAddress || this.dispatchRow.arrivalName || '';
|
||
return {
|
||
label: this.dispatchRow.arrivalName || '终',
|
||
address,
|
||
detail: this.dispatchRow.arrivalDetailAddress || address,
|
||
contact: [this.dispatchRow.arrivalContact, this.dispatchRow.arrivalPhone]
|
||
.filter(Boolean)
|
||
.join(' - '),
|
||
};
|
||
},
|
||
dispatchDialogTitle() {
|
||
return '计划调度';
|
||
},
|
||
dispatchItemDialogTitle() {
|
||
const mode = this.dispatchItemForm.taskEntryMode === 'full' ? '完整录入' : '精简录入';
|
||
return this.dispatchItemIndex < 0 ? `新增/编辑(${mode})` : `编辑调度信息(${mode})`;
|
||
},
|
||
dispatchTransportMode() {
|
||
return this.resolveTransportMode(this.dispatchItemForm.transportType);
|
||
},
|
||
dispatchStationMode() {
|
||
return ['water', 'rail', 'air'].includes(this.dispatchTransportMode);
|
||
},
|
||
dispatchStationNamePlaceholder() {
|
||
if (!this.dispatchStationMode) return '行政区划自动带出';
|
||
const labelMap = {
|
||
water: '请选择港口/码头',
|
||
rail: '请选择车站',
|
||
air: '请选择空港',
|
||
};
|
||
return labelMap[this.dispatchTransportMode] || '请选择站点';
|
||
},
|
||
dispatchAddressDetailPlaceholder() {
|
||
return this.dispatchStationMode ? '选择站点后带出详细地址' : '请输入';
|
||
},
|
||
dispatchDialogNo() {
|
||
return this.dispatchRow.planNo || this.dispatchRow.loadingNo || '';
|
||
},
|
||
dispatchDialogStatusText() {
|
||
return this.displayStatus(this.dispatchRow, this.config.statusProp || 'businessStatus');
|
||
},
|
||
taskFreightAmount() {
|
||
const quantity = Number(this.taskCargoQuantityTotal || this.form.quantity || 0);
|
||
const unitPrice = Number(this.form.unitPrice || 0);
|
||
if (!quantity || !unitPrice) return '';
|
||
const amount = quantity * unitPrice;
|
||
return Number.isInteger(amount) ? String(amount) : String(Number(amount.toFixed(2)));
|
||
},
|
||
isAdmin() {
|
||
const authority = this.userInfo && this.userInfo.authority;
|
||
return Array.isArray(authority)
|
||
? authority.includes('admin')
|
||
: String(authority || '').includes('admin');
|
||
},
|
||
canViewAllDept() {
|
||
return this.isAdmin;
|
||
},
|
||
readonlyScope() {
|
||
return this.config.enableAllDept && this.allDept === 1;
|
||
},
|
||
canCreate() {
|
||
return this.hasPermission(`${this.config.permission}_add`) && !this.readonlyScope;
|
||
},
|
||
showContractCreateActions() {
|
||
return (
|
||
this.config.enableContractCreateActions &&
|
||
this.crudDialogType === 'add' &&
|
||
!this.dialogReadonly
|
||
);
|
||
},
|
||
showTransportPlanCreateActions() {
|
||
return (
|
||
this.config.enableTransportPlanCreateActions &&
|
||
this.crudDialogType === 'add' &&
|
||
!this.dialogReadonly
|
||
);
|
||
},
|
||
organizationCascaderProps() {
|
||
return {
|
||
label: 'label',
|
||
value: 'id',
|
||
children: 'children',
|
||
checkStrictly: true,
|
||
emitPath: false,
|
||
};
|
||
},
|
||
billingMatchRegionCascaderProps() {
|
||
return {
|
||
label: 'title',
|
||
value: 'id',
|
||
children: 'children',
|
||
leaf: 'leaf',
|
||
emitPath: true,
|
||
};
|
||
},
|
||
billingCargoTypeCascaderProps() {
|
||
return {
|
||
label: 'cargoName',
|
||
value: 'id',
|
||
children: 'children',
|
||
disabled: (data, node) => node.level === 1,
|
||
leaf: 'leaf',
|
||
checkStrictly: true,
|
||
emitPath: true,
|
||
};
|
||
},
|
||
transportCargoTypeOptions() {
|
||
return this.billingCargoTypeOptions.filter(item => item.children?.length);
|
||
},
|
||
transportCargoTypeCascaderProps() {
|
||
return {
|
||
label: 'cargoName',
|
||
value: 'id',
|
||
children: 'children',
|
||
emitPath: true,
|
||
};
|
||
},
|
||
showSettlementBillCycleType() {
|
||
return this.settlementRuleForm.settlementType === '月结';
|
||
},
|
||
showSettlementBillCutoffDay() {
|
||
return (
|
||
this.showSettlementBillCycleType && this.settlementRuleForm.billCycleType === '固定截单日'
|
||
);
|
||
},
|
||
showSettlementCycleDays() {
|
||
return this.settlementRuleForm.settlementType === '固定天数周期结算';
|
||
},
|
||
canBatchComplete() {
|
||
return (
|
||
this.hasAction('batchComplete') &&
|
||
this.hasPermission(`${this.config.permission}_batch_complete`) &&
|
||
!this.readonlyScope
|
||
);
|
||
},
|
||
canRoadLoading() {
|
||
const permission =
|
||
this.config.roadLoadingPermission || `${this.config.permission}_road_loading`;
|
||
return this.config.roadLoading && this.hasPermission(permission) && !this.readonlyScope;
|
||
},
|
||
ids() {
|
||
return this.selectionList.map(item => item.id).join(',');
|
||
},
|
||
billCutoffDayOptions() {
|
||
return Array.from({ length: 31 }, (_, index) => {
|
||
const value = index + 1;
|
||
return {
|
||
label: `${value}日`,
|
||
value,
|
||
};
|
||
});
|
||
},
|
||
cycleDayOptions() {
|
||
return Array.from({ length: 365 }, (_, index) => {
|
||
const value = index + 1;
|
||
return {
|
||
label: `${value}天`,
|
||
value,
|
||
};
|
||
});
|
||
},
|
||
transportCargoQuantityTotal() {
|
||
if (!this.transportCargoRows.length) return '系统自动计算';
|
||
const total = this.transportCargoRows.reduce((sum, row) => {
|
||
const value = Number(row.quantity);
|
||
return Number.isFinite(value) ? sum + value : sum;
|
||
}, 0);
|
||
return Number.isInteger(total) ? String(total) : total.toFixed(2);
|
||
},
|
||
transportAddressSelectDisabled() {
|
||
return this.dialogReadonly || this.isBillingFieldEmpty(this.form.transportType);
|
||
},
|
||
transportMode() {
|
||
return this.resolveTransportMode(this.form.transportType);
|
||
},
|
||
transportStationMode() {
|
||
return ['water', 'rail', 'air'].includes(this.transportMode);
|
||
},
|
||
transportStationTypeLabel() {
|
||
const labelMap = {
|
||
water: '港口/码头',
|
||
rail: '铁路车站',
|
||
air: '空港机场',
|
||
};
|
||
return labelMap[this.transportMode] || '';
|
||
},
|
||
transportStationNamePlaceholder() {
|
||
if (!this.transportStationMode) return '行政区划自动带出';
|
||
const labelMap = {
|
||
water: '请选择港口/码头',
|
||
rail: '请选择车站',
|
||
air: '请选择空港',
|
||
};
|
||
return labelMap[this.transportMode] || '请选择站点';
|
||
},
|
||
transportAddressDetailPlaceholder() {
|
||
return this.transportStationMode ? '选择站点后带出详细地址' : '请输入';
|
||
},
|
||
},
|
||
watch: {
|
||
'form.transportType'(value, oldValue) {
|
||
if (!this.shippingInfoFormEnabled || this.suppressTransportTypeClear) return;
|
||
if (String(value ?? '') === String(oldValue ?? '')) return;
|
||
this.handleTransportTypeChange(value);
|
||
},
|
||
},
|
||
methods: {
|
||
cloneOption(option, menuButtonCount) {
|
||
const nextOption = {
|
||
...option,
|
||
column: (option.column || []).map(column => ({ ...column })),
|
||
};
|
||
return applyTableMenuWidth(nextOption, menuButtonCount);
|
||
},
|
||
hasPermission(code) {
|
||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||
},
|
||
hasAction(action) {
|
||
return (this.config.actions || []).includes(action);
|
||
},
|
||
statusValue(row) {
|
||
return row[this.config.statusProp || 'status'];
|
||
},
|
||
inStatus(row, statusList) {
|
||
if (!statusList || !statusList.length) {
|
||
return true;
|
||
}
|
||
const status = this.statusValue(row);
|
||
const statusText = this.config.statusTextProp ? row[this.config.statusTextProp] : '';
|
||
return statusList.includes(status) || statusList.includes(statusText);
|
||
},
|
||
isReadonlyEditAllowed(row) {
|
||
return this.inStatus(row, this.config.ignoreReadonlyEditStatus);
|
||
},
|
||
isReadonlyDeleteAllowed(row) {
|
||
return this.inStatus(row, this.config.ignoreReadonlyDeleteStatus);
|
||
},
|
||
canEdit(row) {
|
||
return (
|
||
this.hasPermission(`${this.config.permission}_edit`) &&
|
||
!this.readonlyScope &&
|
||
(!row.readonly || this.isReadonlyEditAllowed(row)) &&
|
||
this.inStatus(row, this.config.editStatus)
|
||
);
|
||
},
|
||
canDelete(row) {
|
||
return (
|
||
this.hasPermission(`${this.config.permission}_delete`) &&
|
||
!this.readonlyScope &&
|
||
(!row.readonly || this.isReadonlyDeleteAllowed(row)) &&
|
||
this.inStatus(row, this.config.deleteStatus) &&
|
||
(!this.config.deleteStage || this.config.deleteStage.includes(row.contractStage))
|
||
);
|
||
},
|
||
canCopy(row) {
|
||
return (
|
||
this.hasAction('copy') &&
|
||
this.hasPermission(`${this.config.permission}_copy`) &&
|
||
!this.readonlyScope &&
|
||
!row.readonly
|
||
);
|
||
},
|
||
canEnable(row) {
|
||
return (
|
||
this.hasAction('enable') &&
|
||
this.hasPermission(`${this.config.permission}_enable`) &&
|
||
!this.readonlyScope &&
|
||
!row.readonly &&
|
||
[0, 2].includes(row.status)
|
||
);
|
||
},
|
||
canDisable(row) {
|
||
return (
|
||
this.hasAction('disable') &&
|
||
this.hasPermission(`${this.config.permission}_disable`) &&
|
||
!this.readonlyScope &&
|
||
!row.readonly &&
|
||
row.status === 1
|
||
);
|
||
},
|
||
canCancel(row) {
|
||
if (
|
||
!this.hasAction('cancel') ||
|
||
!this.hasPermission(`${this.config.permission}_cancel`) ||
|
||
this.readonlyScope ||
|
||
row.readonly
|
||
) {
|
||
return false;
|
||
}
|
||
const status = this.statusValue(row);
|
||
return this.config.permission === 'transport_plan'
|
||
? ['waiting_dispatch', 'dispatching'].includes(status)
|
||
: ['pending', 'processing'].includes(status);
|
||
},
|
||
canComplete(row) {
|
||
if (
|
||
!this.hasAction('complete') ||
|
||
!this.hasPermission(`${this.config.permission}_complete`) ||
|
||
this.readonlyScope ||
|
||
row.readonly
|
||
) {
|
||
return false;
|
||
}
|
||
const status = this.statusValue(row);
|
||
return this.config.permission === 'transport_plan'
|
||
? status === 'dispatching'
|
||
: status === 'processing';
|
||
},
|
||
canReassign(row) {
|
||
return (
|
||
this.hasAction('reassign') &&
|
||
this.hasPermission(`${this.config.permission}_reassign`) &&
|
||
!this.readonlyScope &&
|
||
!row.readonly &&
|
||
this.statusValue(row) === 'pending'
|
||
);
|
||
},
|
||
customOperations(row) {
|
||
return (this.config.operations || []).filter(operation => this.canOperation(operation, row));
|
||
},
|
||
menuActions(row) {
|
||
const actions = [];
|
||
if (this.canCopy(row)) {
|
||
actions.push({ key: 'copy', label: '复制', action: 'copy' });
|
||
}
|
||
if (this.canEdit(row)) {
|
||
actions.push({
|
||
key: 'edit',
|
||
label: this.editLabel(row),
|
||
action: 'edit',
|
||
});
|
||
}
|
||
this.customOperations(row).forEach(operation => {
|
||
actions.push({
|
||
...operation,
|
||
key: `${operation.action}-${operation.label}`,
|
||
});
|
||
});
|
||
if (this.canDelete(row)) {
|
||
actions.push({ key: 'delete', label: '删除', action: 'delete', type: 'danger' });
|
||
}
|
||
return actions;
|
||
},
|
||
editLabel(row) {
|
||
const status = this.statusValue(row);
|
||
return this.config.editLabelStatusMap?.[status] || '编辑';
|
||
},
|
||
handleMenuAction(action, row, index) {
|
||
if (action.action === 'copy') {
|
||
this.handleCopy(row);
|
||
return;
|
||
}
|
||
if (action.action === 'edit' || action.action === 'attachmentUpload') {
|
||
this.$refs.crud.rowEdit(row, index);
|
||
return;
|
||
}
|
||
if (action.action === 'delete') {
|
||
this.rowDel(row);
|
||
return;
|
||
}
|
||
this.handleCustomOperation(action, row);
|
||
},
|
||
consumeTemplateCreatePayload() {
|
||
const target = this.config.templateCreateTarget;
|
||
if (!target) return;
|
||
const storageKey = 'business-template-create';
|
||
const raw = sessionStorage.getItem(storageKey);
|
||
if (!raw) return;
|
||
let payload;
|
||
try {
|
||
payload = JSON.parse(raw);
|
||
} catch (error) {
|
||
sessionStorage.removeItem(storageKey);
|
||
return;
|
||
}
|
||
if (payload.target !== target) return;
|
||
sessionStorage.removeItem(storageKey);
|
||
this.$nextTick(() => {
|
||
this.$refs.crud.rowAdd();
|
||
this.$nextTick(() => {
|
||
const row = payload.data || {};
|
||
this.suppressTransportTypeClear = true;
|
||
Object.assign(this.form, row);
|
||
this.$nextTick(() => {
|
||
this.suppressTransportTypeClear = false;
|
||
});
|
||
this.form.planName = this.form.planName || this.form.templateName || '';
|
||
this.selectedProjectId = this.form.projectId || '';
|
||
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
|
||
if (this.contractSelectEnabled) {
|
||
this.syncCurrentContractOption();
|
||
this.loadContractOptionsForProject();
|
||
}
|
||
if (this.config.enableProjectSelect) this.syncCurrentProjectOption();
|
||
if (this.config.enableTransportPlanForm) this.initTransportPlanFormRows();
|
||
if (this.taskInfoFormEnabled) this.initTaskInfoForm();
|
||
});
|
||
});
|
||
},
|
||
canOperation(operation, row) {
|
||
const permission = operation.permission || `${this.config.permission}_${operation.action}`;
|
||
if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) {
|
||
return false;
|
||
}
|
||
const prop = operation.statusProp || this.config.statusProp || 'status';
|
||
const statusList = operation.status || [];
|
||
if (statusList.length && !statusList.includes(row[prop])) {
|
||
return false;
|
||
}
|
||
if (operation.stage && !operation.stage.includes(row.contractStage)) {
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
showDetailButton(row) {
|
||
if (!this.config.detailButton || !this.hasPermission(`${this.config.permission}_view`))
|
||
return false;
|
||
const status = this.statusValue(row);
|
||
return status !== 'draft';
|
||
},
|
||
formatDetailValue(row, prop) {
|
||
if (!row) return '-';
|
||
if (prop === 'businessStatus') return this.displayStatus(row, prop);
|
||
const column = this.findColumn(this.option.column, prop);
|
||
if (column?.formatter) return column.formatter(row, {}, row[prop]);
|
||
if (column?.dicData) {
|
||
const item = column.dicData.find(dic => String(dic.value) === String(row[prop]));
|
||
if (item) return item.label;
|
||
}
|
||
const value = row[prop];
|
||
return value === undefined || value === null || value === '' ? '-' : value;
|
||
},
|
||
openDetail(row) {
|
||
this.detailBox = true;
|
||
this.detailLoading = true;
|
||
this.detailRow = { ...row };
|
||
const request =
|
||
typeof this.api.getDetail === 'function'
|
||
? this.api.getDetail(row.id)
|
||
: Promise.resolve({ data: { data: row } });
|
||
request
|
||
.then(res => {
|
||
const detail = res?.data?.data || res?.data || row;
|
||
this.detailRow = detail;
|
||
this.applyFormDetail(detail);
|
||
})
|
||
.finally(() => {
|
||
this.detailLoading = false;
|
||
});
|
||
},
|
||
displayStatus(row, prop) {
|
||
const textProp = prop === this.config.statusProp ? this.config.statusTextProp : '';
|
||
if (textProp && row[textProp]) {
|
||
return row[textProp];
|
||
}
|
||
const column = this.findColumn(this.option.column, prop);
|
||
const item = column && (column.dicData || []).find(dic => dic.value === row[prop]);
|
||
return item ? item.label : row[prop] || '未知';
|
||
},
|
||
syncContractNoDisabled(type) {
|
||
if (!this.config.enableContractForm) return;
|
||
const column = this.findColumn?.(this.option.column, 'contractNo');
|
||
if (column) {
|
||
column.disabled = type !== 'add';
|
||
}
|
||
},
|
||
syncCustomCreateDialogButtons(type) {
|
||
if (
|
||
!this.config.enableContractCreateActions &&
|
||
!this.config.enableTransportPlanCreateActions
|
||
) {
|
||
return;
|
||
}
|
||
if (!this.defaultDialogButtons) {
|
||
this.defaultDialogButtons = {
|
||
saveBtn: this.option.saveBtn,
|
||
cancelBtn: this.option.cancelBtn,
|
||
};
|
||
}
|
||
const customCreate = type === 'add';
|
||
this.option.saveBtn = customCreate ? false : this.defaultDialogButtons.saveBtn;
|
||
this.option.cancelBtn = customCreate ? false : this.defaultDialogButtons.cancelBtn;
|
||
},
|
||
statusTagType(status) {
|
||
if (
|
||
[
|
||
1,
|
||
'pending',
|
||
'waiting_dispatch',
|
||
'processing',
|
||
'dispatching',
|
||
'approved',
|
||
'change_approved',
|
||
].includes(status)
|
||
) {
|
||
return 'success';
|
||
}
|
||
if ([2, 'cancelled', 'withdrawn', 'draft'].includes(status)) {
|
||
return 'info';
|
||
}
|
||
if (['rejected', 'change_rejected'].includes(status)) {
|
||
return 'danger';
|
||
}
|
||
if (status === 'completed') {
|
||
return 'primary';
|
||
}
|
||
return 'warning';
|
||
},
|
||
stageTagType(stage) {
|
||
if (stage === 'formal') {
|
||
return 'success';
|
||
}
|
||
if (stage === 'temporary') {
|
||
return 'warning';
|
||
}
|
||
if (stage === 'terminated') {
|
||
return 'danger';
|
||
}
|
||
return 'info';
|
||
},
|
||
isMonthlySettlement(rule = this.settlementRuleForm) {
|
||
return rule.settlementType === '月结';
|
||
},
|
||
isFixedBillCutoffSettlement(rule = this.settlementRuleForm) {
|
||
return this.isMonthlySettlement(rule) && rule.billCycleType === '固定截单日';
|
||
},
|
||
isFixedCycleSettlement(rule = this.settlementRuleForm) {
|
||
return rule.settlementType === '固定天数周期结算';
|
||
},
|
||
handleSettlementTypeChange(value) {
|
||
if (value === '月结') {
|
||
if (!this.settlementRuleForm.billCycleType) {
|
||
this.settlementRuleForm.billCycleType = '固定截单日';
|
||
}
|
||
if (this.settlementRuleForm.billCycleType === '固定截单日') {
|
||
this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25;
|
||
}
|
||
this.settlementRuleForm.cycleDays = '';
|
||
return;
|
||
}
|
||
this.settlementRuleForm.billCycleType = '';
|
||
this.settlementRuleForm.billCutoffDay = '';
|
||
if (value !== '固定天数周期结算') {
|
||
this.settlementRuleForm.cycleDays = '';
|
||
}
|
||
},
|
||
handleSettlementBillCycleTypeChange(value) {
|
||
if (value === '固定截单日') {
|
||
this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25;
|
||
return;
|
||
}
|
||
this.settlementRuleForm.billCutoffDay = '';
|
||
},
|
||
normalizeSettlementRule(rule = this.settlementRuleForm) {
|
||
const nextRule = {
|
||
...defaultSettlementRule(),
|
||
...(rule || {}),
|
||
};
|
||
if (!this.isMonthlySettlement(nextRule)) {
|
||
nextRule.billCycleType = '';
|
||
nextRule.billCutoffDay = '';
|
||
} else if (!this.isFixedBillCutoffSettlement(nextRule)) {
|
||
nextRule.billCutoffDay = '';
|
||
}
|
||
if (!this.isFixedCycleSettlement(nextRule)) {
|
||
nextRule.cycleDays = '';
|
||
}
|
||
return nextRule;
|
||
},
|
||
validateSettlementRule() {
|
||
const rule = this.normalizeSettlementRule(this.settlementRuleForm);
|
||
if (this.isBillingFieldEmpty(rule.billStartDate)) {
|
||
this.$message.warning('请选择账单起始日期');
|
||
return false;
|
||
}
|
||
if (this.isBillingFieldEmpty(rule.settlementType)) {
|
||
this.$message.warning('请选择结算类型');
|
||
return false;
|
||
}
|
||
if (this.isMonthlySettlement(rule) && this.isBillingFieldEmpty(rule.billCycleType)) {
|
||
this.$message.warning('请选择账单周期类型');
|
||
return false;
|
||
}
|
||
if (this.isFixedBillCutoffSettlement(rule)) {
|
||
const billCutoffDay = Number(rule.billCutoffDay);
|
||
if (!Number.isInteger(billCutoffDay) || billCutoffDay < 1 || billCutoffDay > 31) {
|
||
this.$message.warning('账单截单日必须为1~31的整数');
|
||
return false;
|
||
}
|
||
}
|
||
if (this.isFixedCycleSettlement(rule)) {
|
||
const cycleDays = Number(rule.cycleDays);
|
||
if (!Number.isInteger(cycleDays) || cycleDays < 1 || cycleDays > 365) {
|
||
this.$message.warning('周期天数必须为1~365的整数');
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
normalizeRow(row) {
|
||
const submitRow = { ...row };
|
||
if (this.config.enableTransportPlanForm) {
|
||
if (!this.validateTransportPlanForm(submitRow)) return false;
|
||
submitRow.goodsJson = JSON.stringify(this.transportCargoRows);
|
||
}
|
||
if (this.taskInfoFormEnabled) {
|
||
if (!this.validateTaskInfoForm(submitRow)) return false;
|
||
this.syncTaskInfoToRow(submitRow);
|
||
}
|
||
if (this.config.enableAttachmentTable) {
|
||
submitRow.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||
}
|
||
if (this.config.enableContractFileUpload) {
|
||
submitRow.contractFileJson = JSON.stringify(this.contractFileRows);
|
||
}
|
||
if (this.config.enableBillingPlan) {
|
||
submitRow.billingPlanJson = JSON.stringify(this.billingPlanRows);
|
||
}
|
||
if (this.config.enableSettlementRule) {
|
||
if (!this.validateSettlementRule()) return false;
|
||
submitRow.settlementRuleJson = JSON.stringify(this.normalizeSettlementRule());
|
||
}
|
||
if (this.config.enableReconciliation) {
|
||
submitRow.reconciliationJson = JSON.stringify(this.reconciliationForm);
|
||
}
|
||
if (this.config.enableChangeRecord) {
|
||
submitRow.changeRecordJson = JSON.stringify(this.changeRecordRows);
|
||
}
|
||
if (this.config.saveAsDraft) {
|
||
submitRow.approvalStatus = 'draft';
|
||
}
|
||
delete submitRow.basicInfoTitle;
|
||
delete submitRow.templateInfoTitle;
|
||
delete submitRow.shippingInfoTitle;
|
||
delete submitRow.taskInfoTitle;
|
||
delete submitRow.taskInfoForm;
|
||
delete submitRow.goodsInfoTitle;
|
||
delete submitRow.contractFileTitle;
|
||
delete submitRow.billingInfoTitle;
|
||
delete submitRow.settlementRuleTitle;
|
||
delete submitRow.reconciliationTitle;
|
||
delete submitRow.attachmentTitle;
|
||
delete submitRow.changeRecordTitle;
|
||
delete submitRow.contractPeriod;
|
||
Object.keys(submitRow).forEach(key => {
|
||
if (typeof submitRow[key] === 'string') {
|
||
submitRow[key] = submitRow[key].trim();
|
||
}
|
||
});
|
||
return submitRow;
|
||
},
|
||
validateTransportPlanForm(row) {
|
||
const requiredFields = this.config.transportFormRequiredFields || [
|
||
['projectName', '项目'],
|
||
['contractName', '客户合同'],
|
||
['planName', '计划名称'],
|
||
['transportType', '运输方式'],
|
||
['planStartDate', '计划开始日期'],
|
||
['planEndDate', '计划结束日期'],
|
||
['departureAddress', '发货地址'],
|
||
['arrivalAddress', '收货地址'],
|
||
];
|
||
const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
|
||
if (emptyField) {
|
||
this.$message.warning(`请选择或输入${emptyField[1]}`);
|
||
return false;
|
||
}
|
||
if (row.planStartDate && row.planEndDate && row.planEndDate < row.planStartDate) {
|
||
this.$message.warning('计划结束日期不能早于计划开始日期');
|
||
return false;
|
||
}
|
||
if (row.remark && row.remark.length > 200) {
|
||
this.$message.warning('备注不能超过200个字符');
|
||
return false;
|
||
}
|
||
for (const [index, cargo] of this.transportCargoRows.entries()) {
|
||
if (!cargo.cargoType) {
|
||
this.$message.warning(`第${index + 1}行货物类型不能为空`);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
stopSubmitLoading(loading) {
|
||
if (typeof loading === 'function') {
|
||
loading();
|
||
}
|
||
},
|
||
rowSave(row, done, loading) {
|
||
const submitRow = this.normalizeRow(row);
|
||
if (!submitRow) {
|
||
this.stopSubmitLoading(loading);
|
||
return;
|
||
}
|
||
this.getSaveRequest()(submitRow).then(
|
||
() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
done();
|
||
},
|
||
error => {
|
||
window.console.log(error);
|
||
this.stopSubmitLoading(loading);
|
||
}
|
||
);
|
||
},
|
||
rowUpdate(row, index, done, loading) {
|
||
const submitRow = this.normalizeRow(row);
|
||
if (!submitRow) {
|
||
this.stopSubmitLoading(loading);
|
||
return;
|
||
}
|
||
this.getSaveRequest()(submitRow).then(
|
||
() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
done();
|
||
},
|
||
error => {
|
||
window.console.log(error);
|
||
this.stopSubmitLoading(loading);
|
||
}
|
||
);
|
||
},
|
||
getSaveRequest() {
|
||
if (this.config.saveAsDraft && typeof this.api.saveDraft === 'function') {
|
||
return this.api.saveDraft;
|
||
}
|
||
return this.api.submit;
|
||
},
|
||
buildContractCreateSubmitRow(action) {
|
||
const contractStage = action === 'draft' ? 'draft' : 'temporary';
|
||
const submitRow = this.normalizeRow({
|
||
...this.form,
|
||
contractStage,
|
||
approvalStatus: 'draft',
|
||
});
|
||
return submitRow;
|
||
},
|
||
validateContractCreateAction(action, row) {
|
||
if (!row) return false;
|
||
if (this.isBillingFieldEmpty(row.contractName)) {
|
||
this.$message.warning('请输入合同名称');
|
||
return false;
|
||
}
|
||
if (this.isBillingFieldEmpty(row.contractCategory)) {
|
||
this.$message.warning('请选择合同类别');
|
||
return false;
|
||
}
|
||
if (['temporary', 'formal'].includes(action) && this.isBillingFieldEmpty(row.signType)) {
|
||
this.$message.warning('请选择签约类型');
|
||
return false;
|
||
}
|
||
if (action !== 'formal') return true;
|
||
const formalFields = [
|
||
['partyA', '甲方'],
|
||
['partyB', '乙方'],
|
||
['startDate', '合同期限'],
|
||
['signDate', '签订日期'],
|
||
];
|
||
const emptyField = formalFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
|
||
if (emptyField) {
|
||
this.$message.warning(`请选择或输入${emptyField[1]}`);
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
extractSavedContract(res, fallback = {}) {
|
||
const data = res?.data?.data || res?.data || {};
|
||
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
||
return {
|
||
...fallback,
|
||
...data,
|
||
};
|
||
}
|
||
return fallback;
|
||
},
|
||
getSavedContractId(savedContract) {
|
||
const id = savedContract?.id || this.form.id;
|
||
if (!id) {
|
||
this.$message.warning('合同保存成功但未返回主键,无法继续流转');
|
||
return '';
|
||
}
|
||
return id;
|
||
},
|
||
handleContractCreateAction(action) {
|
||
if (!this.config.enableContractCreateActions || this.contractCreateActionLoading) return;
|
||
const submitRow = this.buildContractCreateSubmitRow(action);
|
||
if (!this.validateContractCreateAction(action, submitRow)) return;
|
||
this.contractCreateActionLoading = action;
|
||
this.api
|
||
.saveDraft(submitRow)
|
||
.then(res => {
|
||
const savedContract = this.extractSavedContract(res, submitRow);
|
||
this.form.id = savedContract.id || this.form.id;
|
||
if (action === 'draft') {
|
||
return savedContract;
|
||
}
|
||
const id = this.getSavedContractId(savedContract);
|
||
if (!id) return Promise.reject(new Error('合同主键为空'));
|
||
if (action === 'formal') {
|
||
return this.api.submitFormal(id).then(() => savedContract);
|
||
}
|
||
return savedContract;
|
||
})
|
||
.then(() => {
|
||
const messageMap = {
|
||
draft: '保存成功',
|
||
temporary: '临时合同提交成功',
|
||
formal: '正式合同提交成功',
|
||
};
|
||
this.$message.success(messageMap[action]);
|
||
this.onLoad(this.page, this.query);
|
||
this.closeCrudDialog();
|
||
})
|
||
.catch(error => {
|
||
if (error?.message !== '合同主键为空') {
|
||
window.console.log(error);
|
||
}
|
||
})
|
||
.finally(() => {
|
||
this.contractCreateActionLoading = '';
|
||
});
|
||
},
|
||
handleTransportPlanCreateAction(action) {
|
||
if (!this.config.enableTransportPlanCreateActions || this.transportPlanCreateActionLoading) {
|
||
return;
|
||
}
|
||
const statusProp = this.config.statusProp || 'businessStatus';
|
||
const submitRow = this.normalizeRow({
|
||
...this.form,
|
||
[statusProp]: action === 'draft' ? 'draft' : 'waiting_dispatch',
|
||
});
|
||
if (!submitRow) return;
|
||
this.transportPlanCreateActionLoading = action;
|
||
this.api
|
||
.submit(submitRow)
|
||
.then(() => {
|
||
this.$message.success(action === 'draft' ? '暂存成功' : '创建成功');
|
||
this.onLoad(this.page, this.query);
|
||
this.closeCrudDialog();
|
||
})
|
||
.finally(() => {
|
||
this.transportPlanCreateActionLoading = '';
|
||
});
|
||
},
|
||
handleDraftSave() {
|
||
const request =
|
||
typeof this.api.saveDraft === 'function' ? this.api.saveDraft : this.api.submit;
|
||
const submitRow = this.normalizeRow(this.form);
|
||
if (!submitRow) return;
|
||
this.draftSaveLoading = true;
|
||
request(submitRow)
|
||
.then(() => {
|
||
this.$message.success('保存成功');
|
||
this.onLoad(this.page, this.query);
|
||
this.closeCrudDialog();
|
||
})
|
||
.finally(() => {
|
||
this.draftSaveLoading = false;
|
||
});
|
||
},
|
||
closeCrudDialog() {
|
||
const crud = this.$refs.crud;
|
||
if (crud && typeof crud.closeDialog === 'function') {
|
||
crud.closeDialog();
|
||
}
|
||
},
|
||
rowDel(row) {
|
||
this.$confirm('确定将选择数据删除?', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => this.api.remove(row.id))
|
||
.then(res => {
|
||
this.afterRemove(res.data.data);
|
||
});
|
||
},
|
||
handleDelete() {
|
||
if (this.selectionList.length === 0) {
|
||
this.$message.warning('请选择至少一条数据');
|
||
return;
|
||
}
|
||
const selection = this.selectionList.filter(item => this.canDelete(item));
|
||
if (!selection.length) {
|
||
this.$message.warning('所选数据当前状态不可删除');
|
||
return;
|
||
}
|
||
this.$confirm('确定将选择数据删除?', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => this.api.remove(selection.map(item => item.id).join(',')))
|
||
.then(res => {
|
||
this.afterRemove(res.data.data);
|
||
});
|
||
},
|
||
afterRemove(result = {}) {
|
||
this.onLoad(this.page);
|
||
const successCount = result.successCount || 0;
|
||
const skippedCount = result.skippedCount || 0;
|
||
if (skippedCount) {
|
||
this.$message.warning(`成功删除${successCount}条,因业务引用跳过${skippedCount}条`);
|
||
} else {
|
||
this.$message({ type: 'success', message: '删除成功!' });
|
||
}
|
||
this.selectionClear();
|
||
},
|
||
beforeOpen(done, type) {
|
||
this.crudDialogType = type;
|
||
this.syncCustomCreateDialogButtons(type);
|
||
this.dialogReadonly = type === 'view';
|
||
this.syncContractNoDisabled(type);
|
||
if (type === 'add') {
|
||
this.form = {
|
||
...(this.config.defaultForm || {}),
|
||
};
|
||
this.form.taskEntryMode = 'simple';
|
||
this.selectedProjectId = '';
|
||
this.selectedOrganizationId = '';
|
||
this.attachmentRows = [];
|
||
this.contractFileRows = [];
|
||
this.shippingPlanOptions = [];
|
||
if (this.contractSelectEnabled) {
|
||
this.contractOptions = [];
|
||
this.form.contractId = '';
|
||
this.form.contractName = '';
|
||
}
|
||
if (this.shippingPlanSelectEnabled) {
|
||
this.clearShippingPlan();
|
||
}
|
||
this.transportCargoRows = [];
|
||
this.billingPlanRows = [];
|
||
this.settlementRuleForm = defaultSettlementRule();
|
||
this.reconciliationForm = defaultReconciliation();
|
||
this.changeRecordRows = [];
|
||
this.applyCurrentUserHandler();
|
||
this.initTaskInfoForm();
|
||
this.initTransportPlanFormRows();
|
||
this.syncContractPeriod(false);
|
||
this.syncBillingPlanJson();
|
||
if (this.config.enableTemplateCodePreview && typeof this.api.nextCode === 'function') {
|
||
this.api
|
||
.nextCode()
|
||
.then(res => {
|
||
this.form.templateCode = res.data?.data || '';
|
||
})
|
||
.finally(done);
|
||
return;
|
||
}
|
||
done();
|
||
return;
|
||
}
|
||
if (['edit', 'view'].includes(type)) {
|
||
this.api.getDetail(this.form.id).then(res => {
|
||
this.applyFormDetail(res.data.data || {});
|
||
done();
|
||
});
|
||
return;
|
||
}
|
||
done();
|
||
},
|
||
applyFormDetail(row) {
|
||
this.suppressTransportTypeClear = true;
|
||
this.form = row || {};
|
||
this.$nextTick(() => {
|
||
this.suppressTransportTypeClear = false;
|
||
});
|
||
this.selectedProjectId = this.form.projectId || '';
|
||
this.selectedOrganizationId = this.form.organizationId || '';
|
||
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
|
||
this.contractFileRows = this.parseJsonArray(this.form.contractFileJson);
|
||
this.initTransportPlanFormRows();
|
||
this.billingPlanRows = this.parseJsonArray(this.form.billingPlanJson);
|
||
this.settlementRuleForm = this.normalizeSettlementRule({
|
||
...defaultSettlementRule(),
|
||
...this.parseJsonObject(this.form.settlementRuleJson),
|
||
});
|
||
this.reconciliationForm = {
|
||
...defaultReconciliation(),
|
||
...this.parseJsonObject(this.form.reconciliationJson),
|
||
};
|
||
this.changeRecordRows = this.parseJsonArray(this.form.changeRecordJson);
|
||
this.applyCurrentUserHandler();
|
||
this.initTaskInfoForm();
|
||
this.syncContractPeriod(false);
|
||
this.syncBillingPlanJson();
|
||
if (this.config.enableOrganizationSelect) {
|
||
this.syncCurrentOrganizationOption();
|
||
}
|
||
if (this.config.enableProjectSelect) {
|
||
this.form.usedFundLimit = 0;
|
||
this.form.remainingFundLimit = 0;
|
||
this.syncCurrentProjectOption();
|
||
}
|
||
if (this.contractSelectEnabled) {
|
||
this.syncCurrentContractOption();
|
||
}
|
||
if (this.shippingPlanSelectEnabled) {
|
||
this.syncCurrentShippingPlanOption();
|
||
}
|
||
},
|
||
parseJsonArray(value) {
|
||
if (!value) return [];
|
||
if (Array.isArray(value)) return value;
|
||
try {
|
||
const list = JSON.parse(value);
|
||
return Array.isArray(list) ? list : [];
|
||
} catch (error) {
|
||
return [];
|
||
}
|
||
},
|
||
parseJsonObject(value) {
|
||
if (!value) return {};
|
||
if (typeof value === 'object' && !Array.isArray(value)) return value;
|
||
try {
|
||
const data = JSON.parse(value);
|
||
return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
|
||
} catch (error) {
|
||
return {};
|
||
}
|
||
},
|
||
parseDispatchQuantity(value) {
|
||
if (value === undefined || value === null || value === '') return 0;
|
||
const text = String(value).replace(/,/g, '');
|
||
const match = text.match(/-?\d+(\.\d+)?/);
|
||
if (!match) return 0;
|
||
const number = Number(match[0]);
|
||
return Number.isFinite(number) ? number : 0;
|
||
},
|
||
formatDispatchQuantity(value) {
|
||
const number = Number(value || 0);
|
||
if (!Number.isFinite(number)) return '0';
|
||
const fixed = Math.round((number + Number.EPSILON) * 1000) / 1000;
|
||
return Number.isInteger(fixed) ? String(fixed) : String(fixed).replace(/\.?0+$/, '');
|
||
},
|
||
loadProjectOptions() {
|
||
if (!this.config.enableProjectSelect || this.projectLoading) return;
|
||
this.projectLoading = true;
|
||
getProjectList(1, 9999, {})
|
||
.then(res => {
|
||
this.projectOptions = extractRecords(res);
|
||
this.syncCurrentProjectOption();
|
||
})
|
||
.finally(() => {
|
||
this.projectLoading = false;
|
||
});
|
||
},
|
||
syncCurrentProjectOption() {
|
||
if (!this.form.projectId || !this.form.projectName) return;
|
||
const exists = this.projectOptions.some(
|
||
item => String(item.id) === String(this.form.projectId)
|
||
);
|
||
if (!exists) {
|
||
this.projectOptions.unshift({
|
||
id: this.form.projectId,
|
||
projectName: this.form.projectName,
|
||
projectCode: this.form.projectCode,
|
||
undertakeDeptName: this.form.undertakeDeptName,
|
||
fundLimit: this.form.projectFundLimit,
|
||
});
|
||
}
|
||
},
|
||
handleProjectChange(projectId) {
|
||
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
||
if (!project) {
|
||
this.form.projectId = '';
|
||
this.form.projectName = '';
|
||
this.form.projectCode = '';
|
||
this.form.undertakeDeptName = '';
|
||
this.form.projectFundLimit = '';
|
||
this.form.usedFundLimit = 0;
|
||
this.form.remainingFundLimit = 0;
|
||
if (this.contractSelectEnabled) {
|
||
this.form.contractId = '';
|
||
this.form.contractName = '';
|
||
this.contractOptions = [];
|
||
}
|
||
if (this.shippingPlanSelectEnabled) {
|
||
this.clearShippingPlan();
|
||
this.shippingPlanOptions = [];
|
||
}
|
||
return;
|
||
}
|
||
this.form.projectId = project.id;
|
||
this.form.projectName = project.projectName || '';
|
||
this.form.projectCode = project.projectCode || '';
|
||
this.form.undertakeDeptName = project.undertakeDeptName || '';
|
||
this.form.projectFundLimit = project.fundLimit || project.projectFundLimit || 0;
|
||
this.form.usedFundLimit = 0;
|
||
this.form.remainingFundLimit = 0;
|
||
if (this.contractSelectEnabled || this.config.enableTransportPlanForm) {
|
||
this.form.customerName =
|
||
project.customerName ||
|
||
project.customerNames ||
|
||
project.customer ||
|
||
this.form.customerName ||
|
||
'';
|
||
}
|
||
if (this.config.enableTransportPlanForm) {
|
||
this.form.transportType =
|
||
project.transportType ||
|
||
project.transportMode ||
|
||
project.transportWay ||
|
||
this.form.transportType ||
|
||
'';
|
||
}
|
||
if (this.contractSelectEnabled) {
|
||
this.form.contractId = '';
|
||
this.form.contractName = '';
|
||
this.loadContractOptionsForProject(true);
|
||
}
|
||
if (this.shippingPlanSelectEnabled) {
|
||
this.clearShippingPlan();
|
||
this.loadShippingPlanOptions();
|
||
}
|
||
this.$refs.crud?.validateField?.('projectName');
|
||
},
|
||
loadContractOptionsForProject(autoPick = false) {
|
||
if (!this.contractSelectEnabled || !this.form.projectId || this.contractLoading) {
|
||
return Promise.resolve([]);
|
||
}
|
||
this.contractLoading = true;
|
||
return getContractList(1, 9999, {
|
||
projectId: this.form.projectId,
|
||
projectName: this.form.projectName,
|
||
})
|
||
.then(res => {
|
||
this.contractOptions = extractRecords(res);
|
||
this.syncCurrentContractOption();
|
||
if (autoPick && this.contractOptions.length) {
|
||
this.applyContract(this.contractOptions[0]);
|
||
}
|
||
return this.contractOptions;
|
||
})
|
||
.finally(() => {
|
||
this.contractLoading = false;
|
||
});
|
||
},
|
||
syncCurrentContractOption() {
|
||
if (!this.form.contractId || !this.form.contractName) return;
|
||
const exists = this.contractOptions.some(
|
||
item => String(item.id) === String(this.form.contractId)
|
||
);
|
||
if (!exists) {
|
||
this.contractOptions.unshift({
|
||
id: this.form.contractId,
|
||
contractName: this.form.contractName,
|
||
});
|
||
}
|
||
},
|
||
handleContractChange(contractId) {
|
||
const contract = this.contractOptions.find(item => String(item.id) === String(contractId));
|
||
if (!contract) {
|
||
this.form.contractId = '';
|
||
this.form.contractName = '';
|
||
return;
|
||
}
|
||
this.applyContract(contract);
|
||
},
|
||
applyContract(contract = {}) {
|
||
this.form.contractId = contract.id || '';
|
||
this.form.contractName = contract.contractName || '';
|
||
this.form.customerName =
|
||
contract.customerName || contract.customerNames || this.form.customerName || '';
|
||
this.$refs.crud?.validateField?.('contractName');
|
||
},
|
||
loadShippingPlanOptions() {
|
||
if (!this.shippingPlanSelectEnabled || this.shippingPlanLoading) {
|
||
return Promise.resolve([]);
|
||
}
|
||
this.shippingPlanLoading = true;
|
||
return getTransportPlanList(1, 9999, {
|
||
projectId: this.form.projectId,
|
||
projectName: this.form.projectName,
|
||
})
|
||
.then(res => {
|
||
this.shippingPlanOptions = extractRecords(res);
|
||
this.syncCurrentShippingPlanOption();
|
||
return this.shippingPlanOptions;
|
||
})
|
||
.finally(() => {
|
||
this.shippingPlanLoading = false;
|
||
});
|
||
},
|
||
syncCurrentShippingPlanOption() {
|
||
if (!this.form.planName) return;
|
||
const exists = this.shippingPlanOptions.some(
|
||
item =>
|
||
String(item.id) === String(this.form.planId) ||
|
||
String(item.planName) === String(this.form.planName)
|
||
);
|
||
if (!exists) {
|
||
this.shippingPlanOptions.unshift({
|
||
id: this.form.planId || this.form.planName,
|
||
planName: this.form.planName,
|
||
});
|
||
}
|
||
},
|
||
handleShippingPlanChange(planName) {
|
||
const plan = this.shippingPlanOptions.find(
|
||
item =>
|
||
String(item.planName) === String(planName) || String(item.id) === String(this.form.planId)
|
||
);
|
||
if (!plan) {
|
||
this.clearShippingPlan();
|
||
return;
|
||
}
|
||
this.applyShippingPlan(plan);
|
||
},
|
||
applyShippingPlan(plan = {}) {
|
||
this.form.planId = plan.id || '';
|
||
this.form.planName = plan.planName || '';
|
||
this.$refs.crud?.validateField?.('planName');
|
||
},
|
||
clearShippingPlan() {
|
||
this.form.planId = '';
|
||
this.form.planName = '';
|
||
},
|
||
initTaskInfoForm() {
|
||
if (!this.taskInfoFormEnabled) return;
|
||
const taskInfo = this.parseJsonObject(this.form.taskInfoJson);
|
||
const carrierRows = this.parseJsonArray(this.form.carrierJson);
|
||
const carrierInfo = carrierRows[0] || this.parseJsonObject(this.form.carrierJson);
|
||
const goodsRows = this.parseJsonArray(this.form.goodsJson);
|
||
const goodsInfo = goodsRows[0] || {};
|
||
this.form.taskEntryMode =
|
||
this.form.taskEntryMode || taskInfo.taskEntryMode || carrierInfo.taskEntryMode || 'simple';
|
||
this.form.carrierType =
|
||
this.form.carrierType ||
|
||
taskInfo.carrierType ||
|
||
carrierInfo.carrierType ||
|
||
carrierInfo.type ||
|
||
'承运商';
|
||
this.form.carrierName =
|
||
this.form.carrierName ||
|
||
taskInfo.carrierName ||
|
||
carrierInfo.carrierName ||
|
||
carrierInfo.customerName ||
|
||
carrierInfo.name ||
|
||
'';
|
||
this.form.driverName =
|
||
this.form.driverName || taskInfo.driverName || carrierInfo.driverName || '';
|
||
this.form.driverPhone =
|
||
this.form.driverPhone ||
|
||
taskInfo.driverPhone ||
|
||
carrierInfo.driverPhone ||
|
||
carrierInfo.mobile ||
|
||
'';
|
||
this.form.vehicleNo =
|
||
this.form.vehicleNo || taskInfo.vehicleNo || carrierInfo.vehicleNo || '';
|
||
this.form.trailerVehicleNo =
|
||
this.form.trailerVehicleNo ||
|
||
taskInfo.trailerVehicleNo ||
|
||
carrierInfo.trailerVehicleNo ||
|
||
'';
|
||
this.form.escortName =
|
||
this.form.escortName || taskInfo.escortName || carrierInfo.escortName || '';
|
||
this.form.escortPhone =
|
||
this.form.escortPhone || taskInfo.escortPhone || carrierInfo.escortPhone || '';
|
||
this.form.cargoType =
|
||
this.form.cargoType ||
|
||
taskInfo.cargoType ||
|
||
goodsInfo.cargoType ||
|
||
goodsInfo.goodsType ||
|
||
'';
|
||
this.form.cargoTypeCode =
|
||
this.form.cargoTypeCode ||
|
||
taskInfo.cargoTypeCode ||
|
||
goodsInfo.cargoTypeCode ||
|
||
goodsInfo.goodsTypeCode ||
|
||
'';
|
||
this.form.cargoName =
|
||
this.form.cargoName ||
|
||
taskInfo.cargoName ||
|
||
goodsInfo.cargoName ||
|
||
goodsInfo.goodsName ||
|
||
'';
|
||
this.form.specification =
|
||
this.form.specification || taskInfo.specification || goodsInfo.specification || '';
|
||
this.form.model = this.form.model || taskInfo.model || goodsInfo.model || '';
|
||
this.form.quantity = this.form.quantity || taskInfo.quantity || goodsInfo.quantity || '';
|
||
this.form.quantityUnit =
|
||
this.form.quantityUnit || taskInfo.quantityUnit || goodsInfo.quantityUnit || '';
|
||
this.form.mileage = this.form.mileage || taskInfo.mileage || goodsInfo.mileage || '';
|
||
this.form.unitPrice = this.form.unitPrice || taskInfo.unitPrice || goodsInfo.unitPrice || '';
|
||
this.form.priceUnit = this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || '';
|
||
this.form.otherFeeTotal =
|
||
this.form.otherFeeTotal || taskInfo.otherFeeTotal || goodsInfo.otherFeeTotal || '';
|
||
this.form.estimatedStartTime =
|
||
this.form.estimatedStartTime ||
|
||
taskInfo.estimatedStartTime ||
|
||
goodsInfo.estimatedStartTime ||
|
||
'';
|
||
this.form.estimatedEndTime =
|
||
this.form.estimatedEndTime || taskInfo.estimatedEndTime || goodsInfo.estimatedEndTime || '';
|
||
this.form.taskRemark = this.form.taskRemark || taskInfo.taskRemark || goodsInfo.remark || '';
|
||
this.form.cargoTypePath = this.normalizeBillingCargoTypePath(this.form.cargoTypePath);
|
||
if (!this.form.cargoTypePath.length) {
|
||
this.ensureBillingCargoTypeOptions().then(() => {
|
||
const cargoType = this.findBillingCargoTypeByCodeOrName({
|
||
cargoTypeCode: this.form.cargoTypeCode,
|
||
cargoType: this.form.cargoType,
|
||
});
|
||
if (cargoType?.path) {
|
||
this.form.cargoTypePath = cargoType.path;
|
||
}
|
||
});
|
||
}
|
||
if (this.isTaskFullMode) {
|
||
this.initTaskFullCargoRows(goodsRows);
|
||
}
|
||
},
|
||
handleTaskEntryModeChange(value) {
|
||
this.form.taskEntryMode = value || 'simple';
|
||
if (this.form.taskEntryMode === 'full') {
|
||
this.initTaskFullCargoRows(this.parseJsonArray(this.form.goodsJson));
|
||
}
|
||
},
|
||
handleTaskCarrierTypeChange(value) {
|
||
const nextValue = value || '承运商';
|
||
if (nextValue === '承运商') {
|
||
this.form.trailerVehicleNo = '';
|
||
this.form.escortName = '';
|
||
this.form.escortPhone = '';
|
||
} else {
|
||
this.form.carrierName = '';
|
||
this.form.carrierId = '';
|
||
}
|
||
this.form.carrierType = nextValue;
|
||
},
|
||
loadTaskCarrierOptions() {
|
||
if (!this.taskInfoFormEnabled || this.taskCarrierLoading) return Promise.resolve([]);
|
||
this.taskCarrierLoading = true;
|
||
return getCarrierCustomerList(1, 9999, { customerType: '承运商' })
|
||
.then(res => {
|
||
this.taskCarrierOptions = extractRecords(res);
|
||
return this.taskCarrierOptions;
|
||
})
|
||
.finally(() => {
|
||
this.taskCarrierLoading = false;
|
||
});
|
||
},
|
||
handleTaskCarrierChange(value) {
|
||
const carrier = this.taskCarrierOptions.find(item =>
|
||
[item.customerName, item.carrierName, item.fullName, item.name].some(
|
||
name => String(name || '') === value
|
||
)
|
||
);
|
||
this.form.carrierId = carrier?.id || '';
|
||
this.form.carrierName = value || '';
|
||
},
|
||
loadTaskDriverOptions() {
|
||
if (!this.taskInfoFormEnabled || this.taskDriverLoading) return Promise.resolve([]);
|
||
this.taskDriverLoading = true;
|
||
return getDriverList(1, 9999, {})
|
||
.then(res => {
|
||
this.taskDriverOptions = extractRecords(res);
|
||
return this.taskDriverOptions;
|
||
})
|
||
.finally(() => {
|
||
this.taskDriverLoading = false;
|
||
});
|
||
},
|
||
handleTaskDriverChange(value) {
|
||
const driver = this.taskDriverOptions.find(item =>
|
||
[item.driverName, item.name].some(name => String(name || '') === String(value || ''))
|
||
);
|
||
this.form.driverId = driver?.id || '';
|
||
this.form.driverName = value || '';
|
||
if (driver) {
|
||
this.form.driverPhone =
|
||
driver.mobile || driver.phone || driver.driverPhone || this.form.driverPhone || '';
|
||
}
|
||
},
|
||
getTaskCargoTypeOptionsKey(path = []) {
|
||
const normalizedPath = this.normalizeBillingCargoTypePath(path);
|
||
if (!normalizedPath.length) return '';
|
||
const cargoType = this.findBillingCargoTypeByPath(normalizedPath);
|
||
return String(cargoType?.cargoCode || cargoType?.code || normalizedPath.join('/'));
|
||
},
|
||
getTaskCargoOptionsByPath(path = []) {
|
||
const key = this.getTaskCargoTypeOptionsKey(path);
|
||
return key ? this.taskCargoOptionsMap[key] || [] : [];
|
||
},
|
||
isTaskCargoOptionsLoading(path = []) {
|
||
const key = this.getTaskCargoTypeOptionsKey(path);
|
||
return key ? !!this.taskCargoLoadingMap[key] : false;
|
||
},
|
||
setTaskCargoOptionsByKey(key, options = []) {
|
||
if (!key) return;
|
||
this.taskCargoOptionsMap = {
|
||
...this.taskCargoOptionsMap,
|
||
[key]: options,
|
||
};
|
||
},
|
||
setTaskCargoLoadingByKey(key, loading) {
|
||
if (!key) return;
|
||
this.taskCargoLoadingMap = {
|
||
...this.taskCargoLoadingMap,
|
||
[key]: loading,
|
||
};
|
||
},
|
||
buildTaskCargoListQueryParams(path = []) {
|
||
const normalizedPath = this.normalizeBillingCargoTypePath(path);
|
||
const labels = this.getBillingCargoTypePathLabels(normalizedPath);
|
||
const secondCargoType = this.findBillingCargoTypeByPath(normalizedPath);
|
||
return {
|
||
secondCargoTypeName: labels[1] || '',
|
||
secondCargoTypeCode: secondCargoType?.cargoCode || secondCargoType?.code || '',
|
||
};
|
||
},
|
||
loadTaskCargoOptionsByPath(path = []) {
|
||
const normalizedPath = this.normalizeBillingCargoTypePath(path);
|
||
if (!normalizedPath.length) return Promise.resolve([]);
|
||
return this.ensureBillingCargoTypeOptions().then(() => {
|
||
const key = this.getTaskCargoTypeOptionsKey(normalizedPath);
|
||
const params = this.buildTaskCargoListQueryParams(normalizedPath);
|
||
if (!params.secondCargoTypeName && !params.secondCargoTypeCode) {
|
||
this.setTaskCargoOptionsByKey(key, []);
|
||
return [];
|
||
}
|
||
this.setTaskCargoLoadingByKey(key, true);
|
||
return getCommonCargoList(1, 500, {
|
||
...params,
|
||
allDept: 0,
|
||
})
|
||
.then(res => {
|
||
const records = extractRecords(res);
|
||
this.setTaskCargoOptionsByKey(key, records);
|
||
return records;
|
||
})
|
||
.catch(error => {
|
||
window.console.log(error);
|
||
this.setTaskCargoOptionsByKey(key, []);
|
||
return [];
|
||
})
|
||
.finally(() => {
|
||
this.setTaskCargoLoadingByKey(key, false);
|
||
});
|
||
});
|
||
},
|
||
loadTaskCargoOptionsForForm() {
|
||
return this.loadTaskCargoOptionsByPath(this.form.cargoTypePath);
|
||
},
|
||
handleTaskCargoTypeChange(value) {
|
||
const path = this.normalizeBillingCargoTypePath(value);
|
||
const cargoType = this.findBillingCargoTypeByPath(path);
|
||
const labels = this.getBillingCargoTypePathLabels(path);
|
||
this.form.cargoTypePath = path;
|
||
this.form.cargoType = labels.length ? labels[labels.length - 1] : '';
|
||
this.form.cargoTypeCode = cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
|
||
this.form.cargoName = '';
|
||
this.form.specification = '';
|
||
this.form.model = '';
|
||
this.loadTaskCargoOptionsForForm();
|
||
},
|
||
handleTaskCargoChange(value) {
|
||
const cargo = this.taskCargoOptions.find(
|
||
item => this.formatBillingCargoTypeLabel(item) === value
|
||
);
|
||
this.form.cargoName = value || '';
|
||
if (cargo) {
|
||
this.form.specification =
|
||
cargo.specification || cargo.spec || this.form.specification || '';
|
||
this.form.model = cargo.model || cargo.modelName || this.form.model || '';
|
||
}
|
||
},
|
||
getTaskCargoRowOptions(row = {}) {
|
||
return this.getTaskCargoOptionsByPath(row.cargoTypePath);
|
||
},
|
||
handleTaskCargoRowNameChange(row, value) {
|
||
const cargo = this.getTaskCargoRowOptions(row).find(
|
||
item => this.formatBillingCargoTypeLabel(item) === value
|
||
);
|
||
row.cargoName = value || '';
|
||
if (cargo) {
|
||
row.specification = cargo.specification || cargo.spec || row.specification || '';
|
||
row.model = cargo.model || cargo.modelName || row.model || '';
|
||
}
|
||
this.syncTransportCargoJson();
|
||
},
|
||
initTaskFullCargoRows(goodsRows = []) {
|
||
if (!this.taskInfoFormEnabled) return;
|
||
const rows = (goodsRows.length ? goodsRows : this.transportCargoRows).map(row =>
|
||
this.normalizeTransportCargoRow(row)
|
||
);
|
||
while (rows.length < 1) {
|
||
rows.push(this.normalizeTransportCargoRow());
|
||
}
|
||
this.transportCargoRows = rows;
|
||
this.syncTransportCargoJson();
|
||
},
|
||
loadTaskQuantityUnitOptions() {
|
||
if (this.taskQuantityUnitOptions.length || this.taskQuantityUnitLoading) {
|
||
return Promise.resolve(this.taskQuantityUnitOptions);
|
||
}
|
||
this.taskQuantityUnitLoading = true;
|
||
return getDictionary({ code: 'quantity_unit' })
|
||
.then(res => {
|
||
this.taskQuantityUnitOptions = this.normalizeDictOptions(res.data.data || []);
|
||
return this.taskQuantityUnitOptions;
|
||
})
|
||
.finally(() => {
|
||
this.taskQuantityUnitLoading = false;
|
||
});
|
||
},
|
||
loadTaskFeeUnitOptions() {
|
||
if (this.taskFeeUnitOptions.length || this.taskFeeUnitLoading) {
|
||
return Promise.resolve(this.taskFeeUnitOptions);
|
||
}
|
||
this.taskFeeUnitLoading = true;
|
||
return getDictionary({ code: 'unit_fee' })
|
||
.then(res => {
|
||
this.taskFeeUnitOptions = this.normalizeDictOptions(res.data.data || []);
|
||
return this.taskFeeUnitOptions;
|
||
})
|
||
.finally(() => {
|
||
this.taskFeeUnitLoading = false;
|
||
});
|
||
},
|
||
normalizeDictOptions(list = []) {
|
||
return (list || []).map(item => ({
|
||
label: item.dictValue || item.label || item.value || '',
|
||
value: item.dictKey || item.value || item.dictValue || '',
|
||
}));
|
||
},
|
||
handleTaskNumberInput(prop, value) {
|
||
this.form[prop] = this.normalizeTaskNumber(value);
|
||
},
|
||
normalizeTaskNumber(value) {
|
||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||
const parts = text.split('.');
|
||
return parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('')}` : parts[0];
|
||
},
|
||
validateTaskCargoRows() {
|
||
if (!this.transportCargoRows.length) {
|
||
this.$message.warning('请至少添加一条货物信息');
|
||
return false;
|
||
}
|
||
for (const [index, cargo] of this.transportCargoRows.entries()) {
|
||
const requiredFields = [
|
||
['cargoName', '货物名称'],
|
||
['cargoType', '货物类型'],
|
||
['quantity', '数量'],
|
||
['quantityUnit', '数量单位'],
|
||
];
|
||
const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(cargo[prop]));
|
||
if (emptyField) {
|
||
this.$message.warning(`第${index + 1}行${emptyField[1]}不能为空`);
|
||
return false;
|
||
}
|
||
const invalidField = [['quantity', '数量']].find(([prop]) => {
|
||
const value = cargo[prop];
|
||
return value !== undefined && value !== null && value !== '' && Number(value) < 0;
|
||
});
|
||
if (invalidField) {
|
||
this.$message.warning(`第${index + 1}行${invalidField[1]}不能小于0`);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
validateTaskInfoForm(row = {}) {
|
||
if (this.isTaskFullMode && !this.validateTaskCargoRows()) {
|
||
return false;
|
||
}
|
||
const requiredFields = this.config.transportFormRequiredFields || [
|
||
['carrierType', '承运类型'],
|
||
['vehicleNo', '车牌号'],
|
||
];
|
||
if (row.carrierType === '承运商') {
|
||
requiredFields.push(['carrierName', '承运商']);
|
||
}
|
||
if (row.carrierType === '自运') {
|
||
requiredFields.push(['driverPhone', '手机号']);
|
||
}
|
||
const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
|
||
if (emptyField) {
|
||
this.$message.warning(`请选择或输入${emptyField[1]}`);
|
||
return false;
|
||
}
|
||
const numberFields = [
|
||
['quantity', '数量'],
|
||
['mileage', '里程'],
|
||
['unitPrice', '单价'],
|
||
['otherFeeTotal', '其他运费合计'],
|
||
];
|
||
const invalidField = numberFields.find(([prop]) => {
|
||
const value = row[prop];
|
||
return value !== undefined && value !== null && value !== '' && Number(value) < 0;
|
||
});
|
||
if (invalidField) {
|
||
this.$message.warning(`${invalidField[1]}不能小于0`);
|
||
return false;
|
||
}
|
||
if (
|
||
row.estimatedStartTime &&
|
||
row.estimatedEndTime &&
|
||
row.estimatedEndTime < row.estimatedStartTime
|
||
) {
|
||
this.$message.warning('预计完成日期不能早于预计发货日期');
|
||
return false;
|
||
}
|
||
if (row.taskRemark && row.taskRemark.length > 200) {
|
||
this.$message.warning('任务备注不能超过200个字符');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
syncTaskInfoToRow(row) {
|
||
row.taskEntryMode = row.taskEntryMode || 'simple';
|
||
const taskInfo = {
|
||
taskEntryMode: row.taskEntryMode,
|
||
carrierType: row.carrierType,
|
||
carrierId: row.carrierId,
|
||
carrierName: row.carrierName,
|
||
driverId: row.driverId,
|
||
driverName: row.driverName,
|
||
driverPhone: row.driverPhone,
|
||
vehicleNo: row.vehicleNo,
|
||
trailerVehicleNo: row.trailerVehicleNo,
|
||
escortName: row.escortName,
|
||
escortPhone: row.escortPhone,
|
||
mileage: row.mileage,
|
||
estimatedStartTime: row.estimatedStartTime,
|
||
estimatedEndTime: row.estimatedEndTime,
|
||
unitPrice: row.unitPrice,
|
||
priceUnit: row.priceUnit,
|
||
otherFeeTotal: row.otherFeeTotal,
|
||
taskRemark: row.taskRemark,
|
||
};
|
||
row.taskInfoJson = JSON.stringify(taskInfo);
|
||
row.carrierJson = JSON.stringify([
|
||
{
|
||
carrierType: row.carrierType,
|
||
carrierId: row.carrierId,
|
||
carrierName: row.carrierName,
|
||
driverId: row.driverId,
|
||
driverName: row.driverName,
|
||
driverPhone: row.driverPhone,
|
||
vehicleNo: row.vehicleNo,
|
||
trailerVehicleNo: row.trailerVehicleNo,
|
||
escortName: row.escortName,
|
||
escortPhone: row.escortPhone,
|
||
mileage: row.mileage,
|
||
taskEntryMode: row.taskEntryMode,
|
||
},
|
||
]);
|
||
const goodsRows = this.isTaskFullMode
|
||
? this.transportCargoRows.map(item => this.normalizeTransportCargoRow(item))
|
||
: [
|
||
{
|
||
cargoType: row.cargoType,
|
||
cargoTypeCode: row.cargoTypeCode,
|
||
cargoTypePath: row.cargoTypePath,
|
||
cargoName: row.cargoName,
|
||
specification: row.specification,
|
||
model: row.model,
|
||
quantity: row.quantity,
|
||
quantityUnit: row.quantityUnit,
|
||
mileage: row.mileage,
|
||
unitPrice: row.unitPrice,
|
||
priceUnit: row.priceUnit,
|
||
remark: row.taskRemark,
|
||
},
|
||
];
|
||
row.goodsJson = JSON.stringify(goodsRows);
|
||
const firstGoodsRow =
|
||
goodsRows.find(
|
||
item =>
|
||
!this.isBillingFieldEmpty(item.cargoName) || !this.isBillingFieldEmpty(item.cargoType)
|
||
) ||
|
||
goodsRows[0] ||
|
||
{};
|
||
row.cargoType = firstGoodsRow.cargoType || row.cargoType || '';
|
||
row.cargoTypeCode = firstGoodsRow.cargoTypeCode || row.cargoTypeCode || '';
|
||
row.cargoTypePath = firstGoodsRow.cargoTypePath || row.cargoTypePath || [];
|
||
row.cargoName = firstGoodsRow.cargoName || row.cargoName || '';
|
||
row.specification = firstGoodsRow.specification || row.specification || '';
|
||
row.model = firstGoodsRow.model || row.model || '';
|
||
row.quantity = firstGoodsRow.quantity || row.quantity || '';
|
||
row.quantityUnit = firstGoodsRow.quantityUnit || row.quantityUnit || '';
|
||
row.mileage = firstGoodsRow.mileage || row.mileage || '';
|
||
row.unitPrice = firstGoodsRow.unitPrice || row.unitPrice || '';
|
||
row.priceUnit = firstGoodsRow.priceUnit || row.priceUnit || '';
|
||
row.taskRemark = firstGoodsRow.remark || row.taskRemark || '';
|
||
},
|
||
initTransportPlanFormRows() {
|
||
if (!this.config.enableTransportPlanForm) return;
|
||
this.transportCargoRows = this.parseJsonArray(this.form.goodsJson).map(row =>
|
||
this.normalizeTransportCargoRow(row)
|
||
);
|
||
this.syncTransportCargoJson();
|
||
this.syncCurrentContractOption();
|
||
this.restoreTransportCargoTypePaths();
|
||
},
|
||
restoreTransportCargoTypePaths() {
|
||
if (!this.transportCargoRows.length) return Promise.resolve();
|
||
return this.ensureBillingCargoTypeOptions().then(() => {
|
||
this.transportCargoRows.forEach(row => {
|
||
if (this.normalizeBillingCargoTypePath(row.cargoTypePath).length) return;
|
||
const cargoType = this.findBillingCargoTypeByCodeOrName({
|
||
cargoTypeCode: row.cargoTypeCode,
|
||
cargoType: row.cargoType,
|
||
});
|
||
if (!cargoType?.path) return;
|
||
row.cargoTypePath = cargoType.path;
|
||
row.cargoType =
|
||
this.getBillingCargoTypePathLabels(cargoType.path).at(-1) || row.cargoType;
|
||
row.cargoTypeCode = cargoType.cargoCode || cargoType.code || row.cargoTypeCode;
|
||
});
|
||
this.syncTransportCargoJson();
|
||
});
|
||
},
|
||
normalizeTransportCargoRow(row = {}) {
|
||
return {
|
||
...defaultTransportCargo(),
|
||
...row,
|
||
cargoTypePath: this.normalizeBillingCargoTypePath(row.cargoTypePath),
|
||
cargoType:
|
||
row.cargoType || row.goodsType || row.secondCargoTypeName || row.firstCargoTypeName || '',
|
||
cargoTypeCode:
|
||
row.cargoTypeCode ||
|
||
row.goodsTypeCode ||
|
||
row.secondCargoTypeCode ||
|
||
row.firstCargoTypeCode ||
|
||
'',
|
||
cargoName: row.cargoName || row.goodsName || '',
|
||
packageType: row.packageType || row.package || '',
|
||
specification: row.specification || row.spec || '',
|
||
};
|
||
},
|
||
syncTransportCargoJson() {
|
||
if (!this.config.enableTransportPlanForm && !this.isTaskFullMode) return;
|
||
this.form.goodsJson = JSON.stringify(
|
||
this.transportCargoRows.map(row => this.normalizeTransportCargoRow(row))
|
||
);
|
||
},
|
||
addTransportCargoRow(index = -1, row = {}) {
|
||
const nextRow = this.normalizeTransportCargoRow(row);
|
||
if (index > -1) {
|
||
this.transportCargoRows.splice(index + 1, 0, nextRow);
|
||
} else {
|
||
this.transportCargoRows.push(nextRow);
|
||
}
|
||
this.syncTransportCargoJson();
|
||
},
|
||
removeTransportCargoRow(index) {
|
||
this.transportCargoRows.splice(index, 1);
|
||
if (
|
||
!this.transportCargoRows.length &&
|
||
(this.isTaskFullMode || this.config.enableTransportPlanForm)
|
||
) {
|
||
this.transportCargoRows.push(this.normalizeTransportCargoRow());
|
||
}
|
||
this.syncTransportCargoJson();
|
||
},
|
||
handleTransportCargoQuantityInput(row, value) {
|
||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||
const parts = text.split('.');
|
||
row.quantity =
|
||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 3)}` : parts[0];
|
||
this.syncTransportCargoJson();
|
||
},
|
||
handleTransportCargoTypeChange(row, value) {
|
||
const path = this.normalizeBillingCargoTypePath(value);
|
||
const cargoType = this.findBillingCargoTypeByPath(path);
|
||
const labels = this.getBillingCargoTypePathLabels(path);
|
||
row.cargoTypePath = path;
|
||
row.cargoType = labels.length ? labels[labels.length - 1] : '';
|
||
row.cargoTypeCode = cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
|
||
row.cargoName = '';
|
||
row.specification = '';
|
||
row.model = '';
|
||
this.syncTransportCargoJson();
|
||
this.loadTaskCargoOptionsByPath(path);
|
||
},
|
||
handleTransportTypeChange(value) {
|
||
this.form.transportTypeName = this.getTransportTypeLabel(value);
|
||
this.clearTransportShippingInfo();
|
||
},
|
||
clearTransportShippingInfo() {
|
||
[
|
||
'departureName',
|
||
'departureAddress',
|
||
'departureAddressCode',
|
||
'departureSiteCode',
|
||
'departureLongitude',
|
||
'departureLatitude',
|
||
'departureContact',
|
||
'departurePhone',
|
||
'arrivalName',
|
||
'arrivalAddress',
|
||
'arrivalAddressCode',
|
||
'arrivalSiteCode',
|
||
'arrivalLongitude',
|
||
'arrivalLatitude',
|
||
'arrivalContact',
|
||
'arrivalPhone',
|
||
].forEach(prop => {
|
||
this.form[prop] = '';
|
||
});
|
||
this.transportRouteSelected = null;
|
||
this.transportAddressSelected = null;
|
||
this.transportMapSelected = {};
|
||
this.transportRouteBox = false;
|
||
this.transportAddressBox = false;
|
||
this.transportStationBox = false;
|
||
this.transportMapBox = false;
|
||
this.transportAddressTarget = '';
|
||
this.transportStationTarget = '';
|
||
this.transportMapTarget = '';
|
||
this.transportMapKeyword = '';
|
||
this.transportMapStatus = '可搜索地址或点击地图选点';
|
||
if (this.transportAmapMarker) {
|
||
this.transportAmapMarker.setMap(null);
|
||
this.transportAmapMarker = null;
|
||
}
|
||
this.$refs.crud?.clearValidate?.([
|
||
'departureAddress',
|
||
'arrivalAddress',
|
||
'departureName',
|
||
'arrivalName',
|
||
'departureContact',
|
||
'departurePhone',
|
||
'arrivalContact',
|
||
'arrivalPhone',
|
||
]);
|
||
},
|
||
getTransportTypeLabel(value) {
|
||
const column = (this.option.column || []).find(item => item.prop === 'transportType');
|
||
const dictionary = column?.dicData || [];
|
||
const item = dictionary.find(
|
||
dic =>
|
||
String(dic.value ?? dic.dictKey ?? '') === String(value ?? '') ||
|
||
String(dic.dictKey ?? '') === String(value ?? '')
|
||
);
|
||
return item?.label || item?.dictValue || '';
|
||
},
|
||
resolveTransportMode(value) {
|
||
const text = [value, this.form.transportTypeName, this.getTransportTypeLabel(value)]
|
||
.filter(Boolean)
|
||
.join(' ')
|
||
.toLowerCase();
|
||
if (!text) return '';
|
||
if (
|
||
text.includes('公路') ||
|
||
text.includes('道路') ||
|
||
text.includes('road') ||
|
||
text.includes('highway') ||
|
||
text === 'gl'
|
||
) {
|
||
return 'road';
|
||
}
|
||
if (
|
||
text.includes('铁路') ||
|
||
text.includes('rail') ||
|
||
text.includes('train') ||
|
||
text === 'tl'
|
||
) {
|
||
return 'rail';
|
||
}
|
||
if (
|
||
text.includes('水路') ||
|
||
text.includes('水运') ||
|
||
text.includes('海运') ||
|
||
text.includes('港') ||
|
||
text.includes('water') ||
|
||
text.includes('ship') ||
|
||
text.includes('sea') ||
|
||
text === 'sl'
|
||
) {
|
||
return 'water';
|
||
}
|
||
if (
|
||
text.includes('航空') ||
|
||
text.includes('空运') ||
|
||
text.includes('空港') ||
|
||
text.includes('air') ||
|
||
text.includes('airport') ||
|
||
text === 'hk'
|
||
) {
|
||
return 'air';
|
||
}
|
||
return '';
|
||
},
|
||
openTransportPrimaryAddressPicker(target) {
|
||
if (this.transportStationMode) {
|
||
this.openTransportStationDialog(target);
|
||
}
|
||
},
|
||
openTransportSecondaryAddressPicker(target) {
|
||
if (this.transportStationMode) {
|
||
this.openTransportStationDialog(target);
|
||
return;
|
||
}
|
||
this.openTransportMapDialog(target);
|
||
},
|
||
ensureTransportTypeBeforeAddress() {
|
||
if (!this.shippingInfoFormEnabled || !this.isBillingFieldEmpty(this.form.transportType)) {
|
||
return true;
|
||
}
|
||
this.$message.warning('请先选择运输方式');
|
||
return false;
|
||
},
|
||
openTransportRouteDialog() {
|
||
if (!this.ensureTransportTypeBeforeAddress()) return;
|
||
this.transportRouteSelected = null;
|
||
this.transportRoutePage.currentPage = 1;
|
||
this.transportRouteQuery = {};
|
||
this.transportRouteBox = true;
|
||
},
|
||
loadTransportRouteList() {
|
||
this.transportRouteLoading = true;
|
||
getCommonRouteList(this.transportRoutePage.currentPage, this.transportRoutePage.pageSize, {
|
||
...this.transportRouteQuery,
|
||
allDept: 0,
|
||
})
|
||
.then(res => {
|
||
const data = res.data.data || {};
|
||
this.transportRouteRows = data.records || [];
|
||
this.transportRoutePage.total = data.total || 0;
|
||
})
|
||
.finally(() => {
|
||
this.transportRouteLoading = false;
|
||
});
|
||
},
|
||
handleTransportRouteSearch() {
|
||
this.transportRoutePage.currentPage = 1;
|
||
this.loadTransportRouteList();
|
||
},
|
||
handleTransportRouteReset() {
|
||
this.transportRouteQuery = {};
|
||
this.transportRoutePage.currentPage = 1;
|
||
this.loadTransportRouteList();
|
||
},
|
||
handleTransportRouteSizeChange(pageSize) {
|
||
this.transportRoutePage.pageSize = pageSize;
|
||
this.transportRoutePage.currentPage = 1;
|
||
this.loadTransportRouteList();
|
||
},
|
||
handleTransportRouteCurrentChange(currentPage) {
|
||
this.transportRoutePage.currentPage = currentPage;
|
||
this.loadTransportRouteList();
|
||
},
|
||
formatTransportRouteUpdateUserName(row) {
|
||
return formatUpdateUserName(row);
|
||
},
|
||
selectTransportRouteRow(row) {
|
||
this.transportRouteSelected = row;
|
||
this.applyTransportRoute(row);
|
||
this.transportRouteBox = false;
|
||
},
|
||
applyTransportRoute(route = {}) {
|
||
this.applyTransportAddress('departure', {
|
||
addressName: route.departureName,
|
||
detailAddress: route.departureAddress,
|
||
longitude: route.departureLongitude,
|
||
latitude: route.departureLatitude,
|
||
contactName: route.departureContact,
|
||
contactPhone: route.departurePhone,
|
||
addressCode: route.departureAddressCode,
|
||
siteCode: route.departureSiteCode,
|
||
});
|
||
this.applyTransportAddress('arrival', {
|
||
addressName: route.arrivalName,
|
||
detailAddress: route.arrivalAddress,
|
||
longitude: route.arrivalLongitude,
|
||
latitude: route.arrivalLatitude,
|
||
contactName: route.arrivalContact,
|
||
contactPhone: route.arrivalPhone,
|
||
addressCode: route.arrivalAddressCode,
|
||
siteCode: route.arrivalSiteCode,
|
||
});
|
||
},
|
||
openTransportStationDialog(target) {
|
||
if (!this.ensureTransportTypeBeforeAddress()) return;
|
||
if (!this.transportStationMode) return;
|
||
this.transportStationTarget = target;
|
||
this.transportStationQuery = {};
|
||
this.transportStationPage.currentPage = 1;
|
||
this.transportStationBox = true;
|
||
},
|
||
getTransportStationListRequest() {
|
||
const requestMap = {
|
||
water: getPortTerminalList,
|
||
rail: getRailwayStationList,
|
||
air: getAirportMasterList,
|
||
};
|
||
return requestMap[this.transportMode];
|
||
},
|
||
loadTransportStationList() {
|
||
const request = this.getTransportStationListRequest();
|
||
if (!request) return;
|
||
this.transportStationLoading = true;
|
||
request(
|
||
this.transportStationPage.currentPage,
|
||
this.transportStationPage.pageSize,
|
||
this.buildTransportStationQueryParams()
|
||
)
|
||
.then(res => {
|
||
const data = res.data.data || {};
|
||
this.transportStationRows = (data.records || []).map(row =>
|
||
this.normalizeTransportStationRow(row)
|
||
);
|
||
this.transportStationPage.total = data.total || 0;
|
||
})
|
||
.finally(() => {
|
||
this.transportStationLoading = false;
|
||
});
|
||
},
|
||
buildTransportStationQueryParams() {
|
||
const query = this.transportStationQuery || {};
|
||
return Object.keys(query).reduce((params, key) => {
|
||
const value = query[key];
|
||
if (value !== undefined && value !== null && value !== '') {
|
||
params[key] = value;
|
||
}
|
||
return params;
|
||
}, {});
|
||
},
|
||
handleTransportStationSearch() {
|
||
this.transportStationPage.currentPage = 1;
|
||
this.loadTransportStationList();
|
||
},
|
||
handleTransportStationReset() {
|
||
this.transportStationQuery = {};
|
||
this.transportStationPage.currentPage = 1;
|
||
this.loadTransportStationList();
|
||
},
|
||
handleTransportStationSizeChange(pageSize) {
|
||
this.transportStationPage.pageSize = pageSize;
|
||
this.transportStationPage.currentPage = 1;
|
||
this.loadTransportStationList();
|
||
},
|
||
handleTransportStationCurrentChange(currentPage) {
|
||
this.transportStationPage.currentPage = currentPage;
|
||
this.loadTransportStationList();
|
||
},
|
||
normalizeTransportStationRow(row = {}) {
|
||
const regionName =
|
||
row.regionName ||
|
||
[row.provinceName, row.cityName, row.districtName].filter(Boolean).join('');
|
||
return {
|
||
...row,
|
||
stationName: row.name || row.stationName || row.addressName || '',
|
||
stationType: row.category || this.transportStationTypeLabel,
|
||
stationCode: row.code || row.tmisCode || row.iataCode || row.icaoCode || row.siteCode || '',
|
||
detailAddress: row.detailAddress || row.address || '',
|
||
regionName,
|
||
};
|
||
},
|
||
selectTransportStationRow(row) {
|
||
const station = this.normalizeTransportStationRow(row);
|
||
this.applyTransportAddress(this.transportStationTarget, {
|
||
addressName: station.stationName,
|
||
addressType: this.transportStationTypeLabel,
|
||
detailAddress: station.detailAddress,
|
||
longitude: station.longitude,
|
||
latitude: station.latitude,
|
||
regionName: station.regionName,
|
||
regionCode: station.regionCode,
|
||
siteCode: station.stationCode,
|
||
siteCodeDisplay: station.stationCode,
|
||
remark: station.remark,
|
||
});
|
||
this.transportStationBox = false;
|
||
},
|
||
openTransportAddressDialog(target) {
|
||
if (!this.ensureTransportTypeBeforeAddress()) return;
|
||
this.transportAddressTarget = target;
|
||
this.transportAddressSelected = null;
|
||
this.transportAddressQuery = {};
|
||
this.transportAddressPage.currentPage = 1;
|
||
this.transportAddressBox = true;
|
||
},
|
||
loadTransportAddressList() {
|
||
this.transportAddressLoading = true;
|
||
getCommonAddressList(
|
||
this.transportAddressPage.currentPage,
|
||
this.transportAddressPage.pageSize,
|
||
{
|
||
...this.transportAddressQuery,
|
||
allDept: 0,
|
||
}
|
||
)
|
||
.then(res => {
|
||
const data = res.data.data || {};
|
||
this.transportAddressRows = data.records || [];
|
||
this.transportAddressPage.total = data.total || 0;
|
||
})
|
||
.finally(() => {
|
||
this.transportAddressLoading = false;
|
||
});
|
||
},
|
||
handleTransportAddressSearch() {
|
||
this.transportAddressPage.currentPage = 1;
|
||
this.loadTransportAddressList();
|
||
},
|
||
handleTransportAddressReset() {
|
||
this.transportAddressQuery = {};
|
||
this.transportAddressPage.currentPage = 1;
|
||
this.loadTransportAddressList();
|
||
},
|
||
handleTransportAddressSizeChange(pageSize) {
|
||
this.transportAddressPage.pageSize = pageSize;
|
||
this.transportAddressPage.currentPage = 1;
|
||
this.loadTransportAddressList();
|
||
},
|
||
handleTransportAddressCurrentChange(currentPage) {
|
||
this.transportAddressPage.currentPage = currentPage;
|
||
this.loadTransportAddressList();
|
||
},
|
||
formatTransportAddressSiteCode(row) {
|
||
return row.addressType === '常规地址' ? '/' : row.siteCodeDisplay || row.siteCode || '';
|
||
},
|
||
selectTransportAddressRow(row) {
|
||
this.transportAddressSelected = row;
|
||
this.applyTransportAddress(this.transportAddressTarget, {
|
||
...row,
|
||
preferRegionName: true,
|
||
});
|
||
this.transportAddressBox = false;
|
||
},
|
||
applyTransportAddress(target, address = {}) {
|
||
const prefix = target === 'departure' ? 'departure' : 'arrival';
|
||
this.form[`${prefix}Name`] = address.preferRegionName
|
||
? address.regionName || address.addressName || ''
|
||
: address.addressName || address.regionName || '';
|
||
this.form[`${prefix}Address`] = address.detailAddress || address.address || '';
|
||
this.form[`${prefix}AddressCode`] = address.addressCode || '';
|
||
this.form[`${prefix}SiteCode`] =
|
||
address.addressType === '常规地址'
|
||
? '/'
|
||
: address.siteCodeDisplay || address.siteCode || '';
|
||
this.form[`${prefix}Longitude`] = address.longitude || '';
|
||
this.form[`${prefix}Latitude`] = address.latitude || '';
|
||
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
|
||
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
|
||
this.$refs.crud?.validateField?.(`${prefix}Address`);
|
||
},
|
||
openTransportMapDialog(target) {
|
||
if (this.dialogReadonly) return;
|
||
if (!this.ensureTransportTypeBeforeAddress()) return;
|
||
const prefix = target === 'departure' ? 'departure' : 'arrival';
|
||
this.transportMapTarget = prefix;
|
||
this.transportMapKeyword = this.form[`${prefix}Address`] || this.form[`${prefix}Name`] || '';
|
||
this.transportMapSelected = this.buildTransportMapSelection({
|
||
lng: this.form[`${prefix}Longitude`],
|
||
lat: this.form[`${prefix}Latitude`],
|
||
address: this.form[`${prefix}Address`],
|
||
regionName: this.form[`${prefix}Name`],
|
||
});
|
||
this.transportMapStatus = this.transportMapSelected.longitude
|
||
? '已加载当前选点,可重新选点'
|
||
: '可搜索地址或点击地图选点';
|
||
this.transportMapBox = true;
|
||
},
|
||
loadAmap() {
|
||
if (window.AMap && window.AMap.Map) {
|
||
return Promise.resolve();
|
||
}
|
||
if (!amapLoader) {
|
||
amapLoader = new Promise((resolve, reject) => {
|
||
window._AMapSecurityConfig = {
|
||
securityJsCode: AMAP_SECURITY_CODE,
|
||
};
|
||
const script = document.createElement('script');
|
||
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
|
||
script.async = true;
|
||
script.onload = resolve;
|
||
script.onerror = () => reject(new Error('高德地图组件加载失败'));
|
||
document.body.appendChild(script);
|
||
});
|
||
}
|
||
return amapLoader;
|
||
},
|
||
initTransportAmap() {
|
||
this.loadAmap()
|
||
.then(() => {
|
||
this.$nextTick(() => {
|
||
if (!this.transportAmap) {
|
||
const center = this.toLngLat(
|
||
this.transportMapSelected.longitude || 116.40769,
|
||
this.transportMapSelected.latitude || 39.89945
|
||
);
|
||
this.transportAmap = new window.AMap.Map(this.$refs.transportAmap, {
|
||
center,
|
||
zoom: this.transportMapSelected.longitude ? 14 : 11,
|
||
});
|
||
window.AMap.plugin(['AMap.ToolBar', 'AMap.Geocoder'], () => {
|
||
this.transportAmap.addControl(new window.AMap.ToolBar());
|
||
if (!this.transportAmapGeocoder) {
|
||
this.transportAmapGeocoder = new window.AMap.Geocoder();
|
||
}
|
||
});
|
||
this.transportAmap.on('click', event => this.pickTransportMapPoint(event.lnglat));
|
||
} else if (typeof this.transportAmap.resize === 'function') {
|
||
this.transportAmap.resize();
|
||
}
|
||
if (this.transportMapSelected.longitude) {
|
||
this.renderTransportMapMarker(
|
||
this.toLngLat(
|
||
this.transportMapSelected.longitude,
|
||
this.transportMapSelected.latitude
|
||
)
|
||
);
|
||
}
|
||
});
|
||
})
|
||
.catch(() => {
|
||
this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗');
|
||
this.transportMapBox = false;
|
||
});
|
||
},
|
||
searchTransportMapKeyword() {
|
||
const keyword = String(this.transportMapKeyword || '').trim();
|
||
if (!keyword) {
|
||
this.$message.warning('请输入地址关键词');
|
||
return;
|
||
}
|
||
this.loadAmap()
|
||
.then(() => {
|
||
this.transportMapLoading = true;
|
||
this.ensureTransportAmapGeocoder()
|
||
.then(() => this.runAmapGeocode('location', keyword))
|
||
.then(result => {
|
||
const point = this.resolveMapPoint(result);
|
||
if (!point) {
|
||
this.transportMapStatus = '未找到匹配地址';
|
||
this.$message.warning('地图搜索无匹配地址');
|
||
return;
|
||
}
|
||
this.pickTransportMapPoint(point, keyword);
|
||
})
|
||
.catch(() => {
|
||
this.transportMapStatus = '地图搜索失败';
|
||
this.$message.error('地图搜索失败,请稍后重试');
|
||
})
|
||
.finally(() => {
|
||
this.transportMapLoading = false;
|
||
});
|
||
})
|
||
.catch(() => {
|
||
this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗');
|
||
});
|
||
},
|
||
pickTransportMapPoint(lnglat, keyword) {
|
||
const longitude = this.getPointLng(lnglat);
|
||
const latitude = this.getPointLat(lnglat);
|
||
if (longitude === undefined || latitude === undefined) {
|
||
this.$message.warning('选点坐标无效');
|
||
return;
|
||
}
|
||
const point = this.toLngLat(longitude, latitude);
|
||
this.renderTransportMapMarker(point);
|
||
this.transportMapSelected = this.buildTransportMapSelection({
|
||
lng: longitude,
|
||
lat: latitude,
|
||
address: keyword,
|
||
});
|
||
this.transportMapStatus = '正在反查地址...';
|
||
this.ensureTransportAmapGeocoder()
|
||
.then(() => this.runAmapGeocode('address', point))
|
||
.then(result => {
|
||
const address = this.resolveMapAddress(result);
|
||
this.transportMapSelected = {
|
||
...this.transportMapSelected,
|
||
detailAddress: address.detailAddress || this.transportMapSelected.detailAddress,
|
||
regionName: address.regionName || this.transportMapSelected.regionName,
|
||
regionCode: address.regionCode || this.transportMapSelected.regionCode,
|
||
};
|
||
this.transportMapKeyword =
|
||
this.transportMapSelected.detailAddress || this.transportMapKeyword;
|
||
this.transportMapStatus = this.transportMapSelected.detailAddress || '已选点,可确认回填';
|
||
})
|
||
.catch(() => {
|
||
this.transportMapStatus = '反查地址失败';
|
||
this.$message.error('反查地址失败,请重新选点');
|
||
});
|
||
},
|
||
renderTransportMapMarker(point) {
|
||
if (!this.transportAmap || !window.AMap) return;
|
||
if (this.transportAmapMarker) {
|
||
this.transportAmapMarker.setMap(null);
|
||
}
|
||
this.transportAmapMarker = new window.AMap.Marker({
|
||
position: point,
|
||
});
|
||
this.transportAmapMarker.setMap(this.transportAmap);
|
||
this.transportAmap.setCenter(point);
|
||
},
|
||
confirmTransportMapPick() {
|
||
if (!this.transportMapSelected.longitude) {
|
||
this.$message.warning('请先搜索或点击地图完成选点');
|
||
return;
|
||
}
|
||
const prefix = this.transportMapTarget;
|
||
this.form[`${prefix}Address`] =
|
||
this.transportMapSelected.detailAddress || this.form[`${prefix}Address`];
|
||
this.form[`${prefix}Name`] =
|
||
this.transportMapSelected.regionName || this.form[`${prefix}Name`];
|
||
this.form[`${prefix}Longitude`] = this.transportMapSelected.longitude;
|
||
this.form[`${prefix}Latitude`] = this.transportMapSelected.latitude;
|
||
this.transportMapBox = false;
|
||
this.$refs.crud?.validateField?.(`${prefix}Address`);
|
||
},
|
||
buildTransportMapSelection({ lng, lat, address, regionName, regionCode }) {
|
||
const longitude = this.formatCoordinate(lng);
|
||
const latitude = this.formatCoordinate(lat);
|
||
return {
|
||
longitude,
|
||
latitude,
|
||
detailAddress: address || '',
|
||
regionName: regionName || '',
|
||
regionCode: regionCode || '',
|
||
};
|
||
},
|
||
formatCoordinate(value) {
|
||
if (value === undefined || value === null || value === '') return '';
|
||
const numberValue = Number(value);
|
||
return Number.isFinite(numberValue) ? numberValue.toFixed(6) : '';
|
||
},
|
||
toLngLat(lng, lat) {
|
||
return new window.AMap.LngLat(Number(lng), Number(lat));
|
||
},
|
||
getPointLng(point) {
|
||
if (!point) return undefined;
|
||
if (typeof point.getLng === 'function') return point.getLng();
|
||
return point.lng ?? point.lon;
|
||
},
|
||
getPointLat(point) {
|
||
if (!point) return undefined;
|
||
if (typeof point.getLat === 'function') return point.getLat();
|
||
return point.lat;
|
||
},
|
||
ensureTransportAmapGeocoder() {
|
||
if (this.transportAmapGeocoder) {
|
||
return Promise.resolve(this.transportAmapGeocoder);
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
if (!window.AMap || !window.AMap.plugin) {
|
||
reject(new Error('高德地图组件未就绪'));
|
||
return;
|
||
}
|
||
window.AMap.plugin(['AMap.Geocoder'], () => {
|
||
try {
|
||
this.transportAmapGeocoder = new window.AMap.Geocoder();
|
||
resolve(this.transportAmapGeocoder);
|
||
} catch (error) {
|
||
reject(error);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
runAmapGeocode(action, input) {
|
||
return new Promise((resolve, reject) => {
|
||
const timer = window.setTimeout(() => {
|
||
reject(new Error('高德地图请求超时'));
|
||
}, 10000);
|
||
const done = (status, result) => {
|
||
window.clearTimeout(timer);
|
||
if (status === 'complete' && result) {
|
||
resolve(result);
|
||
return;
|
||
}
|
||
reject(new Error('高德地图请求失败'));
|
||
};
|
||
try {
|
||
if (action === 'location') {
|
||
this.transportAmapGeocoder.getLocation(input, done);
|
||
} else {
|
||
this.transportAmapGeocoder.getAddress(input, done);
|
||
}
|
||
} catch (error) {
|
||
window.clearTimeout(timer);
|
||
reject(error);
|
||
}
|
||
});
|
||
},
|
||
resolveMapPoint(result) {
|
||
if (!result) return null;
|
||
const geocode = Array.isArray(result.geocodes) ? result.geocodes[0] : null;
|
||
if (geocode && geocode.location) return geocode.location;
|
||
if (result.location) return result.location;
|
||
if (result.lnglat) return result.lnglat;
|
||
if (result.getLng || result.lng || result.lon) return result;
|
||
return null;
|
||
},
|
||
resolveMapAddress(result = {}) {
|
||
const regeocode = result.regeocode || {};
|
||
const component = regeocode.addressComponent || {};
|
||
const city = Array.isArray(component.city) ? '' : component.city;
|
||
const regionName = [component.province, city, component.district].filter(Boolean).join('');
|
||
return {
|
||
detailAddress: regeocode.formattedAddress || '',
|
||
regionName,
|
||
regionCode: component.adcode || '',
|
||
};
|
||
},
|
||
openCommonCargoDialog() {
|
||
this.commonCargoSelected = [];
|
||
this.commonCargoQuery = {};
|
||
this.commonCargoPage.currentPage = 1;
|
||
this.commonCargoBox = true;
|
||
},
|
||
loadCommonCargoList() {
|
||
this.commonCargoLoading = true;
|
||
getCommonCargoList(this.commonCargoPage.currentPage, this.commonCargoPage.pageSize, {
|
||
...this.buildCommonCargoQueryParams(),
|
||
allDept: 0,
|
||
})
|
||
.then(res => {
|
||
const data = res.data.data || {};
|
||
this.commonCargoRows = data.records || [];
|
||
this.commonCargoPage.total = data.total || 0;
|
||
})
|
||
.finally(() => {
|
||
this.commonCargoLoading = false;
|
||
});
|
||
},
|
||
buildCommonCargoQueryParams() {
|
||
const { cargoTypePath, ...query } = this.commonCargoQuery || {};
|
||
return Object.keys(query).reduce((params, key) => {
|
||
const value = query[key];
|
||
if (value !== undefined && value !== null && value !== '') {
|
||
params[key] = value;
|
||
}
|
||
return params;
|
||
}, {});
|
||
},
|
||
handleCommonCargoSearch() {
|
||
this.commonCargoPage.currentPage = 1;
|
||
this.loadCommonCargoList();
|
||
},
|
||
handleCommonCargoReset() {
|
||
this.commonCargoQuery = {};
|
||
this.commonCargoPage.currentPage = 1;
|
||
this.loadCommonCargoList();
|
||
},
|
||
handleCommonCargoSizeChange(pageSize) {
|
||
this.commonCargoPage.pageSize = pageSize;
|
||
this.commonCargoPage.currentPage = 1;
|
||
this.loadCommonCargoList();
|
||
},
|
||
handleCommonCargoCurrentChange(currentPage) {
|
||
this.commonCargoPage.currentPage = currentPage;
|
||
this.loadCommonCargoList();
|
||
},
|
||
handleCommonCargoTypeChange(value) {
|
||
const path = this.normalizeBillingCargoTypePath(value);
|
||
const labels = this.getBillingCargoTypePathLabels(path);
|
||
const firstCargoType = this.billingCargoTypeFlatOptions.find(
|
||
item => String(item.id) === String(path[0] || '') && item.path?.length === 1
|
||
);
|
||
const secondCargoType = this.findBillingCargoTypeByPath(path);
|
||
this.commonCargoQuery = {
|
||
...this.commonCargoQuery,
|
||
cargoTypePath: path,
|
||
firstCargoTypeName: labels[0] || '',
|
||
firstCargoTypeCode: firstCargoType?.cargoCode || firstCargoType?.code || '',
|
||
secondCargoTypeName: labels[1] || '',
|
||
secondCargoTypeCode: secondCargoType?.cargoCode || secondCargoType?.code || '',
|
||
};
|
||
if (!path.length) {
|
||
delete this.commonCargoQuery.firstCargoTypeName;
|
||
delete this.commonCargoQuery.firstCargoTypeCode;
|
||
delete this.commonCargoQuery.secondCargoTypeName;
|
||
delete this.commonCargoQuery.secondCargoTypeCode;
|
||
}
|
||
},
|
||
formatCommonCargoSize(row) {
|
||
return row.cargoSize || row.size || row.dimension || row.dimensions || '';
|
||
},
|
||
async selectCommonCargoRow(row) {
|
||
this.commonCargoSelected = [row];
|
||
await this.ensureBillingCargoTypeOptions();
|
||
this.addTransportCargoRow(-1, this.mapCommonCargoRow(row));
|
||
this.commonCargoBox = false;
|
||
},
|
||
mapCommonCargoRow(row = {}) {
|
||
const cargoTypePath = this.resolveCommonCargoTypePath(row);
|
||
const cargoType = this.findBillingCargoTypeByPath(cargoTypePath);
|
||
const labels = this.getBillingCargoTypePathLabels(cargoTypePath);
|
||
return {
|
||
cargoName: row.cargoName,
|
||
cargoTypePath,
|
||
cargoType: labels[1] || row.secondCargoTypeName || row.firstCargoTypeName,
|
||
cargoTypeCode:
|
||
cargoType?.cargoCode ||
|
||
cargoType?.code ||
|
||
row.secondCargoTypeCode ||
|
||
row.firstCargoTypeCode,
|
||
packageType: row.packageType,
|
||
brand: row.brand,
|
||
specification: row.specification,
|
||
model: row.model,
|
||
remark: row.remark || row.descriptionOne || '',
|
||
};
|
||
},
|
||
async handleCargoImportFileChange(file) {
|
||
const rawFile = file.raw;
|
||
if (!rawFile) return;
|
||
this.cargoImportLoading = true;
|
||
try {
|
||
const ExcelJSModule = await import('exceljs');
|
||
const ExcelJS = ExcelJSModule.default || ExcelJSModule;
|
||
const workbook = new ExcelJS.Workbook();
|
||
await workbook.xlsx.load(await rawFile.arrayBuffer());
|
||
const worksheet = workbook.worksheets[0];
|
||
const headerRow = worksheet.getRow(1);
|
||
const headerMap = {};
|
||
headerRow.eachCell((cell, colNumber) => {
|
||
headerMap[String(cell.value || '').trim()] = colNumber;
|
||
});
|
||
const fieldMap = {
|
||
货物名称: 'cargoName',
|
||
货物类型: 'cargoType',
|
||
数量: 'quantity',
|
||
数量单位: 'quantityUnit',
|
||
包装: 'packageType',
|
||
品牌: 'brand',
|
||
规格: 'specification',
|
||
型号: 'model',
|
||
物料编码: 'materialCode',
|
||
设备编码: 'deviceCode',
|
||
备注: 'remark',
|
||
};
|
||
const rows = [];
|
||
worksheet.eachRow((row, rowNumber) => {
|
||
if (rowNumber === 1) return;
|
||
const nextRow = defaultTransportCargo();
|
||
Object.entries(fieldMap).forEach(([label, prop]) => {
|
||
const columnIndex = headerMap[label];
|
||
if (!columnIndex) return;
|
||
nextRow[prop] = String(row.getCell(columnIndex).value || '').trim();
|
||
});
|
||
if (Object.values(nextRow).some(value => String(value || '').trim())) {
|
||
rows.push(nextRow);
|
||
}
|
||
});
|
||
if (!rows.length) {
|
||
this.$message.warning('未读取到货物数据');
|
||
return;
|
||
}
|
||
rows.forEach(row => this.addTransportCargoRow(-1, row));
|
||
this.$message.success(`成功导入${rows.length}条货物`);
|
||
this.cargoImportBox = false;
|
||
} catch (error) {
|
||
window.console.log(error);
|
||
this.$message.error('货物导入失败,请检查文件格式');
|
||
} finally {
|
||
this.cargoImportLoading = false;
|
||
}
|
||
},
|
||
loadOrganizationOptions() {
|
||
if (!this.config.enableOrganizationSelect || this.organizationLoading) return;
|
||
this.organizationLoading = true;
|
||
getDeptTree(this.userInfo?.tenantId)
|
||
.then(res => {
|
||
this.organizationOptions = this.normalizeOrganizationOptions(res.data.data || []);
|
||
this.organizationFlatOptions = this.flattenOrganizationOptions(this.organizationOptions);
|
||
this.syncOrganizationSearchDic();
|
||
this.syncCurrentOrganizationOption();
|
||
})
|
||
.finally(() => {
|
||
this.organizationLoading = false;
|
||
});
|
||
},
|
||
normalizeOrganizationOptions(tree = []) {
|
||
return (tree || []).map(item => {
|
||
const name = item.title || item.deptName || item.name || item.label || '';
|
||
const children = this.normalizeOrganizationOptions(item.children || []);
|
||
return {
|
||
...item,
|
||
id: String(item.id),
|
||
label: name,
|
||
rawLabel: name,
|
||
children: children.length ? children : undefined,
|
||
};
|
||
});
|
||
},
|
||
flattenOrganizationOptions(tree = []) {
|
||
const result = [];
|
||
tree.forEach(item => {
|
||
result.push({
|
||
id: String(item.id),
|
||
label: item.label,
|
||
rawLabel: item.rawLabel || item.label,
|
||
});
|
||
if (item.children && item.children.length) {
|
||
result.push(...this.flattenOrganizationOptions(item.children));
|
||
}
|
||
});
|
||
return result;
|
||
},
|
||
syncOrganizationSearchDic() {
|
||
const column = this.findColumn?.(this.option.column, 'organizationName');
|
||
if (column) {
|
||
column.dicData = this.organizationOptions;
|
||
}
|
||
},
|
||
syncCurrentOrganizationOption() {
|
||
if (!this.form.organizationId || !this.form.organizationName) return;
|
||
const exists = this.organizationFlatOptions.some(
|
||
item => String(item.id) === String(this.form.organizationId)
|
||
);
|
||
if (!exists) {
|
||
const current = {
|
||
id: String(this.form.organizationId),
|
||
label: this.form.organizationName,
|
||
rawLabel: this.form.organizationName,
|
||
};
|
||
this.organizationOptions.unshift(current);
|
||
this.organizationFlatOptions.unshift(current);
|
||
this.syncOrganizationSearchDic();
|
||
}
|
||
},
|
||
findOrganizationOption(organizationId) {
|
||
const value = Array.isArray(organizationId)
|
||
? organizationId[organizationId.length - 1]
|
||
: organizationId;
|
||
return this.organizationFlatOptions.find(item => String(item.id) === String(value));
|
||
},
|
||
handleOrganizationChange(organizationId) {
|
||
const organization = this.findOrganizationOption(organizationId);
|
||
if (!organization) {
|
||
this.form.organizationId = '';
|
||
this.form.organizationName = '';
|
||
return;
|
||
}
|
||
this.form.organizationId = organization.id;
|
||
this.form.organizationName = organization.rawLabel || organization.label || '';
|
||
this.$refs.crud?.validateField?.('organizationName');
|
||
},
|
||
handleAmountInput(prop, value) {
|
||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||
const parts = text.split('.');
|
||
this.form[prop] =
|
||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||
},
|
||
handleIntegerInput(prop, value) {
|
||
this.form[prop] = String(value || '').replace(/\D/g, '');
|
||
},
|
||
handleContractDateChange() {
|
||
this.syncContractPeriod(true);
|
||
},
|
||
syncContractPeriod(validate = true) {
|
||
if (!this.config.enableContractPeriod) return;
|
||
this.form.contractPeriod =
|
||
this.form.startDate && this.form.endDate
|
||
? `${this.form.startDate}至${this.form.endDate}`
|
||
: '';
|
||
if (validate) {
|
||
this.$refs.crud?.validateField?.('contractPeriod');
|
||
}
|
||
},
|
||
applyCurrentUserHandler() {
|
||
if (!this.config.enableCurrentUserHandler || this.form.handlerUserName) return;
|
||
this.form.handlerUserName =
|
||
this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || '';
|
||
},
|
||
handleAttachmentChange(list) {
|
||
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||
this.attachmentRows = (list || []).map(item => ({
|
||
...item,
|
||
description: item.description || '',
|
||
uploadUserName: item.uploadUserName || userName,
|
||
uploadTime: item.uploadTime || uploadTime,
|
||
}));
|
||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||
},
|
||
removeAttachment(index) {
|
||
this.attachmentRows.splice(index, 1);
|
||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||
},
|
||
formatFileSize(size) {
|
||
const number = Number(size);
|
||
if (!number) return '';
|
||
if (number < 1024) return `${number}B`;
|
||
if (number < 1024 * 1024) return `${(number / 1024).toFixed(1)}KB`;
|
||
return `${(number / 1024 / 1024).toFixed(1)}MB`;
|
||
},
|
||
downloadAttachment(row) {
|
||
const url = row.url || row.link;
|
||
if (!url) {
|
||
this.$message.warning('附件地址为空');
|
||
return;
|
||
}
|
||
downloadFileByUrl(url, row.originalName || row.name || '附件');
|
||
},
|
||
handleBatchDownload() {
|
||
this.attachmentRows.forEach(row => this.downloadAttachment(row));
|
||
},
|
||
handleDispatchItemAttachmentChange(list) {
|
||
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||
this.dispatchItemAttachmentRows = (list || []).map(item => ({
|
||
...item,
|
||
description: item.description || '',
|
||
uploadUserName: item.uploadUserName || userName,
|
||
uploadTime: item.uploadTime || uploadTime,
|
||
}));
|
||
},
|
||
removeDispatchItemAttachment(index) {
|
||
this.dispatchItemAttachmentRows.splice(index, 1);
|
||
},
|
||
handleDispatchItemBatchDownload() {
|
||
this.dispatchItemAttachmentRows.forEach(row => this.downloadAttachment(row));
|
||
},
|
||
handleContractFileChange(list) {
|
||
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
|
||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||
this.contractFileRows = (list || []).map(item => ({
|
||
...item,
|
||
uploadUserName: item.uploadUserName || userName,
|
||
uploadTime: item.uploadTime || uploadTime,
|
||
}));
|
||
this.form.contractFileJson = JSON.stringify(this.contractFileRows);
|
||
},
|
||
removeContractFile(index) {
|
||
this.contractFileRows.splice(index, 1);
|
||
this.form.contractFileJson = JSON.stringify(this.contractFileRows);
|
||
},
|
||
cloneData(data) {
|
||
return JSON.parse(JSON.stringify(data));
|
||
},
|
||
syncBillingPlanJson() {
|
||
if (!this.config.enableBillingPlan) return;
|
||
if (
|
||
this.form.billingEnabled === undefined ||
|
||
this.form.billingEnabled === null ||
|
||
this.form.billingEnabled === ''
|
||
) {
|
||
this.form.billingEnabled = 1;
|
||
}
|
||
this.form.billingPlanJson = JSON.stringify(this.billingPlanRows);
|
||
},
|
||
ensureBillingMatchRegionOptions() {
|
||
if (!this.config.enableBillingPlan && !this.config.enableTransportPlanForm) {
|
||
return Promise.resolve([]);
|
||
}
|
||
if (this.billingMatchRegionOptions.length) {
|
||
return Promise.resolve(this.billingMatchRegionOptions);
|
||
}
|
||
if (this.billingMatchRegionRequest) {
|
||
return this.billingMatchRegionRequest;
|
||
}
|
||
this.billingMatchRegionLoading = true;
|
||
this.billingMatchRegionRequest = getRegionLazyTree()
|
||
.then(res => {
|
||
const regionTree = this.buildBillingMatchRegionTree(res.data.data || []);
|
||
const regions = this.extractBillingMatchChinaRegions(regionTree);
|
||
this.billingMatchRegionOptions = regions
|
||
.map(item => this.normalizeBillingMatchRegionNode(item, 1))
|
||
.filter(Boolean);
|
||
return this.billingMatchRegionOptions;
|
||
})
|
||
.finally(() => {
|
||
this.billingMatchRegionLoading = false;
|
||
this.billingMatchRegionRequest = null;
|
||
});
|
||
return this.billingMatchRegionRequest;
|
||
},
|
||
ensureBillingCargoTypeOptions() {
|
||
if (
|
||
!this.config.enableBillingPlan &&
|
||
!this.config.enableTransportPlanForm &&
|
||
!this.taskInfoFormEnabled
|
||
) {
|
||
return Promise.resolve([]);
|
||
}
|
||
if (this.billingCargoTypeOptions.length) {
|
||
return Promise.resolve(this.billingCargoTypeOptions);
|
||
}
|
||
if (this.billingCargoTypeRequest) {
|
||
return this.billingCargoTypeRequest;
|
||
}
|
||
this.billingCargoTypeLoading = true;
|
||
this.billingCargoTypeRequest = getCargoTypeList(1, 9999)
|
||
.then(res => {
|
||
this.billingCargoTypeOptions = this.buildBillingCargoTypeTree(extractRecords(res));
|
||
this.billingCargoTypeFlatOptions = this.flattenBillingCargoTypeOptions(
|
||
this.billingCargoTypeOptions
|
||
);
|
||
return this.billingCargoTypeOptions;
|
||
})
|
||
.finally(() => {
|
||
this.billingCargoTypeLoading = false;
|
||
this.billingCargoTypeRequest = null;
|
||
});
|
||
return this.billingCargoTypeRequest;
|
||
},
|
||
buildBillingCargoTypeTree(cargoTypes = []) {
|
||
const flatCargoTypes = [];
|
||
const collectCargoTypes = list => {
|
||
(list || []).forEach(item => {
|
||
flatCargoTypes.push({
|
||
...item,
|
||
children: undefined,
|
||
});
|
||
if (item.children?.length) collectCargoTypes(item.children);
|
||
});
|
||
};
|
||
collectCargoTypes(cargoTypes);
|
||
|
||
const nodeMap = new Map();
|
||
const codeMap = new Map();
|
||
flatCargoTypes.forEach(item => {
|
||
const id = item.id ?? item.cargoCode ?? item.code;
|
||
if (id === undefined || id === null) return;
|
||
const node = {
|
||
...item,
|
||
id: String(id),
|
||
cargoName: this.formatBillingCargoTypeLabel(item),
|
||
cargoCode: item.cargoCode || item.code || '',
|
||
parentId:
|
||
item.parentId === undefined || item.parentId === null ? '' : String(item.parentId),
|
||
parentCargoCode: item.parentCargoCode || item.parentCode || '',
|
||
children: [],
|
||
};
|
||
nodeMap.set(node.id, node);
|
||
if (node.cargoCode) {
|
||
codeMap.set(String(node.cargoCode), node);
|
||
}
|
||
});
|
||
|
||
const rootNodes = [];
|
||
nodeMap.forEach(node => {
|
||
const parent =
|
||
nodeMap.get(node.parentId) ||
|
||
codeMap.get(String(node.parentCargoCode || '')) ||
|
||
codeMap.get(String(node.parentId || ''));
|
||
if (parent && parent !== node && Number(node.typeLevel) !== 1) {
|
||
parent.children.push(node);
|
||
} else {
|
||
rootNodes.push(node);
|
||
}
|
||
});
|
||
return rootNodes.map(item => this.normalizeBillingCargoTypeNode(item, 1)).filter(Boolean);
|
||
},
|
||
normalizeBillingCargoTypeNode(cargoType, level = 1, parentPath = []) {
|
||
if (level > 2) return null;
|
||
const id = String(cargoType.id);
|
||
const path = [...parentPath, id];
|
||
const children =
|
||
level === 1
|
||
? (cargoType.children || [])
|
||
.map(item => this.normalizeBillingCargoTypeNode(item, level + 1, path))
|
||
.filter(Boolean)
|
||
: [];
|
||
return {
|
||
...cargoType,
|
||
id,
|
||
cargoName: this.formatBillingCargoTypeLabel(cargoType),
|
||
cargoCode: cargoType.cargoCode || cargoType.code || '',
|
||
path,
|
||
leaf: level === 2,
|
||
children: children.length ? children : undefined,
|
||
};
|
||
},
|
||
flattenBillingCargoTypeOptions(options = []) {
|
||
const result = [];
|
||
const collectOptions = list => {
|
||
(list || []).forEach(item => {
|
||
result.push(item);
|
||
if (item.children?.length) collectOptions(item.children);
|
||
});
|
||
};
|
||
collectOptions(options);
|
||
return result;
|
||
},
|
||
buildBillingMatchRegionTree(regions = []) {
|
||
const flatRegions = [];
|
||
const collectRegions = list => {
|
||
(list || []).forEach(item => {
|
||
flatRegions.push({
|
||
...item,
|
||
children: undefined,
|
||
});
|
||
if (item.children?.length) collectRegions(item.children);
|
||
});
|
||
};
|
||
collectRegions(regions);
|
||
|
||
const nodeMap = new Map();
|
||
flatRegions.forEach(item => {
|
||
const id = item.id ?? item.code ?? item.value;
|
||
if (id === undefined || id === null) return;
|
||
nodeMap.set(String(id), {
|
||
...item,
|
||
id: String(id),
|
||
parentId:
|
||
item.parentId === undefined || item.parentId === null
|
||
? String(item.parentCode || '')
|
||
: String(item.parentId),
|
||
children: [],
|
||
});
|
||
});
|
||
|
||
const rootNodes = [];
|
||
nodeMap.forEach(node => {
|
||
const parent = nodeMap.get(node.parentId);
|
||
if (parent && parent !== node) {
|
||
parent.children.push(node);
|
||
} else {
|
||
rootNodes.push(node);
|
||
}
|
||
});
|
||
return rootNodes;
|
||
},
|
||
normalizeBillingMatchRegionNode(region, level = 1) {
|
||
if (level > 2) return null;
|
||
const children =
|
||
level === 1
|
||
? (region.children || [])
|
||
.map(item => this.normalizeBillingMatchRegionNode(item, level + 1))
|
||
.filter(Boolean)
|
||
: [];
|
||
return {
|
||
...region,
|
||
title: region.title || region.name || '',
|
||
leaf: level === 2,
|
||
children: children.length ? children : undefined,
|
||
};
|
||
},
|
||
isBillingMatchChinaRegion(region = {}) {
|
||
const title = String(region.title || region.name || '').trim();
|
||
return title === '中国' || title === '中华人民共和国';
|
||
},
|
||
extractBillingMatchChinaRegions(regions) {
|
||
const china = (regions || []).find(item => this.isBillingMatchChinaRegion(item));
|
||
if (china?.children?.length) return china.children;
|
||
if (regions.length === 1 && regions[0].children?.length) return regions[0].children;
|
||
return regions;
|
||
},
|
||
getBillingMatchRegionPathLabels(path = []) {
|
||
let options = this.billingMatchRegionOptions;
|
||
const labels = [];
|
||
(path || []).forEach(value => {
|
||
const option = (options || []).find(item => String(item.id) === String(value));
|
||
if (!option) return;
|
||
labels.push(option.title || option.name || '');
|
||
options = option.children || [];
|
||
});
|
||
return labels.filter(Boolean);
|
||
},
|
||
handleBillingMatchRegionChange(prop, value) {
|
||
const pathProp = `${prop}Path`;
|
||
const path = Array.isArray(value) && value.length >= 2 ? value.slice(0, 2) : [];
|
||
const labels = this.getBillingMatchRegionPathLabels(path);
|
||
this.billingMatchForm[pathProp] = path;
|
||
if (!path.length) {
|
||
this.billingMatchForm[prop] = '';
|
||
this.billingMatchForm[`${prop}Code`] = '';
|
||
return;
|
||
}
|
||
if (labels.length) {
|
||
this.billingMatchForm[prop] = labels.join('');
|
||
}
|
||
this.billingMatchForm[`${prop}Code`] = String(path[path.length - 1]);
|
||
},
|
||
formatBillingCargoTypeLabel(item = {}) {
|
||
return item.cargoName || item.name || item.typeName || item.label || '';
|
||
},
|
||
filterBillingCargoType(node, keyword) {
|
||
const text = String(keyword || '').trim();
|
||
if (!text) return true;
|
||
const labels = node.pathLabels?.length ? node.pathLabels : [node.label];
|
||
return (
|
||
labels.some(label => String(label || '').includes(text)) ||
|
||
String(node.text || '').includes(text)
|
||
);
|
||
},
|
||
getBillingCargoTypePathLabels(path = []) {
|
||
let options = this.billingCargoTypeOptions;
|
||
const labels = [];
|
||
(path || []).forEach(value => {
|
||
const option = (options || []).find(item => String(item.id) === String(value));
|
||
if (!option) return;
|
||
labels.push(option.cargoName || '');
|
||
options = option.children || [];
|
||
});
|
||
return labels.filter(Boolean);
|
||
},
|
||
findBillingCargoTypeByPath(path = []) {
|
||
const value = Array.isArray(path) && path.length >= 2 ? String(path[1]) : '';
|
||
if (!value) return null;
|
||
return (
|
||
this.billingCargoTypeFlatOptions.find(
|
||
item => String(item.id) === value && item.path?.length >= 2
|
||
) || null
|
||
);
|
||
},
|
||
findBillingCargoTypeByCodeOrName(condition = {}) {
|
||
const code = String(condition.cargoTypeCode || '').trim();
|
||
const name = String(condition.cargoType || '').trim();
|
||
return (
|
||
this.billingCargoTypeFlatOptions.find(item => {
|
||
const cargoCode = String(item.cargoCode || item.code || item.id || '');
|
||
return item.path?.length >= 2 && code && cargoCode === code;
|
||
}) ||
|
||
this.billingCargoTypeFlatOptions.find(item => {
|
||
const cargoName = this.formatBillingCargoTypeLabel(item);
|
||
return item.path?.length >= 2 && name && (cargoName === name || name.endsWith(cargoName));
|
||
}) ||
|
||
null
|
||
);
|
||
},
|
||
resolveCommonCargoTypePath(row = {}) {
|
||
const path = this.normalizeBillingCargoTypePath(row.cargoTypePath);
|
||
if (path.length) return path;
|
||
const cargoType =
|
||
this.findBillingCargoTypeByCodeOrName({
|
||
cargoTypeCode: row.secondCargoTypeCode || row.firstCargoTypeCode || row.cargoTypeCode,
|
||
cargoType: row.secondCargoTypeName || row.firstCargoTypeName || row.cargoType,
|
||
}) ||
|
||
this.findBillingCargoTypeByCodeOrName({
|
||
cargoTypeCode: row.firstCargoTypeCode || '',
|
||
cargoType: row.firstCargoTypeName || '',
|
||
});
|
||
return cargoType?.path || [];
|
||
},
|
||
handleBillingMatchCargoTypeChange(value) {
|
||
const path =
|
||
Array.isArray(value) && value.length >= 2
|
||
? value.slice(0, 2).map(item => String(item))
|
||
: [];
|
||
const cargoType = this.findBillingCargoTypeByPath(path);
|
||
const labels = this.getBillingCargoTypePathLabels(path);
|
||
this.billingMatchForm.cargoTypePath = path;
|
||
if (!path.length || !cargoType) {
|
||
this.billingMatchForm.cargoType = '';
|
||
this.billingMatchForm.cargoTypeCode = '';
|
||
return;
|
||
}
|
||
this.billingMatchForm.cargoType =
|
||
labels.length > 1
|
||
? labels.join('/')
|
||
: labels[0] || this.formatBillingCargoTypeLabel(cargoType || {});
|
||
this.billingMatchForm.cargoTypeCode =
|
||
cargoType?.cargoCode || cargoType?.code || cargoType?.id || '';
|
||
},
|
||
normalizeBillingCargoTypePath(path) {
|
||
return Array.isArray(path) && path.length >= 2
|
||
? path.slice(0, 2).map(value => String(value))
|
||
: [];
|
||
},
|
||
resolveBillingMatchCargoTypePath(condition = {}) {
|
||
const path = this.normalizeBillingCargoTypePath(condition.cargoTypePath);
|
||
if (path.length) return path;
|
||
const cargoType = this.findBillingCargoTypeByCodeOrName(condition);
|
||
return cargoType?.path || [];
|
||
},
|
||
normalizeBillingMatchRegionPath(path) {
|
||
return Array.isArray(path) && path.length >= 2
|
||
? path.slice(0, 2).map(value => String(value))
|
||
: [];
|
||
},
|
||
normalizeBillingMatchCondition(condition = {}) {
|
||
const defaults = defaultBillingRule().matchCondition;
|
||
const nextCondition = {
|
||
...defaults,
|
||
...(condition || {}),
|
||
};
|
||
nextCondition.originPath = this.normalizeBillingMatchRegionPath(condition?.originPath);
|
||
nextCondition.destinationPath = this.normalizeBillingMatchRegionPath(
|
||
condition?.destinationPath
|
||
);
|
||
nextCondition.cargoTypePath = this.normalizeBillingCargoTypePath(condition?.cargoTypePath);
|
||
return nextCondition;
|
||
},
|
||
ensureBillingFeeCategoryOptions() {
|
||
if (!this.config.enableBillingPlan) return Promise.resolve([]);
|
||
if (this.billingFeeCategoryOptions.length) {
|
||
return Promise.resolve(this.billingFeeCategoryOptions);
|
||
}
|
||
if (this.billingFeeCategoryRequest) {
|
||
return this.billingFeeCategoryRequest;
|
||
}
|
||
this.billingFeeCategoryLoading = true;
|
||
this.billingFeeCategoryRequest = getDictionary({ code: 'fee_category' })
|
||
.then(res => {
|
||
this.billingFeeCategoryOptions = res.data.data || [];
|
||
return this.billingFeeCategoryOptions;
|
||
})
|
||
.finally(() => {
|
||
this.billingFeeCategoryLoading = false;
|
||
this.billingFeeCategoryRequest = null;
|
||
});
|
||
return this.billingFeeCategoryRequest;
|
||
},
|
||
ensureBillingUnitOptions() {
|
||
if (!this.config.enableBillingPlan) return Promise.resolve([]);
|
||
if (this.billingUnitOptions.length) {
|
||
return Promise.resolve(this.billingUnitOptions);
|
||
}
|
||
if (this.billingUnitRequest) {
|
||
return this.billingUnitRequest;
|
||
}
|
||
this.billingUnitLoading = true;
|
||
this.billingUnitRequest = getDictionary({ code: 'unit_fee' })
|
||
.then(res => {
|
||
this.billingUnitOptions = res.data.data || [];
|
||
return this.billingUnitOptions;
|
||
})
|
||
.finally(() => {
|
||
this.billingUnitLoading = false;
|
||
this.billingUnitRequest = null;
|
||
});
|
||
return this.billingUnitRequest;
|
||
},
|
||
resolveBillingFeeType(value) {
|
||
const feeType = String(value || '').trim();
|
||
if (!feeType) return '';
|
||
const option = this.billingFeeCategoryOptions.find(
|
||
item => String(item.dictKey || '') === feeType || String(item.dictValue || '') === feeType
|
||
);
|
||
return String((option && option.dictKey) || feeType);
|
||
},
|
||
getBillingFeeItemOptions(feeType) {
|
||
const key = this.resolveBillingFeeType(feeType);
|
||
return key ? this.billingFeeItemOptionsMap[key] || [] : [];
|
||
},
|
||
loadBillingFeeItemOptions(feeType) {
|
||
const key = this.resolveBillingFeeType(feeType);
|
||
if (!key) return Promise.resolve([]);
|
||
if (this.billingFeeItemOptionsMap[key]) {
|
||
return Promise.resolve(this.billingFeeItemOptionsMap[key]);
|
||
}
|
||
if (this.billingFeeItemRequestMap[key]) {
|
||
return this.billingFeeItemRequestMap[key];
|
||
}
|
||
this.billingFeeItemLoadingMap[key] = true;
|
||
this.billingFeeItemRequestMap[key] = getFeeItemList(1, 9999, { feeCategory: key })
|
||
.then(res => {
|
||
const records = extractRecords(res);
|
||
this.billingFeeItemOptionsMap[key] = records;
|
||
return records;
|
||
})
|
||
.finally(() => {
|
||
this.billingFeeItemLoadingMap[key] = false;
|
||
delete this.billingFeeItemRequestMap[key];
|
||
});
|
||
return this.billingFeeItemRequestMap[key];
|
||
},
|
||
loadBillingRuleFeeItems(rules = []) {
|
||
const feeTypes = Array.from(
|
||
new Set((rules || []).map(row => this.resolveBillingFeeType(row.feeType)).filter(Boolean))
|
||
);
|
||
feeTypes.forEach(feeType => this.loadBillingFeeItemOptions(feeType));
|
||
},
|
||
handleBillingFeeTypeChange(row, value) {
|
||
row.feeType = this.resolveBillingFeeType(value);
|
||
row.feeItem = '';
|
||
this.loadBillingFeeItemOptions(row.feeType);
|
||
},
|
||
openBillingPlan(row, index = -1) {
|
||
this.billingPlanMode = index > -1 ? 'edit' : 'add';
|
||
this.billingPlanIndex = index;
|
||
const plan = row ? this.cloneData(row) : defaultBillingPlan();
|
||
this.billingPlanForm = {
|
||
...defaultBillingPlan(),
|
||
...plan,
|
||
rules: this.normalizeBillingRules(
|
||
Array.isArray(plan.rules) && plan.rules.length ? plan.rules : [defaultBillingRule()]
|
||
),
|
||
};
|
||
this.billingPlanBox = true;
|
||
this.ensureBillingUnitOptions();
|
||
this.ensureBillingFeeCategoryOptions().then(() => {
|
||
this.billingPlanForm.rules = this.normalizeBillingRules(this.billingPlanForm.rules);
|
||
this.loadBillingRuleFeeItems(this.billingPlanForm.rules);
|
||
});
|
||
this.$nextTick(() => this.$refs.billingPlanFormRef?.clearValidate?.());
|
||
},
|
||
submitBillingPlan() {
|
||
this.$refs.billingPlanFormRef?.validate(valid => {
|
||
if (!valid) return;
|
||
if (!this.validateBillingRules()) return;
|
||
const plan = this.cloneData(this.billingPlanForm);
|
||
plan.rules = this.normalizeBillingRules(plan.rules).map(rule => {
|
||
const ranges = this.canEditBillingLimit(rule) ? rule.limitRanges : [];
|
||
return {
|
||
...rule,
|
||
limitRanges: ranges,
|
||
lowerLimit: ranges[0]?.lowerLimit || '',
|
||
upperLimit: ranges[0]?.upperLimit || '',
|
||
minimumBillingWeight: this.canEditMinimumBillingWeight(rule)
|
||
? rule.minimumBillingWeight
|
||
: '',
|
||
};
|
||
});
|
||
if (!this.billingPlanRows.length && this.billingPlanIndex === -1) {
|
||
plan.defaultPlan = true;
|
||
}
|
||
if (plan.defaultPlan) {
|
||
this.billingPlanRows.forEach(item => {
|
||
item.defaultPlan = false;
|
||
});
|
||
}
|
||
if (this.billingPlanIndex > -1) {
|
||
this.billingPlanRows.splice(this.billingPlanIndex, 1, plan);
|
||
} else {
|
||
this.billingPlanRows.push(plan);
|
||
}
|
||
this.syncBillingPlanJson();
|
||
this.billingPlanBox = false;
|
||
});
|
||
},
|
||
validateBillingRules() {
|
||
const groups = {};
|
||
const requiredFields = [
|
||
['feeType', '费用类型'],
|
||
['feeItem', '费用项'],
|
||
['billingElement', '计费要素'],
|
||
['billingType', '计费类型'],
|
||
['billingUnit', '计费单位'],
|
||
['unitPrice', '单价'],
|
||
];
|
||
for (const [index, row] of (this.billingPlanForm.rules || []).entries()) {
|
||
const emptyField = requiredFields.find(([prop]) => this.isBillingFieldEmpty(row[prop]));
|
||
if (emptyField) {
|
||
this.$message.warning(`第${index + 1}行${emptyField[1]}不能为空`);
|
||
return false;
|
||
}
|
||
if (row.billingElement && !this.getBillingTypeOptions(row).includes(row.billingType)) {
|
||
this.$message.warning('请选择计费要素对应的计费类型');
|
||
return false;
|
||
}
|
||
if (!this.canEditBillingLimit(row)) {
|
||
continue;
|
||
}
|
||
const key = row.billingElement || '';
|
||
groups[key] = groups[key] || [];
|
||
const ranges = this.getBillingLimitRanges(row);
|
||
for (const range of ranges) {
|
||
const lower = Number(range.lowerLimit);
|
||
const upper = Number(range.upperLimit);
|
||
if (
|
||
range.lowerLimit === '' ||
|
||
range.upperLimit === '' ||
|
||
Number.isNaN(lower) ||
|
||
Number.isNaN(upper)
|
||
) {
|
||
this.$message.warning('请完整填写计费要素上下限');
|
||
return false;
|
||
}
|
||
if (lower > upper) {
|
||
this.$message.warning('计费要素下限不能大于上限');
|
||
return false;
|
||
}
|
||
groups[key].push({ lower, upper });
|
||
}
|
||
}
|
||
return this.validateBillingLimitRangeGroups(groups);
|
||
},
|
||
validateBillingLimitRangeGroups(groups) {
|
||
const precision = 0.000001;
|
||
for (const [billingElement, ranges] of Object.entries(groups)) {
|
||
if (ranges.length <= 1) continue;
|
||
const sortedRanges = [...ranges].sort((prev, next) => {
|
||
if (prev.lower !== next.lower) return prev.lower - next.lower;
|
||
return prev.upper - next.upper;
|
||
});
|
||
for (let index = 1; index < sortedRanges.length; index += 1) {
|
||
const prevRange = sortedRanges[index - 1];
|
||
const currentRange = sortedRanges[index];
|
||
if (currentRange.lower < prevRange.upper - precision) {
|
||
this.$message.warning(`${billingElement}的计费要素区间不能重叠`);
|
||
return false;
|
||
}
|
||
if (currentRange.lower > prevRange.upper + precision) {
|
||
this.$message.warning(`${billingElement}的计费要素区间必须连续,不能存在间隙`);
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
isBillingFieldEmpty(value) {
|
||
return value === undefined || value === null || String(value).trim() === '';
|
||
},
|
||
removeBillingPlan(index) {
|
||
this.$confirm('确定删除该计费方案?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
this.billingPlanRows.splice(index, 1);
|
||
this.syncBillingPlanJson();
|
||
});
|
||
},
|
||
addBillingRule() {
|
||
const row = defaultBillingRule();
|
||
this.billingPlanForm.rules.push(row);
|
||
this.ensureBillingFeeCategoryOptions();
|
||
},
|
||
copyBillingRule(row) {
|
||
const nextRow = this.cloneData(row);
|
||
this.billingPlanForm.rules.push(nextRow);
|
||
this.loadBillingFeeItemOptions(nextRow.feeType);
|
||
},
|
||
removeBillingRule(index) {
|
||
this.billingPlanForm.rules.splice(index, 1);
|
||
if (!this.billingPlanForm.rules.length) {
|
||
this.billingPlanForm.rules.push(defaultBillingRule());
|
||
}
|
||
},
|
||
getBillingTypeOptions(row) {
|
||
return this.billingElementTypeMap[row.billingElement] || [];
|
||
},
|
||
handleBillingElementChange(row) {
|
||
if (!this.getBillingTypeOptions(row).includes(row.billingType)) {
|
||
row.billingType = '';
|
||
}
|
||
if (!this.canEditMinimumBillingWeight(row)) {
|
||
row.minimumBillingWeight = '';
|
||
}
|
||
this.handleBillingTypeChange(row);
|
||
},
|
||
handleBillingTypeChange(row) {
|
||
if (!this.canEditBillingLimit(row)) {
|
||
row.limitRanges = [{ lowerLimit: '', upperLimit: '' }];
|
||
row.lowerLimit = '';
|
||
row.upperLimit = '';
|
||
return;
|
||
}
|
||
row.limitRanges = this.normalizeBillingLimitRanges(row);
|
||
this.syncBillingLegacyLimit(row);
|
||
},
|
||
canEditBillingLimit(row) {
|
||
return (
|
||
!this.dialogReadonly &&
|
||
Boolean(row.billingElement) &&
|
||
Boolean(row.billingType) &&
|
||
(row.billingType.includes('区间') || row.billingType.includes('阶梯'))
|
||
);
|
||
},
|
||
canEditMinimumBillingWeight(row) {
|
||
return !this.dialogReadonly && ['按重量', '按吨·公里'].includes(row.billingElement);
|
||
},
|
||
getBillingLimitRanges(row) {
|
||
return Array.isArray(row.limitRanges) && row.limitRanges.length
|
||
? row.limitRanges
|
||
: [{ lowerLimit: '', upperLimit: '' }];
|
||
},
|
||
normalizeBillingRules(rules = []) {
|
||
return (rules || []).map(rule => {
|
||
const nextRule = {
|
||
...defaultBillingRule(),
|
||
...rule,
|
||
};
|
||
nextRule.feeType = this.resolveBillingFeeType(nextRule.feeType);
|
||
nextRule.limitRanges = this.normalizeBillingLimitRanges(nextRule);
|
||
this.syncBillingLegacyLimit(nextRule);
|
||
return nextRule;
|
||
});
|
||
},
|
||
normalizeBillingLimitRanges(row) {
|
||
const ranges = Array.isArray(row.limitRanges) ? row.limitRanges : [];
|
||
const nextRanges = ranges
|
||
.map(item => ({
|
||
lowerLimit: item?.lowerLimit ?? '',
|
||
upperLimit: item?.upperLimit ?? '',
|
||
}))
|
||
.filter(item => item.lowerLimit !== '' || item.upperLimit !== '');
|
||
if (!nextRanges.length && (row.lowerLimit !== '' || row.upperLimit !== '')) {
|
||
nextRanges.push({
|
||
lowerLimit: row.lowerLimit ?? '',
|
||
upperLimit: row.upperLimit ?? '',
|
||
});
|
||
}
|
||
return nextRanges.length ? nextRanges : [{ lowerLimit: '', upperLimit: '' }];
|
||
},
|
||
syncBillingLegacyLimit(row) {
|
||
const firstRange = row.limitRanges?.[0] || {};
|
||
row.lowerLimit = firstRange.lowerLimit || '';
|
||
row.upperLimit = firstRange.upperLimit || '';
|
||
},
|
||
addBillingLimitRange(row, index) {
|
||
row.limitRanges = this.getBillingLimitRanges(row);
|
||
row.limitRanges.splice(index + 1, 0, { lowerLimit: '', upperLimit: '' });
|
||
},
|
||
removeBillingLimitRange(row, index) {
|
||
row.limitRanges = this.getBillingLimitRanges(row);
|
||
row.limitRanges.splice(index, 1);
|
||
if (!row.limitRanges.length) {
|
||
row.limitRanges.push({ lowerLimit: '', upperLimit: '' });
|
||
}
|
||
this.syncBillingLegacyLimit(row);
|
||
},
|
||
handleBillingLimitInput(row, index, prop, value) {
|
||
row.limitRanges = this.getBillingLimitRanges(row);
|
||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||
const parts = text.split('.');
|
||
row.limitRanges[index][prop] =
|
||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||
this.syncBillingLegacyLimit(row);
|
||
},
|
||
openBillingMatch(row, index) {
|
||
this.billingMatchRuleIndex = index;
|
||
this.billingMatchForm = this.normalizeBillingMatchCondition(row.matchCondition);
|
||
this.billingMatchBox = true;
|
||
this.ensureBillingMatchRegionOptions();
|
||
this.ensureBillingCargoTypeOptions().then(() => {
|
||
const path = this.resolveBillingMatchCargoTypePath(this.billingMatchForm);
|
||
if (path.length) {
|
||
this.handleBillingMatchCargoTypeChange(path);
|
||
}
|
||
});
|
||
},
|
||
saveBillingMatch() {
|
||
const row = this.billingPlanForm.rules[this.billingMatchRuleIndex];
|
||
if (row) {
|
||
this.handleBillingMatchRegionChange('origin', this.billingMatchForm.originPath);
|
||
this.handleBillingMatchRegionChange('destination', this.billingMatchForm.destinationPath);
|
||
this.handleBillingMatchCargoTypeChange(this.billingMatchForm.cargoTypePath);
|
||
row.matchCondition = this.cloneData(this.billingMatchForm);
|
||
}
|
||
this.billingMatchBox = false;
|
||
},
|
||
handleBillingDecimalInput(row, prop, value) {
|
||
const text = String(value || '').replace(/[^\d.]/g, '');
|
||
const parts = text.split('.');
|
||
row[prop] =
|
||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||
},
|
||
searchReset() {
|
||
this.query = {};
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page);
|
||
},
|
||
searchChange(params, done) {
|
||
this.query = params;
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, params);
|
||
done();
|
||
},
|
||
selectionChange(list) {
|
||
this.selectionList = list;
|
||
},
|
||
selectionClear() {
|
||
this.selectionList = [];
|
||
if (this.$refs.crud) {
|
||
this.$refs.crud.toggleSelection();
|
||
}
|
||
},
|
||
currentChange(currentPage) {
|
||
this.page.currentPage = currentPage;
|
||
},
|
||
sizeChange(pageSize) {
|
||
this.page.pageSize = pageSize;
|
||
},
|
||
refreshChange() {
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
handleScopeChange() {
|
||
if (!this.canViewAllDept) {
|
||
this.allDept = 0;
|
||
}
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page);
|
||
},
|
||
normalizeSearchRangeValue(value, suffix) {
|
||
if (!value) return undefined;
|
||
return suffix ? `${value} ${suffix}` : value;
|
||
},
|
||
normalizeSearchRangeParams(params = {}) {
|
||
const query = { ...params };
|
||
const rangeMap = this.config.searchRangeMap || {};
|
||
Object.entries(rangeMap).forEach(([rangeProp, mapping]) => {
|
||
const [startProp, endProp, startSuffix, endSuffix] = Array.isArray(mapping)
|
||
? mapping
|
||
: [mapping.startProp, mapping.endProp, mapping.startSuffix, mapping.endSuffix];
|
||
const range = query[rangeProp];
|
||
if (Array.isArray(range) && range.length === 2) {
|
||
query[startProp] = this.normalizeSearchRangeValue(range[0], startSuffix);
|
||
query[endProp] = this.normalizeSearchRangeValue(range[1], endSuffix);
|
||
}
|
||
delete query[rangeProp];
|
||
});
|
||
return query;
|
||
},
|
||
normalizeQueryParams(params = {}) {
|
||
let query = { ...params };
|
||
if (Object.keys(this.config.searchRangeMap || {}).length) {
|
||
query = this.normalizeSearchRangeParams(query);
|
||
}
|
||
if (this.config.enableOrganizationSelect && query.organizationName) {
|
||
const organizationId = Array.isArray(query.organizationName)
|
||
? query.organizationName[query.organizationName.length - 1]
|
||
: query.organizationName;
|
||
if (!organizationId) {
|
||
delete query.organizationId;
|
||
delete query.organizationName;
|
||
return query;
|
||
}
|
||
const organization = this.findOrganizationOption(organizationId);
|
||
query.organizationId = organizationId;
|
||
if (organization) {
|
||
query.organizationName = organization.rawLabel || organization.label || '';
|
||
} else {
|
||
delete query.organizationName;
|
||
}
|
||
if (this.canViewAllDept) {
|
||
query.allDept = 1;
|
||
}
|
||
}
|
||
return query;
|
||
},
|
||
onLoad(page, params = {}) {
|
||
this.loading = true;
|
||
const query = this.normalizeQueryParams({ ...params, ...this.query, allDept: this.allDept });
|
||
this.api
|
||
.getList(page.currentPage, page.pageSize, query)
|
||
.then(res => {
|
||
const result = res.data.data;
|
||
this.page.total = result.total;
|
||
this.data = result.records;
|
||
this.selectionClear();
|
||
})
|
||
.finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
buildExportParams() {
|
||
const query = this.normalizeQueryParams({ ...this.query, allDept: this.allDept });
|
||
return {
|
||
...query,
|
||
ids: this.ids,
|
||
[this.website.tokenHeader]: getToken(),
|
||
};
|
||
},
|
||
handleExport() {
|
||
this.$confirm(`是否导出${this.config.exportName || this.config.title}?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
NProgress.start();
|
||
exportBlob(this.config.exportUrl, this.buildExportParams(), { feedback: true })
|
||
.then(res => {
|
||
downloadXls(
|
||
res.data,
|
||
`${this.config.exportName || this.config.title}${this.$dayjs().format(
|
||
'YYYY-MM-DD HH:mm:ss'
|
||
)}.xlsx`
|
||
);
|
||
})
|
||
.finally(() => {
|
||
NProgress.done();
|
||
});
|
||
});
|
||
},
|
||
handleTemplate() {
|
||
NProgress.start();
|
||
exportBlob(
|
||
this.config.templateUrl,
|
||
{
|
||
[this.website.tokenHeader]: getToken(),
|
||
},
|
||
{ feedback: true }
|
||
)
|
||
.then(res => {
|
||
downloadXls(res.data, `${this.config.title}模板.xlsx`);
|
||
})
|
||
.finally(() => {
|
||
NProgress.done();
|
||
});
|
||
},
|
||
handleImport() {
|
||
if (this.config.enableWaybillImport) {
|
||
this.waybillImportBox = true;
|
||
return;
|
||
}
|
||
if (this.config.enableTransportPlanImport) {
|
||
this.transportPlanImportBox = true;
|
||
this.loadProjectOptions();
|
||
return;
|
||
}
|
||
const column = this.findColumn(this.excelOptionConfig.column, 'excelFile');
|
||
column.action = this.config.importUrl;
|
||
openImportDialog(this, this.config.title, () => this.onLoad(this.page, this.query));
|
||
},
|
||
resetTransportPlanImport() {
|
||
this.transportPlanImportForm = {
|
||
projectId: '',
|
||
projectName: '',
|
||
customerName: '',
|
||
contractId: '',
|
||
contractName: '',
|
||
file: null,
|
||
};
|
||
this.transportPlanImportFiles = [];
|
||
this.transportPlanImportSourceFile = null;
|
||
this.transportPlanImportRows = [];
|
||
this.transportPlanImportSelection = [];
|
||
this.transportPlanImportTab = 'all';
|
||
this.transportPlanImportEditingKey = '';
|
||
this.transportPlanImportEditSnapshot = null;
|
||
this.transportPlanImportContractOptions = [];
|
||
this.transportPlanImportContractLoading = false;
|
||
this.$refs.transportPlanImportFormRef?.clearValidate();
|
||
},
|
||
handleTransportPlanImportProjectChange(projectId) {
|
||
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
||
this.transportPlanImportForm.customerName =
|
||
project?.customerName || project?.customerNames || project?.customer || '';
|
||
this.transportPlanImportForm.projectName = project?.projectName || '';
|
||
this.transportPlanImportForm.contractId = '';
|
||
this.transportPlanImportForm.contractName = '';
|
||
this.transportPlanImportContractOptions = [];
|
||
if (!projectId) return;
|
||
this.transportPlanImportContractLoading = true;
|
||
getContractList(1, 9999, { projectId, projectName: project?.projectName })
|
||
.then(res => {
|
||
this.transportPlanImportContractOptions = extractRecords(res);
|
||
const contract = this.transportPlanImportContractOptions[0];
|
||
this.transportPlanImportForm.customerName =
|
||
this.transportPlanImportForm.customerName || contract?.customerName || '';
|
||
this.transportPlanImportForm.contractId = contract?.id || '';
|
||
this.transportPlanImportForm.contractName = contract?.contractName || '';
|
||
})
|
||
.finally(() => {
|
||
this.transportPlanImportContractLoading = false;
|
||
});
|
||
},
|
||
handleTransportPlanImportContractChange(contractId) {
|
||
const contract = this.transportPlanImportContractOptions.find(
|
||
item => String(item.id) === String(contractId)
|
||
);
|
||
this.transportPlanImportForm.contractName = contract?.contractName || '';
|
||
},
|
||
async handleTransportPlanImportFileChange(file, fileList) {
|
||
const isExcel = /\.(xls|xlsx)$/i.test(file.name || '');
|
||
if (!isExcel) {
|
||
this.$message.error('请上传 .xls 或 .xlsx 格式文件');
|
||
this.transportPlanImportFiles = [];
|
||
this.transportPlanImportForm.file = null;
|
||
return;
|
||
}
|
||
try {
|
||
const XLSX = await import('xlsx');
|
||
const workbook = XLSX.read(await file.raw.arrayBuffer(), {
|
||
type: 'array',
|
||
cellDates: false,
|
||
});
|
||
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||
const values = XLSX.utils.sheet_to_json(worksheet, {
|
||
header: 1,
|
||
defval: '',
|
||
raw: false,
|
||
blankrows: false,
|
||
});
|
||
const headers = values[0] || [];
|
||
const headerMap = headers.reduce((result, label, index) => {
|
||
const normalized = String(label || '')
|
||
.replace(/^\*/, '')
|
||
.trim();
|
||
if (normalized) result[normalized] = index;
|
||
return result;
|
||
}, {});
|
||
const rows = values.slice(1).reduce((result, cells, index) => {
|
||
const row = {
|
||
_key: `${Date.now()}-${index}-${Math.random().toString(36).slice(2)}`,
|
||
};
|
||
transportPlanImportColumns.forEach(([label, prop]) => {
|
||
row[prop] = String(cells[headerMap[label]] ?? '').trim();
|
||
});
|
||
if (transportPlanImportColumns.some(([, prop]) => row[prop])) result.push(row);
|
||
return result;
|
||
}, []);
|
||
if (!rows.length) {
|
||
this.$message.warning('未读取到计划明细数据');
|
||
this.transportPlanImportFiles = [];
|
||
this.transportPlanImportForm.file = null;
|
||
return;
|
||
}
|
||
this.transportPlanImportFiles = fileList.slice(-1);
|
||
this.transportPlanImportSourceFile = file.raw;
|
||
this.transportPlanImportForm.file = file.raw;
|
||
this.transportPlanImportRows = rows;
|
||
this.markTransportPlanImportDuplicates();
|
||
this.$refs.transportPlanImportFormRef?.validateField('file');
|
||
this.$message.success(`已读取${rows.length}条计划明细`);
|
||
} catch (error) {
|
||
this.transportPlanImportFiles = [];
|
||
this.transportPlanImportSourceFile = null;
|
||
this.transportPlanImportForm.file = null;
|
||
this.transportPlanImportRows = [];
|
||
this.$message.error('计划明细表读取失败,请检查文件格式');
|
||
}
|
||
},
|
||
handleTransportPlanImportFileRemove() {
|
||
this.transportPlanImportFiles = [];
|
||
this.transportPlanImportSourceFile = null;
|
||
this.transportPlanImportForm.file = null;
|
||
this.transportPlanImportRows = [];
|
||
this.transportPlanImportSelection = [];
|
||
this.transportPlanImportEditingKey = '';
|
||
this.transportPlanImportEditSnapshot = null;
|
||
},
|
||
transportPlanImportDuplicateKey(row) {
|
||
return transportPlanImportColumns
|
||
.map(([, prop]) => String(row[prop] || '').trim())
|
||
.join('\u0001');
|
||
},
|
||
markTransportPlanImportDuplicates() {
|
||
const countMap = this.transportPlanImportRows.reduce((result, row) => {
|
||
const key = this.transportPlanImportDuplicateKey(row);
|
||
result[key] = (result[key] || 0) + 1;
|
||
return result;
|
||
}, {});
|
||
this.transportPlanImportRows.forEach(row => {
|
||
row._duplicate = countMap[this.transportPlanImportDuplicateKey(row)] > 1;
|
||
});
|
||
},
|
||
handleTransportPlanImportSelectionChange(rows) {
|
||
this.transportPlanImportSelection = rows;
|
||
},
|
||
isEditingTransportPlanImportRow(row) {
|
||
return this.transportPlanImportEditingKey === row._key;
|
||
},
|
||
editTransportPlanImportRow(row) {
|
||
if (this.transportPlanImportEditingKey && this.transportPlanImportEditingKey !== row._key) {
|
||
this.$message.warning('请先保存或取消当前编辑');
|
||
return;
|
||
}
|
||
this.transportPlanImportEditingKey = row._key;
|
||
this.transportPlanImportEditSnapshot = { ...row };
|
||
},
|
||
saveTransportPlanImportRow() {
|
||
this.transportPlanImportEditingKey = '';
|
||
this.transportPlanImportEditSnapshot = null;
|
||
this.markTransportPlanImportDuplicates();
|
||
},
|
||
cancelTransportPlanImportRow(row) {
|
||
Object.assign(row, this.transportPlanImportEditSnapshot || {});
|
||
this.transportPlanImportEditingKey = '';
|
||
this.transportPlanImportEditSnapshot = null;
|
||
this.markTransportPlanImportDuplicates();
|
||
},
|
||
removeTransportPlanImportRow(row) {
|
||
this.transportPlanImportRows = this.transportPlanImportRows.filter(
|
||
item => item._key !== row._key
|
||
);
|
||
this.transportPlanImportSelection = this.transportPlanImportSelection.filter(
|
||
item => item._key !== row._key
|
||
);
|
||
this.markTransportPlanImportDuplicates();
|
||
},
|
||
removeSelectedTransportPlanImportRows() {
|
||
const selectedKeys = new Set(this.transportPlanImportSelection.map(row => row._key));
|
||
this.transportPlanImportRows = this.transportPlanImportRows.filter(
|
||
row => !selectedKeys.has(row._key)
|
||
);
|
||
this.transportPlanImportSelection = [];
|
||
this.markTransportPlanImportDuplicates();
|
||
},
|
||
validateTransportPlanImportRows() {
|
||
if (!this.transportPlanImportRows.length) {
|
||
this.$message.warning('请保留至少一条计划明细');
|
||
return false;
|
||
}
|
||
const requiredFields = [
|
||
['planName', '计划名称'],
|
||
['transportType', '运输方式'],
|
||
['planStartDate', '计划开始日期'],
|
||
['planEndDate', '计划结束日期'],
|
||
['cargoType', '货物类型'],
|
||
['departureAddress', '发货地址'],
|
||
['arrivalAddress', '收货地址'],
|
||
];
|
||
for (const [index, row] of this.transportPlanImportRows.entries()) {
|
||
const emptyField = requiredFields.find(([prop]) => !String(row[prop] || '').trim());
|
||
if (emptyField) {
|
||
this.$message.warning(`第${index + 1}行${emptyField[1]}不能为空`);
|
||
return false;
|
||
}
|
||
const isValidDate = value => {
|
||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
||
const date = new Date(`${value}T00:00:00`);
|
||
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
||
};
|
||
if (!isValidDate(row.planStartDate) || !isValidDate(row.planEndDate)) {
|
||
this.$message.warning(`第${index + 1}行计划日期格式必须为 YYYY-MM-DD`);
|
||
return false;
|
||
}
|
||
if (row.planEndDate < row.planStartDate) {
|
||
this.$message.warning(`第${index + 1}行计划结束日期不能早于计划开始日期`);
|
||
return false;
|
||
}
|
||
if (
|
||
row.quantity !== '' &&
|
||
(!Number.isFinite(Number(row.quantity)) || Number(row.quantity) < 0)
|
||
) {
|
||
this.$message.warning(`第${index + 1}行数量必须为非负数`);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
async buildTransportPlanImportFile() {
|
||
const XLSX = await import('xlsx');
|
||
const headers = transportPlanImportColumns.map(([label]) => {
|
||
return [
|
||
'计划名称',
|
||
'运输方式',
|
||
'计划开始日期',
|
||
'计划结束日期',
|
||
'货物类型',
|
||
'发货地址',
|
||
'收货地址',
|
||
].includes(label)
|
||
? `*${label}`
|
||
: label;
|
||
});
|
||
const values = [
|
||
headers,
|
||
...this.transportPlanImportRows.map(row =>
|
||
transportPlanImportColumns.map(([, prop]) => String(row[prop] ?? ''))
|
||
),
|
||
];
|
||
const workbook = XLSX.utils.book_new();
|
||
XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(values), '运输计划导入模板');
|
||
const binary = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
|
||
const fileName = (
|
||
this.transportPlanImportSourceFile?.name || '运输计划导入模板.xlsx'
|
||
).replace(/\.xls$/i, '.xlsx');
|
||
return new File([binary], fileName, {
|
||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
});
|
||
},
|
||
handleTransportPlanTemplate() {
|
||
exportBlob(this.config.transportPlanTemplateUrl, {}, { feedback: true }).then(res => {
|
||
downloadXls(res.data, '运输计划导入模板.xlsx');
|
||
});
|
||
},
|
||
async submitTransportPlanImport() {
|
||
const valid = await this.$refs.transportPlanImportFormRef?.validate().catch(() => false);
|
||
if (!valid || this.transportPlanImportLoading) return;
|
||
if (!this.validateTransportPlanImportRows()) return;
|
||
if (typeof this.api.importTransportPlan !== 'function') {
|
||
this.$message.error('运输计划导入接口不可用');
|
||
return;
|
||
}
|
||
this.transportPlanImportLoading = true;
|
||
try {
|
||
const file = await this.buildTransportPlanImportFile();
|
||
const res = await this.api.importTransportPlan({ ...this.transportPlanImportForm, file });
|
||
const contentType = res.headers?.['content-type'] || res.data?.type || '';
|
||
if (
|
||
contentType.includes('application/vnd.ms-excel') ||
|
||
contentType.includes('spreadsheetml')
|
||
) {
|
||
downloadXls(
|
||
res.data,
|
||
`运输计划导入失败明细${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||
);
|
||
this.$message.warning('部分数据导入失败,已下载失败明细');
|
||
} else {
|
||
const text = await res.data.text();
|
||
const result = text ? JSON.parse(text) : {};
|
||
if (result.code !== 200) throw new Error(result.msg || '导入失败');
|
||
this.$message.success('导入完成');
|
||
}
|
||
this.transportPlanImportBox = false;
|
||
this.onLoad(this.page, this.query);
|
||
} catch (error) {
|
||
this.$message.error(error.message || '导入失败');
|
||
} finally {
|
||
this.transportPlanImportLoading = false;
|
||
}
|
||
},
|
||
handleCopy(row) {
|
||
this.$confirm(`确定复制该${this.config.title}?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => this.api.copy(row.id))
|
||
.then(() => {
|
||
this.$message.success('复制成功');
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
},
|
||
handleAction(action, row) {
|
||
const actionName = {
|
||
enable: '启用',
|
||
disable: '停用',
|
||
cancel: '取消',
|
||
complete: '完成',
|
||
reassign: '重新派单',
|
||
}[action];
|
||
this.$confirm(`确定${actionName}该${this.config.title}?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => this.api[action](row.id))
|
||
.then(() => {
|
||
this.$message.success(`${actionName}成功`);
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
},
|
||
handleCustomOperation(operation, row) {
|
||
if (operation.action === 'createFromTemplate') {
|
||
this.api.getDetail(row.id).then(res => {
|
||
const template = res.data?.data || row;
|
||
const templateType = template.templateType || template.templateTypeName;
|
||
const target =
|
||
templateType === '运单' ? '/business/waybill-manage' : '/business/transport-plan';
|
||
sessionStorage.setItem(
|
||
'business-template-create',
|
||
JSON.stringify({ target: target.slice('/business/'.length), data: template })
|
||
);
|
||
this.$router.push({ path: target, query: { fromTemplate: Date.now().toString() } });
|
||
});
|
||
return;
|
||
}
|
||
if (operation.action === 'flow') {
|
||
this.handleFlow(row);
|
||
return;
|
||
}
|
||
if (operation.action === 'dispatch') {
|
||
this.openDispatchDialog(row);
|
||
return;
|
||
}
|
||
const run = value => {
|
||
const args = operation.prompt ? [row.id, value] : [row.id];
|
||
this.api[operation.action](...args).then(() => {
|
||
this.$message.success(`${operation.label}成功`);
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
};
|
||
if (operation.prompt) {
|
||
this.$prompt(operation.prompt, operation.label, {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
inputPattern: /\S+/,
|
||
inputErrorMessage: operation.promptError || '请输入内容',
|
||
}).then(({ value }) => run(value));
|
||
return;
|
||
}
|
||
this.$confirm(operation.confirm || `确定${operation.label}该${this.config.title}?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => run());
|
||
},
|
||
handleFlow(row, loadedDetail = false) {
|
||
const processInstanceId = row.processInstanceId || row.processInstanceID || row.procInstId;
|
||
if (
|
||
!processInstanceId &&
|
||
!loadedDetail &&
|
||
row.id &&
|
||
typeof this.api.getDetail === 'function'
|
||
) {
|
||
this.api.getDetail(row.id).then(res => this.handleFlow(res.data.data || {}, true));
|
||
return;
|
||
}
|
||
if (!processInstanceId) {
|
||
this.$message.warning('流程实例为空,无法查看流程');
|
||
return;
|
||
}
|
||
if (this.website.design.designMode) {
|
||
this.processInstanceId = processInstanceId;
|
||
} else {
|
||
this.flowUrl = `/api/blade-flow/process/diagram-view?processInstanceId=${processInstanceId}`;
|
||
}
|
||
this.flowBox = true;
|
||
},
|
||
openDispatchDialog(row) {
|
||
if (!row?.id) {
|
||
this.$message.warning('运输计划数据异常,无法调度');
|
||
return;
|
||
}
|
||
this.dispatchRow = { ...row };
|
||
this.dispatchBox = true;
|
||
this.dispatchLoading = true;
|
||
this.dispatchRows = [];
|
||
const request =
|
||
this.api && typeof this.api.getDetail === 'function'
|
||
? this.api.getDetail(row.id)
|
||
: Promise.resolve({ data: { data: row } });
|
||
request
|
||
.then(res => {
|
||
const detail = {
|
||
...row,
|
||
...(res?.data?.data || {}),
|
||
};
|
||
this.dispatchRow = detail;
|
||
this.dispatchRows = this.buildDispatchRows(detail);
|
||
})
|
||
.finally(() => {
|
||
this.dispatchLoading = false;
|
||
});
|
||
},
|
||
resetDispatchDialog() {
|
||
this.dispatchRow = {};
|
||
this.dispatchRows = [];
|
||
this.dispatchLoading = false;
|
||
this.dispatchSaving = '';
|
||
this.dispatchItemBox = false;
|
||
this.dispatchItemIndex = -1;
|
||
this.dispatchItemForm = defaultDispatchRow();
|
||
this.dispatchItemAttachmentRows = [];
|
||
},
|
||
buildDispatchRows(plan = {}) {
|
||
const goodsRows = this.parseJsonArray(plan.goodsJson);
|
||
const sourceRows = goodsRows.length ? goodsRows : [plan];
|
||
return sourceRows.map((item, index) => this.normalizeDispatchRow(plan, item, index));
|
||
},
|
||
normalizeDispatchRow(plan = {}, item = {}, index = 0) {
|
||
const cargoInfo = this.formatDispatchCargoInfo(plan, item);
|
||
return {
|
||
_key: `${plan.id || 'dispatch'}-${index}-${
|
||
item.id || item.cargoName || item.vehicleNo || 'row'
|
||
}`,
|
||
taskEntryMode: item.taskEntryMode || 'simple',
|
||
transportType: item.transportType || plan.transportType || '',
|
||
carrierType: item.carrierType || plan.carrierType || '承运商',
|
||
carrierName: item.carrierName || plan.carrierName || '',
|
||
driverName: item.driverName || plan.driverName || '',
|
||
driverPhone: item.driverPhone || plan.driverPhone || '',
|
||
vehicleNo: item.vehicleNo || plan.vehicleNo || '',
|
||
trailerVehicleNo: item.trailerVehicleNo || '',
|
||
escortName: item.escortName || '',
|
||
escortPhone: item.escortPhone || '',
|
||
mileage: item.mileage || '',
|
||
cargoType: item.cargoType || item.goodsType || plan.cargoType || '',
|
||
cargoName: item.cargoName || item.goodsName || plan.cargoName || '',
|
||
cargoInfo,
|
||
quantity: item.quantity || item.cargoQuantity || item.goodsQuantity || '',
|
||
quantityUnit: item.quantityUnit || item.goodsQuantityUnit || item.unit || '吨',
|
||
specification: item.specification || item.spec || '',
|
||
model: item.model || '',
|
||
packageType: item.packageType || item.package || '',
|
||
estimatedStartTime: item.estimatedStartTime || plan.planStartDate || '',
|
||
estimatedEndTime: item.estimatedEndTime || plan.planEndDate || '',
|
||
departureAddress: item.departureAddress || plan.departureAddress || '',
|
||
departureName: item.departureName || plan.departureName || '',
|
||
departureContact: item.departureContact || plan.departureContact || '',
|
||
departurePhone: item.departurePhone || plan.departurePhone || '',
|
||
arrivalAddress: item.arrivalAddress || plan.arrivalAddress || '',
|
||
arrivalName: item.arrivalName || plan.arrivalName || '',
|
||
arrivalContact: item.arrivalContact || plan.arrivalContact || '',
|
||
arrivalPhone: item.arrivalPhone || plan.arrivalPhone || '',
|
||
dispatchUserName: item.dispatchUserName || plan.dispatchUserName || '',
|
||
dispatchTime: item.dispatchTime || plan.dispatchTime || '',
|
||
attachmentsJson: item.attachmentsJson || '',
|
||
remark: item.remark || item.taskRemark || plan.remark || '',
|
||
};
|
||
},
|
||
formatDispatchCargoInfo(plan = {}, item = {}) {
|
||
const cargoName = item.cargoName || item.goodsName || plan.cargoName || '';
|
||
const cargoType = item.cargoType || item.goodsType || plan.cargoType || '';
|
||
const quantity = item.quantity || item.cargoQuantity || item.goodsQuantity || '';
|
||
const unit = item.quantityUnit || item.goodsQuantityUnit || item.unit || '';
|
||
const quantityText = quantity ? `${quantity}${unit}` : '';
|
||
return (
|
||
[cargoName, cargoType, quantityText].filter(Boolean).join(' - ') || plan.goodsInfo || ''
|
||
);
|
||
},
|
||
openDispatchItemDialog(index = -1, row = defaultDispatchRow()) {
|
||
const defaultGoods = this.dispatchPlanGoodsRows[0] || {};
|
||
const baseRow = {
|
||
...defaultDispatchRow(),
|
||
transportType: this.dispatchRow.transportType || '',
|
||
carrierType: this.dispatchRow.carrierType || '承运商',
|
||
carrierName: this.dispatchRow.carrierName || '',
|
||
driverName: this.dispatchRow.driverName || '',
|
||
driverPhone: this.dispatchRow.driverPhone || '',
|
||
vehicleNo: this.dispatchRow.vehicleNo || '',
|
||
cargoName: defaultGoods.cargoName || this.dispatchRow.cargoName || '',
|
||
cargoType: defaultGoods.cargoType || this.dispatchRow.cargoType || '',
|
||
quantity:
|
||
index >= 0
|
||
? defaultGoods.quantity || ''
|
||
: this.formatDispatchQuantity(this.dispatchSummaryRemainingQuantity),
|
||
quantityUnit: defaultGoods.quantityUnit || '吨',
|
||
specification: defaultGoods.specification || '',
|
||
model: defaultGoods.model || '',
|
||
packageType: defaultGoods.packageType || '',
|
||
departureName: this.dispatchRow.departureName || '',
|
||
departureAddress: this.dispatchRow.departureAddress || '',
|
||
departureContact: this.dispatchRow.departureContact || '',
|
||
departurePhone: this.dispatchRow.departurePhone || '',
|
||
arrivalName: this.dispatchRow.arrivalName || '',
|
||
arrivalAddress: this.dispatchRow.arrivalAddress || '',
|
||
arrivalContact: this.dispatchRow.arrivalContact || '',
|
||
arrivalPhone: this.dispatchRow.arrivalPhone || '',
|
||
};
|
||
this.dispatchItemIndex = index;
|
||
this.dispatchItemForm = {
|
||
...baseRow,
|
||
...(index >= 0 ? row : {}),
|
||
};
|
||
this.dispatchItemForm.carrierType = this.dispatchItemForm.carrierType || '承运商';
|
||
this.dispatchItemAttachmentRows = this.parseJsonArray(
|
||
index >= 0 ? row.attachmentsJson : this.dispatchRow.attachmentsJson
|
||
);
|
||
this.dispatchItemBox = true;
|
||
},
|
||
handleDispatchCarrierTypeChange(value) {
|
||
const nextValue = value || '';
|
||
if (nextValue === '承运商') {
|
||
this.dispatchItemForm.trailerVehicleNo = '';
|
||
this.dispatchItemForm.escortName = '';
|
||
this.dispatchItemForm.escortPhone = '';
|
||
} else {
|
||
this.dispatchItemForm.carrierId = '';
|
||
this.dispatchItemForm.carrierName = '';
|
||
}
|
||
this.dispatchItemForm.carrierType = nextValue;
|
||
},
|
||
loadDispatchCarrierOptions() {
|
||
if (this.taskCarrierLoading) return Promise.resolve([]);
|
||
this.taskCarrierLoading = true;
|
||
return getCarrierCustomerList(1, 9999, { customerType: '承运商' })
|
||
.then(res => {
|
||
this.taskCarrierOptions = extractRecords(res);
|
||
return this.taskCarrierOptions;
|
||
})
|
||
.finally(() => {
|
||
this.taskCarrierLoading = false;
|
||
});
|
||
},
|
||
handleDispatchCarrierChange(value) {
|
||
const carrier = this.taskCarrierOptions.find(item =>
|
||
[item.customerName, item.carrierName, item.fullName, item.name].some(
|
||
name => String(name || '') === String(value || '')
|
||
)
|
||
);
|
||
this.dispatchItemForm.carrierId = carrier?.id || '';
|
||
this.dispatchItemForm.carrierName = value || '';
|
||
},
|
||
loadDispatchDriverOptions() {
|
||
if (this.taskDriverLoading) return Promise.resolve([]);
|
||
this.taskDriverLoading = true;
|
||
return getDriverList(1, 9999, {})
|
||
.then(res => {
|
||
this.taskDriverOptions = extractRecords(res);
|
||
return this.taskDriverOptions;
|
||
})
|
||
.finally(() => {
|
||
this.taskDriverLoading = false;
|
||
});
|
||
},
|
||
handleDispatchDriverChange(value) {
|
||
const driver = this.taskDriverOptions.find(item =>
|
||
[item.driverName, item.name].some(name => String(name || '') === String(value || ''))
|
||
);
|
||
this.dispatchItemForm.driverId = driver?.id || '';
|
||
this.dispatchItemForm.driverName = value || '';
|
||
if (driver) {
|
||
this.dispatchItemForm.driverPhone =
|
||
driver.mobile ||
|
||
driver.phone ||
|
||
driver.driverPhone ||
|
||
this.dispatchItemForm.driverPhone ||
|
||
'';
|
||
}
|
||
},
|
||
saveDispatchItem() {
|
||
const nextRow = {
|
||
...this.dispatchItemForm,
|
||
attachmentsJson: JSON.stringify(this.dispatchItemAttachmentRows),
|
||
};
|
||
const otherDispatchedQuantity = this.dispatchRows.reduce((total, row, index) => {
|
||
if (index === this.dispatchItemIndex) return total;
|
||
return total + this.parseDispatchQuantity(row.quantity);
|
||
}, 0);
|
||
const currentQuantity = this.parseDispatchQuantity(nextRow.quantity);
|
||
const totalQuantity = this.dispatchSummaryTotalQuantity;
|
||
if (totalQuantity > 0 && otherDispatchedQuantity + currentQuantity > totalQuantity) {
|
||
this.$message.warning(
|
||
`已调度数量合计不能超过总数量${this.formatDispatchQuantity(totalQuantity)}${
|
||
this.dispatchSummaryUnit
|
||
}`
|
||
);
|
||
return;
|
||
}
|
||
if (this.dispatchItemIndex >= 0) {
|
||
this.dispatchRows.splice(this.dispatchItemIndex, 1, nextRow);
|
||
} else {
|
||
this.dispatchRows.push(nextRow);
|
||
}
|
||
this.dispatchItemBox = false;
|
||
this.dispatchItemIndex = -1;
|
||
this.dispatchItemForm = defaultDispatchRow();
|
||
this.dispatchItemAttachmentRows = [];
|
||
},
|
||
resetDispatchItemDialog() {
|
||
this.dispatchItemIndex = -1;
|
||
this.dispatchItemForm = defaultDispatchRow();
|
||
this.dispatchItemAttachmentRows = [];
|
||
},
|
||
removeDispatchRow(index) {
|
||
this.$confirm('确定删除该调度明细?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
this.dispatchRows.splice(index, 1);
|
||
});
|
||
},
|
||
handleDispatchAddRow() {
|
||
if (this.dispatchSummaryRemainingQuantity <= 0) {
|
||
this.$message.warning('剩余数量为0,不能新增调度明细');
|
||
return;
|
||
}
|
||
this.openDispatchItemDialog();
|
||
},
|
||
handleDispatchEditRow(row, index) {
|
||
this.openDispatchItemDialog(index, row);
|
||
},
|
||
buildDispatchPayload(mode) {
|
||
const rows = this.dispatchRows.map(
|
||
({ _key, cargoInfo, dispatchUserName, dispatchTime, ...rest }) => rest
|
||
);
|
||
return {
|
||
id: this.dispatchRow.id,
|
||
mode,
|
||
waybills: rows.map(row => ({
|
||
...row,
|
||
taskEntryMode: row.taskEntryMode || 'simple',
|
||
taskRemark: row.remark || '',
|
||
estimatedStartTime: this.formatDispatchDate(row.estimatedStartTime),
|
||
estimatedEndTime: this.formatDispatchDate(row.estimatedEndTime),
|
||
goodsJson: JSON.stringify([
|
||
{
|
||
cargoName: row.cargoName,
|
||
cargoType: row.cargoType,
|
||
quantity: row.quantity,
|
||
quantityUnit: row.quantityUnit,
|
||
specification: row.specification,
|
||
model: row.model,
|
||
packageType: row.packageType,
|
||
},
|
||
]),
|
||
})),
|
||
};
|
||
},
|
||
formatDispatchDate(value) {
|
||
if (!value) return '';
|
||
const date = this.$dayjs(value);
|
||
return date.isValid() ? date.format('YYYY-MM-DD') : value;
|
||
},
|
||
validateDispatchRows(mode) {
|
||
if (!this.dispatchRows.length) {
|
||
this.$message.warning('请先添加调度明细');
|
||
return false;
|
||
}
|
||
if (mode === 'draft') return true;
|
||
const invalidRow = this.dispatchRows.find(row => {
|
||
const selfTransport = row.carrierType !== '承运商';
|
||
return (
|
||
!row.transportType ||
|
||
!row.cargoName ||
|
||
!row.cargoType ||
|
||
!row.quantity ||
|
||
!row.quantityUnit ||
|
||
!row.carrierType ||
|
||
!row.vehicleNo ||
|
||
!row.departureAddress ||
|
||
!row.arrivalAddress ||
|
||
(row.carrierType === '承运商' && !row.carrierName) ||
|
||
(selfTransport &&
|
||
(!row.driverName ||
|
||
!row.driverPhone ||
|
||
!row.trailerVehicleNo ||
|
||
!row.escortName ||
|
||
!row.escortPhone ||
|
||
!row.mileage))
|
||
);
|
||
});
|
||
if (invalidRow) {
|
||
this.$message.warning('请补全调度明细中的必填信息');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
handleDispatchSubmit(mode) {
|
||
if (!this.dispatchRow?.id || typeof this.api.dispatch !== 'function') {
|
||
this.$message.warning('运输计划调度接口不可用');
|
||
return;
|
||
}
|
||
if (!this.validateDispatchRows(mode)) {
|
||
return;
|
||
}
|
||
if (mode === 'submit') {
|
||
this.$confirm(
|
||
`请确认调度信息,将生成 <span style="color: #409eff; font-size: 20px; padding: 0 4px;">${this.dispatchRows.length}</span> 条运单,是否继续?`,
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确认',
|
||
cancelButtonText: '取消',
|
||
dangerouslyUseHTMLString: true,
|
||
type: 'warning',
|
||
}
|
||
)
|
||
.then(() => this.submitDispatch(mode))
|
||
.catch(() => {});
|
||
return;
|
||
}
|
||
this.submitDispatch(mode);
|
||
},
|
||
submitDispatch(mode) {
|
||
this.dispatchSaving = mode;
|
||
const shouldCompletePlan = mode === 'submit' && this.dispatchSummaryRemainingQuantity <= 0;
|
||
this.api
|
||
.dispatch(this.buildDispatchPayload(mode))
|
||
.then(() => {
|
||
if (shouldCompletePlan && typeof this.api.complete === 'function') {
|
||
return this.api.complete(this.dispatchRow.id);
|
||
}
|
||
return undefined;
|
||
})
|
||
.then(() => {
|
||
const message =
|
||
mode === 'draft'
|
||
? '生成草稿成功'
|
||
: shouldCompletePlan
|
||
? '确认生成成功,运输计划已完成'
|
||
: '确认生成成功';
|
||
this.$message.success(message);
|
||
this.dispatchBox = false;
|
||
this.onLoad(this.page, this.query);
|
||
})
|
||
.finally(() => {
|
||
this.dispatchSaving = '';
|
||
});
|
||
},
|
||
dispatchRowKey(row, index) {
|
||
return row._key || `${index}-${row.vehicleNo || row.cargoInfo || row.dispatchTime || 'row'}`;
|
||
},
|
||
dispatchAttachmentLabel(item) {
|
||
return (
|
||
item?.originalName ||
|
||
item?.name ||
|
||
item?.fileName ||
|
||
item?.attachmentName ||
|
||
item?.title ||
|
||
'附件'
|
||
);
|
||
},
|
||
dispatchAttachmentUrl(item) {
|
||
return item?.url || item?.fileUrl || item?.downloadUrl || '';
|
||
},
|
||
formatDispatchRouteContact(contact = '', phone = '') {
|
||
return [contact, phone].filter(Boolean).join(' - ');
|
||
},
|
||
parseDispatchRoutePoint(row, type) {
|
||
const prefix = type === 'start' ? 'departure' : 'arrival';
|
||
const name = row[`${prefix}Name`] || row[`${prefix}Address`] || '';
|
||
const address = row[`${prefix}Address`] || row[`${prefix}DetailAddress`] || '';
|
||
const contact = this.formatDispatchRouteContact(
|
||
row[`${prefix}Contact`],
|
||
row[`${prefix}Phone`]
|
||
);
|
||
return { name, address, contact };
|
||
},
|
||
handleDispatchConfirm() {
|
||
this.handleDispatchSubmit('submit');
|
||
},
|
||
handleChangeRecordFlow(row) {
|
||
const processInstanceId = row.processInstanceId || row.processInstanceID || row.procInstId;
|
||
if (!processInstanceId) {
|
||
this.$message.warning('该变更记录暂无流程实例');
|
||
return;
|
||
}
|
||
this.handleFlow(row, true);
|
||
},
|
||
handleBatchComplete() {
|
||
if (!this.selectionList.length) {
|
||
this.$message.warning('请选择至少一条数据');
|
||
return;
|
||
}
|
||
this.$confirm('确定完成选中的运单?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => this.api.batchComplete(this.ids))
|
||
.then(res => {
|
||
const result = res.data.data || {};
|
||
this.$message.success(
|
||
`成功完成${result.successCount || 0}条,跳过${result.skippedCount || 0}条`
|
||
);
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
},
|
||
handleRoadLoading() {
|
||
if (!this.selectionList.length) {
|
||
this.$message.warning('请选择至少一条数据');
|
||
return;
|
||
}
|
||
if (typeof this.api.roadLoading !== 'function') {
|
||
this.$message.warning('公路配载功能暂未配置');
|
||
return;
|
||
}
|
||
this.$confirm('确定对选中的运单进行公路配载?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => this.api.roadLoading(this.ids))
|
||
.then(() => {
|
||
this.$message.success('公路配载成功');
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.business-crud-page {
|
||
&__field {
|
||
width: 100%;
|
||
}
|
||
|
||
.dialog-section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
color: #303133;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
|
||
&::before {
|
||
display: block;
|
||
width: 4px;
|
||
height: 16px;
|
||
margin-right: 8px;
|
||
background: #409eff;
|
||
content: '';
|
||
}
|
||
|
||
&--action {
|
||
justify-content: space-between;
|
||
}
|
||
}
|
||
|
||
&__shipping-title,
|
||
&__goods-title {
|
||
min-width: 100%;
|
||
}
|
||
|
||
&__goods-title &__section-actions,
|
||
&__shipping-title .el-button {
|
||
margin-left: auto;
|
||
}
|
||
|
||
&__section-actions {
|
||
display: flex;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
&__task {
|
||
width: 100%;
|
||
}
|
||
|
||
&__task-toolbar {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
&__task-form {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
column-gap: 24px;
|
||
row-gap: 16px;
|
||
width: 100%;
|
||
|
||
:deep(.el-form-item) {
|
||
min-width: 0;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
:deep(.el-form-item__content) {
|
||
min-width: 0;
|
||
}
|
||
|
||
:deep(.el-select),
|
||
:deep(.el-cascader),
|
||
:deep(.el-date-editor.el-input) {
|
||
width: 100%;
|
||
}
|
||
|
||
:deep(.el-radio-group) {
|
||
width: 100%;
|
||
}
|
||
|
||
:deep(.el-radio-button) {
|
||
flex: 1;
|
||
}
|
||
|
||
:deep(.el-radio-button__inner) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__task-form--full {
|
||
display: block;
|
||
}
|
||
|
||
&__task-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
column-gap: 24px;
|
||
row-gap: 16px;
|
||
margin-bottom: 16px;
|
||
width: 100%;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
:deep(.el-form-item) {
|
||
min-width: 0;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
:deep(.el-form-item__content) {
|
||
min-width: 0;
|
||
}
|
||
|
||
:deep(.el-select),
|
||
:deep(.el-cascader),
|
||
:deep(.el-date-editor.el-input) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__task-span-4 {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
&__task-span-1 {
|
||
grid-column: span 1;
|
||
}
|
||
|
||
&__task-carrier-type {
|
||
:deep(.el-form-item__content) {
|
||
flex: 0 1 260px;
|
||
max-width: 260px;
|
||
}
|
||
}
|
||
|
||
&__task-remark {
|
||
grid-column: span 3;
|
||
}
|
||
|
||
&__task-note {
|
||
grid-column: 1 / -1;
|
||
|
||
:deep(.el-input) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__suffix-select {
|
||
width: 72px;
|
||
margin-right: -8px;
|
||
|
||
:deep(.el-select__wrapper),
|
||
:deep(.el-select__wrapper:hover),
|
||
:deep(.el-select__wrapper.is-focused) {
|
||
min-height: 24px;
|
||
padding: 0 4px;
|
||
border: 0;
|
||
box-shadow: none !important;
|
||
}
|
||
}
|
||
|
||
&__suffix-select--wide {
|
||
width: 104px;
|
||
}
|
||
|
||
&__append-select {
|
||
width: 92px;
|
||
margin-right: -8px;
|
||
|
||
:deep(.el-select__wrapper),
|
||
:deep(.el-select__wrapper:hover),
|
||
:deep(.el-select__wrapper.is-focused) {
|
||
min-height: 24px;
|
||
padding: 0 4px;
|
||
border: 0;
|
||
box-shadow: none !important;
|
||
}
|
||
}
|
||
|
||
:deep(.business-crud-page__goods-info-cell .cell) {
|
||
overflow: visible;
|
||
line-height: 20px;
|
||
white-space: normal;
|
||
word-break: break-word;
|
||
text-overflow: clip;
|
||
}
|
||
|
||
&__route-address-row {
|
||
display: grid;
|
||
grid-template-columns: 260px minmax(280px, 1fr) 40px 58px minmax(160px, 220px) 72px minmax(
|
||
160px,
|
||
220px
|
||
);
|
||
gap: 8px;
|
||
align-items: center;
|
||
width: 100%;
|
||
|
||
:deep(.el-input),
|
||
:deep(.el-select),
|
||
:deep(.el-cascader) {
|
||
width: 100%;
|
||
}
|
||
|
||
.el-button {
|
||
width: 32px;
|
||
min-width: 32px;
|
||
padding: 0;
|
||
}
|
||
}
|
||
|
||
&__dispatch-shipping-form {
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 16px;
|
||
}
|
||
}
|
||
|
||
&__dispatch-shipping-form &__route-address-row {
|
||
grid-template-columns: 260px minmax(280px, 1fr) 58px minmax(160px, 220px) 72px minmax(
|
||
160px,
|
||
220px
|
||
);
|
||
}
|
||
|
||
&__dispatch-item-attachments {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px 20px;
|
||
min-height: 32px;
|
||
padding: 4px 0 16px 12px;
|
||
color: #909399;
|
||
font-size: 14px;
|
||
}
|
||
|
||
&__dispatch-carrier-title {
|
||
flex-basis: 100%;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
&__dispatch-task-title {
|
||
margin-right: auto;
|
||
}
|
||
|
||
&__route-label {
|
||
color: #606266;
|
||
font-size: 14px;
|
||
line-height: 32px;
|
||
text-align: right;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
&__cargo-wrap {
|
||
display: block;
|
||
width: 100%;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
&__cargo-table {
|
||
width: 100%;
|
||
min-width: 1870px;
|
||
|
||
:deep(.el-table__header th) {
|
||
background: #f5f7fa;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(.el-table__cell) {
|
||
border-color: #eff1f7;
|
||
}
|
||
|
||
:deep(.el-input),
|
||
:deep(.el-select),
|
||
:deep(.el-cascader) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__cargo-actions {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
white-space: nowrap;
|
||
|
||
.el-button {
|
||
margin-left: 0;
|
||
}
|
||
}
|
||
|
||
:deep(.business-crud-page__full-form-item) {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
}
|
||
|
||
:deep(.business-crud-page__full-form-item .el-form-item) {
|
||
width: 100%;
|
||
}
|
||
|
||
:deep(.business-crud-page__full-form-item .el-form-item__label) {
|
||
display: none;
|
||
width: 0 !important;
|
||
padding: 0 !important;
|
||
}
|
||
|
||
:deep(.business-crud-page__full-form-item .el-form-item__content) {
|
||
flex: 1 1 100%;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
:deep(.business-crud-page__full-form-item .el-form-item__content > div) {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
&__cargo-total {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
padding-top: 12px;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
|
||
&__dialog-search {
|
||
padding: 12px 12px 4px;
|
||
margin-bottom: 12px;
|
||
border: 1px solid #eff1f7;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
:deep(.el-input),
|
||
:deep(.el-select),
|
||
:deep(.el-cascader) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__dialog-search-actions {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
&__freight-form {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
column-gap: 24px;
|
||
row-gap: 16px;
|
||
width: 100%;
|
||
|
||
:deep(.el-form-item) {
|
||
min-width: 0;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
:deep(.el-form-item__content) {
|
||
min-width: 0;
|
||
}
|
||
|
||
:deep(.el-input),
|
||
:deep(.el-select) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__dialog-pagination {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
&__map-toolbar {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
|
||
.el-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
}
|
||
|
||
&__map {
|
||
width: 100%;
|
||
height: 420px;
|
||
border: 1px solid #eff1f7;
|
||
}
|
||
|
||
&__map-info {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
margin-top: 10px;
|
||
color: #606266;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
&__date-range {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
&__date {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
&__date-separator {
|
||
flex: 0 0 auto;
|
||
padding: 0 8px;
|
||
color: #303133;
|
||
}
|
||
|
||
&__inline-number {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
|
||
.el-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
span {
|
||
flex: 0 0 auto;
|
||
margin-left: 8px;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
&__billing {
|
||
width: 100%;
|
||
}
|
||
|
||
&__billing-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
&__billing-switch {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
color: #303133;
|
||
}
|
||
|
||
&__billing-table {
|
||
width: 100%;
|
||
|
||
:deep(.el-table__header th) {
|
||
background: #f5f7fa;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(.el-table__cell) {
|
||
border-color: #eff1f7;
|
||
}
|
||
}
|
||
|
||
&__settlement-rule {
|
||
width: 100%;
|
||
}
|
||
|
||
&__settlement-switch {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 18px;
|
||
color: #303133;
|
||
}
|
||
|
||
&__module-form {
|
||
width: 100%;
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
:deep(.el-select),
|
||
:deep(.el-date-editor.el-input) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__module-form--three {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
column-gap: 72px;
|
||
}
|
||
|
||
&__module-form--two {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
column-gap: 96px;
|
||
}
|
||
|
||
&__billing-dialog {
|
||
:deep(.el-dialog__body) {
|
||
padding-top: 28px;
|
||
}
|
||
|
||
:deep(.el-dialog__body),
|
||
:deep(.el-form) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__dispatch-dialog {
|
||
:deep(.el-dialog__header) {
|
||
display: none;
|
||
}
|
||
|
||
:deep(.el-dialog__body) {
|
||
padding: 0;
|
||
}
|
||
}
|
||
|
||
&__dispatch-shell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: calc(100vh - 140px);
|
||
}
|
||
|
||
&__dispatch-top {
|
||
padding: 22px 32px 14px;
|
||
border-bottom: 1px solid #e9edf5;
|
||
}
|
||
|
||
&__dispatch-heading {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
&__dispatch-heading-title {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
&__dispatch-heading-name {
|
||
font-size: 22px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
&__dispatch-heading-split {
|
||
font-size: 20px;
|
||
color: #606266;
|
||
}
|
||
|
||
&__dispatch-heading-no {
|
||
font-size: 15px;
|
||
color: #303133;
|
||
}
|
||
|
||
&__dispatch-summary {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.15fr) minmax(520px, 1fr);
|
||
gap: 32px;
|
||
align-items: start;
|
||
}
|
||
|
||
&__dispatch-summary-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
column-gap: 48px;
|
||
row-gap: 18px;
|
||
}
|
||
|
||
&__dispatch-summary-item {
|
||
min-width: 0;
|
||
}
|
||
|
||
&__dispatch-summary-label {
|
||
margin-bottom: 8px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: 500;
|
||
}
|
||
|
||
&__dispatch-summary-value {
|
||
min-height: 24px;
|
||
font-size: 15px;
|
||
line-height: 1.45;
|
||
color: #333;
|
||
word-break: break-word;
|
||
}
|
||
|
||
&__dispatch-summary-item--attachments {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
&__dispatch-attachment-link {
|
||
display: inline-block;
|
||
margin-right: 22px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
&__dispatch-route {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 72px minmax(0, 1fr);
|
||
gap: 24px;
|
||
align-items: center;
|
||
justify-items: center;
|
||
min-height: 200px;
|
||
}
|
||
|
||
&__dispatch-route-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
&__dispatch-route-badge {
|
||
display: inline-flex;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 46px;
|
||
height: 46px;
|
||
border-radius: 6px;
|
||
color: #fff;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
|
||
&.is-start {
|
||
background: #2a92ff;
|
||
}
|
||
|
||
&.is-end {
|
||
background: #ffb11a;
|
||
}
|
||
}
|
||
|
||
&__dispatch-route-body {
|
||
max-width: 340px;
|
||
text-align: center;
|
||
}
|
||
|
||
&__dispatch-route-title {
|
||
font-size: 22px;
|
||
line-height: 1.25;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
|
||
&__dispatch-route-address {
|
||
margin-top: 12px;
|
||
font-size: 15px;
|
||
line-height: 1.45;
|
||
color: #333;
|
||
}
|
||
|
||
&__dispatch-route-contact {
|
||
margin-top: 10px;
|
||
font-size: 15px;
|
||
color: #333;
|
||
}
|
||
|
||
&__dispatch-route-line {
|
||
width: 72px;
|
||
height: 4px;
|
||
background: #3b3b3b;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
&__dispatch-list {
|
||
padding: 12px 18px 18px;
|
||
}
|
||
|
||
&__dispatch-list-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
padding: 0 0 12px;
|
||
}
|
||
|
||
&__dispatch-list-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
&__dispatch-list-summary {
|
||
margin-left: 8px;
|
||
font-size: 14px;
|
||
color: #3c3c3c;
|
||
}
|
||
|
||
&__dispatch-table {
|
||
font-size: 13px;
|
||
|
||
:deep(.el-table__header .cell) {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(.cell) {
|
||
white-space: normal;
|
||
line-height: 1.35;
|
||
}
|
||
}
|
||
|
||
&__dispatch-actions {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
white-space: nowrap;
|
||
|
||
.el-button {
|
||
margin-left: 0;
|
||
}
|
||
}
|
||
|
||
&__dispatch-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
padding-right: 0;
|
||
}
|
||
|
||
&__dispatch-item-dialog {
|
||
:deep(.el-dialog__body) {
|
||
padding-top: 20px;
|
||
}
|
||
}
|
||
|
||
&__dispatch-item-form {
|
||
width: 100%;
|
||
}
|
||
|
||
&__dispatch-unit-select {
|
||
width: 104px;
|
||
|
||
:deep(.el-select__wrapper),
|
||
:deep(.el-select__wrapper:hover),
|
||
:deep(.el-select__wrapper.is-focused) {
|
||
width: 104px;
|
||
min-height: 24px;
|
||
padding: 0 8px;
|
||
border: 0;
|
||
box-shadow: none !important;
|
||
}
|
||
}
|
||
|
||
&__dispatch-item-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 16px 24px;
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 0;
|
||
min-width: 0;
|
||
}
|
||
|
||
&--compact {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
&__dispatch-item-span-2 {
|
||
grid-column: span 2;
|
||
}
|
||
|
||
&__dispatch-item-span-3 {
|
||
grid-column: span 3;
|
||
}
|
||
|
||
&__dispatch-item-carrier-type {
|
||
grid-column: 1 / -1;
|
||
width: calc((100% - 72px) / 4);
|
||
}
|
||
|
||
&__billing-form-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(360px, 520px) 1fr;
|
||
column-gap: 48px;
|
||
align-items: center;
|
||
}
|
||
|
||
&__billing-default {
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
&__billing-rule-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin: 20px 0 12px;
|
||
}
|
||
|
||
&__required-column {
|
||
&::before {
|
||
margin-right: 4px;
|
||
color: #f56c6c;
|
||
content: '*';
|
||
}
|
||
}
|
||
|
||
&__billing-tip {
|
||
display: flex;
|
||
max-width: 620px;
|
||
color: #ff6f86;
|
||
line-height: 1.7;
|
||
|
||
span {
|
||
position: relative;
|
||
display: inline-flex;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
margin-right: 12px;
|
||
border-radius: 50%;
|
||
background: #ffd6ea;
|
||
color: #ff4f92;
|
||
font-weight: 600;
|
||
|
||
&::after {
|
||
position: absolute;
|
||
right: 6px;
|
||
bottom: -8px;
|
||
width: 0;
|
||
height: 0;
|
||
border: 8px solid transparent;
|
||
border-top-color: #ffd6ea;
|
||
content: '';
|
||
}
|
||
}
|
||
}
|
||
|
||
&__billing-rule-wrap {
|
||
width: 100%;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
&__billing-rule-table {
|
||
width: 100%;
|
||
min-width: 2230px;
|
||
|
||
:deep(.el-table__header th) {
|
||
background: #f5f7fa;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(.el-table__cell) {
|
||
border-color: #eff1f7;
|
||
vertical-align: top;
|
||
}
|
||
}
|
||
|
||
&__limit-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
&__limit-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.el-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.el-button {
|
||
flex: 0 0 auto;
|
||
padding: 0;
|
||
}
|
||
}
|
||
|
||
&__dialog-footer {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
&__match-form {
|
||
padding: 12px 32px 4px;
|
||
|
||
.el-select,
|
||
.el-cascader {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__attachment {
|
||
width: 100%;
|
||
}
|
||
|
||
&__attachment-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
&__attachment-table {
|
||
width: 100%;
|
||
min-width: 1000px;
|
||
|
||
:deep(.el-table__header th) {
|
||
background: #f5f7fa;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(.el-table__cell) {
|
||
border-color: #eff1f7;
|
||
}
|
||
}
|
||
|
||
&__change-table {
|
||
width: 100%;
|
||
|
||
:deep(.el-table__header th) {
|
||
background: #f5f7fa;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(.el-table__cell) {
|
||
border-color: #eff1f7;
|
||
}
|
||
}
|
||
|
||
&__detail-section {
|
||
margin-bottom: 20px;
|
||
|
||
:deep(.el-descriptions__label) {
|
||
width: 140px;
|
||
color: #606266;
|
||
font-weight: 600;
|
||
text-align: right;
|
||
}
|
||
|
||
:deep(.el-descriptions__content) {
|
||
min-height: 42px;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
:deep(.el-table) {
|
||
border-color: #eff1f7;
|
||
}
|
||
}
|
||
|
||
&__transport-plan-import-form {
|
||
padding: 12px 24px 4px;
|
||
|
||
.el-select,
|
||
.el-input {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
&__import-file-item {
|
||
margin-top: 28px;
|
||
|
||
:deep(.el-form-item__content) {
|
||
gap: 16px;
|
||
}
|
||
}
|
||
|
||
&__import-file-tip {
|
||
color: #606266;
|
||
}
|
||
|
||
&__import-preview {
|
||
margin: 8px 24px 0;
|
||
padding-top: 16px;
|
||
border-top: 1px solid #eff1f7;
|
||
}
|
||
|
||
&__import-preview-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24px;
|
||
margin-bottom: 12px;
|
||
|
||
.dialog-section-title {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
&__import-preview-tabs {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
&__import-preview-table {
|
||
overflow-x: auto;
|
||
|
||
:deep(.el-table) {
|
||
min-width: 1550px;
|
||
}
|
||
|
||
:deep(.el-table__cell) {
|
||
border-color: #eff1f7;
|
||
}
|
||
|
||
:deep(.el-table__row:nth-child(even) td) {
|
||
background: #fafafa;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1280px) {
|
||
&__route-address-row {
|
||
grid-template-columns: 240px minmax(240px, 1fr) 40px 58px minmax(140px, 1fr);
|
||
}
|
||
|
||
&__route-address-row &__route-label:nth-of-type(2),
|
||
&__route-address-row .el-input:last-child {
|
||
grid-column: auto;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 960px) {
|
||
&__route-address-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
&__route-label {
|
||
text-align: left;
|
||
}
|
||
|
||
&__dialog-search-actions {
|
||
justify-content: flex-start;
|
||
}
|
||
}
|
||
}
|
||
</style>
|