新增提单管理和提单计费页面
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/** 按提单号查询可绑定的提单 */
|
||||
export function listAirRoleBills(billCodes) {
|
||||
return request({
|
||||
url: '/price/airFee/bills',
|
||||
method: 'get',
|
||||
params: { billCodes }
|
||||
})
|
||||
}
|
||||
|
||||
/** 查询当前入口已绑定到这些提单的费用 */
|
||||
export function listAirRoleFees(scene, billCodes) {
|
||||
return request({
|
||||
url: '/price/airFee/list',
|
||||
method: 'get',
|
||||
params: { scene, billCodes }
|
||||
})
|
||||
}
|
||||
|
||||
/** 保存当前入口的费用 */
|
||||
export function saveAirRoleFees(data) {
|
||||
return request({
|
||||
url: '/price/airFee/save',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 解析导入文件,示例行由后台跳过。空运、报关、清关共用,入口用 scene 区分。 */
|
||||
export function importAirRoleFee(data) {
|
||||
return request({
|
||||
url: '/price/airFee/import',
|
||||
method: 'post',
|
||||
data,
|
||||
// 必须声明 multipart,否则全局 application/json 会把文件流改写成空 JSON
|
||||
headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false },
|
||||
timeout: 120000
|
||||
})
|
||||
}
|
||||
@@ -171,7 +171,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="calcRule" label="计费条件" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-select v-if="isRowEditing(row)" v-model="row.calcRule" size="small" placeholder="人工确认">
|
||||
<el-select v-if="isRowEditing(row)" v-model="row.calcRule" size="small" placeholder="暂不计费">
|
||||
<el-option v-for="item in calcRules" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<span v-else>{{ calcRuleLabel(row.calcRule) }}</span>
|
||||
@@ -452,11 +452,11 @@ const calcRules = [
|
||||
{ value: 'WEIGHT', label: '超重' },
|
||||
{ value: 'SIZE', label: '超尺寸' },
|
||||
{ value: 'RETURN', label: '退件' },
|
||||
{ value: 'MANUAL', label: '人工确认' }
|
||||
{ value: 'MANUAL', label: '暂不计费' }
|
||||
]
|
||||
|
||||
function calcRuleLabel(value) {
|
||||
return calcRules.find(item => item.value === value)?.label || '人工确认'
|
||||
return calcRules.find(item => item.value === value)?.label || '暂不计费'
|
||||
}
|
||||
|
||||
function conditionHint(row) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<air-role-fee scene="air" />
|
||||
</template>
|
||||
|
||||
<script setup name="AirLadingFee">
|
||||
import AirRoleFee from '../components/AirRoleFee.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<air-role-fee scene="clear" />
|
||||
</template>
|
||||
|
||||
<script setup name="ClearLadingFee">
|
||||
import AirRoleFee from '../components/AirRoleFee.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,429 @@
|
||||
<template>
|
||||
<div class="app-container air-role-fee">
|
||||
<div class="page-head">
|
||||
<span class="page-title">{{ title }}</span>
|
||||
</div>
|
||||
|
||||
<div class="bill-bar">
|
||||
<el-input
|
||||
v-model="billCodeText"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
resize="none"
|
||||
placeholder="提单号,多个用逗号或换行分隔,可多选"
|
||||
/>
|
||||
<el-button type="primary" :loading="billLoading" @click="searchBills">查询提单</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="billTableRef"
|
||||
:data="bills"
|
||||
border
|
||||
size="small"
|
||||
max-height="220"
|
||||
row-key="billCode"
|
||||
empty-text="请输入提单号后查询"
|
||||
@selection-change="onBillSelect"
|
||||
>
|
||||
<el-table-column type="selection" width="48" />
|
||||
<el-table-column
|
||||
v-for="col in billColumns"
|
||||
:key="col.prop"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:align="col.align"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ showCell(row, col) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="fee-toolbar">
|
||||
<el-button type="primary" plain icon="Plus" v-hasPermi="[perm]" @click="addRows">新增</el-button>
|
||||
<el-button type="primary" plain icon="Upload" v-hasPermi="[perm]" @click="openImport">导入</el-button>
|
||||
<el-button v-hasPermi="[perm]" :loading="feeLoading" @click="loadFees">查询已有费用</el-button>
|
||||
<span class="page-hint">已选 {{ selected.length }} 个提单。导入和新增都会写到每一个已选提单。</span>
|
||||
</div>
|
||||
<el-table :data="rows" border size="small" max-height="420" row-key="rowKey" empty-text="暂无费用">
|
||||
<el-table-column prop="billCode" label="提单号" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column min-width="180">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>费用名称</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeType" filterable remote clearable reserve-keyword placeholder="输入费用名称"
|
||||
:remote-method="searchSurcharge" :loading="surchargeLoading" :disabled="locked(row)" style="width: 100%"
|
||||
@change="id => onFeeChange(row, id)">
|
||||
<el-option v-for="item in surchargeOptions" :key="item.scId" :label="item.scName" :value="item.scId" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>价格</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.price" :min="0" :precision="4" :controls="false" :value-on-clear="null" :disabled="locked(row)" style="width: 100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="140">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>币种</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.currency" filterable clearable placeholder="请选择" :disabled="locked(row)" style="width: 100%">
|
||||
<el-option v-for="item in bms_currency" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="500" clearable :disabled="locked(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="locked(row)" class="locked-text">{{ row.billFeeStatus === '3' ? '已确定' : '已分摊' }}</span>
|
||||
<el-button v-else link type="danger" v-hasPermi="[perm]" @click="removeRow(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="fee-footer">
|
||||
<el-button type="primary" v-hasPermi="[perm]" :disabled="!dirty" :loading="saving" @click="save">保存</el-button>
|
||||
</div>
|
||||
|
||||
<excel-import-dialog
|
||||
ref="importRef"
|
||||
:title="`${title}导入`"
|
||||
width="640px"
|
||||
:show-update-support="false"
|
||||
:import-desc="importDesc"
|
||||
:template-download-handler="downloadTemplate"
|
||||
:submit-handler="submitImport"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listAirRoleBills, listAirRoleFees, saveAirRoleFees, importAirRoleFee } from '@/api/price/airFee'
|
||||
import { listSurchargeOptions } from '@/api/price/surcharge'
|
||||
import { download } from '@/utils/request'
|
||||
import { parseTime } from '@/utils/bms'
|
||||
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
||||
|
||||
const props = defineProps({
|
||||
scene: { type: String, required: true }
|
||||
})
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_currency } = useDict('bms_currency')
|
||||
|
||||
const titles = { air: '空运费用', declare: '报关费用', clear: '清关费用' }
|
||||
const perms = { air: 'price:airFee:air', declare: 'price:airFee:declare', clear: 'price:airFee:clear' }
|
||||
const title = computed(() => titles[props.scene] || '提单费用')
|
||||
const perm = computed(() => perms[props.scene] || '')
|
||||
const billColumnMap = {
|
||||
air: [
|
||||
{ prop: 'billCode', label: '提单号', minWidth: 160 },
|
||||
{ prop: 'airNo', label: '航班号', minWidth: 120 },
|
||||
{ prop: 'actualAirNo', label: '实际航班号', minWidth: 120 },
|
||||
{ prop: 'airCompany', label: '航空公司', minWidth: 120 },
|
||||
{ prop: 'airCompanyAgent', label: '航空公司代理', minWidth: 150 },
|
||||
{ prop: 'startAirport', label: '始发机场', width: 100 },
|
||||
{ prop: 'endAirport', label: '目的机场', width: 100 },
|
||||
{ prop: 'desPort', label: '目的港', width: 100 },
|
||||
{ prop: 'routeName', label: '航线名称', minWidth: 120 },
|
||||
{ prop: 'boardNo', label: '板号', width: 110 },
|
||||
{ prop: 'pieceNumber', label: '总件数', width: 90, align: 'right' },
|
||||
{ prop: 'actualWeight', label: '实际重量', width: 110, align: 'right' },
|
||||
{ prop: 'volumeWeight', label: '体积重', width: 100, align: 'right' },
|
||||
{ prop: 'bandBoxWeight', label: '打板重量', width: 110, align: 'right' },
|
||||
{ prop: 'firstLandingTime', label: '首程落地时间', width: 170, time: true },
|
||||
{ prop: 'startTime', label: '起飞时间', width: 170, time: true },
|
||||
{ prop: 'arriveTime', label: '到达时间', width: 170, time: true },
|
||||
{ prop: 'airOtherFee', label: '空运其他费用', width: 120, align: 'right' }
|
||||
],
|
||||
declare: [
|
||||
{ prop: 'billCode', label: '提单号', minWidth: 160 },
|
||||
{ prop: 'declareCode', label: '报关公司编码', minWidth: 140 },
|
||||
{ prop: 'baoguanFee', label: '报关费', width: 120, align: 'right' },
|
||||
{ prop: 'baoguanOtherFee', label: '报关其他费用', width: 130, align: 'right' }
|
||||
],
|
||||
clear: [
|
||||
{ prop: 'billCode', label: '提单号', minWidth: 160 },
|
||||
{ prop: 'clearanceCode', label: '清关编码', minWidth: 120 },
|
||||
{ prop: 'qingguanAgent', label: '清关代理', minWidth: 140 },
|
||||
{ prop: 'clearanceOpCode', label: '清关操作人', minWidth: 120 },
|
||||
{ prop: 'desPort', label: '目的港', width: 100 },
|
||||
{ prop: 'desPortAgentCode', label: '目的港代理编码', minWidth: 140 },
|
||||
{ prop: 'desPortAgentFee', label: '目的港代理费', width: 130, align: 'right' },
|
||||
{ prop: 'clearanceTime', label: '清关时间', width: 170, time: true },
|
||||
{ prop: 'arriveStockTime', label: '到仓时间', width: 170, time: true },
|
||||
{ prop: 'stockAddr', label: '出仓仓库地址', minWidth: 160 }
|
||||
]
|
||||
}
|
||||
const billColumns = computed(() => billColumnMap[props.scene] || billColumnMap.air)
|
||||
|
||||
function showCell(row, col) {
|
||||
const value = row?.[col.prop]
|
||||
if (value === null || value === undefined || value === '') return '-'
|
||||
if (col.time) return parseTime(value) || '-'
|
||||
return value
|
||||
}
|
||||
const importDesc = [
|
||||
'模板字段:费用名称、价格、币种、备注。',
|
||||
'示例数据:燃油附加费、1000、EUR、导入时需清空这行示例数据。',
|
||||
'导入前请删除示例行,否则这一行不会写入。',
|
||||
'导入结果会添加到当前勾选的每一个提单,确认后点保存。'
|
||||
]
|
||||
|
||||
const billCodeText = ref('')
|
||||
const bills = ref([])
|
||||
const selected = ref([])
|
||||
const rows = ref([])
|
||||
const deleted = ref([])
|
||||
const billLoading = ref(false)
|
||||
const feeLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const surchargeOptions = ref([])
|
||||
const surchargeLoading = ref(false)
|
||||
const importRef = ref()
|
||||
let rowSeed = 0
|
||||
let surchargeSeq = 0
|
||||
|
||||
const dirty = computed(() => deleted.value.length > 0 || rows.value.some(row => rowChanged(row)))
|
||||
|
||||
function parseCodes(raw) {
|
||||
return [...new Set(String(raw || '').split(/[\s,,;;]+/).map(item => item.trim()).filter(Boolean))]
|
||||
}
|
||||
|
||||
function searchBills() {
|
||||
const codes = parseCodes(billCodeText.value)
|
||||
if (!codes.length) {
|
||||
proxy.$modal.msgWarning('请输入提单号')
|
||||
return
|
||||
}
|
||||
billLoading.value = true
|
||||
listAirRoleBills(codes.join(',')).then(res => {
|
||||
const list = res.data || []
|
||||
bills.value = list
|
||||
selected.value = []
|
||||
const found = new Set(list.map(item => item.billCode))
|
||||
const missing = codes.filter(code => !found.has(code))
|
||||
if (missing.length) proxy.$modal.msgWarning('未找到提单:' + missing.join('、'))
|
||||
}).finally(() => {
|
||||
billLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onBillSelect(list) {
|
||||
selected.value = list || []
|
||||
}
|
||||
|
||||
async function loadFees(force = false) {
|
||||
if (!selected.value.length) {
|
||||
proxy.$modal.msgWarning('请先勾选提单')
|
||||
return
|
||||
}
|
||||
if (force !== true && dirty.value) {
|
||||
try {
|
||||
await proxy.$modal.confirm('查询会覆盖当前未保存的费用,是否继续?')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
feeLoading.value = true
|
||||
const codes = selected.value.map(item => item.billCode).join(',')
|
||||
listAirRoleFees(props.scene, codes).then(res => {
|
||||
deleted.value = []
|
||||
rows.value = (res.data || []).map(item => toRow(item, true))
|
||||
pinSelectedFees()
|
||||
}).finally(() => {
|
||||
feeLoading.value = false
|
||||
})
|
||||
searchSurcharge('')
|
||||
}
|
||||
|
||||
function toRow(item, saved) {
|
||||
const row = {
|
||||
rowKey: saved ? `fee-${item.id}` : `new-${++rowSeed}`,
|
||||
id: saved ? item.id : null,
|
||||
billCode: item.billCode || '',
|
||||
feeType: item.feeType == null ? '' : String(item.feeType),
|
||||
feeName: item.feeName || '',
|
||||
price: item.price === null || item.price === undefined ? null : Number(item.price),
|
||||
currency: item.currency || '',
|
||||
remark: item.remark || '',
|
||||
billFeeStatus: item.billFeeStatus == null || item.billFeeStatus === '' ? '0' : String(item.billFeeStatus)
|
||||
}
|
||||
row.origin = saved ? snapshot(row) : null
|
||||
return row
|
||||
}
|
||||
|
||||
function snapshot(row) {
|
||||
return {
|
||||
feeType: row.feeType || '',
|
||||
price: row.price === null || row.price === undefined || row.price === '' ? '' : String(Number(row.price)),
|
||||
currency: row.currency || '',
|
||||
remark: (row.remark || '').trim()
|
||||
}
|
||||
}
|
||||
|
||||
function currentOf(row) {
|
||||
return {
|
||||
feeType: row.feeType || '',
|
||||
price: row.price === null || row.price === undefined || row.price === '' ? '' : String(Number(row.price)),
|
||||
currency: row.currency || '',
|
||||
remark: (row.remark || '').trim()
|
||||
}
|
||||
}
|
||||
|
||||
function rowChanged(row) {
|
||||
if (!row.origin) return !!(row.feeType || row.price || row.currency || row.remark)
|
||||
const now = currentOf(row)
|
||||
return now.feeType !== row.origin.feeType || now.price !== row.origin.price
|
||||
|| now.currency !== row.origin.currency || now.remark !== row.origin.remark
|
||||
}
|
||||
|
||||
function locked(row) {
|
||||
return row?.billFeeStatus === '1' || row?.billFeeStatus === '3'
|
||||
}
|
||||
|
||||
function addRows() {
|
||||
if (!selected.value.length) {
|
||||
proxy.$modal.msgWarning('请先勾选提单')
|
||||
return
|
||||
}
|
||||
selected.value.forEach(bill => rows.value.push(toRow({ billCode: bill.billCode }, false)))
|
||||
}
|
||||
|
||||
function removeRow(row) {
|
||||
if (row.id != null && !deleted.value.some(item => String(item.id) === String(row.id))) {
|
||||
deleted.value.push({ id: row.id, billCode: row.billCode })
|
||||
}
|
||||
rows.value = rows.value.filter(item => item.rowKey !== row.rowKey)
|
||||
}
|
||||
|
||||
function rowError(row) {
|
||||
if (!row.billCode) return '请选择提单'
|
||||
if (!row.feeType) return '请选择费用名称'
|
||||
if (row.price === null || row.price === undefined || row.price === '') return '请填写价格'
|
||||
if (Number(row.price) < 0) return '价格不能小于0'
|
||||
if (!row.currency) return '请选择币种'
|
||||
return ''
|
||||
}
|
||||
|
||||
function save() {
|
||||
const invalid = rows.value.find(row => rowChanged(row) && rowError(row))
|
||||
if (invalid) {
|
||||
proxy.$modal.msgError(rowError(invalid))
|
||||
return
|
||||
}
|
||||
const bills = new Map()
|
||||
function bucket(code) {
|
||||
if (!bills.has(code)) bills.set(code, { billCode: code, inserts: [], updates: [], deletes: [] })
|
||||
return bills.get(code)
|
||||
}
|
||||
rows.value.forEach(row => {
|
||||
if (!rowChanged(row)) return
|
||||
const payload = {
|
||||
feeType: row.feeType || '',
|
||||
price: row.price,
|
||||
currency: row.currency || '',
|
||||
remark: (row.remark || '').trim()
|
||||
}
|
||||
const bag = bucket(row.billCode)
|
||||
if (row.id) bag.updates.push({ id: row.id, ...payload })
|
||||
else bag.inserts.push(payload)
|
||||
})
|
||||
deleted.value.forEach(item => bucket(item.billCode).deletes.push(item.id))
|
||||
const list = [...bills.values()].filter(item => item.inserts.length || item.updates.length || item.deletes.length)
|
||||
if (!list.length) {
|
||||
proxy.$modal.msgError('没有需要保存的变更')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
saveAirRoleFees({ scene: props.scene, bills: list }).then(res => {
|
||||
proxy.$modal.msgSuccess(res.msg || '保存成功')
|
||||
deleted.value = []
|
||||
loadFees(true)
|
||||
}).finally(() => {
|
||||
saving.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function openImport() {
|
||||
if (!selected.value.length) {
|
||||
proxy.$modal.msgWarning('请先勾选提单')
|
||||
return
|
||||
}
|
||||
importRef.value?.open?.()
|
||||
}
|
||||
|
||||
function downloadTemplate() {
|
||||
return download('/price/airFee/importTemplate', {}, '提单费用导入模板.xlsx')
|
||||
}
|
||||
|
||||
async function submitImport(file) {
|
||||
const raw = file?.raw || file
|
||||
const form = new FormData()
|
||||
form.append('file', raw)
|
||||
form.append('scene', props.scene)
|
||||
const res = await importAirRoleFee(form)
|
||||
const imported = res.data || []
|
||||
if (!imported.length) throw new Error('没有可导入的费用')
|
||||
selected.value.forEach(bill => {
|
||||
imported.forEach(item => rows.value.push(toRow({ ...item, billCode: bill.billCode }, false)))
|
||||
})
|
||||
pinSelectedFees()
|
||||
return res.msg || `已读入 ${imported.length} 条费用,请确认后保存`
|
||||
}
|
||||
|
||||
function searchSurcharge(keyword) {
|
||||
const seq = ++surchargeSeq
|
||||
surchargeLoading.value = true
|
||||
const params = { ssCode: 'on' }
|
||||
const name = String(keyword || '').trim()
|
||||
if (name) params.scName = name
|
||||
listSurchargeOptions(params).then(res => {
|
||||
if (seq !== surchargeSeq) return
|
||||
surchargeOptions.value = (res.data || []).map(item => ({
|
||||
scId: item.scId == null ? '' : String(item.scId),
|
||||
scName: item.scName || ''
|
||||
})).filter(item => item.scId)
|
||||
pinSelectedFees()
|
||||
}).finally(() => {
|
||||
if (seq === surchargeSeq) surchargeLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onFeeChange(row, id) {
|
||||
const found = surchargeOptions.value.find(item => String(item.scId) === String(id || ''))
|
||||
row.feeName = found ? found.scName : ''
|
||||
}
|
||||
|
||||
function pinSelectedFees() {
|
||||
const known = new Map(surchargeOptions.value.map(item => [String(item.scId), item]))
|
||||
rows.value.forEach(row => {
|
||||
if (row.feeType && !known.has(String(row.feeType))) {
|
||||
known.set(String(row.feeType), { scId: String(row.feeType), scName: row.feeName || String(row.feeType) })
|
||||
}
|
||||
})
|
||||
surchargeOptions.value = [...known.values()]
|
||||
}
|
||||
|
||||
searchSurcharge('')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-head, .bill-bar, .fee-toolbar, .fee-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.page-head { margin-bottom: 12px; }
|
||||
.page-title { font-size: 16px; font-weight: 600; }
|
||||
.page-hint { color: var(--el-text-color-secondary); font-size: 12px; }
|
||||
.bill-bar { margin-bottom: 10px; align-items: flex-start; }
|
||||
.bill-bar .el-textarea { max-width: 520px; }
|
||||
.fee-toolbar { margin: 16px 0 10px; }
|
||||
.fee-footer { margin-top: 12px; justify-content: flex-end; }
|
||||
.col-label { display: inline-flex; align-items: flex-start; }
|
||||
.req-star { color: var(--el-color-danger); font-style: normal; font-weight: 700; margin-right: 2px; }
|
||||
.locked-text { color: var(--el-text-color-secondary); font-size: 12px; }
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<air-role-fee scene="declare" />
|
||||
</template>
|
||||
|
||||
<script setup name="DeclareLadingFee">
|
||||
import AirRoleFee from '../components/AirRoleFee.vue'
|
||||
</script>
|
||||
@@ -20,16 +20,6 @@
|
||||
/>
|
||||
<el-table :data="rows" border size="small" max-height="420" empty-text="所选提单没有需要分摊的费用">
|
||||
<el-table-column prop="billCode" label="提单号" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="费用阶段" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bms_fee_level" :value="row.feeLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="费用状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bill_fee_status" :value="row.billFeeStatus || '0'" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="feeName" label="费用项" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="价格" width="110" align="right">
|
||||
<template #default="{ row }">{{ priceText(row.price) }}</template>
|
||||
@@ -40,11 +30,6 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分摊维度" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bill_fee_alloc_dim" :value="row.allocDim" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderCount" label="订单数" width="80" align="center" />
|
||||
</el-table>
|
||||
</div>
|
||||
@@ -61,9 +46,7 @@ import { useDict } from '@/utils/dict'
|
||||
|
||||
const emit = defineEmits(['done'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_fee_level, bill_fee_status, bms_currency, bill_fee_alloc_dim } = useDict(
|
||||
'bms_fee_level', 'bill_fee_status', 'bms_currency', 'bill_fee_alloc_dim'
|
||||
)
|
||||
const { bms_currency } = useDict('bms_currency')
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -108,7 +91,7 @@ function priceText(value) {
|
||||
async function submit() {
|
||||
if (!canSubmit.value) return
|
||||
try {
|
||||
await proxy.$modal.confirm('确认将费用分摊到订单吗?确定后会按重量或订单数量摊到每一票,费用状态改为已分摊。')
|
||||
await proxy.$modal.confirm('确认将费用分摊到订单吗?确定后会按重量摊到每一票,费用状态改为已分摊。')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,38 +3,9 @@
|
||||
<div v-loading="loading">
|
||||
<div class="fee-toolbar">
|
||||
<el-button type="primary" plain icon="Plus" v-hasPermi="['price:airBill:fee']" @click="addRow">新增</el-button>
|
||||
<span class="fee-hint">只保存本次新增、修改和删除的费用,未改动的行不会重写。</span>
|
||||
<span class="fee-hint">费用统一按重量分摊。只保存本次新增、修改和删除的费用。</span>
|
||||
</div>
|
||||
<el-table :data="rows" border size="small" max-height="320" row-key="rowKey" empty-text="暂无费用,点击新增">
|
||||
<el-table-column min-width="180">
|
||||
<template #header><span class="col-label"><i v-if="allocRequired" class="req-star">*</i>费用阶段</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeLevel" filterable clearable placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bms_fee_level" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>是否分摊</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.allocFlag" placeholder="请选择" style="width: 100%" :disabled="locked(row)" @change="changeAlloc(row)">
|
||||
<el-option v-for="item in bill_fee_alloc" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="allocRequired" min-width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>分摊维度(票)</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-if="String(row.allocFlag) === '1'" v-model="row.allocDim" placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bill_fee_alloc_dim" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="费用状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bill_fee_status" :value="row.billFeeStatus || '0'" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="160">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>费用项</span></template>
|
||||
<template #default="{ row }">
|
||||
@@ -130,7 +101,7 @@ import { listSurchargeOptions } from '@/api/price/surcharge'
|
||||
import { parseTime } from '@/utils/bms'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_fee_level, bms_currency, bill_fee_status, bill_fee_alloc, bill_fee_alloc_dim } = useDict('bms_fee_level', 'bms_currency', 'bill_fee_status', 'bill_fee_alloc', 'bill_fee_alloc_dim')
|
||||
const { bms_currency } = useDict('bms_currency')
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -151,7 +122,6 @@ let logSeq = 0
|
||||
let surchargeSeq = 0
|
||||
|
||||
const title = computed(() => billCode.value ? `提单费用 - ${billCode.value}` : '提单费用')
|
||||
const allocRequired = computed(() => rows.value.some(row => String(row.allocFlag) === '1'))
|
||||
const dirty = computed(() => deletedIds.value.length > 0 || rows.value.some(row => rowChanged(row)))
|
||||
|
||||
function open(row) {
|
||||
@@ -307,10 +277,6 @@ function addRow() {
|
||||
})
|
||||
}
|
||||
|
||||
function changeAlloc(row) {
|
||||
if (String(row.allocFlag) !== '1') row.allocDim = ''
|
||||
}
|
||||
|
||||
function locked(row) {
|
||||
return row?.billFeeStatus === '1' || row?.billFeeStatus === '3'
|
||||
}
|
||||
@@ -382,18 +348,14 @@ function normPrice(value) {
|
||||
function rowChanged(row) {
|
||||
if (!row.origin) {
|
||||
const now = currentOf(row)
|
||||
return !!(now.feeType || now.feeLevel || now.allocFlag === '1' || now.allocDim || now.price || now.currency || now.remark)
|
||||
return !!(now.feeType || now.price || now.currency || now.remark)
|
||||
}
|
||||
const now = currentOf(row)
|
||||
return now.feeType !== row.origin.feeType || now.feeLevel !== row.origin.feeLevel || now.allocFlag !== row.origin.allocFlag
|
||||
|| now.allocDim !== row.origin.allocDim || now.price !== row.origin.price
|
||||
return now.feeType !== row.origin.feeType || now.price !== row.origin.price
|
||||
|| now.currency !== row.origin.currency || now.remark !== row.origin.remark
|
||||
}
|
||||
|
||||
function rowError(row) {
|
||||
if (String(row.allocFlag) !== '0' && String(row.allocFlag) !== '1') return '请选择是否分摊'
|
||||
if (String(row.allocFlag) === '1' && !row.feeLevel) return '如需分摊费用至订单,请选择费用所属阶段'
|
||||
if (String(row.allocFlag) === '1' && !row.allocDim) return '如需分摊费用至订单,请选择分摊维度'
|
||||
if (!row.feeType) return '请选择费用项'
|
||||
if (row.price === null || row.price === undefined || row.price === '') return '请填写价格'
|
||||
if (Number(row.price) < 0) return '价格不能小于0'
|
||||
@@ -414,8 +376,6 @@ function save() {
|
||||
const payload = {
|
||||
feeType: row.feeType || '',
|
||||
feeLevel: row.feeLevel || '',
|
||||
allocFlag: row.allocFlag || '',
|
||||
allocDim: String(row.allocFlag) === '1' ? (row.allocDim || '') : '',
|
||||
price: row.price,
|
||||
currency: row.currency || '',
|
||||
remark: (row.remark || '').trim()
|
||||
|
||||
@@ -149,10 +149,10 @@
|
||||
<el-table-column label="费用名称" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ displayFeeName(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="费用类型" width="110" align="center">
|
||||
<el-table-column label="费用类型" min-width="140" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="feeTypeTag(row.feeType)" effect="plain">
|
||||
{{ feeTypeLabel(row.feeType) || row.feeType }}
|
||||
{{ payFeeTypeLabel(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -247,21 +247,18 @@ watch(() => props.detail, () => {
|
||||
function handlePayBill() {
|
||||
const billId = props.detail?.billId
|
||||
if (!billId) return
|
||||
ElMessageBox.confirm('按订单的渠道服务商和已配置的应付产品计算应付?', '生成应付', { type: 'warning' })
|
||||
ElMessageBox.confirm('确认生成应付?', '生成应付', { type: 'warning' })
|
||||
.then(() => generatePayBill(billId))
|
||||
.then(res => {
|
||||
const data = res.data || {}
|
||||
if (data.skipped && data.skipped.length) {
|
||||
ElMessageBox.alert(data.skipped.join('\n'), data.message || '应付已生成', { type: 'warning' })
|
||||
} else {
|
||||
ElMessage.success(data.message || '应付已生成')
|
||||
}
|
||||
.then(() => {
|
||||
ElMessage.success('操作成功')
|
||||
return getBill(billId)
|
||||
})
|
||||
.then(res => {
|
||||
if (res && res.data) emit('updated', res.data)
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch(err => {
|
||||
if (err === 'cancel' || err === 'close') return
|
||||
})
|
||||
}
|
||||
|
||||
function canEditManualFee(row) {
|
||||
@@ -422,6 +419,12 @@ function roundModeLabel(mode) {
|
||||
return map[mode] || mode || '四舍五入'
|
||||
}
|
||||
|
||||
function payFeeTypeLabel(row) {
|
||||
const name = String(row?.feeName || row?.fee_name || '').trim()
|
||||
if (row?.feeType === 'LADING' || row?.feeSource === 'LADING') return name || '提单费用'
|
||||
return feeTypeLabel(row?.feeType) || row?.feeType || '-'
|
||||
}
|
||||
|
||||
function feeTypeLabel(type) {
|
||||
const found = (bms_fee_type.value || []).find(d => d.value === type)
|
||||
return found ? found.label : ''
|
||||
|
||||
Reference in New Issue
Block a user