积分与余额账务系统硬切重构
This commit is contained in:
1 parent
ca12226b39
commit
5cae5b5c93
159 files changed
+7275
-4718
No files matched your search
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<h2 class=”aksrt-page-title”>积分配置</h2>
|
||||
<p class=”aksrt-page-desc”>
|
||||
配置积分规则与发放策略。可自定义货币名称(如”金币”、”魔石”等)。用户余额与手工调整已迁移到 WordPress 原生”用户”菜单中管理。
|
||||
<h2 class="aksrt-page-title">积分配置</h2>
|
||||
<p class="aksrt-page-desc">
|
||||
配置积分规则与发放策略。可自定义货币名称(如”金币”、”魔石”等)。用户余额与手工调整在 WordPress 原生”用户”菜单中管理。
|
||||
</p>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
@@ -23,6 +23,35 @@
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card title="统一资产账本" class="aksrt-section-card">
|
||||
<n-space vertical :size="12">
|
||||
<n-space wrap>
|
||||
<n-select v-model:value="ledgerFilters.asset" :options="assetOptions" clearable placeholder="资产" style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.transaction_id" placeholder="交易号" clearable style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.operation_key" placeholder="幂等键" clearable style="width: 240px" />
|
||||
<n-input v-model:value="ledgerFilters.ref_type" placeholder="引用类型" clearable style="width: 130px" />
|
||||
<n-input v-model:value="ledgerFilters.ref_key" placeholder="引用键" clearable style="width: 180px" />
|
||||
<n-input v-model:value="ledgerFilters.actor_user_id" placeholder="操作者 ID" clearable style="width: 130px" />
|
||||
<n-button type="primary" :loading="ledgerLoading" @click="searchLedger">查询</n-button>
|
||||
<n-button @click="resetLedger">重置</n-button>
|
||||
</n-space>
|
||||
<n-data-table
|
||||
:columns="ledgerColumns"
|
||||
:data="ledgerRows"
|
||||
:loading="ledgerLoading"
|
||||
:row-key="row => row.id"
|
||||
:scroll-x="1600"
|
||||
size="small"
|
||||
/>
|
||||
<n-pagination
|
||||
v-model:page="ledgerPage"
|
||||
:page-count="ledgerPageCount"
|
||||
:page-size="20"
|
||||
@update:page="loadLedger"
|
||||
/>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header"><h3>每日签到</h3></div>
|
||||
<n-form :model="cfg.daily_signin" label-placement="left" label-width="200" class="aksrt-form">
|
||||
@@ -97,7 +126,7 @@
|
||||
<n-form-item :label="`默认积分价参考(${cfg.currency_name || '积分'})`">
|
||||
<n-input-number v-model:value="cfg.download_default_cost" :min="0" :max="100000" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">兼容字段。实际价格以文章「付费内容设置」的积分价/现金价为准;下载解锁走收银台订单,不按次扣费。</span>
|
||||
<span class="aksrt-hint-text">实际价格以文章「付费内容设置」的积分价或现金价为准;下载解锁走收银台订单,不按次扣费。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
@@ -135,10 +164,88 @@ const cfg = reactive({
|
||||
})
|
||||
const streakJson = ref('{}')
|
||||
const saving = ref(false)
|
||||
const ledgerLoading = ref(false)
|
||||
const ledgerRows = ref([])
|
||||
const ledgerPage = ref(1)
|
||||
const ledgerPageCount = ref(1)
|
||||
const ledgerFilters = reactive({
|
||||
asset: null,
|
||||
transaction_id: '',
|
||||
operation_key: '',
|
||||
ref_type: '',
|
||||
ref_key: '',
|
||||
actor_user_id: '',
|
||||
})
|
||||
const assetOptions = [
|
||||
{ label: '积分', value: 'points' },
|
||||
{ label: '余额', value: 'balance' },
|
||||
]
|
||||
const ledgerColumns = [
|
||||
{ title: '交易号', key: 'transaction_label', width: 100, fixed: 'left' },
|
||||
{ title: '幂等键', key: 'operation_key', width: 260, ellipsis: { tooltip: true } },
|
||||
{ title: '资产', key: 'assets_label', width: 100 },
|
||||
{ title: '原因', key: 'reason', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '业务引用', key: 'reference_label', width: 200, ellipsis: { tooltip: true } },
|
||||
{ title: '操作者', key: 'actor_label', width: 100 },
|
||||
{ title: '增减 / 手续费', key: 'delta_label', width: 180, ellipsis: { tooltip: true } },
|
||||
{ title: '变动后余额', key: 'balance_label', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '备注', key: 'remark', width: 180, ellipsis: { tooltip: true } },
|
||||
{ title: 'UTC 时间', key: 'created_at', width: 190 },
|
||||
]
|
||||
|
||||
function formatAssetValue(asset, value) {
|
||||
if (asset === 'balance') return typeof value === 'string' && /^[+-]?\d+\.\d{2}$/.test(value) ? value : '0.00'
|
||||
const raw = String(value ?? '0')
|
||||
return /^-?\d+$/.test(raw) ? raw : '0'
|
||||
}
|
||||
|
||||
async function loadLedger() {
|
||||
ledgerLoading.value = true
|
||||
try {
|
||||
const params = { page: ledgerPage.value, per_page: 20 }
|
||||
Object.entries(ledgerFilters).forEach(([key, value]) => {
|
||||
if (value !== null && String(value).trim() !== '') params[key] = value
|
||||
})
|
||||
const response = await api.getCreditLog(params)
|
||||
const data = response?.data || response || {}
|
||||
ledgerRows.value = (data.items || []).map((row) => {
|
||||
const entries = Array.isArray(row.entries) ? row.entries : []
|
||||
return {
|
||||
...row,
|
||||
transaction_label: `#${row.id}`,
|
||||
assets_label: (row.assets || []).join(' / '),
|
||||
reference_label: [row.ref_type, row.ref_key].filter(Boolean).join(':') || '—',
|
||||
actor_label: row.actor_user_id ? `#${row.actor_user_id}` : '系统',
|
||||
delta_label: entries.map((entry) => {
|
||||
const value = formatAssetValue(entry.asset, entry.delta)
|
||||
const signed = entry.asset === 'points' && !value.startsWith('-') && value !== '0' ? `+${value}` : value
|
||||
return `${entry.asset}:${signed}`
|
||||
}).join(';'),
|
||||
balance_label: entries.map((entry) => `${entry.asset}:${formatAssetValue(entry.asset, entry.balance_after)}`).join(';'),
|
||||
}
|
||||
})
|
||||
ledgerPageCount.value = Math.max(1, Math.ceil(Number(data.total || 0) / Number(data.per_page || 20)))
|
||||
} catch (e) {
|
||||
message.error(e.message || '加载账本失败')
|
||||
} finally {
|
||||
ledgerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function searchLedger() {
|
||||
ledgerPage.value = 1
|
||||
loadLedger()
|
||||
}
|
||||
|
||||
function resetLedger() {
|
||||
Object.assign(ledgerFilters, { asset: null, transaction_id: '', operation_key: '', ref_type: '', ref_key: '', actor_user_id: '' })
|
||||
searchLedger()
|
||||
}
|
||||
|
||||
async function loadCfg() {
|
||||
try {
|
||||
const data = await api.getCreditSettings()
|
||||
const response = await api.getCreditSettings()
|
||||
const data = response?.data || response || {}
|
||||
Object.assign(cfg, data)
|
||||
streakJson.value = JSON.stringify(cfg.daily_signin?.streak || {}, null, 0)
|
||||
} catch (e) {
|
||||
@@ -163,5 +270,6 @@ async function saveCfg() {
|
||||
|
||||
onMounted(() => {
|
||||
loadCfg()
|
||||
loadLedger()
|
||||
})
|
||||
</script>
|
||||
Reference in new issue
Block a user