Browse Source

feat: add read-only traffic ledger tab

Allow admins to filter and page platform/user traffic ledger entries.
main
xiaosi 3 weeks ago
parent
commit
16dee9441a
  1. 106
      src/views/TrafficOpsView/TrafficOpsView.vue

106
src/views/TrafficOpsView/TrafficOpsView.vue

@ -141,7 +141,53 @@
</div>
</div>
<div v-show="tab === 'ledger'">账务流水占位</div>
<div v-show="tab === 'ledger'" class="ledger-panel">
<div class="asset-toolbar">
<div class="toolbar-group">
<t-select
v-model="ledgerAccountType"
:options="ledgerAccountTypeOptions"
placeholder="全部账户"
style="width: 140px"
@change="reloadLedger"
/>
</div>
<div class="asset-search">
<t-input
v-model="ledgerAccountId"
clearable
placeholder="按账户ID筛选"
@keyup.enter="reloadLedger"
@clear="reloadLedger"
>
<template #prefix-icon><t-icon name="search" /></template>
</t-input>
</div>
<div class="page-actions">
<t-button shape="square" variant="outline" title="刷新" @click="loadLedger">
<svg><use href="#i-refresh" /></svg>
</t-button>
</div>
</div>
<div class="asset-table-wrap">
<t-table
row-key="id"
:data="ledgerList"
:columns="ledgerColumns"
empty="暂无流水"
/>
</div>
<div class="table-footer">
<t-pagination
v-model:current="ledgerPageNum"
:total="ledgerTotal"
:page-size="ledgerPageSize"
@current-change="loadLedger"
/>
</div>
</div>
</div>
<t-dialog
@ -612,10 +658,65 @@ async function submitPurchase() {
}
}
const ledgerList = ref([])
const ledgerTotal = ref(0)
const ledgerPageNum = ref(1)
const ledgerPageSize = ref(10)
const ledgerAccountType = ref('')
const ledgerAccountId = ref('')
const ledgerAccountTypeOptions = [
{ label: '全部账户', value: '' },
{ label: '用户', value: 'user' },
{ label: '平台', value: 'platform' }
]
const ledgerColumns = [
{ colKey: 'createdAt', title: '时间', width: '14%' },
{ colKey: 'accountType', title: '账户类型', width: '8%' },
{ colKey: 'accountId', title: '账户ID', width: '10%' },
{ colKey: 'direction', title: '方向', width: '7%' },
{ colKey: 'amountText', title: '金额', width: '12%' },
{ colKey: 'balanceText', title: '余额前→后', width: '14%' },
{ colKey: 'sourceType', title: '来源', width: '10%' },
{ colKey: 'sourceId', title: '来源ID', width: '10%', ellipsis: true },
{ colKey: 'idempotencyKey', title: '幂等键', width: '12%', ellipsis: true }
]
async function loadLedger() {
try {
const params = { pageNum: ledgerPageNum.value, pageSize: ledgerPageSize.value }
if (ledgerAccountType.value) params.accountType = ledgerAccountType.value
if (ledgerAccountId.value) params.accountId = Number(ledgerAccountId.value)
const page = await request.get(urls.ADMIN_TRAFFIC_LEDGER, { params })
ledgerTotal.value = page?.total || 0
ledgerList.value = (page?.records || []).map((r) => ({
id: String(r.id),
createdAt: fmtTime(r.createdAt),
accountType: r.accountType === 'user' ? '用户' : (r.accountType === 'platform' ? '平台' : (r.accountType || '—')),
accountId: String(r.accountId),
direction: r.direction === 'credit' ? '入账' : (r.direction === 'debit' ? '扣减' : r.direction),
amountText: `${bytesToGbText(r.amountBytes)} (${r.amountBytes})`,
balanceText: `${bytesToGbText(r.balanceBefore)}${bytesToGbText(r.balanceAfter)}`,
sourceType: r.sourceType || '—',
sourceId: r.sourceId || '—',
idempotencyKey: r.idempotencyKey || '—'
}))
} catch (e) {
ui.toast(e.message || '加载流水失败')
}
}
function reloadLedger() {
ledgerPageNum.value = 1
loadLedger()
}
watch(tab, (v) => {
if (v === 'packages') loadPackages()
if (v === 'purchases') loadPurchasesTab()
if (v === 'ledger') loadLedger()
})
onMounted(() => {
@ -625,7 +726,8 @@ onMounted(() => {
<style scoped>
.packages-panel,
.purchases-panel {
.purchases-panel,
.ledger-panel {
flex: 1;
display: flex;
flex-direction: column;

Loading…
Cancel
Save