266 lines
8.1 KiB
Vue
266 lines
8.1 KiB
Vue
<template>
|
|
<el-drawer
|
|
:model-value="modelValue"
|
|
:title="title"
|
|
size="860px"
|
|
append-to-body
|
|
destroy-on-close
|
|
@update:model-value="$emit('update:modelValue', $event)"
|
|
>
|
|
<div class="hist-wrap" v-loading="loading">
|
|
<el-table :data="rows" border height="calc(100vh - 220px)" :empty-text="emptyText">
|
|
<el-table-column :label="colTime" width="170" align="center" prop="updateTime" />
|
|
<el-table-column :label="colAction" width="100" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag size="small" :type="actionTag(row)">{{ actionLabel(row) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="colStatus" width="90" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag size="small" :type="statusTag(row.status)">{{ statusLabel(row.status) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="colSet" min-width="150" show-overflow-tooltip>
|
|
<template #default="{ row }">{{ priceSetOf(row) }}</template>
|
|
</el-table-column>
|
|
<el-table-column :label="colVer" width="90" align="center">
|
|
<template #default="{ row }">{{ versionOf(row) }}</template>
|
|
</el-table-column>
|
|
<el-table-column :label="colPsd" width="100" align="center">
|
|
<template #default="{ row }">{{ psdOf(row) }}</template>
|
|
</el-table-column>
|
|
<el-table-column :label="colAmount" width="110" align="right">
|
|
<template #default="{ row }">{{ amountOf(row) }}</template>
|
|
</el-table-column>
|
|
<el-table-column :label="colOps" width="80" align="center" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button link type="primary" @click="openDetail(row)">查看</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<div class="pager">
|
|
<el-pagination
|
|
v-model:current-page="query.pageNum"
|
|
v-model:page-size="query.pageSize"
|
|
:total="total"
|
|
:page-sizes="[10, 20, 50]"
|
|
layout="total, sizes, prev, pager, next"
|
|
@size-change="reload"
|
|
@current-change="reload"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<el-drawer v-model="detailOpen" size="720px" :title="detailTitle" append-to-body destroy-on-close>
|
|
<div v-if="!active">
|
|
<el-empty :description="emptyDetail" />
|
|
</div>
|
|
<div v-else>
|
|
<el-descriptions :column="2" border size="small" class="mb12">
|
|
<el-descriptions-item :label="colTime">{{ active.updateTime || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item :label="colAction">{{ actionLabel(active) }}</el-descriptions-item>
|
|
<el-descriptions-item :label="colStatus">{{ statusLabel(active.status) }}</el-descriptions-item>
|
|
<el-descriptions-item :label="colSet">{{ priceSetOf(active) }}</el-descriptions-item>
|
|
<el-descriptions-item :label="colVer">{{ versionOf(active) }}</el-descriptions-item>
|
|
<el-descriptions-item :label="colPsd">{{ psdOf(active) }}</el-descriptions-item>
|
|
<el-descriptions-item :label="colAmount">{{ amountOf(active) }}</el-descriptions-item>
|
|
<el-descriptions-item :label="lbMatch">{{ matchMsgOf(active) }}</el-descriptions-item>
|
|
<el-descriptions-item v-if="active.errorMessage" :label="lbError" :span="2">
|
|
<span class="err">{{ active.errorMessage }}</span>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
<div class="json-panel">
|
|
<div class="json-panel__head">请求报文</div>
|
|
<pre class="code-pre">{{ formatJson(active.requestJson) }}</pre>
|
|
</div>
|
|
<div class="json-panel">
|
|
<div class="json-panel__head">返回报文</div>
|
|
<pre class="code-pre">{{ formatJson(active.responseJson) }}</pre>
|
|
</div>
|
|
</div>
|
|
</el-drawer>
|
|
</el-drawer>
|
|
</template>
|
|
|
|
<script setup name="BillBillingHistoryDrawer">
|
|
import { listBillHistory } from '@/api/price/billing'
|
|
|
|
const props = defineProps({
|
|
modelValue: { type: Boolean, default: false },
|
|
billId: { type: String, default: '' },
|
|
orderNo: { type: String, default: '' }
|
|
})
|
|
defineEmits(['update:modelValue'])
|
|
|
|
const loading = ref(false)
|
|
const rows = ref([])
|
|
const total = ref(0)
|
|
const query = reactive({ pageNum: 1, pageSize: 10 })
|
|
const detailOpen = ref(false)
|
|
const active = ref(null)
|
|
|
|
const emptyText = '暂无历史计费记录'
|
|
const emptyDetail = '暂无详情'
|
|
const colTime = '写入时间'
|
|
const colAction = '类型'
|
|
const colStatus = '状态'
|
|
const colSet = '套餐'
|
|
const colVer = '版本'
|
|
const colPsd = '明细ID'
|
|
const colAmount = '应收总额'
|
|
const colOps = '操作'
|
|
const lbMatch = '命中说明'
|
|
const lbError = '错误信息'
|
|
|
|
const title = computed(() => {
|
|
const no = props.orderNo || ''
|
|
return no ? '历史计费记录 · ' + no : '历史计费记录'
|
|
})
|
|
const detailTitle = computed(() => '计费记录详情')
|
|
|
|
function parseJson(val) {
|
|
if (val == null || val === '') return {}
|
|
if (typeof val === 'object') return val
|
|
try { return JSON.parse(val) } catch (e) { return {} }
|
|
}
|
|
|
|
function respOf(row) {
|
|
return parseJson(row && row.responseJson)
|
|
}
|
|
|
|
function reqOf(row) {
|
|
return parseJson(row && row.requestJson)
|
|
}
|
|
|
|
function actionLabel(row) {
|
|
const req = reqOf(row)
|
|
if (req.action === 'UPSTREAM_SYNC') return '上游同步'
|
|
if (row && row.status === 'FAIL') return '计费失败'
|
|
const resp = respOf(row)
|
|
if (resp.matchSource === 'BILLING_FAIL' || resp.billingFailStatus) return '计费失败'
|
|
return '计费'
|
|
}
|
|
|
|
function actionTag(row) {
|
|
const label = actionLabel(row)
|
|
if (label === '上游同步') return 'info'
|
|
if (label === '计费失败') return 'danger'
|
|
return 'success'
|
|
}
|
|
|
|
function statusLabel(status) {
|
|
return status === 'FAIL' ? '失败' : '成功'
|
|
}
|
|
|
|
function statusTag(status) {
|
|
return status === 'FAIL' ? 'danger' : 'success'
|
|
}
|
|
|
|
function priceSetOf(row) {
|
|
const r = respOf(row)
|
|
if (r.priceSetName && r.priceSetCode) return r.priceSetName + ' (' + r.priceSetCode + ')'
|
|
return r.priceSetName || r.priceSetCode || (r.priceSetId != null ? String(r.priceSetId) : '-')
|
|
}
|
|
|
|
function versionOf(row) {
|
|
const r = respOf(row)
|
|
if (r.versionNo) return r.versionNo
|
|
return r.psvId != null ? ('psv=' + r.psvId) : '-'
|
|
}
|
|
|
|
function psdOf(row) {
|
|
const r = respOf(row)
|
|
return r.psdId != null ? String(r.psdId) : '-'
|
|
}
|
|
|
|
function amountOf(row) {
|
|
const r = respOf(row)
|
|
const v = r.incomeTotal != null ? r.incomeTotal : r.payableTotal
|
|
if (v == null || v === '') return '-'
|
|
const n = Number(v)
|
|
return Number.isNaN(n) ? String(v) : n.toFixed(2)
|
|
}
|
|
|
|
function matchMsgOf(row) {
|
|
return respOf(row).matchMessage || '-'
|
|
}
|
|
|
|
function formatJson(val) {
|
|
if (val == null || val === '') return '无数据'
|
|
if (typeof val === 'object') {
|
|
try { return JSON.stringify(val, null, 2) } catch (e) { return String(val) }
|
|
}
|
|
try { return JSON.stringify(JSON.parse(val), null, 2) } catch (e) { return String(val) }
|
|
}
|
|
|
|
function openDetail(row) {
|
|
active.value = row
|
|
detailOpen.value = true
|
|
}
|
|
|
|
async function reload() {
|
|
if (!props.billId) {
|
|
rows.value = []
|
|
total.value = 0
|
|
return
|
|
}
|
|
loading.value = true
|
|
try {
|
|
const res = await listBillHistory(props.billId, {
|
|
pageNum: query.pageNum,
|
|
pageSize: query.pageSize
|
|
})
|
|
rows.value = res.rows || []
|
|
total.value = Number(res.total) || 0
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => [props.modelValue, props.billId],
|
|
([open]) => {
|
|
if (open) {
|
|
query.pageNum = 1
|
|
detailOpen.value = false
|
|
active.value = null
|
|
reload()
|
|
}
|
|
}
|
|
)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.hist-wrap { min-height: 200px; }
|
|
.pager {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
margin-top: 12px;
|
|
}
|
|
.mb12 { margin-bottom: 12px; }
|
|
.err { color: #f56c6c; word-break: break-all; }
|
|
.json-panel {
|
|
margin-bottom: 12px;
|
|
background: #f7f9fb;
|
|
border: 1px solid #e8ecf0;
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
}
|
|
.json-panel__head {
|
|
padding: 8px 12px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #606266;
|
|
background: #fafbfc;
|
|
border-bottom: 1px solid #ebeef5;
|
|
}
|
|
.code-pre {
|
|
margin: 0;
|
|
padding: 12px 14px;
|
|
max-height: 260px;
|
|
overflow: auto;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
</style>
|