修改公共组件
This commit is contained in:
@@ -34,4 +34,6 @@ public class BmsProductCountry implements Serializable
|
||||
private Date updatedAt;
|
||||
/** query only: true = expire_date is null */
|
||||
private Boolean openOnly;
|
||||
/** query only: ACTIVE已生效 / PENDING待生效 / EXPIRED已失效(按生效/失效日期与当前日期派生) */
|
||||
private String effectStatus;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="expireDate" column="expire_date" />
|
||||
<result property="createdAt" column="created_at" />
|
||||
<result property="updatedAt" column="updated_at" />
|
||||
<result property="effectStatus" column="effect_status" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 派生状态:已失效(过期) > 待生效(未到生效日) > 已生效 -->
|
||||
<sql id="effectStatusCase">
|
||||
case
|
||||
when expire_date is not null and expire_date <= curdate() then 'EXPIRED'
|
||||
when effective_date is null or effective_date > curdate() then 'PENDING'
|
||||
else 'ACTIVE'
|
||||
end
|
||||
</sql>
|
||||
|
||||
<sql id="selectVo">
|
||||
select detail_id, product_code, country_name, country_code, weight_min, weight_max,
|
||||
freight_price, registration_fee, size_limit, last_mile, lead_time,
|
||||
effective_date, expire_date, created_at, updated_at
|
||||
effective_date, expire_date, created_at, updated_at,
|
||||
<include refid="effectStatusCase"/> as effect_status
|
||||
from bms_product_country
|
||||
</sql>
|
||||
|
||||
@@ -53,6 +64,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="openOnly != null and openOnly">
|
||||
AND expire_date is null
|
||||
</if>
|
||||
<if test="effectStatus != null and effectStatus != ''">
|
||||
AND (<include refid="effectStatusCase"/>) = #{effectStatus}
|
||||
</if>
|
||||
</where>
|
||||
order by effective_date desc, country_name asc, weight_min asc, detail_id asc
|
||||
</select>
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
class="simple-table"
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
:row-key="rowKey"
|
||||
:border="border"
|
||||
:stripe="stripe"
|
||||
:size="size"
|
||||
:height="height"
|
||||
:max-height="maxHeight"
|
||||
:span-method="spanMethod"
|
||||
:empty-text="emptyText"
|
||||
:highlight-current-row="highlightCurrentRow"
|
||||
@selection-change="(val) => $emit('selection-change', val)"
|
||||
@row-click="(row, col, e) => $emit('row-click', row, col, e)"
|
||||
>
|
||||
<el-table-column v-if="selection" type="selection" width="55" align="center" />
|
||||
<el-table-column v-if="showIndex" type="index" :label="indexLabel" width="55" align="center" />
|
||||
|
||||
<el-table-column
|
||||
v-for="col in columns"
|
||||
:key="col.key || col.prop || col.label"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:align="col.align"
|
||||
:fixed="col.fixed"
|
||||
:sortable="col.sortable"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip"
|
||||
>
|
||||
<template #header="scope">
|
||||
<slot :name="`header-${col.prop}`" v-bind="scope">{{ col.label }}</slot>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<slot :name="`col-${col.slot || col.prop}`" v-bind="scope" :row="scope.row">
|
||||
<dict-tag
|
||||
v-if="col.dict"
|
||||
:options="getDictOptions(col.dict)"
|
||||
:value="scope.row[col.prop]"
|
||||
/>
|
||||
<span v-else-if="col.formatter" :class="col.className">{{ col.formatter(scope.row, col, scope.row[col.prop], scope.$index) }}</span>
|
||||
<span v-else :class="col.className">{{ scope.row[col.prop] }}</span>
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* SimpleTable ¡ª local-data table (no search / toolbar / pagination).
|
||||
* Column config aligns with BasicTable: prop, label, width, minWidth, align, fixed,
|
||||
* sortable, showOverflowTooltip, dict, formatter, slot, className.
|
||||
*
|
||||
* <simple-table :data="list" :columns="columns" :loading="loading" border>
|
||||
* <template #col-action="{ row }">...</template>
|
||||
* </simple-table>
|
||||
*/
|
||||
defineOptions({ name: 'SimpleTable' })
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Array, default: () => [] },
|
||||
columns: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
rowKey: { type: [String, Function], default: undefined },
|
||||
border: { type: Boolean, default: false },
|
||||
stripe: { type: Boolean, default: false },
|
||||
size: { type: String, default: undefined },
|
||||
height: { type: [String, Number], default: undefined },
|
||||
maxHeight: { type: [String, Number], default: undefined },
|
||||
spanMethod: { type: Function, default: null },
|
||||
emptyText: { type: String, default: undefined },
|
||||
selection: { type: Boolean, default: false },
|
||||
showIndex: { type: Boolean, default: false },
|
||||
indexLabel: { type: String, default: '#' },
|
||||
highlightCurrentRow: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
defineEmits(['selection-change', 'row-click'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const tableRef = ref()
|
||||
const dictMap = reactive({})
|
||||
|
||||
const dictTypes = computed(() => {
|
||||
const set = new Set()
|
||||
props.columns.forEach((c) => c.dict && set.add(c.dict))
|
||||
return [...set]
|
||||
})
|
||||
|
||||
watch(
|
||||
dictTypes,
|
||||
(types) => {
|
||||
if (!types.length) return
|
||||
const dictRefs = proxy.useDict(...types)
|
||||
types.forEach((t) => {
|
||||
const d = dictRefs[t]
|
||||
dictMap[t] = isRef(d) ? d : ref(d)
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function getDictOptions(type) {
|
||||
const d = dictMap[type]
|
||||
return isRef(d) ? d.value : d || []
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getTableRef: () => tableRef.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.simple-table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -47,6 +47,7 @@ import DictTag from '@/components/DictTag'
|
||||
import BasicForm from '@/components/BasicForm'
|
||||
// 公共表格组件
|
||||
import BasicTable from '@/components/BasicTable'
|
||||
import SimpleTable from '@/components/SimpleTable'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
@@ -71,6 +72,7 @@ app.component('RightToolbar', RightToolbar)
|
||||
app.component('Editor', Editor)
|
||||
app.component('BasicForm', BasicForm)
|
||||
app.component('BasicTable', BasicTable)
|
||||
app.component('SimpleTable', SimpleTable)
|
||||
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="920px" :title="title" append-to-body>
|
||||
<div class="mb8" style="display: flex; align-items: center; gap: 12px">
|
||||
<el-input v-model="keyword" placeholder="搜索国家(中文名 / ISO2)" clearable style="width: 260px" />
|
||||
<!-- <el-select v-model="status" placeholder="状态(全部)" clearable style="width: 160px">
|
||||
<el-option v-for="d in bms_product_effect_status" :key="d.value" :label="d.label" :value="d.value" />
|
||||
</el-select> -->
|
||||
</div>
|
||||
<simple-table
|
||||
:data="filteredList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
border
|
||||
max-height="calc(100vh - 120px)"
|
||||
empty-text="暂无国家明细"
|
||||
:span-method="spanMethod"
|
||||
>
|
||||
<template #col-sizeLimit="{ row }">
|
||||
<span class="pre-wrap">{{ row.sizeLimit }}</span>
|
||||
</template>
|
||||
<template #col-lastMile="{ row }">
|
||||
<span class="pre-wrap">{{ row.lastMile }}</span>
|
||||
</template>
|
||||
</simple-table>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SimpleTable from '@/components/SimpleTable'
|
||||
import { listProductCountries } from '@/api/price/product'
|
||||
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
|
||||
const columns = [
|
||||
{ prop: 'countryName', label: '国家', width: 90, showOverflowTooltip: true },
|
||||
{ prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
|
||||
{
|
||||
prop: 'weightMin',
|
||||
label: '重量段(KG)',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
formatter: (row) => (row.weightMin != null || row.weightMax != null ? (row.weightMin + ' ~ ' + row.weightMax) : '-')
|
||||
},
|
||||
{ prop: 'freightPrice', label: '运费(RMB/KG)', width: 110, align: 'right' },
|
||||
{ prop: 'registrationFee', label: '挂号费(RMB/票)', width: 120, align: 'right' },
|
||||
{ prop: 'sizeLimit', label: '尺寸限制', minWidth: 140 },
|
||||
{ prop: 'lastMile', label: '尾程', minWidth: 110 },
|
||||
{ prop: 'leadTime', label: '时效', width: 100, showOverflowTooltip: true },
|
||||
{ prop: 'effectiveDate', label: '生效日期', width: 110, align: 'center' },
|
||||
{ prop: 'expireDate', label: '失效日期', width: 110, align: 'center', formatter: (row) => row.expireDate || '长期' },
|
||||
{ prop: 'effectStatus', label: '状态', width: 90, align: 'center', dict: 'bms_product_effect_status' }
|
||||
]
|
||||
|
||||
const visible = ref(false)
|
||||
const title = ref('')
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const keyword = ref('')
|
||||
const status = ref('')
|
||||
|
||||
const filteredList = computed(() => {
|
||||
const kw = keyword.value.trim().toLowerCase()
|
||||
const st = status.value
|
||||
return list.value.filter(r => {
|
||||
if (kw && !(String(r.countryName || '').toLowerCase().includes(kw) ||
|
||||
String(r.countryCode || '').toLowerCase().includes(kw))) return false
|
||||
if (st && r.effectStatus !== st) return false
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
function open(row) {
|
||||
title.value = row.productCode + ' \u00b7 ' + row.productName + ' \u00b7 \u56fd\u5bb6\u670d\u52a1\u660e\u7ec6'
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
list.value = []
|
||||
keyword.value = ''
|
||||
status.value = ''
|
||||
listProductCountries(row.productCode).then(res => {
|
||||
list.value = (res.data || []).slice().sort((a, b) => {
|
||||
return String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function spanMethod({ column, rowIndex }) {
|
||||
if (column.property !== 'countryName' && column.property !== 'countryCode') return
|
||||
const rows = filteredList.value
|
||||
if (!rows.length) return
|
||||
const cur = rows[rowIndex]?.countryCode
|
||||
if (rowIndex > 0 && rows[rowIndex - 1]?.countryCode === cur) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
}
|
||||
let count = 1
|
||||
while (rowIndex + count < rows.length && rows[rowIndex + count]?.countryCode === cur) count++
|
||||
return { rowspan: count, colspan: 1 }
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
.pre-wrap { white-space: pre-wrap; font-size: 12px; line-height: 1.4; }
|
||||
</style>
|
||||
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<el-dialog :title="title" v-model="visible" width="640px" append-to-body>
|
||||
<basic-form ref="formRef" v-model="form" :schemas="schemas" label-width="100px" />
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import BasicForm from '@/components/BasicForm'
|
||||
import { getProduct, addProduct, updateProduct } from '@/api/price/product'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const formRef = ref()
|
||||
const visible = ref(false)
|
||||
const title = ref('')
|
||||
const form = ref(emptyForm())
|
||||
|
||||
const schemas = computed(() => [
|
||||
{
|
||||
label: '产品代码',
|
||||
prop: 'productCode',
|
||||
component: 'input',
|
||||
placeholder: '如 WSP037',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
disabled: !!form.value.productId,
|
||||
props: { maxlength: 20 },
|
||||
rules: [{ required: true, message: '产品代码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
{
|
||||
label: '产品名称',
|
||||
prop: 'productName',
|
||||
component: 'input',
|
||||
placeholder: '产品名称',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 100 },
|
||||
rules: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
{
|
||||
label: '时效等级',
|
||||
prop: 'serviceLevel',
|
||||
component: 'input',
|
||||
placeholder: '如 标准',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 20 }
|
||||
},
|
||||
{
|
||||
label: '走货属性',
|
||||
prop: 'cargoAttr',
|
||||
component: 'input',
|
||||
placeholder: '普货/带电等',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 100 }
|
||||
},
|
||||
{
|
||||
label: '目的区域',
|
||||
prop: 'destRegion',
|
||||
component: 'input',
|
||||
placeholder: '如 欧盟',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
props: { maxlength: 50 }
|
||||
},
|
||||
{
|
||||
label: '税模式',
|
||||
prop: 'taxMode',
|
||||
component: 'select',
|
||||
placeholder: '请选择',
|
||||
dict: 'bms_tax_mode',
|
||||
span: 12,
|
||||
width: '100%',
|
||||
rules: [{ required: true, message: '请选择税模式', trigger: 'change' }]
|
||||
},
|
||||
{
|
||||
label: '生效日期',
|
||||
prop: 'effectiveDate',
|
||||
component: 'date',
|
||||
placeholder: '选择日期',
|
||||
span: 12,
|
||||
width: '100%'
|
||||
},
|
||||
{
|
||||
label: '更新内容',
|
||||
prop: 'updateNote',
|
||||
component: 'textarea',
|
||||
span: 24,
|
||||
width: '100%',
|
||||
props: { rows: 2, maxlength: 500 }
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
component: 'textarea',
|
||||
span: 24,
|
||||
width: '100%',
|
||||
props: { rows: 2, maxlength: 500 }
|
||||
}
|
||||
])
|
||||
|
||||
function emptyForm() {
|
||||
return {
|
||||
productId: undefined,
|
||||
productCode: undefined,
|
||||
productName: undefined,
|
||||
serviceLevel: undefined,
|
||||
cargoAttr: undefined,
|
||||
destRegion: undefined,
|
||||
updateNote: undefined,
|
||||
taxMode: 'TAX_INCLUDED',
|
||||
effectiveDate: undefined,
|
||||
status: '0',
|
||||
remark: undefined
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
form.value = emptyForm()
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function open(row) {
|
||||
reset()
|
||||
if (!row) {
|
||||
title.value = '新建产品'
|
||||
visible.value = true
|
||||
return
|
||||
}
|
||||
getProduct(row.productId).then(res => {
|
||||
form.value = { ...emptyForm(), ...(res.data || {}) }
|
||||
title.value = '修改产品'
|
||||
visible.value = true
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
})
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
formRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
const req = form.value.productId ? updateProduct(form.value) : addProduct(form.value)
|
||||
req.then(() => {
|
||||
proxy.$modal.msgSuccess('操作成功')
|
||||
visible.value = false
|
||||
emit('success')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="920px" :title="title" append-to-body>
|
||||
<div class="mb8" style="display: flex; justify-content: space-between">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleRelAdd" v-hasPermi="['price:surcharge:add']">新增关联</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="list" border max-height="calc(100vh - 160px)" empty-text="该产品暂无关联附加费">
|
||||
<el-table-column label="附加费名称" prop="scName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="计费方式" width="110" align="center">
|
||||
<template #default="{ row }">{{ chargeTypeLabel(row.chargeType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计费数值" width="140" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="charge-value">{{ formatChargeValue(row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="取值方式" width="100" align="center">
|
||||
<template #default="{ row }">{{ roundModeLabel(row.roundMode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适用国家" width="110" align="center">
|
||||
<template #default="{ row }">{{ countryLabel(row.countryCode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="作用范围" width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.detailId == null" type="primary" size="small" effect="plain">产品级 · 全部明细</el-tag>
|
||||
<el-tag v-else type="warning" size="small" effect="plain">{{ row.countryName || '-' }} · {{ row.weightMin }}-{{ row.weightMax }}kg</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="String(row.ssCode).toLowerCase() === 'on' ? 'success' : 'info'" size="small">
|
||||
{{ String(row.ssCode).toLowerCase() === 'on' ? '有效' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="scNote" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" icon="Delete" @click="handleRelDelete(row.relId)" v-hasPermi="['price:surcharge:remove']">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<surcharge-rel-dialog ref="relRef" @success="handleRelSuccess" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { listSurchargeByProduct, delSurchargeRel } from '@/api/price/surcharge'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
import SurchargeRelDialog from './SurchargeRelDialog.vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_round_mode, bms_charge_type } = useDict('bms_round_mode', 'bms_charge_type')
|
||||
|
||||
const relRef = ref()
|
||||
const visible = ref(false)
|
||||
const title = ref('')
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const product = ref(null)
|
||||
const regionOptions = ref([])
|
||||
|
||||
function open(row) {
|
||||
product.value = row
|
||||
title.value = row.productCode + ' · ' + row.productName + ' · 附加费关联'
|
||||
visible.value = true
|
||||
loadList()
|
||||
}
|
||||
|
||||
function loadList() {
|
||||
if (!product.value) return
|
||||
loading.value = true
|
||||
listSurchargeByProduct(product.value.productCode).then(res => {
|
||||
list.value = res.data || []
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleRelAdd() {
|
||||
relRef.value.open(product.value)
|
||||
}
|
||||
|
||||
function handleRelSuccess() {
|
||||
loadList()
|
||||
emit('success')
|
||||
}
|
||||
|
||||
function handleRelDelete(relId) {
|
||||
proxy.$modal.confirm('是否确认解绑该附加费?').then(() => {
|
||||
return delSurchargeRel(relId)
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess('解绑成功')
|
||||
loadList()
|
||||
emit('success')
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function chargeTypeLabel(val) {
|
||||
const found = bms_charge_type.value.find(c => c.value === val)
|
||||
return found ? found.label : (val || '-')
|
||||
}
|
||||
|
||||
function roundModeLabel(val) {
|
||||
const found = bms_round_mode.value.find(d => d.value === val)
|
||||
return found ? found.label : (val || '-')
|
||||
}
|
||||
|
||||
function formatChargeValue(row) {
|
||||
if (row.chargeValue == null) return '-'
|
||||
const v = Number(row.chargeValue)
|
||||
if (row.chargeType === 'RATE') return (v * 100).toFixed(2) + '%'
|
||||
return v + ' ' + (row.currency || 'CNY')
|
||||
}
|
||||
|
||||
function countryLabel(code) {
|
||||
if (!code) return '全部'
|
||||
const found = regionOptions.value.find(r => r.countryCode2 === code)
|
||||
return found ? (found.countryNameCn || found.countryNameEn || code) : code
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
listRegionOptions().then(res => {
|
||||
regionOptions.value = res.data || []
|
||||
})
|
||||
})
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
.charge-value { font-weight: 600; color: #d63031; }
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<el-dialog :title="title" v-model="visible" width="560px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="附加费" prop="scId">
|
||||
<el-select v-model="form.scId" placeholder="请选择附加费" filterable style="width: 100%">
|
||||
<el-option v-for="s in surchargePool" :key="s.scId" :label="s.scName" :value="s.scId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="作用范围" prop="scope">
|
||||
<el-radio-group v-model="form.scope">
|
||||
<el-radio value="PRODUCT">产品级(全部明细)</el-radio>
|
||||
<el-radio value="DETAIL">明细级(指定国家×重量段)</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.scope === 'DETAIL'" label="明细" prop="detailId">
|
||||
<el-select v-model="form.detailId" placeholder="请选择明细" filterable style="width: 100%">
|
||||
<el-option v-for="d in countryList" :key="d.detailId" :label="detailLabel(d)" :value="d.detailId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submit">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listProductCountries } from '@/api/price/product'
|
||||
import { listSurcharge, addSurchargeRel } from '@/api/price/surcharge'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const formRef = ref()
|
||||
const visible = ref(false)
|
||||
const title = ref('新增附加费关联')
|
||||
const product = ref(null)
|
||||
const surchargePool = ref([])
|
||||
const countryList = ref([])
|
||||
const form = ref({})
|
||||
const rules = {
|
||||
scId: [{ required: true, message: '请选择附加费', trigger: 'change' }]
|
||||
}
|
||||
|
||||
function detailLabel(d) {
|
||||
const w = d.weightMin != null && d.weightMax != null ? (d.weightMin + '-' + d.weightMax + 'kg') : '-'
|
||||
return (d.countryName || d.countryCode || '-') + ' · ' + w
|
||||
}
|
||||
|
||||
function reset() {
|
||||
form.value = {
|
||||
scId: undefined,
|
||||
scope: 'PRODUCT',
|
||||
detailId: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
function open(row) {
|
||||
product.value = row
|
||||
reset()
|
||||
visible.value = true
|
||||
listSurcharge({ ssCode: 'on', pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
surchargePool.value = res.rows || []
|
||||
})
|
||||
if (row && row.productCode) {
|
||||
listProductCountries(row.productCode).then(res => {
|
||||
countryList.value = res.data || []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
formRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
if (form.value.scope === 'DETAIL' && !form.value.detailId) {
|
||||
proxy.$modal.msgError('请选择明细')
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
scId: form.value.scId,
|
||||
productId: product.value ? product.value.productId : undefined,
|
||||
productCode: product.value ? product.value.productCode : undefined,
|
||||
detailId: form.value.scope === 'DETAIL' ? form.value.detailId : null
|
||||
}
|
||||
addSurchargeRel(payload).then(() => {
|
||||
proxy.$modal.msgSuccess('关联成功')
|
||||
visible.value = false
|
||||
emit('success')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
@@ -39,186 +39,16 @@
|
||||
</template>
|
||||
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="View" @click="openCountries(row)" v-hasPermi="['price:product:query']">产品明细</el-button>
|
||||
<el-button link type="primary" icon="Money" @click="openSurcharges(row)" v-hasPermi="['price:surcharge:query']">附加费</el-button>
|
||||
<el-button link type="primary" icon="View" @click="countryRef.open(row)" v-hasPermi="['price:product:query']">产品明细</el-button>
|
||||
<el-button link type="primary" icon="Money" @click="surchargeRef.open(row)" v-hasPermi="['price:surcharge:query']">附加费</el-button>
|
||||
<el-button :disabled="isProductActive(row)" :title="isProductActive(row) ? '已生效不可编辑' : ''" link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:product:edit']">编辑</el-button>
|
||||
<el-button :disabled="isProductActive(row)" :title="isProductActive(row) ? '已生效不可删除' : ''" link type="primary" icon="Delete" @click="handleDelete(row.productId)" v-hasPermi="['price:product:remove']">删除</el-button>
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
<el-dialog :title="title" v-model="open" width="640px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产品代码" prop="productCode">
|
||||
<el-input v-model="form.productCode" placeholder="如 WSP037" maxlength="20" :disabled="!!form.productId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产品名称" prop="productName">
|
||||
<el-input v-model="form.productName" placeholder="产品名称" maxlength="100" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="时效等级" prop="serviceLevel">
|
||||
<el-input v-model="form.serviceLevel" placeholder="如 标准" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="走货属性" prop="cargoAttr">
|
||||
<el-input v-model="form.cargoAttr" placeholder="普货/带电等" maxlength="100" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="目的区域" prop="destRegion">
|
||||
<el-input v-model="form.destRegion" placeholder="如 欧盟" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="税模式" prop="taxMode">
|
||||
<el-select v-model="form.taxMode" placeholder="请选择" style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in bms_tax_mode"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生效日期" prop="effectiveDate">
|
||||
<el-date-picker v-model="form.effectiveDate" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio value="0">启用(按生效日期自动:待生效/已生效)</el-radio>
|
||||
<el-radio value="1">已失效</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="更新内容" prop="updateNote">
|
||||
<el-input v-model="form.updateNote" type="textarea" :rows="2" maxlength="500" />
|
||||
</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="submitForm">确 定</el-button>
|
||||
<el-button @click="open = false">取 消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-drawer v-model="countryOpen" size="920px" :title="countryTitle" append-to-body>
|
||||
<el-input v-model="countryKeyword" placeholder="搜索国家(中文名 / ISO2)" clearable class="mb8" style="width: 260px" />
|
||||
<el-table v-loading="countryLoading" :data="filteredCountryList" border :span-method="countrySpanMethod" max-height="calc(100vh - 120px)" empty-text="暂无国家明细">
|
||||
<el-table-column label="国家" prop="countryName" width="90" show-overflow-tooltip />
|
||||
<el-table-column label="ISO2" prop="countryCode" width="60" align="center" />
|
||||
<el-table-column label="重量段(KG)" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.weightMin != null || row.weightMax != null">{{ row.weightMin }} ~ {{ row.weightMax }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运费(RMB/KG)" prop="freightPrice" width="110" align="right" />
|
||||
<el-table-column label="挂号费(RMB/票)" prop="registrationFee" width="120" align="right" />
|
||||
<el-table-column label="尺寸限制" prop="sizeLimit" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<span class="pre-wrap">{{ row.sizeLimit }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="尾程" prop="lastMile" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<span class="pre-wrap">{{ row.lastMile }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时效" prop="leadTime" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="生效日期" prop="effectiveDate" width="110" align="center" />
|
||||
<el-table-column label="失效日期" width="110" align="center">
|
||||
<template #default="{ row }">{{ row.expireDate || '长期' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer v-model="surchargeOpen" size="920px" :title="surchargeTitle" append-to-body>
|
||||
<div class="mb8" style="display: flex; justify-content: space-between">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleRelAdd" v-hasPermi="['price:surcharge:add']">新增关联</el-button>
|
||||
</div>
|
||||
<el-table v-loading="surchargeLoading" :data="surchargeList" border max-height="calc(100vh - 160px)" empty-text="该产品暂无关联附加费">
|
||||
<el-table-column label="附加费名称" prop="scName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="计费方式" width="110" align="center">
|
||||
<template #default="{ row }">{{ chargeTypeLabel(row.chargeType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计费数值" width="140" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="charge-value">{{ formatChargeValue(row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="取值方式" width="100" align="center">
|
||||
<template #default="{ row }">{{ roundModeLabel(row.roundMode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适用国家" width="110" align="center">
|
||||
<template #default="{ row }">{{ countryLabel(row.countryCode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="作用范围" width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.detailId == null" type="primary" size="small" effect="plain">产品级 · 全部明细</el-tag>
|
||||
<el-tag v-else type="warning" size="small" effect="plain">{{ row.countryName || '-' }} · {{ row.weightMin }}-{{ row.weightMax }}kg</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="String(row.ssCode).toLowerCase() === 'on' ? 'success' : 'info'" size="small">
|
||||
{{ String(row.ssCode).toLowerCase() === 'on' ? '有效' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="scNote" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" icon="Delete" @click="handleRelDelete(row.relId)" v-hasPermi="['price:surcharge:remove']">解绑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
|
||||
<el-dialog :title="relTitle" v-model="relOpen" width="560px" append-to-body>
|
||||
<el-form ref="relFormRef" :model="relForm" :rules="relRules" label-width="100px">
|
||||
<el-form-item label="附加费" prop="scId">
|
||||
<el-select v-model="relForm.scId" placeholder="请选择附加费" filterable style="width: 100%">
|
||||
<el-option v-for="s in surchargePool" :key="s.scId" :label="s.scName" :value="s.scId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="作用范围" prop="scope">
|
||||
<el-radio-group v-model="relForm.scope">
|
||||
<el-radio value="PRODUCT">产品级(全部明细)</el-radio>
|
||||
<el-radio value="DETAIL">明细级(指定国家×重量段)</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="relForm.scope === 'DETAIL'" label="明细" prop="detailId">
|
||||
<el-select v-model="relForm.detailId" placeholder="请选择明细" filterable style="width: 100%">
|
||||
<el-option v-for="d in countryList" :key="d.detailId" :label="detailLabel(d)" :value="d.detailId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitRel">确 定</el-button>
|
||||
<el-button @click="relOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<product-form-dialog ref="formRef" @success="() => tableRef.refresh()" />
|
||||
<product-country-drawer ref="countryRef" />
|
||||
<product-surcharge-drawer ref="surchargeRef" @success="() => tableRef.refresh()" />
|
||||
<excel-import-dialog
|
||||
ref="importRef"
|
||||
title="产品 Excel 导入"
|
||||
@@ -234,43 +64,19 @@
|
||||
|
||||
<script setup name="PriceProduct">
|
||||
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
||||
import { listProduct, getProduct, addProduct, updateProduct, delProduct, listProductCountries } from '@/api/price/product'
|
||||
import { listSurchargeByProduct, listSurcharge, addSurchargeRel, delSurchargeRel } from '@/api/price/surcharge'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
import { onMounted } from 'vue'
|
||||
import ProductFormDialog from './components/ProductFormDialog.vue'
|
||||
import ProductCountryDrawer from './components/ProductCountryDrawer.vue'
|
||||
import ProductSurchargeDrawer from './components/ProductSurchargeDrawer.vue'
|
||||
import { listProduct, delProduct } from '@/api/price/product'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_product_effect_status, bms_tax_mode, bms_round_mode, bms_charge_type } = useDict('bms_product_effect_status', 'bms_tax_mode', 'bms_round_mode', 'bms_charge_type')
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
|
||||
const tableRef = ref()
|
||||
const importRef = ref()
|
||||
const formRef = ref()
|
||||
const open = ref(false)
|
||||
const title = ref('')
|
||||
const countryOpen = ref(false)
|
||||
const countryTitle = ref('')
|
||||
const countryLoading = ref(false)
|
||||
const countryList = ref([])
|
||||
const countryKeyword = ref('')
|
||||
|
||||
const surchargeOpen = ref(false)
|
||||
const surchargeTitle = ref('')
|
||||
const surchargeLoading = ref(false)
|
||||
const surchargeList = ref([])
|
||||
const currentProduct = ref(null)
|
||||
const regionOptions = ref([])
|
||||
|
||||
const surchargePool = ref([])
|
||||
const relOpen = ref(false)
|
||||
const relTitle = ref('')
|
||||
const relFormRef = ref()
|
||||
const filteredCountryList = computed(() => {
|
||||
const kw = countryKeyword.value.trim().toLowerCase()
|
||||
if (!kw) return countryList.value
|
||||
return countryList.value.filter(r =>
|
||||
String(r.countryName || '').toLowerCase().includes(kw) ||
|
||||
String(r.countryCode || '').toLowerCase().includes(kw)
|
||||
)
|
||||
})
|
||||
const countryRef = ref()
|
||||
const surchargeRef = ref()
|
||||
const importRef = ref()
|
||||
|
||||
const columns = [
|
||||
{ prop: 'productCode', label: '产品代码', minWidth: 110 },
|
||||
@@ -308,16 +114,6 @@ const searchColumns = [
|
||||
}
|
||||
]
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
rules: {
|
||||
productCode: [{ required: true, message: '产品代码不能为空', trigger: 'blur' }],
|
||||
productName: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
|
||||
taxMode: [{ required: true, message: '请选择税模式', trigger: 'change' }]
|
||||
}
|
||||
})
|
||||
const { form, rules } = toRefs(data)
|
||||
|
||||
function queryList(params) {
|
||||
return listProduct(params)
|
||||
}
|
||||
@@ -334,32 +130,12 @@ function resolveEffectStatus(row) {
|
||||
return String(row.effectiveDate).slice(0, 10) <= todayStr() ? 'ACTIVE' : 'PENDING'
|
||||
}
|
||||
|
||||
// 已生效不可编辑/删除
|
||||
function isProductActive(row) {
|
||||
return resolveEffectStatus(row) === 'ACTIVE'
|
||||
}
|
||||
|
||||
function reset() {
|
||||
form.value = {
|
||||
productId: undefined,
|
||||
productCode: undefined,
|
||||
productName: undefined,
|
||||
serviceLevel: undefined,
|
||||
cargoAttr: undefined,
|
||||
destRegion: undefined,
|
||||
updateNote: undefined,
|
||||
taxMode: 'TAX_INCLUDED',
|
||||
effectiveDate: undefined,
|
||||
status: '0',
|
||||
remark: undefined
|
||||
}
|
||||
proxy.resetForm('formRef')
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
reset()
|
||||
open.value = true
|
||||
title.value = '新建产品'
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
@@ -367,24 +143,7 @@ function handleUpdate(row) {
|
||||
proxy.$modal.msgError('已生效的产品不可编辑')
|
||||
return
|
||||
}
|
||||
reset()
|
||||
getProduct(row.productId).then(res => {
|
||||
form.value = res.data
|
||||
open.value = true
|
||||
title.value = '修改产品'
|
||||
})
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
proxy.$refs['formRef'].validate(valid => {
|
||||
if (!valid) return
|
||||
const req = form.value.productId ? updateProduct(form.value) : addProduct(form.value)
|
||||
req.then(() => {
|
||||
proxy.$modal.msgSuccess('操作成功')
|
||||
open.value = false
|
||||
tableRef.value.refresh()
|
||||
})
|
||||
})
|
||||
formRef.value.open(row)
|
||||
}
|
||||
|
||||
function handleDelete(ids) {
|
||||
@@ -400,155 +159,8 @@ function handleDelete(ids) {
|
||||
function handleImport() {
|
||||
importRef.value.open()
|
||||
}
|
||||
|
||||
function openCountries(row) {
|
||||
countryTitle.value = `${row.productCode} · ${row.productName} · 国家服务明细`
|
||||
countryOpen.value = true
|
||||
countryLoading.value = true
|
||||
countryList.value = []
|
||||
countryKeyword.value = ''
|
||||
listProductCountries(row.productCode).then(res => {
|
||||
// 按 ISO2 排序,保证相同国家相邻,便于合并单元格
|
||||
const list = (res.data || []).slice().sort((a, b) => {
|
||||
return String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
|
||||
})
|
||||
countryList.value = list
|
||||
}).finally(() => {
|
||||
countryLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗表格合并:相同 countryCode 的连续行合并「国家」「ISO2」列
|
||||
function countrySpanMethod({ column, rowIndex }) {
|
||||
if (column.property !== 'countryName' && column.property !== 'countryCode') return
|
||||
const list = filteredCountryList.value
|
||||
if (!list.length) return
|
||||
const cur = list[rowIndex]?.countryCode
|
||||
if (rowIndex > 0 && list[rowIndex - 1]?.countryCode === cur) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
}
|
||||
let count = 1
|
||||
while (rowIndex + count < list.length && list[rowIndex + count]?.countryCode === cur) count++
|
||||
return { rowspan: count, colspan: 1 }
|
||||
}
|
||||
|
||||
// ===== 产品附加费关联 =====
|
||||
const relData = reactive({
|
||||
relForm: {},
|
||||
relRules: {
|
||||
scId: [{ required: true, message: '请选择附加费', trigger: 'change' }]
|
||||
}
|
||||
})
|
||||
const { relForm, relRules } = toRefs(relData)
|
||||
|
||||
function openSurcharges(row) {
|
||||
currentProduct.value = row
|
||||
surchargeTitle.value = `${row.productCode} · ${row.productName} · 附加费关联`
|
||||
surchargeOpen.value = true
|
||||
loadSurcharges()
|
||||
}
|
||||
|
||||
function loadSurcharges() {
|
||||
if (!currentProduct.value) return
|
||||
surchargeLoading.value = true
|
||||
listSurchargeByProduct(currentProduct.value.productCode).then(res => {
|
||||
surchargeList.value = res.data || []
|
||||
}).finally(() => {
|
||||
surchargeLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function loadSurchargePool() {
|
||||
return listSurcharge({ ssCode: 'on', pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
surchargePool.value = res.rows || []
|
||||
})
|
||||
}
|
||||
|
||||
function resetRelForm() {
|
||||
relForm.value = {
|
||||
scId: undefined,
|
||||
scope: 'PRODUCT',
|
||||
detailId: undefined
|
||||
}
|
||||
relFormRef.value && relFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
function handleRelAdd() {
|
||||
resetRelForm()
|
||||
relTitle.value = '新增附加费关联'
|
||||
relOpen.value = true
|
||||
loadSurchargePool()
|
||||
}
|
||||
|
||||
function submitRel() {
|
||||
relFormRef.value.validate(valid => {
|
||||
if (!valid) return
|
||||
if (relForm.value.scope === 'DETAIL' && !relForm.value.detailId) {
|
||||
proxy.$modal.msgError('请选择明细')
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
scId: relForm.value.scId,
|
||||
productId: currentProduct.value ? currentProduct.value.productId : undefined,
|
||||
productCode: currentProduct.value ? currentProduct.value.productCode : undefined,
|
||||
detailId: relForm.value.scope === 'DETAIL' ? relForm.value.detailId : null
|
||||
}
|
||||
addSurchargeRel(payload).then(() => {
|
||||
proxy.$modal.msgSuccess('关联成功')
|
||||
relOpen.value = false
|
||||
loadSurcharges()
|
||||
tableRef.value.refresh()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleRelDelete(relId) {
|
||||
proxy.$modal.confirm('是否确认解绑该附加费?').then(() => {
|
||||
return delSurchargeRel(relId)
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess('解绑成功')
|
||||
loadSurcharges()
|
||||
tableRef.value.refresh()
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function detailLabel(d) {
|
||||
const w = d.weightMin != null && d.weightMax != null ? `${d.weightMin}-${d.weightMax}kg` : '-'
|
||||
return `${d.countryName || d.countryCode || '-'} · ${w}`
|
||||
}
|
||||
|
||||
function chargeTypeLabel(val) {
|
||||
const found = bms_charge_type.value.find(c => c.value === val)
|
||||
return found ? found.label : (val || '-')
|
||||
}
|
||||
|
||||
function roundModeLabel(val) {
|
||||
const found = bms_round_mode.value.find(d => d.value === val)
|
||||
return found ? found.label : (val || '-')
|
||||
}
|
||||
|
||||
function formatChargeValue(row) {
|
||||
if (row.chargeValue == null) return '-'
|
||||
const v = Number(row.chargeValue)
|
||||
if (row.chargeType === 'RATE') return (v * 100).toFixed(2) + '%'
|
||||
return v + ' ' + (row.currency || 'CNY')
|
||||
}
|
||||
|
||||
function countryLabel(code) {
|
||||
if (!code) return '全部'
|
||||
const found = regionOptions.value.find(r => r.countryCode2 === code)
|
||||
return found ? (found.countryNameCn || found.countryNameEn || code) : code
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
listRegionOptions().then(res => {
|
||||
regionOptions.value = res.data || []
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb16 { margin-bottom: 16px; }
|
||||
.pre-wrap { white-space: pre-wrap; font-size: 12px; line-height: 1.4; }
|
||||
.charge-value { font-weight: 600; color: #d63031; }
|
||||
</style>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<el-empty v-if="!viewResult" description="暂无详情" />
|
||||
<div v-else class="quote-result">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="试算ID">{{ viewResult.quoteId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户ID">{{ viewResult.customerId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品">{{ viewResult.productName || '-' }} ({{ viewResult.productCode }})</el-descriptions-item>
|
||||
<el-descriptions-item label="国家">{{ viewResult.countryName || '-' }} ({{ viewResult.countryCode }})</el-descriptions-item>
|
||||
@@ -240,7 +239,6 @@ const historyLoading = ref(false)
|
||||
const viewResult = ref(null)
|
||||
|
||||
const historyColumns = [
|
||||
{ label: '试算ID', prop: 'quoteId', align: 'center', width: 90 },
|
||||
{ label: '客户ID', prop: 'customerId', align: 'center', width: 90 },
|
||||
{ label: '产品', prop: 'product', minWidth: 160, slot: 'product' },
|
||||
{ label: '国家', prop: 'country', minWidth: 130, slot: 'country' },
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<el-step title="基本信息" />
|
||||
<el-step title="套餐明细" />
|
||||
<el-step title="绑定客户" />
|
||||
<el-step title="校验确认" />
|
||||
</el-steps>
|
||||
|
||||
<!-- 第 1 步 -->
|
||||
@@ -256,29 +255,10 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 第 4 步 -->
|
||||
<el-card v-show="active === 3" shadow="never" class="step-card">
|
||||
<template #header>
|
||||
<div class="card-head">
|
||||
<span>第 4 步 · 校验确认</span>
|
||||
<el-button type="primary" size="small" @click="runValidate">重新校验</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="checkItems" border empty-text="点击「下一步」或「重新校验」执行校验">
|
||||
<el-table-column label="校验项" prop="item" min-width="200" />
|
||||
<el-table-column label="结果" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="checkTag(row.result)" size="small">{{ checkLabel(row.result) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="说明" prop="message" min-width="280" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<div class="wizard-actions">
|
||||
<el-button @click="goBack">取消</el-button>
|
||||
<el-button v-if="active > 0" @click="active--">上一步</el-button>
|
||||
<el-button v-if="active < 3" type="primary" @click="nextStep">下一步</el-button>
|
||||
<el-button v-if="active < 2" type="primary" @click="nextStep">下一步</el-button>
|
||||
<el-button type="info" :loading="saving" @click="saveDraft">保存草稿</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="saveAndSubmit">保存并提交审核</el-button>
|
||||
</div>
|
||||
@@ -286,7 +266,7 @@
|
||||
</template>
|
||||
|
||||
<script setup name="PriceSetCreate">
|
||||
import { getPriceSet, savePriceSet, validatePriceSet } from '@/api/price/set'
|
||||
import { getPriceSet, savePriceSet } from '@/api/price/set'
|
||||
import { listProduct, listProductCountries } from '@/api/price/product'
|
||||
import { listSurchargeByProduct } from '@/api/price/surcharge'
|
||||
|
||||
@@ -296,7 +276,6 @@ const route = useRoute()
|
||||
const active = ref(0)
|
||||
const saving = ref(false)
|
||||
const formRef = ref()
|
||||
const checkItems = ref([])
|
||||
const productOptions = ref([])
|
||||
/** productCode -> bms_product_country rows */
|
||||
const countryCache = reactive({})
|
||||
@@ -571,6 +550,8 @@ function buildPayload(submitFlag) {
|
||||
...c,
|
||||
customerId: Number(c.customerId)
|
||||
}))
|
||||
console.log('details', details)
|
||||
|
||||
return {
|
||||
priceSetId: form.priceSetId,
|
||||
priceSetName: form.priceSetName,
|
||||
@@ -592,20 +573,9 @@ function nextStep() {
|
||||
})
|
||||
return
|
||||
}
|
||||
if (active.value === 2) {
|
||||
active.value++
|
||||
runValidate()
|
||||
return
|
||||
}
|
||||
active.value++
|
||||
}
|
||||
|
||||
function runValidate() {
|
||||
validatePriceSet(buildPayload(false)).then(res => {
|
||||
checkItems.value = res.data || []
|
||||
})
|
||||
}
|
||||
|
||||
function saveDraft() {
|
||||
formRef.value.validate(valid => {
|
||||
if (!valid) {
|
||||
@@ -646,16 +616,6 @@ function goBack() {
|
||||
}
|
||||
}
|
||||
|
||||
function checkTag(result) {
|
||||
const map = { PASS: 'success', WARN: 'warning', FAIL: 'danger', INFO: 'primary' }
|
||||
return map[result] || 'info'
|
||||
}
|
||||
|
||||
function checkLabel(result) {
|
||||
const map = { PASS: '通过', WARN: '待确认', FAIL: '失败', INFO: '提示' }
|
||||
return map[result] || result
|
||||
}
|
||||
|
||||
function loadExisting(id) {
|
||||
getPriceSet(id).then(res => {
|
||||
const data = res.data || {}
|
||||
|
||||
Reference in New Issue
Block a user