6f0f1842cb
2、修复业务模块 3、拆分页面代码
3259 lines
114 KiB
Vue
3259 lines
114 KiB
Vue
<!-- 发货模板专用页面。页面只负责模板 CRUD 及模板表单相关交互。 -->
|
||
<template>
|
||
<basic-container
|
||
:class="[
|
||
'shipping-template-page',
|
||
{ 'shipping-template-page--form-page': isStandaloneFormPage },
|
||
]"
|
||
>
|
||
<component
|
||
:is="crudContainer"
|
||
ref="crud"
|
||
:option="pageFormOption"
|
||
:form-page-title="isStandaloneFormPage ? formPageTitle : undefined"
|
||
:table-loading="loading"
|
||
:data="data"
|
||
v-model:page="page"
|
||
v-model="form"
|
||
:status="isStandaloneFormPage ? dialogReadonly : undefined"
|
||
: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 v-if="canCreate" type="primary" @click="openBusinessForm('add')">
|
||
{{ config.createText || '新增' }}
|
||
</el-button>
|
||
<el-button
|
||
v-if="config.exportUrl && hasPermission(`${config.permission}_export`)"
|
||
type="primary"
|
||
plain
|
||
@click="handleExport"
|
||
>
|
||
批量导出
|
||
</el-button>
|
||
<el-button
|
||
v-if="config.batchDelete !== false && hasPermission(`${config.permission}_delete`)"
|
||
type="danger"
|
||
plain
|
||
@click="handleDelete"
|
||
>
|
||
批量删除
|
||
</el-button>
|
||
</template>
|
||
|
||
<template #contractName="{ row }">
|
||
<span>{{ row.contractName || '-' }}</span>
|
||
</template>
|
||
|
||
<template #departureAddress="{ row }">
|
||
<el-tooltip v-if="row.departureAddress" :content="row.departureAddress" placement="top">
|
||
<span>{{ row.departureAddress }}</span>
|
||
</el-tooltip>
|
||
<span v-else>-</span>
|
||
</template>
|
||
|
||
<template #arrivalAddress="{ row }">
|
||
<el-tooltip v-if="row.arrivalAddress" :content="row.arrivalAddress" placement="top">
|
||
<span>{{ row.arrivalAddress }}</span>
|
||
</el-tooltip>
|
||
<span v-else>-</span>
|
||
</template>
|
||
|
||
<template #templateInfoTitle-form>
|
||
<div class="dialog-section-title">模板信息</div>
|
||
</template>
|
||
|
||
<template #basicInfoTitle-form>
|
||
<div class="dialog-section-title">基本信息</div>
|
||
</template>
|
||
|
||
<template #projectName-form>
|
||
<el-select
|
||
v-model="selectedProjectId"
|
||
class="shipping-template-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-model="form.contractId"
|
||
class="shipping-template-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>
|
||
</template>
|
||
|
||
<template #shippingInfoTitle-form>
|
||
<div
|
||
class="dialog-section-title dialog-section-title--action shipping-template-page__shipping-title"
|
||
>
|
||
<span>收发货信息</span>
|
||
<el-link
|
||
v-if="!dialogReadonly"
|
||
class="shipping-template-page__shipping-route-action"
|
||
type="primary"
|
||
@click="openTransportRouteDialog"
|
||
>
|
||
选择线路
|
||
</el-link>
|
||
</div>
|
||
</template>
|
||
|
||
<template #departureAddress-form>
|
||
<div class="shipping-template-page__route-row">
|
||
<el-input
|
||
v-model="form.departureName"
|
||
readonly
|
||
placeholder="行政区划/站点名称"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportPrimaryAddressPicker('departure')"
|
||
/>
|
||
<el-input
|
||
v-model="form.departureAddress"
|
||
placeholder="请输入发货地址"
|
||
:readonly="transportStationMode || !config.editableRoadAddress"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="handleTransportSecondaryAddressInputClick('departure')"
|
||
>
|
||
<template #suffix>
|
||
<el-icon
|
||
v-if="!transportStationMode"
|
||
@click.stop="openTransportMapDialog('departure')"
|
||
>
|
||
<Location />
|
||
</el-icon>
|
||
</template>
|
||
</el-input>
|
||
<span>联系人</span>
|
||
<el-input
|
||
v-model="form.departureContact"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
/>
|
||
<span>联系方式</span>
|
||
<el-input v-model="form.departurePhone" placeholder="请输入" :disabled="dialogReadonly" />
|
||
<el-tooltip content="选择常用地址" placement="top">
|
||
<el-button
|
||
link
|
||
type="primary"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportAddressPicker('departure')"
|
||
>
|
||
选择
|
||
</el-button>
|
||
</el-tooltip>
|
||
</div>
|
||
</template>
|
||
|
||
<template #arrivalAddress-form>
|
||
<div class="shipping-template-page__route-row">
|
||
<el-input
|
||
v-model="form.arrivalName"
|
||
readonly
|
||
placeholder="行政区划/站点名称"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportPrimaryAddressPicker('arrival')"
|
||
/>
|
||
<el-input
|
||
v-model="form.arrivalAddress"
|
||
placeholder="请输入收货地址"
|
||
:readonly="transportStationMode || !config.editableRoadAddress"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="handleTransportSecondaryAddressInputClick('arrival')"
|
||
>
|
||
<template #suffix>
|
||
<el-icon v-if="!transportStationMode" @click.stop="openTransportMapDialog('arrival')">
|
||
<Location />
|
||
</el-icon>
|
||
</template>
|
||
</el-input>
|
||
<span>联系人</span>
|
||
<el-input v-model="form.arrivalContact" placeholder="请输入" :disabled="dialogReadonly" />
|
||
<span>联系方式</span>
|
||
<el-input v-model="form.arrivalPhone" placeholder="请输入" :disabled="dialogReadonly" />
|
||
<el-tooltip content="选择常用地址" placement="top">
|
||
<el-button
|
||
link
|
||
type="primary"
|
||
:disabled="transportAddressSelectDisabled"
|
||
@click="openTransportAddressPicker('arrival')"
|
||
>
|
||
选择
|
||
</el-button>
|
||
</el-tooltip>
|
||
</div>
|
||
</template>
|
||
|
||
<template #goodsInfoTitle-form>
|
||
<div
|
||
class="dialog-section-title dialog-section-title--action shipping-template-page__goods-title"
|
||
>
|
||
<span>货物信息</span>
|
||
<div v-if="!dialogReadonly" class="shipping-template-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 class="shipping-template-page__cargo-wrap">
|
||
<el-table :data="transportCargoRows" border empty-text="暂无货物信息">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column label="货物类型" min-width="200" align="center">
|
||
<template #default="{ row }">
|
||
<el-cascader
|
||
v-model="row.cargoTypePath"
|
||
:options="cargoTypeOptions"
|
||
:props="cargoTypeCascaderProps"
|
||
placeholder="请选择"
|
||
clearable
|
||
filterable
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && ensureCargoTypeOptions()"
|
||
@change="value => handleCargoTypeChange(row, value)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="货物名称" min-width="220" align="center">
|
||
<template #default="{ row }">
|
||
<el-autocomplete
|
||
v-model="row.cargoName"
|
||
placeholder="请输入"
|
||
clearable
|
||
:fetch-suggestions="(query, cb) => fetchCargoSuggestions(query, cb, row)"
|
||
:disabled="dialogReadonly"
|
||
@select="item => selectCargoSuggestion(row, item)"
|
||
/>
|
||
</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="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-model="row.quantity"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleCargoQuantityInput(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"
|
||
@change="syncFreightItems"
|
||
>
|
||
<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-input v-model="row.brand" :disabled="dialogReadonly"
|
||
/></template>
|
||
</el-table-column>
|
||
<el-table-column label="规格" min-width="120" align="center">
|
||
<template #default="{ row }"
|
||
><el-input v-model="row.specification" :disabled="dialogReadonly"
|
||
/></template>
|
||
</el-table-column>
|
||
<el-table-column label="型号" min-width="120" align="center">
|
||
<template #default="{ row }"
|
||
><el-input v-model="row.model" :disabled="dialogReadonly"
|
||
/></template>
|
||
</el-table-column>
|
||
<el-table-column label="物料编码" min-width="140" align="center">
|
||
<template #default="{ row }"
|
||
><el-input v-model="row.materialCode" :disabled="dialogReadonly"
|
||
/></template>
|
||
</el-table-column>
|
||
<el-table-column label="设备编码" min-width="140" align="center">
|
||
<template #default="{ row }"
|
||
><el-input v-model="row.deviceCode" :disabled="dialogReadonly"
|
||
/></template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" min-width="160" align="center">
|
||
<template #default="{ row }"
|
||
><el-input v-model="row.remark" maxlength="200" :disabled="dialogReadonly"
|
||
/></template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||
<template #default="{ $index }">
|
||
<el-link v-if="!dialogReadonly" type="primary" @click="addCargoRow($index)"
|
||
>新增</el-link
|
||
>
|
||
<el-link v-if="!dialogReadonly" type="danger" @click="removeCargoRow($index)"
|
||
>删除</el-link
|
||
>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="shipping-template-page__cargo-total">
|
||
运输数量合计:{{ cargoQuantityTotal }}
|
||
</div>
|
||
<section v-if="freightVisible" class="shipping-template-page__freight-section">
|
||
<div class="dialog-section-title">
|
||
运费信息{{ transportMode === 'road' ? '(公路)' : '' }}
|
||
</div>
|
||
<el-form
|
||
:model="shippingTemplateFreight"
|
||
label-position="right"
|
||
label-width="auto"
|
||
:class="[
|
||
'shipping-template-page__freight-form',
|
||
`shipping-template-page__freight-form--${
|
||
transportMode === 'road' ? 'road' : 'non-road'
|
||
}`,
|
||
]"
|
||
>
|
||
<template v-if="transportMode === 'road'">
|
||
<template
|
||
v-for="(item, index) in shippingTemplateFreight.freightItems"
|
||
:key="item.quantityUnit || `empty-${index}`"
|
||
>
|
||
<el-form-item :label="`单价${index + 1}`">
|
||
<el-input
|
||
v-model="item.unitPrice"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleFreightNumberInput(item, 'unitPrice', value)"
|
||
>
|
||
<template #append
|
||
><el-select
|
||
v-model="item.priceUnit"
|
||
class="shipping-template-page__append-select"
|
||
placeholder="单位"
|
||
:disabled="dialogReadonly"
|
||
@visible-change="visible => visible && loadFeeUnitOptions()"
|
||
><el-option
|
||
v-for="unit in feeUnitOptions"
|
||
:key="unit.value"
|
||
:label="unit.label"
|
||
:value="unit.value" /></el-select
|
||
></template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item :label="`运费${index + 1}`"
|
||
><el-input
|
||
:model-value="freightAmount(item)"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="value => handleFreightAmountInput(item, value)"
|
||
><template #append>{{ currencyLabel }}</template>
|
||
</el-input></el-form-item
|
||
>
|
||
</template>
|
||
</template>
|
||
<el-form-item v-else label="运费"
|
||
><el-input
|
||
v-model="shippingTemplateFreight.freightAmount"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="
|
||
value =>
|
||
handleFreightNumberInput(shippingTemplateFreight, 'freightAmount', value)
|
||
"
|
||
><template #append>{{ currencyLabel }}</template></el-input
|
||
></el-form-item
|
||
>
|
||
<el-form-item label="其他运费合计"
|
||
><el-input
|
||
v-model="shippingTemplateFreight.otherFreightAmount"
|
||
placeholder="请输入"
|
||
:disabled="dialogReadonly"
|
||
@input="
|
||
value =>
|
||
handleFreightNumberInput(shippingTemplateFreight, 'otherFreightAmount', value)
|
||
"
|
||
><template #append>{{ currencyLabel }}</template></el-input
|
||
></el-form-item
|
||
>
|
||
<el-form-item label="运费合计"
|
||
><el-input :model-value="freightTotal" disabled
|
||
><template #append>{{ currencyLabel }}</template>
|
||
</el-input></el-form-item
|
||
>
|
||
</el-form>
|
||
</section>
|
||
</div>
|
||
</template>
|
||
|
||
<template #attachmentTitle-form
|
||
><div class="dialog-section-title">{{ config.attachmentTitle || '附件' }}</div></template
|
||
>
|
||
<template #attachmentsJson-form>
|
||
<div class="shipping-template-page__attachment">
|
||
<div class="shipping-template-page__attachment-head">
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!attachmentRows.length"
|
||
@click="handleBatchDownload"
|
||
>批量下载</el-button
|
||
>
|
||
</div>
|
||
<el-table
|
||
:data="attachmentRows"
|
||
border
|
||
@selection-change="handleAttachmentSelectionChange"
|
||
>
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column type="index" label="序号" width="70" />
|
||
<el-table-column label="文件名" min-width="220"
|
||
><template #default="{ row }"
|
||
><el-link type="primary" @click="previewAttachment(row)">{{
|
||
attachmentName(row)
|
||
}}</el-link></template
|
||
></el-table-column
|
||
>
|
||
<el-table-column label="附件描述" min-width="220"
|
||
><template #default="{ row }"
|
||
><el-input v-model="row.description" :disabled="dialogReadonly" /></template
|
||
></el-table-column>
|
||
<el-table-column label="文件大小" width="110"
|
||
><template #default="{ row }">{{
|
||
formatFileSize(row.size)
|
||
}}</template></el-table-column
|
||
>
|
||
<el-table-column prop="uploadUserName" label="上传人" width="130" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="170" />
|
||
<el-table-column label="操作" width="100"
|
||
><template #default="{ row, $index }"
|
||
><el-link v-if="!dialogReadonly" type="danger" @click="removeAttachment($index)"
|
||
>删除</el-link
|
||
><el-link v-else type="primary" @click="downloadAttachment(row)"
|
||
>下载</el-link
|
||
></template
|
||
></el-table-column
|
||
>
|
||
</el-table>
|
||
<div class="shipping-template-page__attachment-upload">
|
||
<vehicle-attachment-upload
|
||
v-model="attachmentRows"
|
||
:readonly="dialogReadonly"
|
||
:file-types="attachmentFileTypes"
|
||
:max-size="500"
|
||
:show-file-list="false"
|
||
button-text="上传附件"
|
||
@change="handleAttachmentChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template #menu-form>
|
||
<div
|
||
v-if="
|
||
((config.enableShippingTemplateFreight === true && freightVisible) ||
|
||
hasProjectProcessConfig) &&
|
||
!dialogReadonly
|
||
"
|
||
class="shipping-template-page__footer-summary"
|
||
>
|
||
<span v-if="freightVisible"
|
||
>运费合计:<strong>¥{{ footerFreightTotal }}</strong></span
|
||
>
|
||
<span v-else />
|
||
<el-link
|
||
v-if="hasProjectProcessConfig"
|
||
type="primary"
|
||
@click="openShippingTemplateProcessConfig"
|
||
>
|
||
查看过程配置
|
||
</el-link>
|
||
</div>
|
||
<div v-if="isStandaloneFormPage" class="shipping-template-page__standalone-actions">
|
||
<el-button @click="closeCrudDialog">取消</el-button>
|
||
</div>
|
||
</template>
|
||
|
||
<template #menu="{ row, index }">
|
||
<div class="shipping-template-page__row-actions">
|
||
<el-link
|
||
v-if="hasPermission(`${config.permission}_view`)"
|
||
type="primary"
|
||
@click="openDetail(row)"
|
||
>查看</el-link
|
||
>
|
||
<el-link v-if="canEdit(row)" type="primary" @click="openBusinessForm('edit', row)"
|
||
>编辑</el-link
|
||
>
|
||
<el-link v-if="canCopy(row)" type="primary" @click="handleCopy(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 v-if="canDelete(row)" type="danger" @click="rowDel(row)">删除</el-link>
|
||
</div>
|
||
</template>
|
||
</component>
|
||
|
||
<el-dialog
|
||
v-model="detailBox"
|
||
title="发货模板详情"
|
||
append-to-body
|
||
width="92%"
|
||
class="shipping-template-page__detail-dialog"
|
||
>
|
||
<div v-loading="detailLoading" class="shipping-template-page__detail">
|
||
<section class="shipping-template-page__detail-section">
|
||
<div class="dialog-section-title">模板信息</div>
|
||
<el-form
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="shipping-template-page__detail-form shipping-template-page__detail-grid"
|
||
>
|
||
<el-form-item label="模板编号"><span>{{ detailRow.templateCode || '-' }}</span></el-form-item>
|
||
<el-form-item label="模板名称"><span>{{ detailRow.templateName || '-' }}</span></el-form-item>
|
||
<el-form-item label="模板类型"><span>{{ detailRow.templateType || '-' }}</span></el-form-item>
|
||
<el-form-item label="备注" class="shipping-template-page__detail-form-wide">
|
||
<span>{{ detailRow.remark || '-' }}</span>
|
||
</el-form-item>
|
||
</el-form>
|
||
</section>
|
||
|
||
<section class="shipping-template-page__detail-section">
|
||
<div class="dialog-section-title">基本信息</div>
|
||
<el-form
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="shipping-template-page__detail-form shipping-template-page__detail-grid"
|
||
>
|
||
<el-form-item label="项目"><span>{{ detailRow.projectName || '-' }}</span></el-form-item>
|
||
<el-form-item label="客户合同"><span>{{ detailRow.contractName || '-' }}</span></el-form-item>
|
||
<el-form-item label="运输方式">
|
||
<span>{{ getTransportTypeLabel(detailRow.transportType) || detailRow.transportType || '-' }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="备注" class="shipping-template-page__detail-form-wide">
|
||
<span>{{ detailRow.basicRemark || '-' }}</span>
|
||
</el-form-item>
|
||
</el-form>
|
||
</section>
|
||
|
||
<section class="shipping-template-page__detail-section">
|
||
<div class="dialog-section-title">收发货信息</div>
|
||
<el-form label-position="right" label-width="auto" class="shipping-template-page__detail-form">
|
||
<el-form-item label="发货地址">
|
||
<div class="shipping-template-page__detail-route-values">
|
||
<span>{{ detailRow.departureName || '-' }}</span>
|
||
<span>{{ detailRow.departureAddress || '-' }}</span>
|
||
<span>联系人:{{ detailRow.departureContact || '-' }}</span>
|
||
<span>联系方式:{{ detailRow.departurePhone || '-' }}</span>
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item label="收货地址">
|
||
<div class="shipping-template-page__detail-route-values">
|
||
<span>{{ detailRow.arrivalName || '-' }}</span>
|
||
<span>{{ detailRow.arrivalAddress || '-' }}</span>
|
||
<span>联系人:{{ detailRow.arrivalContact || '-' }}</span>
|
||
<span>联系方式:{{ detailRow.arrivalPhone || '-' }}</span>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
</section>
|
||
|
||
<section class="shipping-template-page__detail-section">
|
||
<div class="dialog-section-title">货物信息</div>
|
||
<div class="shipping-template-page__cargo-wrap">
|
||
<el-table :data="detailGoodsRows" border empty-text="暂无货物信息">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column prop="cargoType" label="货物类型" min-width="200" align="center" />
|
||
<el-table-column prop="cargoName" label="货物名称" min-width="220" align="center" />
|
||
<el-table-column prop="packageType" label="包装" min-width="120" align="center" />
|
||
<el-table-column prop="quantity" label="数量" min-width="120" align="center" />
|
||
<el-table-column prop="quantityUnit" label="数量单位" min-width="120" align="center" />
|
||
<el-table-column prop="brand" label="品牌" min-width="120" align="center" />
|
||
<el-table-column prop="specification" label="规格" min-width="120" align="center" />
|
||
<el-table-column prop="model" label="型号" min-width="120" align="center" />
|
||
<el-table-column prop="materialCode" label="物料编码" min-width="140" align="center" />
|
||
<el-table-column prop="deviceCode" label="设备编码" min-width="140" align="center" />
|
||
<el-table-column prop="remark" label="备注" min-width="160" align="center" />
|
||
</el-table>
|
||
</div>
|
||
<div class="shipping-template-page__cargo-total">
|
||
运输数量合计:{{ detailCargoQuantityTotal }}
|
||
</div>
|
||
<section v-if="detailFreightVisible" class="shipping-template-page__freight-section">
|
||
<div class="dialog-section-title">
|
||
运费信息{{ detailTransportMode === 'road' ? '(公路)' : '' }}
|
||
</div>
|
||
<el-form
|
||
label-position="right"
|
||
label-width="auto"
|
||
:class="[
|
||
'shipping-template-page__freight-form',
|
||
`shipping-template-page__freight-form--${detailTransportMode === 'road' ? 'road' : 'non-road'}`,
|
||
]"
|
||
>
|
||
<template v-if="detailTransportMode === 'road'">
|
||
<template v-for="(item, index) in detailFreightItems" :key="item.quantityUnit || index">
|
||
<el-form-item :label="`单价${index + 1}`">
|
||
<el-input :model-value="item.unitPrice || '-'" disabled>
|
||
<template #append>{{ item.priceUnit || '-' }}</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item :label="`运费${index + 1}`">
|
||
<el-input :model-value="item.freightAmount || '-'" disabled>
|
||
<template #append>{{ detailFreight.currency || '-' }}</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</template>
|
||
</template>
|
||
<el-form-item v-else label="运费">
|
||
<el-input :model-value="detailFreight.freightAmount || '-'" disabled>
|
||
<template #append>{{ detailFreight.currency || '-' }}</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="其他运费合计">
|
||
<el-input :model-value="detailFreight.otherFreightAmount || '-'" disabled>
|
||
<template #append>{{ detailFreight.currency || '-' }}</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="运费合计">
|
||
<el-input :model-value="detailFreightTotal" disabled>
|
||
<template #append>{{ detailFreight.currency || '-' }}</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-form>
|
||
</section>
|
||
</section>
|
||
|
||
<section class="shipping-template-page__detail-section">
|
||
<div class="dialog-section-title">{{ config.attachmentTitle || '附件' }}</div>
|
||
<div class="shipping-template-page__attachment">
|
||
<div class="shipping-template-page__attachment-head">
|
||
<el-button
|
||
type="primary"
|
||
:disabled="!detailAttachmentRows.length"
|
||
@click="handleDetailBatchDownload"
|
||
>
|
||
批量下载
|
||
</el-button>
|
||
</div>
|
||
<el-table :data="detailAttachmentRows" border empty-text="暂无附件">
|
||
<el-table-column type="index" label="序号" width="70" />
|
||
<el-table-column label="文件名" min-width="220">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click="previewAttachment(row)">{{ attachmentName(row) }}</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="附件描述" min-width="220">
|
||
<template #default="{ row }">{{ row.description || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="文件大小" width="110">
|
||
<template #default="{ row }">{{ formatFileSize(row.size) || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploadUserName" label="上传人" width="130" />
|
||
<el-table-column prop="uploadTime" label="上传时间" width="170" />
|
||
<el-table-column label="操作" width="100">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<template #footer
|
||
><el-button type="primary" @click="detailBox = false">关闭</el-button></template
|
||
>
|
||
</el-dialog>
|
||
|
||
<empty-pagination
|
||
v-show="!isStandaloneFormPage"
|
||
:page="page"
|
||
@size-change="sizeChange"
|
||
@current-change="currentChange"
|
||
@load="onLoad(page, query)"
|
||
/>
|
||
|
||
<el-dialog
|
||
v-model="attachmentDocumentPreviewVisible"
|
||
:title="attachmentPreviewFile.name || '附件预览'"
|
||
append-to-body
|
||
destroy-on-close
|
||
width="90%"
|
||
top="4vh"
|
||
>
|
||
<open-file-viewer
|
||
v-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
|
||
:file="attachmentPreviewFile.url"
|
||
:file-name="attachmentPreviewFile.name"
|
||
:mime-type="attachmentPreviewFile.mimeType"
|
||
width="100%"
|
||
height="72vh"
|
||
fit="contain"
|
||
theme="auto"
|
||
locale="zh-CN"
|
||
:toolbar="attachmentViewerToolbar"
|
||
:plugins="attachmentViewerPlugins"
|
||
@unsupported="handleAttachmentPreviewUnsupported"
|
||
@error="handleAttachmentPreviewError"
|
||
/>
|
||
</el-dialog>
|
||
<el-image-viewer
|
||
v-if="attachmentImagePreviewVisible"
|
||
:url-list="attachmentImagePreviewUrls"
|
||
:initial-index="attachmentImagePreviewIndex"
|
||
@close="attachmentImagePreviewVisible = false"
|
||
/>
|
||
|
||
<el-dialog
|
||
title="选择线路"
|
||
v-model="transportRouteBox"
|
||
append-to-body
|
||
width="1000px"
|
||
@opened="loadTransportRouteList"
|
||
>
|
||
<el-form :model="transportRouteQuery" inline
|
||
><el-form-item label="线路名称"
|
||
><el-input v-model="transportRouteQuery.routeName" clearable /></el-form-item
|
||
><el-button type="primary" @click="handleTransportRouteSearch">查询</el-button
|
||
><el-button @click="handleTransportRouteReset">重置</el-button></el-form
|
||
>
|
||
<el-table :data="transportRouteRows" border v-loading="transportRouteLoading"
|
||
><el-table-column prop="routeName" label="线路名称" min-width="180" /><el-table-column
|
||
prop="departureAddress"
|
||
label="发货地址"
|
||
min-width="260"
|
||
/><el-table-column prop="arrivalAddress" label="收货地址" min-width="260" /><el-table-column
|
||
label="操作"
|
||
width="90"
|
||
><template #default="{ row }"
|
||
><el-link type="primary" @click="selectTransportRouteRow(row)">选择</el-link></template
|
||
></el-table-column
|
||
></el-table
|
||
>
|
||
<template #footer
|
||
><el-pagination
|
||
v-model:current-page="transportRoutePage.currentPage"
|
||
v-model:page-size="transportRoutePage.pageSize"
|
||
layout="total, prev, pager, next, sizes"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:total="transportRoutePage.total"
|
||
@current-change="handleTransportRouteCurrentChange"
|
||
@size-change="handleTransportRouteSizeChange"
|
||
/></template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="选择常用地址"
|
||
v-model="transportAddressBox"
|
||
append-to-body
|
||
width="1100px"
|
||
@opened="loadTransportAddressList"
|
||
>
|
||
<el-form :model="transportAddressQuery" inline
|
||
><el-form-item label="地址名称"
|
||
><el-input v-model="transportAddressQuery.addressName" clearable /></el-form-item
|
||
><el-form-item label="详细地址"
|
||
><el-input v-model="transportAddressQuery.detailAddress" clearable /></el-form-item
|
||
><el-button type="primary" @click="handleTransportAddressSearch">查询</el-button
|
||
><el-button @click="handleTransportAddressReset">重置</el-button></el-form
|
||
>
|
||
<el-table :data="transportAddressRows" border v-loading="transportAddressLoading"
|
||
><el-table-column prop="addressName" label="地址名称" min-width="160" /><el-table-column
|
||
prop="addressType"
|
||
label="类型"
|
||
width="120"
|
||
/><el-table-column prop="detailAddress" label="详细地址" min-width="280" /><el-table-column
|
||
prop="contactName"
|
||
label="联系人"
|
||
width="120"
|
||
/><el-table-column prop="contactPhone" label="联系方式" width="140" /><el-table-column
|
||
label="操作"
|
||
width="90"
|
||
><template #default="{ row }"
|
||
><el-link type="primary" @click="selectTransportAddressRow(row)"
|
||
>选择</el-link
|
||
></template
|
||
></el-table-column
|
||
></el-table
|
||
>
|
||
<template #footer
|
||
><el-pagination
|
||
v-model:current-page="transportAddressPage.currentPage"
|
||
v-model:page-size="transportAddressPage.pageSize"
|
||
layout="total, prev, pager, next, sizes"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:total="transportAddressPage.total"
|
||
@current-change="handleTransportAddressCurrentChange"
|
||
@size-change="handleTransportAddressSizeChange"
|
||
/></template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:title="`选择${transportStationTypeLabel || '站点'}`"
|
||
v-model="transportStationBox"
|
||
append-to-body
|
||
width="1050px"
|
||
@opened="loadTransportStationList"
|
||
>
|
||
<el-form :model="transportStationQuery" inline
|
||
><el-form-item label="名称"
|
||
><el-input v-model="transportStationQuery.name" clearable /></el-form-item
|
||
><el-form-item label="详细地址"
|
||
><el-input v-model="transportStationQuery.detailAddress" clearable /></el-form-item
|
||
><el-button type="primary" @click="handleTransportStationSearch">查询</el-button></el-form
|
||
>
|
||
<el-table :data="transportStationRows" border v-loading="transportStationLoading"
|
||
><el-table-column prop="stationName" label="站点名称" min-width="180" /><el-table-column
|
||
prop="stationCode"
|
||
label="站点编码"
|
||
width="140"
|
||
/><el-table-column prop="detailAddress" label="详细地址" min-width="300" /><el-table-column
|
||
prop="regionName"
|
||
label="行政区划"
|
||
width="180"
|
||
/><el-table-column label="操作" width="90"
|
||
><template #default="{ row }"
|
||
><el-link type="primary" @click="selectTransportStationRow(row)"
|
||
>选择</el-link
|
||
></template
|
||
></el-table-column
|
||
></el-table
|
||
>
|
||
<template #footer
|
||
><el-pagination
|
||
v-model:current-page="transportStationPage.currentPage"
|
||
v-model:page-size="transportStationPage.pageSize"
|
||
layout="total, prev, pager, next, sizes"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:total="transportStationPage.total"
|
||
@current-change="handleTransportStationCurrentChange"
|
||
@size-change="handleTransportStationSizeChange"
|
||
/></template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="地图选择地址"
|
||
v-model="transportMapBox"
|
||
append-to-body
|
||
width="920px"
|
||
@opened="initTransportMap"
|
||
>
|
||
<div class="shipping-template-page__map-toolbar">
|
||
<el-input
|
||
v-model="transportMapKeyword"
|
||
clearable
|
||
@keyup.enter="searchTransportMapKeyword"
|
||
/><el-button type="primary" @click="searchTransportMapKeyword">搜索</el-button>
|
||
</div>
|
||
<div ref="transportMap" class="shipping-template-page__map" />
|
||
<div class="shipping-template-page__map-info">{{ transportMapStatus }}</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="选择常用货物"
|
||
v-model="commonCargoBox"
|
||
append-to-body
|
||
width="1200px"
|
||
@opened="loadCommonCargoList"
|
||
>
|
||
<el-form :model="commonCargoQuery" inline
|
||
><el-form-item label="货物名称"
|
||
><el-input v-model="commonCargoQuery.cargoName" clearable /></el-form-item
|
||
><el-button type="primary" @click="handleCommonCargoSearch">查询</el-button
|
||
><el-button @click="handleCommonCargoReset">重置</el-button></el-form
|
||
>
|
||
<el-table
|
||
:data="commonCargoRows"
|
||
border
|
||
v-loading="commonCargoLoading"
|
||
@selection-change="handleCommonCargoSelectionChange"
|
||
><el-table-column type="selection" width="55" /><el-table-column
|
||
prop="cargoName"
|
||
label="货物名称"
|
||
min-width="160"
|
||
/><el-table-column
|
||
prop="firstCargoTypeName"
|
||
label="一级类型"
|
||
min-width="140"
|
||
/><el-table-column
|
||
prop="secondCargoTypeName"
|
||
label="二级类型"
|
||
min-width="140"
|
||
/><el-table-column prop="packageType" label="包装" width="110" /><el-table-column
|
||
prop="brand"
|
||
label="品牌"
|
||
width="120"
|
||
/><el-table-column prop="specification" label="规格" width="120" /><el-table-column
|
||
prop="model"
|
||
label="型号"
|
||
width="120"
|
||
/><el-table-column label="操作" width="90"
|
||
><template #default="{ row }"
|
||
><el-link type="primary" @click="selectCommonCargoRow(row)">选择</el-link></template
|
||
></el-table-column
|
||
></el-table
|
||
>
|
||
<template #footer
|
||
><el-pagination
|
||
v-model:current-page="commonCargoPage.currentPage"
|
||
v-model:page-size="commonCargoPage.pageSize"
|
||
layout="total, prev, pager, next, sizes"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:total="commonCargoPage.total"
|
||
@current-change="handleCommonCargoCurrentChange"
|
||
@size-change="handleCommonCargoSizeChange"
|
||
/><el-button
|
||
type="primary"
|
||
:disabled="!commonCargoSelected.length"
|
||
@click="confirmCommonCargoSelection"
|
||
>确定</el-button
|
||
></template
|
||
>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="导入货物"
|
||
v-model="cargoImportBox"
|
||
append-to-body
|
||
width="560px"
|
||
@closed="cargoImportRows = []"
|
||
>
|
||
<el-upload
|
||
action="#"
|
||
accept=".xls,.xlsx"
|
||
:auto-upload="false"
|
||
:show-file-list="false"
|
||
:disabled="cargoImportLoading"
|
||
:on-change="handleCargoImportFileChange"
|
||
><el-button type="primary" :loading="cargoImportLoading">上传</el-button></el-upload
|
||
>
|
||
<el-link type="primary" @click="handleCargoImportTemplate">下载模板</el-link>
|
||
<template #footer
|
||
><el-button @click="closeCargoImportDialog">关闭</el-button
|
||
><el-button
|
||
type="primary"
|
||
:loading="cargoImportLoading"
|
||
:disabled="!cargoImportRows.length"
|
||
@click="confirmCargoImport"
|
||
>确定</el-button
|
||
></template
|
||
>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
v-model="shippingTemplateProcessConfigBox"
|
||
title="过程配置"
|
||
append-to-body
|
||
width="900px"
|
||
>
|
||
<div v-loading="shippingTemplateProcessConfigLoading">
|
||
<el-empty
|
||
v-if="!shippingTemplateProcessConfigRows.length"
|
||
description="当前项目暂无过程配置"
|
||
/>
|
||
<section
|
||
v-for="row in shippingTemplateProcessConfigRows"
|
||
:key="row.id || row.configCode"
|
||
class="shipping-template-page__process-section"
|
||
>
|
||
<strong>{{ row.configName || '过程配置' }}</strong>
|
||
<div
|
||
v-for="node in getProcessConfigEnabledNodes(row)"
|
||
:key="node.key"
|
||
class="shipping-template-page__process-node"
|
||
>
|
||
{{ node.name }}:{{ processConfigTimelineDescriptions(node).join(';') }}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<template #footer
|
||
><el-button type="primary" @click="shippingTemplateProcessConfigBox = false"
|
||
>关闭</el-button
|
||
></template
|
||
>
|
||
</el-dialog>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<script>
|
||
import { exportBlob, importBlob } 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 getPortTerminalList } from '@/api/base/port-terminal';
|
||
import { getList as getRailwayStationList } from '@/api/base/railway-station';
|
||
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 {
|
||
getDetail as getProcessConfigDetail,
|
||
getList as getProcessConfigList,
|
||
} from '@/api/business/process-config';
|
||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||
import { getDictionary } from '@/api/system/dictbiz';
|
||
import { packageOptions } from '@/option/business/common';
|
||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||
import { isMobile } from '@/utils/validate';
|
||
import { Location } from '@element-plus/icons-vue';
|
||
import { ElImageViewer } from 'element-plus';
|
||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||
import {
|
||
fallbackPlugin,
|
||
imagePlugin,
|
||
officePlugin,
|
||
pdfPlugin,
|
||
textPlugin,
|
||
} from '@open-file-viewer/core';
|
||
import '@open-file-viewer/core/style.css';
|
||
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||
import { h, resolveComponent } from 'vue';
|
||
import { mapGetters } from 'vuex';
|
||
|
||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||
let amapLoader;
|
||
const attachmentViewerPlugins = [
|
||
imagePlugin(),
|
||
pdfPlugin({ workerSrc: pdfWorkerSrc, useFetchData: true }),
|
||
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||
textPlugin(),
|
||
fallbackPlugin(),
|
||
];
|
||
|
||
const defaultCargo = () => ({
|
||
cargoName: '',
|
||
cargoType: '',
|
||
cargoTypeCode: '',
|
||
cargoTypePath: [],
|
||
packageType: '',
|
||
quantity: '',
|
||
quantityUnit: '',
|
||
brand: '',
|
||
specification: '',
|
||
model: '',
|
||
materialCode: '',
|
||
deviceCode: '',
|
||
remark: '',
|
||
});
|
||
|
||
const defaultFreight = () => ({
|
||
currency: 'RMB',
|
||
freightAmount: '',
|
||
otherFreightAmount: '',
|
||
freightItems: [],
|
||
});
|
||
|
||
const extractRecords = res => {
|
||
const data = res?.data?.data || res?.data || res || {};
|
||
if (Array.isArray(data)) return data;
|
||
return data.records || data.rows || data.data?.records || [];
|
||
};
|
||
|
||
// Avue 的默认分组组件内部使用折叠容器。独立表单页需要静态分区,
|
||
// 因此仅在本页面替换分组渲染器,保留 avue-form 的字段渲染和校验能力。
|
||
const PlainAvueGroup = {
|
||
name: 'PlainAvueGroup',
|
||
inheritAttrs: false,
|
||
props: {
|
||
display: { type: Boolean, default: true },
|
||
header: { type: Boolean, default: true },
|
||
label: { type: String, default: '' },
|
||
icon: { type: String, default: '' },
|
||
},
|
||
render() {
|
||
if (!this.display) return null;
|
||
const children = [];
|
||
if (this.$slots.tabs) children.push(...this.$slots.tabs());
|
||
if (this.header && (this.$slots.header || this.label || this.icon)) {
|
||
children.push(
|
||
h('div', { class: 'avue-group__header' }, [
|
||
this.$slots.header
|
||
? this.$slots.header()
|
||
: h('h1', { class: 'avue-group__title' }, this.label),
|
||
])
|
||
);
|
||
}
|
||
if (this.$slots.default) children.push(...this.$slots.default());
|
||
return h('div', { class: 'avue-group' }, children);
|
||
},
|
||
};
|
||
|
||
const plainAvueFormCache = new WeakMap();
|
||
const getPlainAvueForm = avueForm => {
|
||
if (!avueForm || typeof avueForm !== 'object') return avueForm;
|
||
if (!plainAvueFormCache.has(avueForm)) {
|
||
plainAvueFormCache.set(avueForm, {
|
||
...avueForm,
|
||
components: {
|
||
...(avueForm.components || {}),
|
||
avueGroup: PlainAvueGroup,
|
||
'avue-group': PlainAvueGroup,
|
||
},
|
||
});
|
||
}
|
||
return plainAvueFormCache.get(avueForm);
|
||
};
|
||
|
||
const PageAvueForm = {
|
||
inheritAttrs: false,
|
||
props: { formPageTitle: { type: String, default: '' } },
|
||
render() {
|
||
const attrs = { ...this.$attrs };
|
||
const save = attrs.onRowSave;
|
||
const update = attrs.onRowUpdate;
|
||
delete attrs.onRowSave;
|
||
delete attrs.onRowUpdate;
|
||
attrs.onSubmit = (row, hide) => {
|
||
if (attrs.option?.boxType === 'edit') update?.(row, undefined, hide, hide);
|
||
else save?.(row, hide, hide);
|
||
};
|
||
const slots = Object.fromEntries(
|
||
Object.entries(this.$slots).flatMap(([name, slot]) => {
|
||
if (name === 'menu-form' || name === 'menu-form-before') return [[name, slot]];
|
||
const prop = name.replace(/-form$/, '');
|
||
return [
|
||
[prop, slot],
|
||
[`${prop}-header`, slot],
|
||
];
|
||
})
|
||
);
|
||
const children = [];
|
||
if (this.formPageTitle)
|
||
children.push(h('div', { class: 'archive-page-form__title' }, this.formPageTitle));
|
||
children.push(h(getPlainAvueForm(resolveComponent('avue-form')), attrs, slots));
|
||
return h(
|
||
'div',
|
||
{ class: attrs.option?.dialogCustomClass || 'shipping-template-form-page-dialog' },
|
||
children
|
||
);
|
||
},
|
||
};
|
||
|
||
export default {
|
||
name: 'ShippingTemplatePage',
|
||
components: { PageAvueForm, ElImageViewer, OpenFileViewer },
|
||
props: {
|
||
api: { type: Object, required: true },
|
||
config: { type: Object, required: true },
|
||
crudOption: { type: Object, required: true },
|
||
crudExcelOption: { type: Object, default: () => ({ column: [] }) },
|
||
menuWidth: { type: Number, default: null },
|
||
detailId: { type: [String, Number], default: null },
|
||
standaloneFormPage: { type: Boolean, default: false },
|
||
},
|
||
data() {
|
||
return {
|
||
Location,
|
||
form: {},
|
||
query: {},
|
||
loading: true,
|
||
data: [],
|
||
page: { pageSize: 10, pageSizes: [10, 20, 50, 100], currentPage: 1, total: 0 },
|
||
selectionList: [],
|
||
dialogReadonly: false,
|
||
crudDialogType: '',
|
||
standaloneFormKey: '',
|
||
detailBox: false,
|
||
detailLoading: false,
|
||
detailRow: {},
|
||
option: this.buildOption(this.crudOption),
|
||
selectedProjectId: '',
|
||
projectOptions: [],
|
||
projectLoading: false,
|
||
contractOptions: [],
|
||
contractLoading: false,
|
||
cargoTypeOptions: [],
|
||
cargoTypeFlatOptions: [],
|
||
cargoTypeLoading: false,
|
||
cargoTypeRequest: null,
|
||
transportCargoRows: [],
|
||
packageOptions,
|
||
quantityUnitOptions: ['吨', '千克', '立方米', '立方', '公里', '单', '件', '车', '箱', '托盘'],
|
||
feeUnitOptions: [],
|
||
currencyOptions: [],
|
||
currencyLoading: false,
|
||
shippingTemplateFreight: defaultFreight(),
|
||
attachmentRows: [],
|
||
selectedAttachmentRows: [],
|
||
attachmentFileTypes: [
|
||
'pdf',
|
||
'bmp',
|
||
'jpeg',
|
||
'png',
|
||
'jpg',
|
||
'doc',
|
||
'docx',
|
||
'ppt',
|
||
'pptx',
|
||
'xlsx',
|
||
'xls',
|
||
'eml',
|
||
'msg',
|
||
'zip',
|
||
],
|
||
attachmentViewerPlugins,
|
||
attachmentViewerToolbar: {
|
||
download: true,
|
||
fullscreen: true,
|
||
print: true,
|
||
rotate: true,
|
||
zoom: true,
|
||
},
|
||
attachmentImagePreviewVisible: false,
|
||
attachmentImagePreviewUrls: [],
|
||
attachmentImagePreviewIndex: 0,
|
||
attachmentDocumentPreviewVisible: false,
|
||
attachmentPreviewFile: {},
|
||
transportTypeOptions: [],
|
||
transportTypeOptionsLoading: false,
|
||
transportRouteBox: false,
|
||
transportRouteLoading: false,
|
||
transportRouteQuery: {},
|
||
transportRouteRows: [],
|
||
transportRoutePage: { pageSize: 10, currentPage: 1, total: 0 },
|
||
transportAddressBox: false,
|
||
transportAddressTarget: '',
|
||
transportAddressLoading: false,
|
||
transportAddressQuery: {},
|
||
transportAddressRows: [],
|
||
transportAddressPage: { pageSize: 10, currentPage: 1, total: 0 },
|
||
transportStationBox: false,
|
||
transportStationTarget: '',
|
||
transportStationLoading: false,
|
||
transportStationQuery: {},
|
||
transportStationRows: [],
|
||
transportStationPage: { pageSize: 10, currentPage: 1, total: 0 },
|
||
transportMapBox: false,
|
||
transportMapTarget: '',
|
||
transportMapKeyword: '',
|
||
transportMapLoading: false,
|
||
transportMapStatus: '可搜索地址或点击地图选点',
|
||
transportMapSelected: {},
|
||
transportMapInstance: null,
|
||
transportMapMarker: null,
|
||
transportMapGeocoder: null,
|
||
commonCargoBox: false,
|
||
commonCargoLoading: false,
|
||
commonCargoQuery: {},
|
||
commonCargoRows: [],
|
||
commonCargoSelected: [],
|
||
commonCargoPage: { pageSize: 10, currentPage: 1, total: 0 },
|
||
cargoImportBox: false,
|
||
cargoImportLoading: false,
|
||
cargoImportRows: [],
|
||
shippingTemplateProcessConfigBox: false,
|
||
shippingTemplateProcessConfigLoading: false,
|
||
shippingTemplateProcessConfigRows: [],
|
||
hasProjectProcessConfig: false,
|
||
};
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
permissionList() {
|
||
return { addBtn: this.canCreate };
|
||
},
|
||
isAdmin() {
|
||
return (
|
||
String(this.userInfo?.authority || '').includes('admin') ||
|
||
this.userInfo?.authority?.includes?.('admin')
|
||
);
|
||
},
|
||
canCreate() {
|
||
return this.hasPermission(`${this.config.permission}_add`);
|
||
},
|
||
isStandaloneBusinessPage() {
|
||
return (
|
||
this.standaloneFormPage &&
|
||
['/business/shipping-template', '/business/shipping-template/form'].includes(
|
||
this.$route.path
|
||
)
|
||
);
|
||
},
|
||
isStandaloneFormPage() {
|
||
return this.isStandaloneBusinessPage && ['add', 'edit'].includes(this.$route.query.mode);
|
||
},
|
||
formPageTitle() {
|
||
return this.isStandaloneFormPage
|
||
? this.$route.query.name ||
|
||
`${this.$route.query.mode === 'edit' ? '编辑' : '新增'}${this.config.title}`
|
||
: '';
|
||
},
|
||
crudContainer() {
|
||
return this.isStandaloneFormPage ? 'PageAvueForm' : 'avue-crud';
|
||
},
|
||
pageFormOption() {
|
||
if (!this.isStandaloneFormPage) return this.option;
|
||
return this.buildFormGroupOption({
|
||
...this.option,
|
||
boxType: this.$route.query.mode === 'edit' ? 'edit' : 'add',
|
||
menuBtn: true,
|
||
submitBtn: true,
|
||
emptyBtn: false,
|
||
dialogCustomClass: 'shipping-template-form-page-dialog',
|
||
});
|
||
},
|
||
transportMode() {
|
||
return this.resolveTransportMode(this.form.transportType);
|
||
},
|
||
transportStationMode() {
|
||
return ['water', 'rail', 'air'].includes(this.transportMode);
|
||
},
|
||
transportStationTypeLabel() {
|
||
return { water: '港口/码头', rail: '铁路车站', air: '空港机场' }[this.transportMode] || '';
|
||
},
|
||
transportAddressSelectDisabled() {
|
||
return this.dialogReadonly || !this.form.transportType;
|
||
},
|
||
cargoTypeCascaderProps() {
|
||
return { label: 'cargoName', value: 'id', children: 'children', emitPath: true };
|
||
},
|
||
cargoQuantityTotal() {
|
||
const total = this.transportCargoRows.reduce(
|
||
(sum, row) => sum + (Number(row.quantity) || 0),
|
||
0
|
||
);
|
||
return total ? String(Number(total.toFixed(3))) : '系统自动计算';
|
||
},
|
||
freightVisible() {
|
||
return (
|
||
this.config.enableShippingTemplateFreight === true && this.form.templateType === '运单'
|
||
);
|
||
},
|
||
currencyLabel() {
|
||
const option = this.currencyOptions.find(
|
||
item =>
|
||
String(item.value).toUpperCase() ===
|
||
String(this.shippingTemplateFreight.currency || '').toUpperCase()
|
||
);
|
||
return this.currencyName(option?.label || this.shippingTemplateFreight.currency);
|
||
},
|
||
roadFreightTotal() {
|
||
const total = this.shippingTemplateFreight.freightItems.reduce(
|
||
(sum, item) => sum + Number(this.freightAmount(item) || 0),
|
||
0
|
||
);
|
||
return this.formatFreightNumber(total);
|
||
},
|
||
freightTotal() {
|
||
const base =
|
||
this.transportMode === 'road'
|
||
? Number(this.roadFreightTotal || 0)
|
||
: Number(this.shippingTemplateFreight.freightAmount || 0);
|
||
return this.formatFreightNumber(
|
||
base + Number(this.shippingTemplateFreight.otherFreightAmount || 0)
|
||
);
|
||
},
|
||
footerFreightTotal() {
|
||
return Number(this.freightTotal || 0).toFixed(2);
|
||
},
|
||
detailGoodsRows() {
|
||
return this.parseJsonArray(this.detailRow.goodsJson);
|
||
},
|
||
detailCargoQuantityTotal() {
|
||
const total = this.detailGoodsRows.reduce(
|
||
(sum, row) => sum + (Number(row.quantity) || 0),
|
||
0
|
||
);
|
||
return total ? String(Number(total.toFixed(3))) : '系统自动计算';
|
||
},
|
||
detailAttachmentRows() {
|
||
return this.parseJsonArray(this.detailRow.attachmentsJson);
|
||
},
|
||
detailFreight() {
|
||
return this.parseJsonObject(this.detailRow.freightJson);
|
||
},
|
||
detailFreightVisible() {
|
||
return (
|
||
this.config.enableShippingTemplateFreight === true &&
|
||
this.detailRow.templateType === '运单' &&
|
||
Boolean(this.detailFreight.currency)
|
||
);
|
||
},
|
||
detailTransportMode() {
|
||
return this.resolveTransportMode(this.detailRow.transportType);
|
||
},
|
||
detailFreightItems() {
|
||
return Array.isArray(this.detailFreight.freightItems)
|
||
? this.detailFreight.freightItems
|
||
: [];
|
||
},
|
||
detailFreightTotal() {
|
||
const storedTotal = Number(this.detailFreight.totalFreightAmount);
|
||
if (
|
||
this.detailFreight.totalFreightAmount !== undefined &&
|
||
this.detailFreight.totalFreightAmount !== null &&
|
||
this.detailFreight.totalFreightAmount !== '' &&
|
||
Number.isFinite(storedTotal)
|
||
)
|
||
return this.formatFreightNumber(storedTotal);
|
||
const base =
|
||
this.detailTransportMode === 'road'
|
||
? this.detailFreightItems.reduce(
|
||
(sum, item) => sum + (Number(item.freightAmount) || 0),
|
||
0
|
||
)
|
||
: Number(this.detailFreight.freightAmount || 0);
|
||
return this.formatFreightNumber(base + Number(this.detailFreight.otherFreightAmount || 0));
|
||
},
|
||
},
|
||
watch: {
|
||
$route(to, from) {
|
||
if (
|
||
this.isStandaloneFormPage &&
|
||
(to.query.mode !== from?.query?.mode ||
|
||
String(to.query.id || '') !== String(from?.query.id || ''))
|
||
)
|
||
this.$nextTick(() => this.initStandaloneFormPage());
|
||
},
|
||
detailId: {
|
||
immediate: true,
|
||
handler(id) {
|
||
if (id) this.openDetail({ id });
|
||
},
|
||
},
|
||
'form.transportType'(value, oldValue) {
|
||
if (value !== oldValue) {
|
||
this.handleTransportTypeChange(value);
|
||
this.syncFreightItems();
|
||
}
|
||
},
|
||
},
|
||
created() {
|
||
this.loadProjectOptions();
|
||
this.ensureCargoTypeOptions();
|
||
this.loadFeeUnitOptions();
|
||
this.loadCurrencyOptions();
|
||
if (this.isStandaloneFormPage) this.$nextTick(() => this.initStandaloneFormPage());
|
||
},
|
||
methods: {
|
||
buildOption(option) {
|
||
const next = { ...option, column: (option.column || []).map(column => ({ ...column })) };
|
||
if (this.menuWidth != null) next.menuWidth = this.menuWidth;
|
||
if (
|
||
this.standaloneFormPage &&
|
||
['/business/shipping-template', '/business/shipping-template/form'].includes(
|
||
this.$route.path
|
||
)
|
||
) {
|
||
next.dialogAppendToBody = false;
|
||
next.dialogModal = false;
|
||
next.dialogCustomClass = 'shipping-template-form-page-dialog';
|
||
next.dialogTop = '0';
|
||
next.dialogWidth = '100%';
|
||
} else {
|
||
next.dialogCustomClass = next.dialogCustomClass || 'shipping-template-dialog';
|
||
}
|
||
return next;
|
||
},
|
||
buildFormGroupOption(option) {
|
||
const titles = [
|
||
'templateInfoTitle',
|
||
'basicInfoTitle',
|
||
'shippingInfoTitle',
|
||
'goodsInfoTitle',
|
||
'attachmentTitle',
|
||
];
|
||
const groups = [];
|
||
let current = [];
|
||
[...(option.column || [])]
|
||
.sort((a, b) => (b.order || 0) - (a.order || 0))
|
||
.forEach(column => {
|
||
if (titles.includes(column.prop) && current.length) {
|
||
groups.push(current);
|
||
current = [];
|
||
}
|
||
current.push(column);
|
||
});
|
||
if (current.length) groups.push(current);
|
||
if (groups.length <= 1) return option;
|
||
|
||
const [mainColumn, ...groupColumns] = groups;
|
||
return {
|
||
...option,
|
||
column: mainColumn,
|
||
group: groupColumns.map((column, index) => {
|
||
const title = column.find(item => titles.includes(item.prop));
|
||
return {
|
||
prop: title?.prop || `shippingTemplateGroup${index + 1}`,
|
||
label: '',
|
||
arrow: false,
|
||
column: title ? column.filter(item => item.prop !== title.prop) : column,
|
||
};
|
||
}),
|
||
};
|
||
},
|
||
hasPermission(code) {
|
||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||
},
|
||
hasAction(action) {
|
||
return (this.config.actions || []).includes(action);
|
||
},
|
||
canEdit(row) {
|
||
return this.hasPermission(`${this.config.permission}_edit`) && !row.readonly;
|
||
},
|
||
canDelete(row) {
|
||
return this.hasPermission(`${this.config.permission}_delete`) && !row.readonly;
|
||
},
|
||
canCopy(row) {
|
||
return (
|
||
this.hasAction('copy') &&
|
||
this.hasPermission(`${this.config.permission}_copy`) &&
|
||
!row.readonly
|
||
);
|
||
},
|
||
customOperations(row) {
|
||
return (this.config.operations || []).filter(
|
||
operation =>
|
||
this.hasPermission(
|
||
operation.permission || `${this.config.permission}_${operation.action}`
|
||
) && !row.readonly
|
||
);
|
||
},
|
||
openBusinessForm(mode, row = {}) {
|
||
if (this.isStandaloneBusinessPage) {
|
||
this.$router.push({
|
||
path: '/business/shipping-template/form',
|
||
query: {
|
||
mode,
|
||
...(mode === 'edit' && row.id ? { id: row.id } : {}),
|
||
name: `${mode === 'edit' ? '编辑' : '新增'}${this.config.title}`,
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
this.$refs.crud?.[mode === 'edit' ? 'rowEdit' : 'rowAdd'](row);
|
||
},
|
||
initStandaloneFormPage() {
|
||
if (!this.isStandaloneFormPage) return;
|
||
const mode = this.$route.query.mode;
|
||
const id = this.$route.query.id || '';
|
||
const key = `${mode}:${id}`;
|
||
if (this.standaloneFormKey === key) return;
|
||
this.standaloneFormKey = key;
|
||
if (mode === 'edit') this.form = { id };
|
||
this.beforeOpen(() => {}, mode);
|
||
},
|
||
parseJsonArray(value) {
|
||
if (!value) return [];
|
||
if (Array.isArray(value)) return value;
|
||
try {
|
||
const result = JSON.parse(value);
|
||
return Array.isArray(result) ? result : [];
|
||
} catch {
|
||
return [];
|
||
}
|
||
},
|
||
parseJsonObject(value) {
|
||
if (!value) return {};
|
||
if (typeof value === 'object' && !Array.isArray(value)) return value;
|
||
try {
|
||
const result = JSON.parse(value);
|
||
return result && typeof result === 'object' && !Array.isArray(result) ? result : {};
|
||
} catch {
|
||
return {};
|
||
}
|
||
},
|
||
normalizeSearch(params = {}) {
|
||
return Object.keys(params).reduce((result, key) => {
|
||
const value = params[key];
|
||
if (value !== undefined && value !== null && value !== '') result[key] = value;
|
||
return result;
|
||
}, {});
|
||
},
|
||
searchReset() {
|
||
this.query = {};
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
searchChange(params, done) {
|
||
this.query = this.normalizeSearch(params);
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, this.query);
|
||
done?.();
|
||
},
|
||
selectionChange(list) {
|
||
this.selectionList = list || [];
|
||
},
|
||
currentChange(currentPage) {
|
||
this.page.currentPage = currentPage;
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
sizeChange(pageSize) {
|
||
this.page.pageSize = pageSize;
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
refreshChange() {
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
onLoad(page, params = {}) {
|
||
this.loading = true;
|
||
this.api
|
||
.getList(page.currentPage, page.pageSize, { ...this.query, ...params })
|
||
.then(res => {
|
||
const data = res?.data?.data || {};
|
||
this.data = extractRecords(res);
|
||
this.page.total = data.total || this.data.length;
|
||
})
|
||
.finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
handleExport() {
|
||
if (!this.config.exportUrl) return;
|
||
exportBlob(this.config.exportUrl, this.normalizeSearch(this.query)).then(res =>
|
||
downloadXls(res.data, `${this.config.title}导出.xlsx`)
|
||
);
|
||
},
|
||
handleDelete() {
|
||
if (!this.selectionList.length) return this.$message.warning('请选择至少一条数据');
|
||
this.$confirm('确定将选择数据删除?', '提示', { type: 'warning' })
|
||
.then(() => this.api.remove(this.selectionList.map(row => row.id).join(',')))
|
||
.then(() => {
|
||
this.$message.success('删除成功');
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
},
|
||
rowDel(row) {
|
||
this.$confirm('确定将选择数据删除?', '提示', { type: 'warning' })
|
||
.then(() => this.api.remove(row.id))
|
||
.then(() => {
|
||
this.$message.success('删除成功');
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
},
|
||
rowSave(row, done, loading) {
|
||
const submit = this.normalizeRow(row);
|
||
if (!submit) {
|
||
loading?.();
|
||
return;
|
||
}
|
||
this.api
|
||
.submit(submit)
|
||
.then(() => {
|
||
this.$message.success('操作成功');
|
||
done?.();
|
||
this.closeCrudDialog();
|
||
this.onLoad(this.page, this.query);
|
||
})
|
||
.catch(() => loading?.());
|
||
},
|
||
rowUpdate(row, index, done, loading) {
|
||
const submit = this.normalizeRow(row);
|
||
if (!submit) {
|
||
loading?.();
|
||
return;
|
||
}
|
||
this.api
|
||
.submit(submit)
|
||
.then(() => {
|
||
this.$message.success('操作成功');
|
||
done?.();
|
||
this.closeCrudDialog();
|
||
this.onLoad(this.page, this.query);
|
||
})
|
||
.catch(() => loading?.());
|
||
},
|
||
beforeOpen(done, type) {
|
||
this.crudDialogType = type;
|
||
this.dialogReadonly = type === 'view';
|
||
if (type === 'add') {
|
||
this.form = { ...(this.config.defaultForm || {}) };
|
||
this.selectedProjectId = '';
|
||
this.contractOptions = [];
|
||
this.transportCargoRows = [defaultCargo()];
|
||
this.attachmentRows = [];
|
||
this.shippingTemplateFreight = defaultFreight();
|
||
this.loadCurrencyOptions();
|
||
if (typeof this.api.nextCode === 'function')
|
||
this.api
|
||
.nextCode()
|
||
.then(res => {
|
||
this.form.templateCode = res?.data?.data || '';
|
||
})
|
||
.finally(() => done?.());
|
||
else done?.();
|
||
return;
|
||
}
|
||
if (['edit', 'view'].includes(type)) {
|
||
this.api
|
||
.getDetail(this.form.id)
|
||
.then(res => {
|
||
this.applyFormDetail(res?.data?.data || {});
|
||
done?.();
|
||
})
|
||
.catch(() => done?.());
|
||
return;
|
||
}
|
||
done?.();
|
||
},
|
||
applyFormDetail(row) {
|
||
this.form = { ...(row || {}) };
|
||
this.selectedProjectId = this.form.projectId || '';
|
||
this.transportCargoRows = this.parseJsonArray(this.form.goodsJson).map(item =>
|
||
this.normalizeCargo(item)
|
||
);
|
||
if (!this.transportCargoRows.length) this.transportCargoRows = [defaultCargo()];
|
||
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
|
||
this.shippingTemplateFreight = {
|
||
...defaultFreight(),
|
||
...this.parseJsonObject(this.form.freightJson),
|
||
};
|
||
this.setFreightCurrencyFromContract();
|
||
this.syncFreightItems();
|
||
this.syncCurrentProjectOption();
|
||
this.syncCurrentContractOption();
|
||
if (this.form.projectId) this.loadContractOptionsForProject();
|
||
this.loadProjectProcessConfigState();
|
||
},
|
||
normalizeRow(row) {
|
||
const submit = { ...row };
|
||
const required = this.config.transportFormRequiredFields || [
|
||
['projectName', '项目'],
|
||
['contractName', '客户合同'],
|
||
['transportType', '运输方式'],
|
||
['departureAddress', '发货地址'],
|
||
['arrivalAddress', '收货地址'],
|
||
];
|
||
const missing = required.find(([prop]) => !String(submit[prop] || '').trim());
|
||
if (missing) {
|
||
this.$message.warning(`请选择或输入${missing[1]}`);
|
||
return null;
|
||
}
|
||
if (
|
||
!this.validateMobileFields([
|
||
[submit.departurePhone, '发货联系方式'],
|
||
[submit.arrivalPhone, '收货联系方式'],
|
||
])
|
||
)
|
||
return null;
|
||
if (!this.validateCargoRows()) return null;
|
||
if (this.freightVisible && !this.validateFreight()) return null;
|
||
submit.goodsJson = JSON.stringify(
|
||
this.transportCargoRows.map(item => this.normalizeCargo(item))
|
||
);
|
||
submit.freightJson = this.freightVisible ? this.serializeFreight() : '';
|
||
submit.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||
[
|
||
'templateInfoTitle',
|
||
'basicInfoTitle',
|
||
'shippingInfoTitle',
|
||
'goodsInfoTitle',
|
||
'attachmentTitle',
|
||
].forEach(prop => delete submit[prop]);
|
||
Object.keys(submit).forEach(key => {
|
||
if (typeof submit[key] === 'string') submit[key] = submit[key].trim();
|
||
});
|
||
return submit;
|
||
},
|
||
validateMobileFields(fields) {
|
||
const invalid = fields.find(([value]) => value && !isMobile(value));
|
||
if (invalid) {
|
||
this.$message.warning(`${invalid[1]}格式不正确`);
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
validateCargoRows() {
|
||
if (!this.transportCargoRows.length) {
|
||
this.$message.warning('请至少添加一条货物信息');
|
||
return false;
|
||
}
|
||
for (const [index, row] of this.transportCargoRows.entries()) {
|
||
if (!row.cargoType || !row.cargoName || !row.quantity || !row.quantityUnit) {
|
||
this.$message.warning(`第${index + 1}行货物信息不完整`);
|
||
return false;
|
||
}
|
||
if (Number(row.quantity) <= 0) {
|
||
this.$message.warning(`第${index + 1}行数量必须大于0`);
|
||
return false;
|
||
}
|
||
if (String(row.remark || '').length > 200) {
|
||
this.$message.warning(`第${index + 1}行备注不能超过200个字符`);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
validateFreight() {
|
||
if (!this.shippingTemplateFreight.currency) {
|
||
this.$message.warning('请选择币种');
|
||
return false;
|
||
}
|
||
if (this.transportMode === 'road') {
|
||
const invalid = this.shippingTemplateFreight.freightItems.findIndex(
|
||
item =>
|
||
Number(item.unitPrice || 0) < 0 ||
|
||
!item.priceUnit ||
|
||
(!this.isFreightAutoCalculable(item) &&
|
||
(item.freightAmount === '' ||
|
||
item.freightAmount === null ||
|
||
item.freightAmount === undefined)) ||
|
||
Number(item.freightAmount || 0) < 0
|
||
);
|
||
if (invalid > -1) {
|
||
this.$message.warning(`第${invalid + 1}条货物的单价、计价单位或运费无效`);
|
||
return false;
|
||
}
|
||
} else if (Number(this.shippingTemplateFreight.freightAmount || 0) < 0) {
|
||
this.$message.warning('运费不能小于0');
|
||
return false;
|
||
}
|
||
if (Number(this.shippingTemplateFreight.otherFreightAmount || 0) < 0) {
|
||
this.$message.warning('其他运费合计不能小于0');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
validateFormText() {
|
||
return true;
|
||
},
|
||
closeCrudDialog() {
|
||
if (this.isStandaloneBusinessPage) {
|
||
this.$router.push({ path: '/business/shipping-template', query: {} });
|
||
return;
|
||
}
|
||
this.$refs.crud?.closeDialog?.();
|
||
},
|
||
openDetail(row = {}) {
|
||
if (!row.id) return;
|
||
this.detailBox = true;
|
||
this.detailLoading = true;
|
||
const request =
|
||
typeof this.api.getDetail === 'function'
|
||
? this.api.getDetail(row.id)
|
||
: Promise.resolve({ data: { data: row } });
|
||
request
|
||
.then(res => {
|
||
this.detailRow = res?.data?.data || res?.data || row;
|
||
this.attachmentRows = this.parseJsonArray(this.detailRow.attachmentsJson);
|
||
})
|
||
.finally(() => {
|
||
this.detailLoading = false;
|
||
});
|
||
},
|
||
handleCopy(row) {
|
||
if (typeof this.api.copy !== 'function') return;
|
||
this.api.copy(row.id).then(() => {
|
||
this.$message.success('复制成功');
|
||
this.onLoad(this.page, this.query);
|
||
});
|
||
},
|
||
handleCustomOperation(operation, row) {
|
||
if (operation.action === 'createFromTemplate') {
|
||
const loadTemplate =
|
||
typeof this.api.getDetail === 'function'
|
||
? this.api.getDetail(row.id).then(res => res?.data?.data || row)
|
||
: Promise.resolve(row);
|
||
loadTemplate.then(template => {
|
||
const templateType = template.templateType || template.templateTypeName;
|
||
const listPath =
|
||
templateType === '运单' ? '/business/waybill-manage' : '/business/transport-plan';
|
||
sessionStorage.setItem(
|
||
'business-template-create',
|
||
JSON.stringify({ target: listPath.slice('/business/'.length), data: template })
|
||
);
|
||
this.$router.push({
|
||
path: `${listPath}/form`,
|
||
query: { mode: 'add', fromTemplate: Date.now().toString() },
|
||
});
|
||
});
|
||
}
|
||
},
|
||
loadProjectOptions() {
|
||
if (this.projectLoading) return;
|
||
this.projectLoading = true;
|
||
getProjectList(1, 9999, this.config.projectQueryParams || {})
|
||
.then(res => {
|
||
this.projectOptions = extractRecords(res).filter(
|
||
item => !this.config.excludeVoidedProjects || item.approvalStatus !== 'voided'
|
||
);
|
||
this.syncCurrentProjectOption();
|
||
})
|
||
.finally(() => {
|
||
this.projectLoading = false;
|
||
});
|
||
},
|
||
syncCurrentProjectOption() {
|
||
if (
|
||
this.form.projectId &&
|
||
this.form.projectName &&
|
||
!this.projectOptions.some(item => String(item.id) === String(this.form.projectId))
|
||
)
|
||
this.projectOptions.unshift({
|
||
id: this.form.projectId,
|
||
projectName: this.form.projectName,
|
||
});
|
||
},
|
||
handleProjectChange(projectId) {
|
||
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
||
if (!project) {
|
||
this.form.projectId = '';
|
||
this.form.projectName = '';
|
||
this.form.contractId = '';
|
||
this.form.contractName = '';
|
||
this.contractOptions = [];
|
||
this.setFreightCurrencyFromContract();
|
||
return;
|
||
}
|
||
this.form.projectId = project.id;
|
||
this.form.projectName = project.projectName || '';
|
||
this.form.projectCode = project.projectCode || '';
|
||
this.form.contractId = '';
|
||
this.form.contractName = '';
|
||
this.setFreightCurrencyFromContract();
|
||
this.loadContractOptionsForProject(true);
|
||
this.loadProjectProcessConfigState();
|
||
},
|
||
loadContractOptionsForProject(autoPick = false) {
|
||
if (!this.form.projectId || this.contractLoading) return;
|
||
this.contractLoading = true;
|
||
getContractList(1, 9999, {
|
||
projectId: this.form.projectId,
|
||
...(this.config.contractQueryParams || {}),
|
||
})
|
||
.then(res => {
|
||
this.contractOptions = extractRecords(res);
|
||
this.syncCurrentContractOption();
|
||
this.syncFreightCurrencyFromContract();
|
||
if (autoPick && this.contractOptions.length) this.applyContract(this.contractOptions[0]);
|
||
})
|
||
.finally(() => {
|
||
this.contractLoading = false;
|
||
});
|
||
},
|
||
syncCurrentContractOption() {
|
||
if (
|
||
this.form.contractId &&
|
||
this.form.contractName &&
|
||
!this.contractOptions.some(item => String(item.id) === String(this.form.contractId))
|
||
)
|
||
this.contractOptions.unshift({
|
||
id: this.form.contractId,
|
||
contractName: this.form.contractName,
|
||
settlementCurrency: this.form.settlementCurrency || '',
|
||
});
|
||
},
|
||
handleContractChange(id) {
|
||
const contract = this.contractOptions.find(item => String(item.id) === String(id));
|
||
if (contract) this.applyContract(contract);
|
||
else this.setFreightCurrencyFromContract();
|
||
},
|
||
applyContract(contract) {
|
||
this.form.contractId = contract.id || '';
|
||
this.form.contractName = contract.contractName || '';
|
||
this.form.customerName = contract.customerName || this.form.customerName || '';
|
||
this.setFreightCurrencyFromContract(contract);
|
||
},
|
||
syncFreightCurrencyFromContract() {
|
||
const contract = this.contractOptions.find(
|
||
item => String(item.id) === String(this.form.contractId)
|
||
);
|
||
this.setFreightCurrencyFromContract(contract);
|
||
},
|
||
setFreightCurrencyFromContract(contract = null) {
|
||
const value =
|
||
contract?.settlementCurrency ||
|
||
contract?.settlementCurrencyCode ||
|
||
contract?.currency ||
|
||
this.form.settlementCurrency ||
|
||
'RMB';
|
||
this.shippingTemplateFreight.currency = this.normalizeCurrencyValue(value);
|
||
},
|
||
normalizeCurrencyValue(value) {
|
||
const raw =
|
||
typeof value === 'object'
|
||
? value.dictKey || value.value || value.code || value.dictValue || ''
|
||
: value;
|
||
const normalized = String(raw || 'RMB')
|
||
.trim()
|
||
.toUpperCase();
|
||
const code = normalized.includes(' - ') ? normalized.split(' - ')[0].trim() : normalized;
|
||
const option = this.currencyOptions.find(
|
||
item => String(item.value || '').toUpperCase() === code
|
||
);
|
||
return option?.value || code || 'RMB';
|
||
},
|
||
normalizeDictOptions(list = []) {
|
||
return list.map(item => ({
|
||
label: item.dictValue || item.label || item.value || '',
|
||
value: item.dictKey || item.value || item.dictValue || '',
|
||
remark: item.remark || '',
|
||
}));
|
||
},
|
||
currencyName(value) {
|
||
const text = String(value || '');
|
||
if (text.includes(' - ')) return text.slice(text.indexOf(' - ') + 3);
|
||
return (
|
||
{
|
||
CNY: '人民币',
|
||
RMB: '人民币',
|
||
USD: '美元',
|
||
EUR: '欧元',
|
||
JPY: '日元',
|
||
GBP: '英镑',
|
||
}[text.toUpperCase()] || text
|
||
);
|
||
},
|
||
loadFeeUnitOptions() {
|
||
getDictionary({ code: 'unit_fee' }).then(res => {
|
||
this.feeUnitOptions = this.normalizeDictOptions(res?.data?.data || []);
|
||
});
|
||
},
|
||
loadCurrencyOptions() {
|
||
if (this.currencyLoading || this.currencyOptions.length)
|
||
return Promise.resolve(this.currencyOptions);
|
||
this.currencyLoading = true;
|
||
return getSystemDictionary({ code: 'currency_type' })
|
||
.then(res => {
|
||
const names = {
|
||
CNY: '人民币',
|
||
RMB: '人民币',
|
||
USD: '美元',
|
||
EUR: '欧元',
|
||
JPY: '日元',
|
||
GBP: '英镑',
|
||
};
|
||
this.currencyOptions = this.normalizeDictOptions(res?.data?.data || []).map(item => {
|
||
const code = String(item.value || '').toUpperCase();
|
||
return {
|
||
...item,
|
||
label: `${code} - ${names[code] || item.label || item.value}`,
|
||
};
|
||
});
|
||
this.shippingTemplateFreight.currency = this.normalizeCurrencyValue(
|
||
this.shippingTemplateFreight.currency
|
||
);
|
||
return this.currencyOptions;
|
||
})
|
||
.finally(() => {
|
||
this.currencyLoading = false;
|
||
});
|
||
},
|
||
resolveTransportMode(value) {
|
||
const text = [value, this.form.transportTypeName, this.getTransportTypeLabel(value)]
|
||
.map(item => (typeof item === 'object' ? Object.values(item).join(' ') : item))
|
||
.filter(Boolean)
|
||
.join(' ')
|
||
.toLowerCase();
|
||
if (/公路|道路|汽车|road|highway|gl/.test(text)) return 'road';
|
||
if (/水路|水运|海运|港|water|ship|sea|sl/.test(text)) return 'water';
|
||
if (/铁路|rail|train|tl/.test(text)) return 'rail';
|
||
if (/航空|空运|空港|air|airport|hk/.test(text)) return 'air';
|
||
return '';
|
||
},
|
||
getTransportTypeLabel(value) {
|
||
const column = (this.option.column || []).find(item => item.prop === 'transportType');
|
||
const item = [...(column?.dicData || []), ...this.transportTypeOptions].find(
|
||
dic => String(dic.value ?? dic.dictKey) === String(value)
|
||
);
|
||
return item?.label || item?.dictValue || '';
|
||
},
|
||
handleTransportTypeChange(value) {
|
||
this.form.transportTypeName = this.getTransportTypeLabel(value);
|
||
},
|
||
openTransportPrimaryAddressPicker(target) {
|
||
if (this.transportStationMode) this.openTransportStationDialog(target);
|
||
},
|
||
handleTransportSecondaryAddressInputClick(target) {
|
||
if (this.transportStationMode || !this.config.editableRoadAddress)
|
||
this.openTransportSecondaryAddressPicker(target);
|
||
},
|
||
openTransportSecondaryAddressPicker(target) {
|
||
this.transportStationMode
|
||
? this.openTransportStationDialog(target)
|
||
: this.openTransportMapDialog(target);
|
||
},
|
||
openTransportRouteDialog() {
|
||
if (!this.form.transportType) return this.$message.warning('请先选择运输方式');
|
||
this.transportRoutePage.currentPage = 1;
|
||
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.handleTransportRouteSearch();
|
||
},
|
||
handleTransportRouteCurrentChange(page) {
|
||
this.transportRoutePage.currentPage = page;
|
||
this.loadTransportRouteList();
|
||
},
|
||
handleTransportRouteSizeChange(size) {
|
||
this.transportRoutePage.pageSize = size;
|
||
this.handleTransportRouteSearch();
|
||
},
|
||
selectTransportRouteRow(row) {
|
||
this.applyAddress('departure', {
|
||
addressName: row.departureName,
|
||
detailAddress: row.departureAddress,
|
||
contactName: row.departureContact,
|
||
contactPhone: row.departurePhone,
|
||
longitude: row.departureLongitude,
|
||
latitude: row.departureLatitude,
|
||
});
|
||
this.applyAddress('arrival', {
|
||
addressName: row.arrivalName,
|
||
detailAddress: row.arrivalAddress,
|
||
contactName: row.arrivalContact,
|
||
contactPhone: row.arrivalPhone,
|
||
longitude: row.arrivalLongitude,
|
||
latitude: row.arrivalLatitude,
|
||
});
|
||
this.transportRouteBox = false;
|
||
},
|
||
openTransportAddressPicker(target) {
|
||
if (!this.form.transportType) return this.$message.warning('请先选择运输方式');
|
||
this.transportAddressTarget = target;
|
||
this.transportAddressPage.currentPage = 1;
|
||
this.transportAddressBox = true;
|
||
},
|
||
loadTransportAddressList() {
|
||
this.transportAddressLoading = true;
|
||
getCommonAddressList(
|
||
this.transportAddressPage.currentPage,
|
||
this.transportAddressPage.pageSize,
|
||
{ ...this.transportAddressQuery, addressType: this.getFixedAddressType(), 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.handleTransportAddressSearch();
|
||
},
|
||
handleTransportAddressCurrentChange(page) {
|
||
this.transportAddressPage.currentPage = page;
|
||
this.loadTransportAddressList();
|
||
},
|
||
handleTransportAddressSizeChange(size) {
|
||
this.transportAddressPage.pageSize = size;
|
||
this.handleTransportAddressSearch();
|
||
},
|
||
getFixedAddressType() {
|
||
if (!this.config.fixedTransportAddressType) return '';
|
||
return (
|
||
{ road: '常规地址', rail: '铁路车站', water: '港口/码头', air: '空港机场' }[
|
||
this.transportMode
|
||
] || ''
|
||
);
|
||
},
|
||
selectTransportAddressRow(row) {
|
||
this.applyAddress(this.transportAddressTarget, row);
|
||
this.transportAddressBox = false;
|
||
},
|
||
applyAddress(target, address = {}) {
|
||
const prefix = target === 'departure' ? 'departure' : 'arrival';
|
||
this.form[`${prefix}Name`] = address.regionName || address.addressName || '';
|
||
this.form[`${prefix}Address`] = address.detailAddress || address.address || '';
|
||
this.form[`${prefix}AddressCode`] = address.addressCode || '';
|
||
this.form[`${prefix}SiteCode`] = 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`] || '';
|
||
},
|
||
openTransportStationDialog(target) {
|
||
if (!this.form.transportType) return this.$message.warning('请先选择运输方式');
|
||
this.transportStationTarget = target;
|
||
this.transportStationPage.currentPage = 1;
|
||
this.transportStationBox = true;
|
||
},
|
||
loadTransportStationList() {
|
||
const request = {
|
||
water: getPortTerminalList,
|
||
rail: getRailwayStationList,
|
||
air: getAirportMasterList,
|
||
}[this.transportMode];
|
||
if (!request) return;
|
||
this.transportStationLoading = true;
|
||
request(
|
||
this.transportStationPage.currentPage,
|
||
this.transportStationPage.pageSize,
|
||
this.transportStationQuery
|
||
)
|
||
.then(res => {
|
||
const data = res?.data?.data || {};
|
||
this.transportStationRows = (data.records || []).map(row => ({
|
||
...row,
|
||
stationName: row.name || row.stationName || row.addressName,
|
||
stationCode: row.code || row.siteCode,
|
||
detailAddress: row.detailAddress || row.address,
|
||
regionName: [row.provinceName, row.cityName, row.districtName].filter(Boolean).join(''),
|
||
}));
|
||
this.transportStationPage.total = data.total || 0;
|
||
})
|
||
.finally(() => {
|
||
this.transportStationLoading = false;
|
||
});
|
||
},
|
||
handleTransportStationSearch() {
|
||
this.transportStationPage.currentPage = 1;
|
||
this.loadTransportStationList();
|
||
},
|
||
handleTransportStationCurrentChange(page) {
|
||
this.transportStationPage.currentPage = page;
|
||
this.loadTransportStationList();
|
||
},
|
||
handleTransportStationSizeChange(size) {
|
||
this.transportStationPage.pageSize = size;
|
||
this.handleTransportStationSearch();
|
||
},
|
||
selectTransportStationRow(row) {
|
||
this.applyAddress(this.transportStationTarget, {
|
||
addressName: row.stationName,
|
||
detailAddress: row.detailAddress,
|
||
regionName: row.regionName,
|
||
siteCode: row.stationCode,
|
||
longitude: row.longitude,
|
||
latitude: row.latitude,
|
||
});
|
||
this.transportStationBox = false;
|
||
},
|
||
openTransportMapDialog(target) {
|
||
if (this.dialogReadonly || !this.form.transportType) return;
|
||
this.transportMapTarget = target;
|
||
this.transportMapKeyword = this.form[`${target}Address`] || '';
|
||
this.transportMapBox = true;
|
||
},
|
||
loadAmap() {
|
||
if (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.onload = resolve;
|
||
script.onerror = reject;
|
||
document.body.appendChild(script);
|
||
});
|
||
return amapLoader;
|
||
},
|
||
initTransportMap() {
|
||
this.loadAmap()
|
||
.then(() => {
|
||
this.$nextTick(() => {
|
||
if (!this.transportMapInstance) {
|
||
this.transportMapInstance = new window.AMap.Map(this.$refs.transportMap, {
|
||
center: [116.40769, 39.89945],
|
||
zoom: 11,
|
||
});
|
||
this.transportMapInstance.on('click', event => this.pickMapPoint(event.lnglat));
|
||
} else this.transportMapInstance.resize();
|
||
});
|
||
})
|
||
.catch(() => {
|
||
this.$message.error('高德地图组件加载失败');
|
||
this.transportMapBox = false;
|
||
});
|
||
},
|
||
searchTransportMapKeyword() {
|
||
if (!this.transportMapKeyword) return this.$message.warning('请输入地址关键词');
|
||
this.loadAmap().then(() => {
|
||
this.transportMapLoading = true;
|
||
window.AMap.plugin('AMap.Geocoder', () => {
|
||
this.transportMapGeocoder = this.transportMapGeocoder || new window.AMap.Geocoder();
|
||
this.transportMapGeocoder.getLocation(this.transportMapKeyword, (status, result) => {
|
||
this.transportMapLoading = false;
|
||
const location = status === 'complete' && result.geocodes?.[0]?.location;
|
||
if (location) this.pickMapPoint(location, this.transportMapKeyword);
|
||
else this.$message.warning('地图搜索无匹配地址');
|
||
});
|
||
});
|
||
});
|
||
},
|
||
pickMapPoint(point, keyword = '') {
|
||
const longitude = point.getLng ? point.getLng() : point.lng;
|
||
const latitude = point.getLat ? point.getLat() : point.lat;
|
||
if (longitude === undefined || latitude === undefined) return;
|
||
this.transportMapSelected = {
|
||
longitude: Number(longitude).toFixed(6),
|
||
latitude: Number(latitude).toFixed(6),
|
||
detailAddress: keyword || this.transportMapKeyword,
|
||
};
|
||
this.transportMapStatus = '已选点,可确认回填';
|
||
if (this.transportMapInstance) {
|
||
this.transportMapMarker?.setMap(null);
|
||
this.transportMapMarker = new window.AMap.Marker({ position: [longitude, latitude] });
|
||
this.transportMapMarker.setMap(this.transportMapInstance);
|
||
this.transportMapInstance.setCenter([longitude, latitude]);
|
||
}
|
||
},
|
||
confirmTransportMapPick() {
|
||
const prefix = this.transportMapTarget;
|
||
this.applyAddress(prefix, this.transportMapSelected);
|
||
this.transportMapBox = false;
|
||
},
|
||
ensureCargoTypeOptions() {
|
||
if (this.cargoTypeRequest) return this.cargoTypeRequest;
|
||
if (this.cargoTypeOptions.length) return Promise.resolve(this.cargoTypeOptions);
|
||
this.cargoTypeLoading = true;
|
||
this.cargoTypeRequest = getCargoTypeList(1, 9999)
|
||
.then(res => {
|
||
this.cargoTypeOptions = this.buildCargoTree(extractRecords(res));
|
||
this.cargoTypeFlatOptions = this.flattenCargo(this.cargoTypeOptions);
|
||
return this.cargoTypeOptions;
|
||
})
|
||
.finally(() => {
|
||
this.cargoTypeLoading = false;
|
||
this.cargoTypeRequest = null;
|
||
});
|
||
return this.cargoTypeRequest;
|
||
},
|
||
buildCargoTree(list = []) {
|
||
const flat = [];
|
||
const flatten = (items, parentId = '') => {
|
||
(items || []).forEach(item => {
|
||
const id = String(item.id ?? item.cargoCode ?? item.code);
|
||
flat.push({ ...item, id, parentId: item.parentId ?? parentId, children: [] });
|
||
flatten(item.children, id);
|
||
});
|
||
};
|
||
flatten(list);
|
||
const nodes = flat.map(item => ({
|
||
...item,
|
||
cargoName: item.cargoName || item.name || item.label,
|
||
cargoCode: item.cargoCode || item.code || '',
|
||
}));
|
||
const map = new Map(nodes.map(item => [item.id, item]));
|
||
const roots = [];
|
||
nodes.forEach(node => {
|
||
const parent = map.get(String(node.parentId || node.parentCode || ''));
|
||
if (parent && parent !== node) parent.children.push(node);
|
||
else roots.push(node);
|
||
});
|
||
const normalize = (node, parentPath = []) => {
|
||
const path = [...parentPath, node.id];
|
||
return {
|
||
...node,
|
||
path,
|
||
leaf: !node.children.length,
|
||
children: node.children.length
|
||
? node.children.map(child => normalize(child, path))
|
||
: undefined,
|
||
};
|
||
};
|
||
return roots.map(node => normalize(node));
|
||
},
|
||
flattenCargo(options, result = []) {
|
||
options.forEach(item => {
|
||
result.push(item);
|
||
if (item.children) this.flattenCargo(item.children, result);
|
||
});
|
||
return result;
|
||
},
|
||
cargoPath(path) {
|
||
return Array.isArray(path) ? path.slice(0, 2).map(String) : [];
|
||
},
|
||
normalizeCargo(row = {}) {
|
||
return {
|
||
...defaultCargo(),
|
||
...row,
|
||
cargoTypePath: this.cargoPath(row.cargoTypePath),
|
||
cargoType: row.cargoType || row.secondCargoTypeName || row.firstCargoTypeName || '',
|
||
cargoTypeCode: row.cargoTypeCode || row.secondCargoTypeCode || row.firstCargoTypeCode || '',
|
||
cargoName: row.cargoName || row.goodsName || '',
|
||
};
|
||
},
|
||
handleCargoTypeChange(row, value) {
|
||
const path = this.cargoPath(value);
|
||
const item = this.cargoTypeFlatOptions.find(
|
||
option => option.path?.join('/') === path.join('/')
|
||
);
|
||
const labels = path
|
||
.map(id => this.cargoTypeFlatOptions.find(option => option.id === id)?.cargoName)
|
||
.filter(Boolean);
|
||
row.cargoTypePath = path;
|
||
row.cargoType = labels.at(-1) || '';
|
||
row.cargoTypeCode = item?.cargoCode || '';
|
||
row.cargoName = '';
|
||
},
|
||
fetchCargoSuggestions(query, callback, row) {
|
||
getCommonCargoList(1, 20, {
|
||
cargoName: query,
|
||
...(row.cargoTypeCode ? { secondCargoTypeCode: row.cargoTypeCode } : {}),
|
||
allDept: 0,
|
||
})
|
||
.then(res =>
|
||
callback(extractRecords(res).map(item => ({ ...item, value: item.cargoName })))
|
||
)
|
||
.catch(() => callback([]));
|
||
},
|
||
selectCargoSuggestion(row, item) {
|
||
Object.assign(row, this.normalizeCargo(item));
|
||
row.value = undefined;
|
||
this.syncFreightItems();
|
||
},
|
||
handleCargoQuantityInput(row, value) {
|
||
row.quantity = String(value || '')
|
||
.replace(/[^\d.]/g, '')
|
||
.replace(/(\.\d{0,3}).*/, '$1');
|
||
},
|
||
addCargoRow(index = -1, row = {}) {
|
||
const next = this.normalizeCargo(row);
|
||
if (index >= 0) this.transportCargoRows.splice(index + 1, 0, next);
|
||
else if (
|
||
this.transportCargoRows.length === 1 &&
|
||
!this.transportCargoRows[0].cargoName &&
|
||
!this.transportCargoRows[0].cargoType
|
||
)
|
||
this.transportCargoRows.splice(0, 1, next);
|
||
else this.transportCargoRows.push(next);
|
||
this.syncFreightItems();
|
||
},
|
||
removeCargoRow(index) {
|
||
this.transportCargoRows.splice(index, 1);
|
||
if (!this.transportCargoRows.length) this.transportCargoRows.push(defaultCargo());
|
||
this.syncFreightItems();
|
||
},
|
||
cargoQuantity(quantityUnit) {
|
||
const unit = String(quantityUnit || '');
|
||
const total = this.transportCargoRows.reduce((sum, row) => {
|
||
if (String(row.quantityUnit || '') !== unit) return sum;
|
||
return sum + (Number(row.quantity) || 0);
|
||
}, 0);
|
||
return this.formatFreightNumber(total);
|
||
},
|
||
handleFreightNumberInput(row, prop, value) {
|
||
row[prop] = String(value || '')
|
||
.replace(/[^\d.]/g, '')
|
||
.replace(/(\.\d{0,2}).*/, '$1');
|
||
},
|
||
handleFreightAmountInput(item, value) {
|
||
this.handleFreightNumberInput(item, 'freightAmount', value);
|
||
item.freightAmountManual = true;
|
||
},
|
||
syncFreightItems() {
|
||
if (this.transportMode !== 'road') return;
|
||
const previousItems = Array.isArray(this.shippingTemplateFreight.freightItems)
|
||
? this.shippingTemplateFreight.freightItems
|
||
: [];
|
||
const quantityUnits = [];
|
||
this.transportCargoRows.forEach(row => {
|
||
const quantityUnit = row.quantityUnit || '';
|
||
if (!quantityUnits.some(unit => unit === quantityUnit)) quantityUnits.push(quantityUnit);
|
||
});
|
||
this.shippingTemplateFreight.freightItems = quantityUnits.map((quantityUnit, index) => {
|
||
const matched =
|
||
previousItems.find(item => String(item.quantityUnit || '') === String(quantityUnit)) ||
|
||
previousItems[index] ||
|
||
{};
|
||
return {
|
||
quantityUnit,
|
||
unitPrice: matched.unitPrice || '',
|
||
priceUnit: matched.priceUnit || '',
|
||
freightAmount: matched.freightAmount ?? '',
|
||
freightAmountManual:
|
||
matched.freightAmountManual === true || matched.manualFreightAmount === true,
|
||
};
|
||
});
|
||
},
|
||
quantityUnits() {
|
||
return [
|
||
...new Set(
|
||
this.transportCargoRows
|
||
.map(row => String(row.quantityUnit || '').trim())
|
||
.filter(Boolean)
|
||
),
|
||
];
|
||
},
|
||
priceUnitMatchesQuantity(priceUnit, quantityUnit) {
|
||
const normalizeUnit = value =>
|
||
String(value || '')
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace(/\s+/g, '')
|
||
.replace(/立方米|立方公尺|方/g, '立方');
|
||
const unit = normalizeUnit(quantityUnit).replace(/[·*.]/g, '');
|
||
const value = String(priceUnit || '')
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace(/\s+/g, '');
|
||
if (!unit || !value) return false;
|
||
const parts = value.split('/');
|
||
if (parts.length !== 2) return false;
|
||
const moneyUnit = part => /元|人民币|rmb|cny|\$|美元|usd/.test(part);
|
||
const quantityPart = part => normalizeUnit(part).replace(/[·*.]/g, '');
|
||
return (
|
||
(quantityPart(parts[0]) === unit && moneyUnit(parts[1])) ||
|
||
(quantityPart(parts[1]) === unit && moneyUnit(parts[0]))
|
||
);
|
||
},
|
||
isFreightAutoCalculable(item = {}) {
|
||
const units = this.quantityUnits();
|
||
return (
|
||
units.length === 1 &&
|
||
String(item.quantityUnit || '').trim() === units[0] &&
|
||
this.priceUnitMatchesQuantity(item.priceUnit, units[0]) &&
|
||
item.unitPrice !== '' &&
|
||
item.unitPrice !== null &&
|
||
item.unitPrice !== undefined &&
|
||
Number.isFinite(Number(item.unitPrice)) &&
|
||
Number(item.unitPrice) >= 0
|
||
);
|
||
},
|
||
freightAmount(item) {
|
||
if (item.freightAmountManual && item.freightAmount !== '') {
|
||
return this.formatFreightNumber(item.freightAmount);
|
||
}
|
||
if (!this.isFreightAutoCalculable(item)) {
|
||
return this.formatFreightNumber(item.freightAmount);
|
||
}
|
||
return this.formatFreightNumber(
|
||
Number(item.unitPrice || 0) * Number(this.cargoQuantity(item.quantityUnit) || 0)
|
||
);
|
||
},
|
||
formatFreightNumber(value) {
|
||
const number = Number(value || 0);
|
||
if (!Number.isFinite(number)) return '';
|
||
return Number(number.toFixed(2)).toString();
|
||
},
|
||
serializeFreight() {
|
||
const freight = {
|
||
currency: this.shippingTemplateFreight.currency,
|
||
otherFreightAmount: this.shippingTemplateFreight.otherFreightAmount,
|
||
};
|
||
if (this.transportMode === 'road') {
|
||
freight.totalFreightAmount = this.roadFreightTotal;
|
||
freight.freightItems = this.shippingTemplateFreight.freightItems.map(item => ({
|
||
...item,
|
||
quantity: this.cargoQuantity(item.quantityUnit),
|
||
freightAmount: this.freightAmount(item),
|
||
manualFreightAmount: item.freightAmountManual === true,
|
||
}));
|
||
} else {
|
||
freight.quantity = this.cargoQuantityTotal;
|
||
freight.freightAmount = this.shippingTemplateFreight.freightAmount;
|
||
}
|
||
freight.totalFreightAmount = this.freightTotal;
|
||
return JSON.stringify(freight);
|
||
},
|
||
openCommonCargoDialog() {
|
||
this.commonCargoSelected = [];
|
||
this.commonCargoPage.currentPage = 1;
|
||
this.commonCargoBox = true;
|
||
},
|
||
loadCommonCargoList() {
|
||
this.commonCargoLoading = true;
|
||
getCommonCargoList(this.commonCargoPage.currentPage, this.commonCargoPage.pageSize, {
|
||
...this.normalizeSearch(this.commonCargoQuery),
|
||
allDept: 0,
|
||
})
|
||
.then(res => {
|
||
const data = res?.data?.data || {};
|
||
this.commonCargoRows = data.records || [];
|
||
this.commonCargoPage.total = data.total || 0;
|
||
})
|
||
.finally(() => {
|
||
this.commonCargoLoading = false;
|
||
});
|
||
},
|
||
handleCommonCargoSearch() {
|
||
this.commonCargoPage.currentPage = 1;
|
||
this.loadCommonCargoList();
|
||
},
|
||
handleCommonCargoReset() {
|
||
this.commonCargoQuery = {};
|
||
this.handleCommonCargoSearch();
|
||
},
|
||
handleCommonCargoCurrentChange(page) {
|
||
this.commonCargoPage.currentPage = page;
|
||
this.loadCommonCargoList();
|
||
},
|
||
handleCommonCargoSizeChange(size) {
|
||
this.commonCargoPage.pageSize = size;
|
||
this.handleCommonCargoSearch();
|
||
},
|
||
handleCommonCargoSelectionChange(rows) {
|
||
this.commonCargoSelected = rows || [];
|
||
},
|
||
selectCommonCargoRow(row) {
|
||
this.addCargoRow(-1, row);
|
||
this.commonCargoBox = false;
|
||
},
|
||
confirmCommonCargoSelection() {
|
||
this.commonCargoSelected.forEach(row => this.addCargoRow(-1, row));
|
||
this.commonCargoBox = false;
|
||
},
|
||
handleCargoImportFileChange(file) {
|
||
const raw = file.raw;
|
||
if (!raw || !/\.(xls|xlsx)$/i.test(raw.name || ''))
|
||
return this.$message.warning('请上传 Excel 文件');
|
||
this.cargoImportLoading = true;
|
||
importBlob('/blade-transport/shipping-template/import-goods', raw)
|
||
.then(async res => {
|
||
const type = res.headers?.['content-type'] || res.data?.type || '';
|
||
if (type.includes('excel')) {
|
||
downloadXls(
|
||
res.data,
|
||
`发货模板货物导入失败明细${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||
);
|
||
this.$message.warning('部分数据导入失败,已下载失败明细');
|
||
return;
|
||
}
|
||
const result = JSON.parse(await res.data.text());
|
||
if (result.code !== 200) throw new Error(result.msg);
|
||
this.cargoImportRows = result.data || [];
|
||
})
|
||
.catch(() => this.$message.error('货物导入失败,请检查文件格式'))
|
||
.finally(() => {
|
||
this.cargoImportLoading = false;
|
||
});
|
||
},
|
||
confirmCargoImport() {
|
||
this.cargoImportRows.forEach(row => this.addCargoRow(-1, row));
|
||
this.closeCargoImportDialog();
|
||
},
|
||
closeCargoImportDialog() {
|
||
if (!this.cargoImportLoading) {
|
||
this.cargoImportBox = false;
|
||
this.cargoImportRows = [];
|
||
}
|
||
},
|
||
handleCargoImportTemplate() {
|
||
exportBlob('/blade-transport/shipping-template/export-goods-template').then(res =>
|
||
downloadXls(res.data, '发货模板货物导入模板.xlsx')
|
||
);
|
||
},
|
||
handleAttachmentChange(list) {
|
||
const name = this.userInfo?.realName || this.userInfo?.userName || '';
|
||
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||
this.attachmentRows = (list || []).map(item => ({
|
||
...item,
|
||
description: item.description || '',
|
||
uploadUserName: item.uploadUserName || name,
|
||
uploadTime: item.uploadTime || time,
|
||
}));
|
||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||
},
|
||
handleAttachmentSelectionChange(rows) {
|
||
this.selectedAttachmentRows = rows || [];
|
||
},
|
||
removeAttachment(index) {
|
||
this.attachmentRows.splice(index, 1);
|
||
this.form.attachmentsJson = JSON.stringify(this.attachmentRows);
|
||
},
|
||
attachmentName(row = {}) {
|
||
return row.originalName || row.name || row.fileName || '附件';
|
||
},
|
||
attachmentUrl(row = {}) {
|
||
return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || '';
|
||
},
|
||
attachmentExtension(row) {
|
||
const source = this.attachmentName(row) || this.attachmentUrl(row);
|
||
return source.split('?')[0].split('.').pop().toLowerCase();
|
||
},
|
||
formatFileSize(size) {
|
||
const value = Number(size);
|
||
if (!value) return '';
|
||
if (value < 1024) return `${value}B`;
|
||
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)}KB`;
|
||
return `${(value / 1024 / 1024).toFixed(1)}MB`;
|
||
},
|
||
previewAttachment(row) {
|
||
const url = this.attachmentUrl(row);
|
||
if (!url) return this.$message.warning('附件地址为空');
|
||
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(row))) {
|
||
this.attachmentImagePreviewUrls = this.attachmentRows
|
||
.filter(item =>
|
||
['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentExtension(item))
|
||
)
|
||
.map(item => this.attachmentUrl(item));
|
||
this.attachmentImagePreviewIndex = Math.max(
|
||
this.attachmentImagePreviewUrls.indexOf(url),
|
||
0
|
||
);
|
||
this.attachmentImagePreviewVisible = true;
|
||
} else {
|
||
this.attachmentPreviewFile = {
|
||
name: this.attachmentName(row),
|
||
url,
|
||
mimeType: row.mimeType || row.contentType || '',
|
||
};
|
||
this.attachmentDocumentPreviewVisible = true;
|
||
}
|
||
},
|
||
handleAttachmentPreviewUnsupported() {
|
||
this.$message.warning('当前文件暂不支持在线预览');
|
||
},
|
||
handleAttachmentPreviewError() {
|
||
this.$message.error('附件预览失败');
|
||
},
|
||
downloadAttachment(row) {
|
||
const url = this.attachmentUrl(row);
|
||
if (url) downloadFileByUrl(url, this.attachmentName(row));
|
||
},
|
||
handleBatchDownload() {
|
||
(this.selectedAttachmentRows.length
|
||
? this.selectedAttachmentRows
|
||
: this.attachmentRows
|
||
).forEach(row => this.downloadAttachment(row));
|
||
},
|
||
handleDetailBatchDownload() {
|
||
this.detailAttachmentRows.forEach(row => this.downloadAttachment(row));
|
||
},
|
||
openShippingTemplateProcessConfig() {
|
||
if (!this.form.projectId) return;
|
||
this.shippingTemplateProcessConfigBox = true;
|
||
this.shippingTemplateProcessConfigLoading = true;
|
||
this.getProjectProcessConfigRows(this.form.projectId)
|
||
.then(rows =>
|
||
Promise.all(
|
||
rows.map(row =>
|
||
getProcessConfigDetail(row.id)
|
||
.then(res => ({ ...row, ...(res?.data?.data || {}) }))
|
||
.catch(() => row)
|
||
)
|
||
)
|
||
)
|
||
.then(rows => {
|
||
this.shippingTemplateProcessConfigRows = rows;
|
||
})
|
||
.finally(() => {
|
||
this.shippingTemplateProcessConfigLoading = false;
|
||
});
|
||
},
|
||
getProjectProcessConfigRows(projectId) {
|
||
return getProcessConfigList(1, 100, { projectIds: projectId, status: 1 }).then(res =>
|
||
extractRecords(res).filter(row =>
|
||
String(row.projectIds || '')
|
||
.split(',')
|
||
.includes(String(projectId))
|
||
)
|
||
);
|
||
},
|
||
loadProjectProcessConfigState() {
|
||
if (!this.form.projectId) {
|
||
this.hasProjectProcessConfig = false;
|
||
return;
|
||
}
|
||
this.getProjectProcessConfigRows(this.form.projectId)
|
||
.then(rows => {
|
||
this.hasProjectProcessConfig = rows.length > 0;
|
||
})
|
||
.catch(() => {
|
||
this.hasProjectProcessConfig = false;
|
||
});
|
||
},
|
||
getProcessConfigEnabledNodes(row = {}) {
|
||
const parsed = this.parseJsonArray(row.nodeConfigJson);
|
||
const value = parsed.length ? parsed : this.parseJsonObject(row.nodeConfigJson);
|
||
const nodes = Array.isArray(value) ? value : value.nodes || [];
|
||
return nodes.filter(node => node.enabled !== false);
|
||
},
|
||
processConfigTimelineDescriptions(node = {}) {
|
||
return [`打卡操作:${node.punch ? '是' : '否'}`, `打卡定位:${node.location ? '是' : '否'}`];
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.shipping-template-page__field {
|
||
width: 100%;
|
||
}
|
||
.dialog-section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
min-height: 20px;
|
||
box-sizing: border-box;
|
||
gap: 0;
|
||
color: #303133;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin: 8px 0 16px;
|
||
|
||
&::before {
|
||
margin-right: 8px;
|
||
}
|
||
}
|
||
.dialog-section-title--action {
|
||
justify-content: space-between;
|
||
}
|
||
.shipping-template-page__route-row {
|
||
display: grid;
|
||
grid-template-columns:
|
||
minmax(160px, 1fr) minmax(220px, 2fr) auto minmax(140px, 1fr) auto minmax(160px, 1fr)
|
||
auto;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
.shipping-template-page__section-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-left: auto;
|
||
}
|
||
.shipping-template-page__cargo-wrap {
|
||
width: 100%;
|
||
overflow-x: auto;
|
||
}
|
||
.shipping-template-page__cargo-total {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
padding: 12px 0;
|
||
color: #303133;
|
||
font-weight: 600;
|
||
}
|
||
.shipping-template-page__freight-section {
|
||
margin-top: 8px;
|
||
border-top: 1px solid #eff1f7;
|
||
padding-top: 12px;
|
||
}
|
||
.shipping-template-page__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%;
|
||
}
|
||
|
||
&--road {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
column-gap: 56px;
|
||
|
||
:deep(.shipping-template-page__append-select) {
|
||
width: 112px;
|
||
}
|
||
}
|
||
}
|
||
.shipping-template-page__attachment-head {
|
||
margin-bottom: 8px;
|
||
}
|
||
.shipping-template-page__footer-summary {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 24px;
|
||
margin-bottom: 8px;
|
||
color: #606266;
|
||
}
|
||
.shipping-template-page__footer-summary strong {
|
||
margin-left: 4px;
|
||
color: #303133;
|
||
font-size: 15px;
|
||
}
|
||
.shipping-template-page__attachment-upload {
|
||
margin-top: 12px;
|
||
}
|
||
.shipping-template-page__row-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.shipping-template-page__detail {
|
||
padding: 0;
|
||
background: #f5f6fa;
|
||
}
|
||
.shipping-template-page__detail-section {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
margin-bottom: 12px;
|
||
padding: 0 16px 4px;
|
||
overflow: hidden;
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||
}
|
||
.shipping-template-page__detail-section:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
.shipping-template-page__detail-section > .dialog-section-title {
|
||
margin: 8px 0 16px;
|
||
}
|
||
.shipping-template-page__detail-form {
|
||
width: 100%;
|
||
margin: 0;
|
||
}
|
||
.shipping-template-page__detail-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 0 24px;
|
||
}
|
||
.shipping-template-page__detail-form :deep(.el-form-item) {
|
||
min-width: 0;
|
||
margin-bottom: 16px;
|
||
}
|
||
.shipping-template-page__detail-form :deep(.el-form-item__label) {
|
||
color: #909399;
|
||
line-height: 32px;
|
||
white-space: nowrap;
|
||
}
|
||
.shipping-template-page__detail-form :deep(.el-form-item__content) {
|
||
min-width: 0;
|
||
color: #303133;
|
||
line-height: 32px;
|
||
word-break: break-all;
|
||
}
|
||
.shipping-template-page__detail-form-wide {
|
||
grid-column: 1 / -1;
|
||
}
|
||
.shipping-template-page__detail-route-values {
|
||
display: grid;
|
||
grid-template-columns: minmax(160px, 1fr) minmax(220px, 2fr) minmax(140px, 1fr) minmax(
|
||
160px,
|
||
1fr
|
||
);
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
.shipping-template-page__detail-route-values > span {
|
||
min-width: 0;
|
||
min-height: 32px;
|
||
box-sizing: border-box;
|
||
padding: 4px 11px;
|
||
overflow: hidden;
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
background: #f5f7fa;
|
||
color: #606266;
|
||
line-height: 22px;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.shipping-template-page__detail-freight {
|
||
margin-bottom: 8px;
|
||
}
|
||
.shipping-template-page__detail-grid {
|
||
margin-bottom: 0;
|
||
}
|
||
.shipping-template-page__detail-attachments {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
.shipping-template-page__standalone-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
:global(.shipping-template-page__detail-dialog .el-dialog__body) {
|
||
padding: 12px;
|
||
background: #f5f6fa;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .shipping-template-page__full-form-item) {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog .shipping-template-page__full-form-item > .el-form-item
|
||
) {
|
||
width: 100%;
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog
|
||
.shipping-template-page__full-form-item
|
||
> .el-form-item
|
||
> .el-form-item__label
|
||
) {
|
||
display: none;
|
||
width: 0 !important;
|
||
padding: 0 !important;
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog
|
||
.shipping-template-page__full-form-item
|
||
> .el-form-item
|
||
> .el-form-item__content
|
||
) {
|
||
flex: 1 1 100%;
|
||
width: 100%;
|
||
max-width: 100%;
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog
|
||
.shipping-template-page__full-form-item
|
||
> .el-form-item
|
||
> .el-form-item__content
|
||
> div
|
||
) {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* 独立表单页:每个业务分组独立成白色圆角模块,底部操作区固定在视口底部。 */
|
||
:global(.shipping-template-form-page-dialog) {
|
||
width: 100% !important;
|
||
min-height: 100%;
|
||
box-sizing: border-box;
|
||
padding-bottom: 80px;
|
||
background: #f5f6fa;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-form) {
|
||
padding: 0;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-form > .el-form > .el-row) {
|
||
margin: 0 !important;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group) {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
margin: 0 0 12px;
|
||
overflow: hidden;
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group:last-child) {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group .avue-form__group) {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
flex-wrap: wrap;
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0 16px 4px;
|
||
background: transparent;
|
||
box-shadow: none;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group .avue-group__header) {
|
||
height: auto;
|
||
min-height: 20px;
|
||
box-sizing: border-box;
|
||
padding: 0 16px !important;
|
||
border: 0;
|
||
line-height: 20px;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group .avue-form__group--flex) {
|
||
display: flex;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group .shipping-template-page__shipping-title),
|
||
:global(.shipping-template-form-page-dialog .avue-group .shipping-template-page__goods-title) {
|
||
justify-content: flex-start !important;
|
||
padding-left: 0;
|
||
text-align: left;
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog .avue-group .shipping-template-page__shipping-title > span
|
||
) {
|
||
text-align: left;
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog .avue-group .shipping-template-page__shipping-route-action
|
||
),
|
||
:global(
|
||
.shipping-template-form-page-dialog
|
||
.avue-group
|
||
.shipping-template-page__goods-title
|
||
.shipping-template-page__section-actions
|
||
) {
|
||
margin-left: auto;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group .avue-form__row) {
|
||
padding: 0 !important;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-group .el-form-item) {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-form__menu) {
|
||
position: fixed !important;
|
||
right: 0;
|
||
bottom: 0;
|
||
left: 230px;
|
||
z-index: 1001;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
min-height: 64px;
|
||
box-sizing: border-box;
|
||
padding: 12px 24px;
|
||
background: #fff;
|
||
border-top: 1px solid #eff1f7;
|
||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog .avue-form__menu > .shipping-template-page__footer-summary
|
||
) {
|
||
order: 1;
|
||
margin-right: auto;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
:global(
|
||
.shipping-template-form-page-dialog
|
||
.avue-form__menu
|
||
> .shipping-template-page__standalone-actions
|
||
) {
|
||
display: flex;
|
||
order: 2;
|
||
gap: 8px;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-form__menu > .el-button) {
|
||
order: 3;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
:global(.shipping-template-form-page-dialog .avue-form__menu > .el-button .el-icon) {
|
||
display: none;
|
||
}
|
||
|
||
:global(.avue--collapse .shipping-template-form-page-dialog .avue-form__menu) {
|
||
left: 60px;
|
||
}
|
||
|
||
:global(.avue-layout--horizontal .shipping-template-form-page-dialog .avue-form__menu) {
|
||
left: 0;
|
||
}
|
||
.shipping-template-page__map-toolbar {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.shipping-template-page__map {
|
||
height: 480px;
|
||
margin-top: 12px;
|
||
}
|
||
.shipping-template-page__map-info {
|
||
margin-top: 8px;
|
||
color: #606266;
|
||
}
|
||
.shipping-template-page__process-section {
|
||
padding: 12px 0;
|
||
border-bottom: 1px solid #eff1f7;
|
||
}
|
||
.shipping-template-page__process-node {
|
||
padding: 8px 0 0 12px;
|
||
color: #606266;
|
||
}
|
||
@media (max-width: 900px) {
|
||
.shipping-template-page__route-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.shipping-template-page__detail-grid,
|
||
.shipping-template-page__detail-route-values {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.shipping-template-page__detail-form-wide {
|
||
grid-column: auto;
|
||
}
|
||
.shipping-template-page__route-row > span {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|