新增订单计费失败原因的价格周期字段,并优化前端展示,支持查看价格周期详情。

This commit is contained in:
wuweihua
2026-11-11 13:12:32 +08:00
parent 2430e344a0
commit 0545de88b4
6 changed files with 439 additions and 28 deletions
@@ -0,0 +1,165 @@
<template>
<el-drawer
:model-value="modelValue"
:title="drawerTitle"
size="720px"
append-to-body
destroy-on-close
@close="close"
>
<div v-loading="loading" class="cycle-body">
<el-empty v-if="!loading && !versions.length" :description="emptyText" :image-size="72" />
<el-table v-else :data="versions" border height="calc(100vh - 220px)">
<el-table-column :label="colVer" prop="versionNo" width="90" align="center" />
<el-table-column :label="colEff" min-width="170">
<template #default="{ row }">{{ row.effectiveTime || '-' }}</template>
</el-table-column>
<el-table-column :label="colExp" min-width="170">
<template #default="{ row }">{{ row.expireTime || '-' }}</template>
</el-table-column>
<el-table-column :label="colStatus" width="100" align="center">
<template #default="{ row }">
<el-tag :type="versionStatusTag(row.status)" size="small">{{ versionStatusLabel(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="colRows" width="90" align="center">
<template #default="{ row }">{{ row.detailCount != null ? row.detailCount : '-' }}</template>
</el-table-column>
<el-table-column :label="colAction" width="100" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openDetail(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</div>
<template #footer>
<el-button @click="close">关闭</el-button>
</template>
<set-version-detail-drawer
v-model="detailVisible"
:version="detailVersion"
:set-type="setType || 'FIXED'"
/>
</el-drawer>
</template>
<script setup name="OrderBillPriceCycleListDrawer">
import { priceSetApi } from '@/api/price/set'
import { exclusiveQuoteApi } from '@/api/price/exclusive'
import { versionStatusLabel, versionStatusTag } from '../../set/utils/priceSet'
import SetVersionDetailDrawer from '../../set/components/detail/SetVersionDetailDrawer.vue'
const props = defineProps({
modelValue: { type: Boolean, default: false },
priceSetId: { type: [Number, String], default: null },
priceSetCode: { type: String, default: '' },
priceSetName: { type: String, default: '' },
setType: { type: String, default: 'FIXED' }
})
const emit = defineEmits(['update:modelValue'])
const loading = ref(false)
const versions = ref([])
const detailVisible = ref(false)
const detailVersion = ref(null)
const emptyText = '暂无价格周期'
const colVer = '版本号'
const colEff = '生效时间'
const colExp = '失效时间'
const colStatus = '状态'
const colRows = '明细行数'
const colAction = '操作'
const drawerTitle = computed(() => {
const code = (props.priceSetCode || '').trim()
const name = (props.priceSetName || '').trim()
let pkg = '-'
if (code && name && code !== name) pkg = code + ' / ' + name
else if (code || name) pkg = code || name
return '价格周期列表 · ' + pkg
})
function close() {
emit('update:modelValue', false)
}
function toEffMs(v) {
if (!v || !v.effectiveTime) return NaN
const t = new Date(String(v.effectiveTime).replace(/-/g, '/')).getTime()
return Number.isNaN(t) ? NaN : t
}
function statusRank(v) {
const s = String(v && v.status != null ? v.status : '')
if (s === '0') return 0
if (s === '3') return 1
if (s === '1') return 2
if (s === '2') return 3
return 9
}
function sortVersions(list) {
return (list || []).slice().sort((a, b) => {
const ra = statusRank(a)
const rb = statusRank(b)
if (ra !== rb) return ra - rb
const ta = toEffMs(a)
const tb = toEffMs(b)
const aNull = Number.isNaN(ta)
const bNull = Number.isNaN(tb)
if (aNull && bNull) return (Number(b.versionSeq) || 0) - (Number(a.versionSeq) || 0)
if (aNull) return 1
if (bNull) return -1
if (tb !== ta) return tb - ta
return (Number(b.versionSeq) || 0) - (Number(a.versionSeq) || 0)
})
}
async function loadVersions() {
versions.value = []
if (!props.priceSetId) return
loading.value = true
try {
let data = null
try {
const res = await priceSetApi.getPriceSet(props.priceSetId)
data = (res && res.data) || null
} catch (e1) {
const res2 = await exclusiveQuoteApi.getPriceSet(props.priceSetId)
data = (res2 && res2.data) || null
}
const list = (data && data.versions) || []
versions.value = sortVersions(list.filter(v => v && String(v.isDel) !== '1'))
} catch (e) {
versions.value = []
} finally {
loading.value = false
}
}
function openDetail(row) {
if (!row || row.psvId == null) return
detailVersion.value = {
psvId: row.psvId,
versionNo: row.versionNo,
effectiveTime: row.effectiveTime || null,
expireTime: row.expireTime || null
}
detailVisible.value = true
}
watch(
() => [props.modelValue, props.priceSetId],
([vis]) => {
if (vis) loadVersions()
}
)
</script>
<style scoped>
.cycle-body {
min-height: 240px;
}
</style>
+88 -3
View File
@@ -342,10 +342,28 @@
<span :class="frValueClass(failReason.destPostcode || '-')">{{ failReason.destPostcode || '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label-class-name="fr-label-key" class-name="fr-cell-key" label="套餐">
<span :class="frValueClass(joinCodeName(failReason.priceSetCode, failReason.priceSetName), true)">{{ joinCodeName(failReason.priceSetCode, failReason.priceSetName) }}</span>
<div class="fr-cycle-row">
<span :class="frValueClass(joinCodeName(failReason.priceSetCode, failReason.priceSetName), true)">{{ joinCodeName(failReason.priceSetCode, failReason.priceSetName) }}</span>
<el-button
v-if="failReason.priceSetId"
link
type="primary"
class="fr-cycle-btn"
@click="openPackagePriceCycles"
>查看</el-button>
</div>
</el-descriptions-item>
<el-descriptions-item label="套餐版本">
<span :class="frValueClass(failReason.versionNo || '-')">{{ failReason.versionNo || '-' }}</span>
<el-descriptions-item label-class-name="fr-label-key" class-name="fr-cell-key" label="价格周期">
<div class="fr-cycle-row">
<span :class="frValueClass(formatPriceCycle(failReason), true)">{{ formatPriceCycle(failReason) }}</span>
<el-button
v-if="failReason.psvId"
link
type="primary"
class="fr-cycle-btn"
@click="openPriceCycleDetail"
>查看</el-button>
</div>
</el-descriptions-item>
<el-descriptions-item label="重量段">
<span :class="frValueClass(failReason.weightLabel || formatWeightRange(failReason.weightMin, failReason.weightMax))">{{ failReason.weightLabel || formatWeightRange(failReason.weightMin, failReason.weightMax) }}</span>
@@ -380,6 +398,20 @@
<el-button type="primary" @click="failReasonVisible = false">关闭</el-button>
</template>
</el-dialog>
<set-version-detail-drawer
v-model="priceCycleDrawerVisible"
:version="priceCycleVersion"
:set-type="failReason.setType || 'FIXED'"
/>
<order-bill-price-cycle-list-drawer
v-model="packageCycleDrawerVisible"
:price-set-id="failReason.priceSetId"
:price-set-code="failReason.priceSetCode"
:price-set-name="failReason.priceSetName"
:set-type="failReason.setType || 'FIXED'"
/>
</div>
</template>
@@ -391,6 +423,8 @@ import ExcelExportFieldDialog from '@/components/ExcelExportFieldDialog/index.vu
import { ORDER_BILL_EXPORT_FIELDS } from '@/utils/exportFieldDefs'
import OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue'
import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue'
import SetVersionDetailDrawer from '../set/components/detail/SetVersionDetailDrawer.vue'
import OrderBillPriceCycleListDrawer from './components/OrderBillPriceCycleListDrawer.vue'
const tableRef = ref()
const exportFieldRef = ref()
@@ -1171,9 +1205,14 @@ const failReason = reactive({
zoneCode: '',
zoneName: '',
destPostcode: '',
priceSetId: null,
priceSetCode: '',
priceSetName: '',
setType: '',
psvId: null,
versionNo: '',
effectiveTime: null,
expireTime: null,
weightLabel: '',
weightMin: null,
weightMax: null,
@@ -1225,6 +1264,37 @@ function formatDateTime(val) {
' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
}
function formatPriceCycle(row) {
const r = row || {}
const eff = formatDateTime(r.effectiveTime)
const exp = formatDateTime(r.expireTime)
if (eff === '-' && exp === '-') {
return r.versionNo || '-'
}
return eff + ' ~ ' + exp
}
const priceCycleDrawerVisible = ref(false)
const priceCycleVersion = ref(null)
function openPriceCycleDetail() {
if (!failReason.psvId) return
priceCycleVersion.value = {
psvId: failReason.psvId,
versionNo: failReason.versionNo,
effectiveTime: failReason.effectiveTime ? formatDateTime(failReason.effectiveTime) : null,
expireTime: failReason.expireTime ? formatDateTime(failReason.expireTime) : null
}
priceCycleDrawerVisible.value = true
}
const packageCycleDrawerVisible = ref(false)
function openPackagePriceCycles() {
if (!failReason.priceSetId) return
packageCycleDrawerVisible.value = true
}
function applyFailReason(data, row) {
const src = data || {}
const base = row || {}
@@ -1241,9 +1311,14 @@ function applyFailReason(data, row) {
failReason.zoneCode = src.zoneCode || ''
failReason.zoneName = src.zoneName || ''
failReason.destPostcode = src.destPostcode || base.destPostcode || ''
failReason.priceSetId = src.priceSetId != null ? src.priceSetId : (base.priceSetId != null ? base.priceSetId : null)
failReason.priceSetCode = src.priceSetCode || base.priceSetCode || ''
failReason.priceSetName = src.priceSetName || base.priceSetName || ''
failReason.setType = src.setType || base.setType || ''
failReason.psvId = src.psvId != null ? src.psvId : (base.psvId != null ? base.psvId : null)
failReason.versionNo = src.versionNo || ''
failReason.effectiveTime = src.effectiveTime || null
failReason.expireTime = src.expireTime || null
failReason.weightLabel = src.weightLabel || base.weightLabel || ''
failReason.weightMin = src.weightMin != null ? src.weightMin : base.weightMin
failReason.weightMax = src.weightMax != null ? src.weightMax : base.weightMax
@@ -1487,6 +1562,16 @@ onBeforeUnmount(() => {
color: #0f172a;
font-weight: 500;
}
.fr-cycle-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.fr-cycle-btn {
flex-shrink: 0;
padding: 0;
}
.fr-val-key {
color: #c2410c;
font-weight: 700;
@@ -3,6 +3,7 @@
:model-value="modelValue"
:title="title"
size="72%"
append-to-body
destroy-on-close
@close="close"
>