应付账单初始化

This commit is contained in:
caojia
2026-11-04 16:04:38 +08:00
committed by liuminghui
parent 04be15deab
commit 51a89b18e1
90 changed files with 15661 additions and 6 deletions
@@ -0,0 +1,280 @@
<template>
<div class="app-container">
<basic-table
ref="tableRef"
:columns="columns"
:search-columns="searchColumns"
:query="queryList"
row-key="groupId"
selection
search-action-align="right"
>
<template #toolbar="{ ids, selection }">
<el-col :span="1.5">
<el-button type="primary" plain icon="Upload" @click="open = true" v-hasPermi="['data:payPostcode:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="selection.length !== 1" @click="handleUpdate(selection[0])" v-hasPermi="['data:payPostcode:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="!ids.length" @click="handleDelete(ids)" v-hasPermi="['data:payPostcode:remove']">删除</el-button>
</el-col>
</template>
<template #col-configKind="{ row }">
<el-tag size="small" :type="row.configKind === 'FEE' ? 'warning' : 'success'">{{ row.configKind === 'FEE' ? '附加费' : '邮编分区' }}</el-tag>
</template>
<template #col-coverage="{ row }">
<el-tag v-if="row.coverage" size="small" :type="coverageType(row.coverage)">{{ coverageLabel(row.coverage) }}</el-tag>
</template>
<template #col-action="{ row }">
<el-button link type="primary" @click="openLines(row)" v-hasPermi="['data:payPostcode:query']">邮编</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['data:payPostcode:edit']">修改</el-button>
<el-button link type="danger" @click="handleDelete([row.groupId])" v-hasPermi="['data:payPostcode:remove']">删除</el-button>
</template>
</basic-table>
<el-dialog v-model="open" title="导入应付邮编" width="480px" append-to-body destroy-on-close>
<p class="import-tip">表头里带邮编或附加费的工作表会被导入。目录里的分区表会带上服务类型、国家、销售产品和报价表。可达、可派记为可达,不可达记为不可达,偏远和岛屿记为偏远。渠道名称从标题里识别,例如 Royal Mail、EVRI。邮编末尾的 * 会去掉,同一国家、同一渠道、同一附加费、金额相同只保留一条。</p>
<el-upload :auto-upload="false" :limit="1" accept=".xlsx" :on-change="onFile" :on-remove="() => file = null">
<el-button type="primary" plain>选择 xlsx</el-button>
</el-upload>
<template #footer>
<el-button type="primary" :loading="importing" @click="submitImport">开始导入</el-button>
<el-button @click="open = false">取消</el-button>
</template>
</el-dialog>
<el-dialog title="修改应付邮编" v-model="editOpen" width="560px" append-to-body destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="服务类型" prop="serviceType">
<el-input v-model="form.serviceType" maxlength="100" placeholder="目录中的服务类型" />
</el-form-item>
<el-form-item label="目录国家" prop="catalogCountry">
<el-input v-model="form.catalogCountry" maxlength="100" placeholder="目录中的国家" />
</el-form-item>
<el-form-item label="销售产品" prop="productName">
<el-input v-model="form.productName" maxlength="200" placeholder="目录中的销售产品" />
</el-form-item>
<el-form-item label="报价表" prop="quoteSheet">
<el-input v-model="form.quoteSheet" maxlength="200" placeholder="目录中的报价工作表" />
</el-form-item>
<el-form-item label="渠道名称" prop="channelName">
<el-input v-model="form.channelName" maxlength="100" placeholder="如 Royal Mail、EVRI" />
</el-form-item>
<el-form-item label="类型" prop="configKind">
<el-select v-model="form.configKind" style="width: 100%">
<el-option v-for="item in kindOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="邮编属性" prop="coverage">
<el-select v-model="form.coverage" clearable placeholder="普通分区" style="width: 100%">
<el-option v-for="item in coverageOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="国家" prop="countryName">
<el-input v-model="form.countryName" maxlength="100" />
</el-form-item>
<el-form-item label="国家二字码" prop="countryCode">
<el-input v-model="form.countryCode" maxlength="20" />
</el-form-item>
<el-form-item label="附加费" prop="feeName">
<el-input v-model="form.feeName" maxlength="200" />
</el-form-item>
<el-form-item label="金额原文" prop="feeText">
<el-input v-model="form.feeText" maxlength="200" />
</el-form-item>
<el-form-item label="区块标题" prop="blockTitle">
<el-input v-model="form.blockTitle" maxlength="200" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" maxlength="500" />
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitEdit">确定</el-button>
<el-button @click="editOpen = false">取消</el-button>
</template>
</el-dialog>
<el-drawer v-model="lineOpen" :title="lineTitle" size="72%" append-to-body>
<el-input v-model="lineQuery.postcode" clearable placeholder="筛选邮编" class="line-filter" @change="loadLines" />
<el-table :data="lineRows" border size="small" max-height="640" empty-text="没有邮编">
<el-table-column prop="countryName" label="国家" width="90" show-overflow-tooltip />
<el-table-column prop="regionName" label="地区" min-width="140" show-overflow-tooltip />
<el-table-column prop="regionNameCn" label="地区中文" min-width="120" show-overflow-tooltip />
<el-table-column prop="cityName" label="城市" min-width="120" show-overflow-tooltip />
<el-table-column prop="regionCode" label="地区代码" width="100" show-overflow-tooltip />
<el-table-column prop="zoneName" label="分区" min-width="140" show-overflow-tooltip />
<el-table-column prop="postcode" label="邮编" min-width="120" show-overflow-tooltip />
<el-table-column prop="postcodeRaw" label="邮编原文" min-width="120" show-overflow-tooltip />
<el-table-column prop="postcodeEnd" label="截止邮编" width="120" show-overflow-tooltip />
<el-table-column prop="extraText" label="其余列" min-width="180" show-overflow-tooltip />
</el-table>
<el-pagination
v-if="lineTotal > lineQuery.pageSize"
class="line-page"
layout="total, prev, pager, next"
:total="lineTotal"
:page-size="lineQuery.pageSize"
v-model:current-page="lineQuery.pageNum"
@current-change="loadLines"
/>
</el-drawer>
</div>
</template>
<script setup name="PayPostcode">
import { listPayPostcode, getPayPostcode, updatePayPostcode, listPayPostcodeLine, importPayPostcode, delPayPostcode } from '@/api/data/payPostcode'
const { proxy } = getCurrentInstance()
const tableRef = ref()
const formRef = ref()
const open = ref(false)
const editOpen = ref(false)
const importing = ref(false)
const file = ref(null)
const lineOpen = ref(false)
const lineTitle = ref('邮编')
const lineRows = ref([])
const lineTotal = ref(0)
const lineQuery = reactive({ groupId: undefined, postcode: '', pageNum: 1, pageSize: 20 })
const kindOptions = [
{ label: '附加费', value: 'FEE' },
{ label: '邮编分区', value: 'ZONE' }
]
const coverageOptions = [
{ label: '可达', value: 'REACH' },
{ label: '不可达', value: 'UNREACH' },
{ label: '偏远', value: 'REMOTE' }
]
function coverageLabel(value) {
return coverageOptions.find(item => item.value === value)?.label || ''
}
function coverageType(value) {
if (value === 'UNREACH') return 'danger'
if (value === 'REMOTE') return 'warning'
return 'success'
}
const searchColumns = [
{ label: '销售产品', prop: 'productName', component: 'input', placeholder: '销售产品', width: '140px' },
{ label: '报价表', prop: 'quoteSheet', component: 'input', placeholder: '报价表', width: '160px' },
{ label: '来源文件', prop: 'sourceFile', component: 'input', placeholder: '文件名', width: '180px' },
{ label: '工作表', prop: 'sheetName', component: 'input', placeholder: '工作表', width: '160px' },
{ label: '国家', prop: 'countryName', component: 'input', placeholder: '国家或二字码', width: '140px' },
{ label: '渠道', prop: 'channelName', component: 'input', placeholder: '渠道名称', width: '140px' },
{ label: '类型', prop: 'configKind', component: 'select', options: kindOptions, placeholder: '全部', width: '130px', props: { clearable: true } },
{ label: '邮编属性', prop: 'coverage', component: 'select', options: coverageOptions, placeholder: '全部', width: '130px', props: { clearable: true } },
{ label: '附加费', prop: 'feeName', component: 'input', placeholder: '附加费名称', width: '160px' },
{ label: '邮编', prop: 'postcode', component: 'input', placeholder: '邮编', width: '140px' }
]
const columns = [
{ label: '类型', prop: 'configKind', width: 100, align: 'center', slot: 'configKind' },
{ label: '邮编属性', prop: 'coverage', width: 100, align: 'center', slot: 'coverage' },
{ label: '服务类型', prop: 'serviceType', width: 100, showOverflowTooltip: true },
{ label: '目录国家', prop: 'catalogCountry', width: 100, showOverflowTooltip: true },
{ label: '销售产品', prop: 'productName', minWidth: 120, showOverflowTooltip: true },
{ label: '报价表', prop: 'quoteSheet', minWidth: 140, showOverflowTooltip: true },
{ label: '渠道名称', prop: 'channelName', minWidth: 120, showOverflowTooltip: true },
{ label: '国家', prop: 'countryName', width: 90, showOverflowTooltip: true },
{ label: '工作表', prop: 'sheetName', minWidth: 140, showOverflowTooltip: true },
{ label: '区块', prop: 'blockTitle', minWidth: 180, showOverflowTooltip: true },
{ label: '附加费', prop: 'feeName', minWidth: 140, showOverflowTooltip: true },
{ label: '金额原文', prop: 'feeText', minWidth: 140, showOverflowTooltip: true },
{ label: '邮编行数', prop: 'lineCount', width: 100, align: 'right' },
{ label: '来源文件', prop: 'sourceFile', minWidth: 220, showOverflowTooltip: true },
{ label: '导入时间', prop: 'createTime', width: 170, align: 'center' },
{ label: '操作', prop: 'action', width: 170, align: 'center', fixed: 'right', slot: 'action', showInToolbar: false }
]
const form = ref({})
const rules = {
configKind: [{ required: true, message: '请选择类型', trigger: 'change' }]
}
function queryList(params) {
return listPayPostcode(params)
}
function onFile(uploadFile) {
file.value = uploadFile.raw
}
function submitImport() {
if (!file.value) {
proxy.$modal.msgWarning('请选择 xlsx 文件')
return
}
importing.value = true
importPayPostcode(file.value).then(res => {
proxy.$modal.msgSuccess(res.data?.message || res.msg || '导入完成')
open.value = false
file.value = null
tableRef.value?.reload?.() || tableRef.value?.getList?.()
}).finally(() => {
importing.value = false
})
}
function handleUpdate(row) {
getPayPostcode(row.groupId).then(res => {
form.value = Object.assign({}, res.data || {})
editOpen.value = true
})
}
function submitEdit() {
formRef.value.validate(valid => {
if (!valid) return
updatePayPostcode(form.value).then(() => {
proxy.$modal.msgSuccess('保存成功')
editOpen.value = false
tableRef.value?.reload?.()
})
})
}
function openLines(row) {
lineTitle.value = `${coverageLabel(row.coverage)} ${row.countryName || ''} ${row.feeName || row.blockTitle || row.sheetName}`.trim()
lineQuery.groupId = row.groupId
lineQuery.postcode = ''
lineQuery.pageNum = 1
lineOpen.value = true
loadLines()
}
function loadLines() {
listPayPostcodeLine(lineQuery).then(res => {
lineRows.value = res.rows || []
lineTotal.value = res.total || 0
})
}
function handleDelete(ids) {
proxy.$modal.confirm('确认删除选中的邮编配置吗?邮编明细会一起删除。').then(() => delPayPostcode(ids)).then(() => {
proxy.$modal.msgSuccess('删除成功')
tableRef.value?.reload?.() || tableRef.value?.getList?.()
}).catch(() => {})
}
</script>
<style scoped>
.import-tip {
margin: 0 0 12px;
color: var(--el-text-color-secondary);
line-height: 1.5;
}
.line-filter {
width: 240px;
margin-bottom: 8px;
}
.line-page {
margin-top: 8px;
justify-content: flex-end;
}
</style>
@@ -140,6 +140,14 @@
<el-input v-model="form.remarks" type="textarea" :rows="2" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="应付产品">
<el-select v-model="payOriginId" filterable clearable placeholder="选择已导入的应付产品" style="width: 280px" :disabled="!form.id" @change="onPayOrigin">
<el-option v-for="item in payOptions" :key="item.productId" :label="payOptionLabel(item)" :value="item.productId" />
</el-select>
<div class="pay-tip">{{ payTip }}</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
@@ -147,6 +155,7 @@
<el-button @click="open = false">取 消</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -156,12 +165,22 @@ import {
listServiceChannel, getServiceChannel, addServiceChannel,
updateServiceChannel, delServiceChannel, changeServiceChannelStatus
} from '@/api/masterdata/serviceChannel'
import { payOptions as listPayOptions, getChannelPay, bindChannelPay } from '@/api/price/pay'
const { proxy } = getCurrentInstance()
const tableRef = ref()
const formRef = ref()
const open = ref(false)
const title = ref('')
const payOptions = ref([])
const payOriginId = ref()
const payProduct = ref(null)
const syncingPay = ref(false)
const payTip = computed(() => {
if (!form.value?.id) return '先保存渠道,再选择应付产品。渠道绑定的是产品编号,更新价格不会换编号。'
if (payProduct.value?.versionNo) return `当前按有效期使用第 ${payProduct.value.versionNo} 版。历史订单按下单或计费时间取当时的版本。`
return '选择产品后即绑定,按应付 AP 保存。'
})
const openOptions = [
{ label: '启用', value: '1' },
@@ -233,8 +252,42 @@ function emptyForm() {
}
}
function resetPay() {
syncingPay.value = true
payOriginId.value = undefined
payProduct.value = null
nextTick(() => {
syncingPay.value = false
})
}
function payOptionLabel(item) {
const version = item.versionNo > 1 ? `(当前第 ${item.versionNo} 版)` : ''
return `${item.productName}${version}`
}
function loadPayOptions() {
listPayOptions('AP').then(res => {
payOptions.value = res.data || []
})
}
function loadChannelPay(channelId) {
syncingPay.value = true
getChannelPay(channelId).then(res => {
payProduct.value = res.data || null
payOriginId.value = res.data?.originId || res.data?.productId
}).finally(() => {
nextTick(() => {
syncingPay.value = false
})
})
}
function handleAdd() {
form.value = emptyForm()
resetPay()
loadPayOptions()
title.value = '新增渠道'
open.value = true
nextTick(() => formRef.value && formRef.value.clearValidate())
@@ -243,12 +296,28 @@ function handleAdd() {
function handleUpdate(row) {
getServiceChannel(row.id).then(res => {
form.value = Object.assign(emptyForm(), res.data || {})
resetPay()
loadPayOptions()
loadChannelPay(row.id)
title.value = '修改渠道'
open.value = true
nextTick(() => formRef.value && formRef.value.clearValidate())
})
}
function onPayOrigin(id) {
if (!form.value.id || syncingPay.value) return
if (!id) {
payProduct.value = null
bindChannelPay(form.value.id, null, 'AP')
return
}
bindChannelPay(form.value.id, id, 'AP').then(res => {
payProduct.value = res.data || null
payOriginId.value = res.data?.originId || id
})
}
function submitForm() {
formRef.value.validate(valid => {
if (!valid) return
@@ -302,3 +371,12 @@ function handleExport() {
}, `service_channel_${new Date().getTime()}.xlsx`)
}
</script>
<style scoped>
.pay-tip {
margin-top: 6px;
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 1.5;
}
</style>