diff --git a/web/src/api/backend/user/moneyLog.ts b/web/src/api/backend/user/moneyLog.ts index e2078fe..013368b 100644 --- a/web/src/api/backend/user/moneyLog.ts +++ b/web/src/api/backend/user/moneyLog.ts @@ -11,3 +11,11 @@ export function add(userId: string) { }, }) } + +export function logHistory(params: { id: number | string }) { + return createAxios({ + url: url + 'logHistory', + method: 'get', + params, + }) +} diff --git a/web/src/views/backend/user/moneyLog/index.vue b/web/src/views/backend/user/moneyLog/index.vue index c1139dd..e97c0e8 100644 --- a/web/src/views/backend/user/moneyLog/index.vue +++ b/web/src/views/backend/user/moneyLog/index.vue @@ -22,6 +22,20 @@ + + +

Transaction edit history is only kept for the most recent 12 months.

+

Transaction 的编辑历史记录仅保存最近 12 个月。

+ + + + + + + +
@@ -31,7 +45,7 @@ import { provide, reactive, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useRoute } from 'vue-router' import PopupForm from './popupForm.vue' -import { add, url } from '/@/api/backend/user/moneyLog' +import { add, logHistory, url } from '/@/api/backend/user/moneyLog' import { baTableApi } from '/@/api/common' import TableHeader from '/@/components/table/header/index.vue' import Table from '/@/components/table/index.vue' @@ -48,6 +62,84 @@ const defalutUser = (route.query.user_id ?? '') as string const state = reactive({ userInfo: {} as anyObj, }) +interface HistoryRow { + id: number | string + editedBy: string + editedTime: string + changes: string +} + +const historyDialog = reactive({ + visible: false, + loading: false, + rows: [] as HistoryRow[], +}) +const gameTypeMap = scoreLog.game_type as Record +const optButtons: OptButton[] = [ + { + render: 'tipButton', + name: 'history', + title: 'History', + text: '', + type: 'primary', + icon: 'fa fa-history', + class: 'table-row-history', + disabledTip: false, + click: (row: TableRow) => { + openHistory(row) + }, + }, + ...defaultOptButtons(['edit']), + ...defaultOptButtons(['delete']), +] + +const toNumber = (value: unknown) => { + const number = Number(value) + return Number.isFinite(number) ? number : 0 +} + +const formatDateTime = (value: unknown) => { + if (typeof value === 'string' && value.trim() && !Number.isFinite(Number(value))) { + return value + } + + const timestamp = toNumber(value) + if (!timestamp) return '' + + const date = new Date(timestamp * 1000) + const pad = (number: number) => number.toString().padStart(2, '0') + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +} + +const stringifyChange = (history: Record) => { + if (history.bank_befter !== undefined || history.bank_after !== undefined) { + return `Bank:${history.bank_befter ?? ''}→${history.bank_after ?? ''}` + } + + return JSON.stringify(history) +} + +const mapHistory = (history: Record): HistoryRow => ({ + id: (history.id || history.money_log_id || '') as number | string, + editedBy: String(history.admin_name || ''), + editedTime: formatDateTime(history.create_time), + changes: stringifyChange(history), +}) + +const openHistory = (row: TableRow) => { + historyDialog.visible = true + historyDialog.loading = true + historyDialog.rows = [] + + logHistory({ id: row.id }) + .then((res) => { + const data = Array.isArray(res.data) ? res.data : res.data?.list + historyDialog.rows = Array.isArray(data) ? data.map((item) => mapHistory(item as Record)) : [] + }) + .finally(() => { + historyDialog.loading = false + }) +} const baTable = new baTableClass( new baTableApi(url), @@ -61,7 +153,7 @@ const baTable = new baTableClass( width: 70, formatter: (row: TableRow) => { return row.admin?.username || 'web' - } + }, }, { label: t('user.moneyLog.User name'), prop: 'user.username', align: 'center', operator: 'LIKE', operatorPlaceholder: t('Fuzzy query') }, { @@ -81,11 +173,13 @@ const baTable = new baTableClass( // 使用渲染函数手动构建显示内容 formatter: (row: TableRow) => { if (!row.scoreLog || row.scoreLog.length === 0) return '-' - return row.scoreLog.map((item: any) => { - const gameName = scoreLog.game_type[item.game_type] || item.game_type - return `${gameName}: ${item.score}` - }).join(' | ') - } + return row.scoreLog + .map((item: any) => { + const gameName = gameTypeMap[String(item.game_type)] || item.game_type + return `${gameName}: ${item.score}` + }) + .join(' | ') + }, }, { label: t('user.moneyLog.type'), @@ -111,7 +205,7 @@ const baTable = new baTableClass( showOverflowTooltip: true, }, { label: t('Create time'), prop: 'create_time', align: 'center', render: 'datetime', sortable: 'custom', operator: 'RANGE', width: 160 }, - { label: t('Operate'), align: 'center', width: '100', render: 'buttons', buttons: defaultOptButtons(['edit', 'delete']) }, + { label: t('Operate'), align: 'center', width: '130', render: 'buttons', buttons: optButtons }, ], dblClickNotEditColumn: ['all'], },