|
|
@@ -7,10 +7,22 @@
|
|
|
<el-button type="danger" size="small" @click="openDownDialog">人工下分</el-button>
|
|
|
</div>
|
|
|
<el-form inline :model="query" class="filter-form">
|
|
|
+ <el-form-item label="会员ID">
|
|
|
+ <el-input v-model="query.member_id" clearable placeholder="请输入" style="width: 150px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="会员昵称">
|
|
|
+ <el-input v-model="query.first_name" clearable placeholder="请输入" style="width: 150px" />
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="交易类型">
|
|
|
<el-select v-model="query.pay_type" clearable placeholder="请选择" style="width: 150px">
|
|
|
- <el-option label="人工上分" value="up" />
|
|
|
- <el-option label="人工下分" value="down" />
|
|
|
+ <el-option label="全部" value="" />
|
|
|
+ <el-option v-for="t in transactionTypeOptions" :key="t.value" :label="t.label" :value="t.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-select v-model="query.status" clearable placeholder="请选择" style="width: 150px">
|
|
|
+ <el-option label="全部" value="" />
|
|
|
+ <el-option v-for="s in statusFilterOptions" :key="s.value" :label="s.label" :value="s.value" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="时间">
|
|
|
@@ -36,21 +48,37 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="table-section">
|
|
|
- <el-table v-loading="loading" :data="pagedData"
|
|
|
+ <el-table v-loading="loading" :data="tableData"
|
|
|
height="calc(100vh - 340px)" style="width: 100%; border-radius: 12px; overflow: hidden;"
|
|
|
empty-text="暂无数据">
|
|
|
<el-table-column type="index" label="序号" width="80" align="center" />
|
|
|
- <el-table-column prop="member_account" label="会员账号" width="140" align="center" />
|
|
|
+ <el-table-column prop="member_account" label="会员ID" width="140" align="center" />
|
|
|
<el-table-column prop="member_name" label="会员昵称" width="140" align="center" />
|
|
|
- <el-table-column prop="pay_type" label="支付类型" width="100" align="center" />
|
|
|
- <el-table-column prop="before_level" label="充值前等级" width="110" align="center" />
|
|
|
- <el-table-column prop="amount" label="充值金额" width="110" align="center">
|
|
|
+ <el-table-column prop="pay_type" label="类型" width="100" align="center">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span :style="{ color: row.pay_type === '人工上分' ? '#18C80A' : row.pay_type === '人工下分' ? '#F56C6C' : '#606266' }">{{ row.pay_type }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="change_type" label="上分类型" width="100" align="center" />
|
|
|
+ <el-table-column prop="amount" label="金额" width="110" align="center">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span>{{ row.amount != null ? Number(row.amount).toFixed(2) : '--' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="signed_amount" label="实际变动" width="110" align="center">
|
|
|
<template slot-scope="{ row }">
|
|
|
- <span style="color: #18C80A;">
|
|
|
- {{ row.amount != null ? Number(row.amount).toFixed(2) : '--' }}
|
|
|
+ <span :style="{ color: Number(row.signed_amount) >= 0 ? '#18C80A' : '#F56C6C' }">
|
|
|
+ {{ row.signed_amount != null ? Number(row.signed_amount).toFixed(2) : '--' }}
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
+ <el-table-column prop="before_balance" label="变动前余额" width="110" align="center" />
|
|
|
+ <el-table-column prop="after_balance" label="变动后余额" width="110" align="center">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span style="color: #1677FF;">{{ row.after_balance != null ? Number(row.after_balance).toFixed(2) : '--' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="before_level" label="充值前等级" width="110" align="center" />
|
|
|
<el-table-column prop="after_level" label="充值后等级" width="110" align="center" />
|
|
|
<el-table-column prop="status" label="状态" width="90" align="center">
|
|
|
<template slot-scope="{ row }">
|
|
|
@@ -58,18 +86,24 @@
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="remark" label="备注" min-width="140" align="center" />
|
|
|
+ <el-table-column prop="failure_reason" label="失败原因" min-width="140" align="center">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span :style="{ color: row.status === '失败' ? '#F56C6C' : '#909399' }">{{ row.failure_reason || '--' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
<el-table-column prop="trade_date" label="交易日期" width="175" align="center" />
|
|
|
+ <el-table-column prop="admin_id" label="操作管理员" width="110" align="center" />
|
|
|
<el-table-column label="操作" width="90" align="center" fixed="right">
|
|
|
<template slot-scope="{ row }">
|
|
|
<el-button type="text" size="small" class="ym-view-btn" @click="handleView(row)">查看</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
- <el-pagination v-if="tableData.length" background
|
|
|
+ <el-pagination v-if="total" background
|
|
|
layout="total, prev, pager, next, jumper" :page-size="pageSize"
|
|
|
:page-sizes="[15, 30, 45, 60]" :current-page="page"
|
|
|
- :total="tableData.length"
|
|
|
- @current-change="page = $event" @size-change="handleSizeChange" />
|
|
|
+ :total="total"
|
|
|
+ @current-change="page = $event; fetchList()" @size-change="handleSizeChange" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -82,7 +116,7 @@
|
|
|
</el-form-item>
|
|
|
<el-form-item label="充值类型" prop="change_type">
|
|
|
<el-select v-model="upForm.change_type" placeholder="请选择" style="width: 280px">
|
|
|
- <el-option v-for="item in listTypes" :key="item" :label="item" :value="item" />
|
|
|
+ <el-option v-for="item in listTypes" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="充值金额" prop="amount">
|
|
|
@@ -126,10 +160,10 @@
|
|
|
<div :style="{ color: field.color || '#303133', fontSize: '15px' }">{{ field.value }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <!-- 操作按钮:充值/扣款 -->
|
|
|
+ <!-- 操作按钮:人工上分/人工下分 -->
|
|
|
<div style="margin-top: 16px; text-align: center; border-top: 1px solid #e4e7ed; padding-top: 16px;">
|
|
|
- <el-button type="primary" size="small" @click="openViewUp">充值</el-button>
|
|
|
- <el-button type="danger" size="small" @click="openViewDown">扣款</el-button>
|
|
|
+ <el-button type="success" size="small" @click="openViewUp">人工上分</el-button>
|
|
|
+ <el-button type="danger" size="small" @click="openViewDown">人工下分</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</Dialog>
|
|
|
@@ -137,19 +171,10 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { withdraw, walletdebiting, getChangeTypes } from '@/api/data'
|
|
|
+import { getManualAuditList, getManualAuditDetail, manualAuditTopUp, manualAuditDebit } from '@/api/data'
|
|
|
import Dialog from '@/components/Dialog/index.vue'
|
|
|
import { buildDatePickerOptions } from '@/utils/index'
|
|
|
|
|
|
-// 假数据
|
|
|
-const MANUAL_AUDIT_MOCK = [
|
|
|
- { id: 1, member_account: 'test01', member_name: 'test01', pay_type: '人工上分', before_level: '普通会员', amount: 2000, after_level: '普通会员', status: '成功', remark: '人工上分', trade_date: '2026-08-05 10:20:30' },
|
|
|
- { id: 2, member_account: 'test02', member_name: 'test02', pay_type: '人工上分', before_level: '普通会员', amount: 5000, after_level: '白银会员', status: '成功', remark: '人工上分', trade_date: '2026-08-05 11:00:00' },
|
|
|
- { id: 3, member_account: 'test03', member_name: 'test03', pay_type: '人工下分', before_level: '白银会员', amount: 1000, after_level: '普通会员', status: '成功', remark: '人工下分', trade_date: '2026-08-05 14:30:00' },
|
|
|
- { id: 4, member_account: 'test04', member_name: 'test04', pay_type: '人工上分', before_level: '普通会员', amount: 8000, after_level: '黄金会员', status: '成功', remark: '人工上分', trade_date: '2026-08-06 09:15:00' },
|
|
|
- { id: 5, member_account: 'test05', member_name: 'test05', pay_type: '人工下分', before_level: '黄金会员', amount: 3000, after_level: '白银会员', status: '失败', remark: '余额不足', trade_date: '2026-08-06 16:40:00' }
|
|
|
-]
|
|
|
-
|
|
|
export default {
|
|
|
name: 'ManualAudit',
|
|
|
components: {
|
|
|
@@ -160,12 +185,17 @@ export default {
|
|
|
loading: false,
|
|
|
page: 1,
|
|
|
pageSize: 15,
|
|
|
- tableData: [...MANUAL_AUDIT_MOCK],
|
|
|
- filteredData: [...MANUAL_AUDIT_MOCK],
|
|
|
+ total: 0,
|
|
|
+ tableData: [],
|
|
|
listTypes: [],
|
|
|
+ transactionTypeOptions: [],
|
|
|
+ statusFilterOptions: [],
|
|
|
query: {
|
|
|
+ member_id: '',
|
|
|
+ first_name: '',
|
|
|
pay_type: '',
|
|
|
- dateRange: [this.getDefaultDate(0) + ' 00:00:00', this.getDefaultDate(0) + ' 23:59:59']
|
|
|
+ status: '',
|
|
|
+ dateRange: []
|
|
|
},
|
|
|
upForm: {
|
|
|
member_id: '',
|
|
|
@@ -224,28 +254,33 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
- pagedData() {
|
|
|
- const start = (this.page - 1) * this.pageSize
|
|
|
- return this.filteredData.slice(start, start + this.pageSize)
|
|
|
- },
|
|
|
viewFields() {
|
|
|
if (!this.viewRow) return []
|
|
|
const row = this.viewRow
|
|
|
const statusColor = row.status === '成功' ? '#18C80A' : row.status === '失败' ? '#F56C6C' : '#909399'
|
|
|
+ const payTypeColor = row.pay_type === '人工上分' ? '#18C80A' : row.pay_type === '人工下分' ? '#F56C6C' : '#606266'
|
|
|
return [
|
|
|
- { label: '序号', prop: 'id', value: row.id },
|
|
|
- { label: '会员账号', prop: 'member_account', value: row.member_account || '--' },
|
|
|
+ { label: '会员ID', prop: 'member_account', value: row.member_account || '--' },
|
|
|
{ label: '会员昵称', prop: 'member_name', value: row.member_name || '--' },
|
|
|
- { label: '支付类型', prop: 'pay_type', value: row.pay_type || '--' },
|
|
|
+ { label: '类型', prop: 'pay_type', value: row.pay_type || '--', color: payTypeColor },
|
|
|
+ { label: '上分类型', prop: 'change_type', value: row.change_type || '--' },
|
|
|
+ { label: '金额', prop: 'amount', value: row.amount != null ? Number(row.amount).toFixed(2) : '--' },
|
|
|
+ { label: '实际变动', prop: 'signed_amount', value: row.signed_amount != null ? Number(row.signed_amount).toFixed(2) : '--', color: Number(row.signed_amount) >= 0 ? '#18C80A' : '#F56C6C' },
|
|
|
+ { label: '变动前余额', prop: 'before_balance', value: row.before_balance != null ? Number(row.before_balance).toFixed(2) : '--' },
|
|
|
+ { label: '变动后余额', prop: 'after_balance', value: row.after_balance != null ? Number(row.after_balance).toFixed(2) : '--', color: '#1677FF' },
|
|
|
{ label: '充值前等级', prop: 'before_level', value: row.before_level || '--' },
|
|
|
- { label: '充值金额', prop: 'amount', value: row.amount != null ? Number(row.amount).toFixed(2) : '--', color: '#18C80A' },
|
|
|
{ label: '充值后等级', prop: 'after_level', value: row.after_level || '--' },
|
|
|
{ label: '状态', prop: 'status', value: row.status || '--', color: statusColor },
|
|
|
{ label: '备注', prop: 'remark', value: row.remark || '--' },
|
|
|
- { label: '交易日期', prop: 'trade_date', value: row.trade_date || '--' }
|
|
|
+ { label: '失败原因', prop: 'failure_reason', value: row.failure_reason || '--', color: row.status === '失败' ? '#F56C6C' : '#909399' },
|
|
|
+ { label: '交易日期', prop: 'trade_date', value: row.trade_date || '--' },
|
|
|
+ { label: '操作管理员', prop: 'admin_id', value: row.admin_id != null ? row.admin_id : '--' }
|
|
|
]
|
|
|
}
|
|
|
},
|
|
|
+ created() {
|
|
|
+ this.fetchList()
|
|
|
+ },
|
|
|
methods: {
|
|
|
getDefaultDate(offsetDays) {
|
|
|
const d = new Date()
|
|
|
@@ -253,24 +288,85 @@ export default {
|
|
|
const pad = n => String(n).padStart(2, '0')
|
|
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
|
|
},
|
|
|
+ // 列表字段映射
|
|
|
+ mapRow(row) {
|
|
|
+ return {
|
|
|
+ id: row.id,
|
|
|
+ request_id: row.request_id || '--',
|
|
|
+ admin_id: row.admin_id != null ? row.admin_id : '--',
|
|
|
+ member_account: row.username || row.member_id || '--',
|
|
|
+ member_name: row.first_name || '--',
|
|
|
+ member_id: row.member_id,
|
|
|
+ pay_type: row.transaction_type_text || (row.transaction_type === 'credit' ? '人工上分' : '人工下分'),
|
|
|
+ change_type: row.change_type || '--',
|
|
|
+ before_level: row.level_before_name || '--',
|
|
|
+ after_level: row.level_after_name || '--',
|
|
|
+ amount: row.amount,
|
|
|
+ signed_amount: row.signed_amount,
|
|
|
+ before_balance: row.before_balance,
|
|
|
+ after_balance: row.after_balance,
|
|
|
+ status: row.status_text || (Number(row.status) === 1 ? '成功' : '失败'),
|
|
|
+ status_code: row.status,
|
|
|
+ failure_reason: row.failure_reason || '--',
|
|
|
+ remark: row.remark || '--',
|
|
|
+ trade_date: row.created_at || '--'
|
|
|
+ }
|
|
|
+ },
|
|
|
handleSizeChange(val) {
|
|
|
this.pageSize = val
|
|
|
this.page = 1
|
|
|
+ this.fetchList()
|
|
|
},
|
|
|
handleSearch() {
|
|
|
this.page = 1
|
|
|
- let filtered = [...this.tableData]
|
|
|
- if (this.query.pay_type) {
|
|
|
- filtered = filtered.filter(r => r.pay_type === (this.query.pay_type === 'up' ? '人工上分' : '人工下分'))
|
|
|
+ this.fetchList()
|
|
|
+ },
|
|
|
+ fetchList() {
|
|
|
+ this.loading = true
|
|
|
+ const [start, end] = this.query.dateRange || []
|
|
|
+ // status 可能是 0(失败),不能用 || 兜底,避免丢失 0
|
|
|
+ const statusVal = this.query.status === '' || this.query.status === null || this.query.status === undefined ? '' : this.query.status
|
|
|
+ const params = {
|
|
|
+ page: this.page,
|
|
|
+ limit: this.pageSize,
|
|
|
+ member_id: this.query.member_id || '',
|
|
|
+ first_name: this.query.first_name || '',
|
|
|
+ transaction_type: this.query.pay_type || '',
|
|
|
+ status: statusVal
|
|
|
}
|
|
|
- this.filteredData = filtered
|
|
|
+ // 仅当用户选择了日期范围才传 start_date/end_date(首屏不带日期,返回全部数据)
|
|
|
+ if (start && end) {
|
|
|
+ params.start_date = start.split(' ')[0]
|
|
|
+ params.end_date = end.split(' ')[0]
|
|
|
+ }
|
|
|
+ getManualAuditList(params).then(res => {
|
|
|
+ if (res.code === 0 || res.code === 99) {
|
|
|
+ const data = res.data || {}
|
|
|
+ this.total = Number(data.total) || 0
|
|
|
+ this.tableData = (data.data || []).map(row => this.mapRow(row))
|
|
|
+ // 填充筛选与弹窗选项
|
|
|
+ const opts = data.options || {}
|
|
|
+ if (Array.isArray(opts.credit_types)) {
|
|
|
+ this.listTypes = opts.credit_types
|
|
|
+ }
|
|
|
+ if (Array.isArray(opts.transaction_types)) {
|
|
|
+ this.transactionTypeOptions = opts.transaction_types
|
|
|
+ }
|
|
|
+ if (Array.isArray(opts.statuses)) {
|
|
|
+ this.statusFilterOptions = opts.statuses
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.msg || '获取列表失败')
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ this.$message.error('请求异常')
|
|
|
+ }).finally(() => {
|
|
|
+ this.loading = false
|
|
|
+ })
|
|
|
},
|
|
|
openUpDialog() {
|
|
|
this.upForm = { member_id: '', change_type: '', amount: '', remark: '' }
|
|
|
this.$refs.upDialog.open()
|
|
|
- getChangeTypes().then((res) => {
|
|
|
- this.listTypes = res.data
|
|
|
- })
|
|
|
},
|
|
|
// 查看弹窗内的充值操作
|
|
|
openViewUp() {
|
|
|
@@ -281,9 +377,6 @@ export default {
|
|
|
remark: ''
|
|
|
}
|
|
|
this.$refs.upDialog.open()
|
|
|
- getChangeTypes().then((res) => {
|
|
|
- this.listTypes = res.data
|
|
|
- })
|
|
|
},
|
|
|
// 查看弹窗内的扣款操作
|
|
|
openViewDown() {
|
|
|
@@ -295,8 +388,18 @@ export default {
|
|
|
this.$refs.downDialog.open()
|
|
|
},
|
|
|
handleView(row) {
|
|
|
- this.viewRow = { ...row }
|
|
|
- this.$refs.viewDialog.open()
|
|
|
+ // 拉取最新详情
|
|
|
+ getManualAuditDetail({ id: row.id }).then(res => {
|
|
|
+ if (res.code === 0 || res.code === 99) {
|
|
|
+ this.viewRow = this.mapRow(res.data || {})
|
|
|
+ } else {
|
|
|
+ this.viewRow = { ...row }
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ this.viewRow = { ...row }
|
|
|
+ }).finally(() => {
|
|
|
+ this.$refs.viewDialog.open()
|
|
|
+ })
|
|
|
},
|
|
|
openDownDialog() {
|
|
|
this.downForm = { member_id: '', amount: '', remark: '' }
|
|
|
@@ -306,17 +409,19 @@ export default {
|
|
|
this.$refs.upFormRef.validate((valid) => {
|
|
|
if (!valid) return
|
|
|
this.loading = true
|
|
|
- withdraw({
|
|
|
+ manualAuditTopUp({
|
|
|
+ request_id: this.genUUID(),
|
|
|
member_id: this.upForm.member_id,
|
|
|
- remark: this.upForm.remark,
|
|
|
+ change_type: this.upForm.change_type,
|
|
|
amount: this.upForm.amount,
|
|
|
- change_type: this.upForm.change_type
|
|
|
+ remark: this.upForm.remark
|
|
|
}).then((res) => {
|
|
|
this.loading = false
|
|
|
if (res.code === 0) {
|
|
|
this.$message.success('操作成功')
|
|
|
this.$refs.upDialog.close()
|
|
|
- this.handleSearch()
|
|
|
+ this.$refs.viewDialog.close()
|
|
|
+ this.fetchList()
|
|
|
} else {
|
|
|
this.$message.error(res.msg || '操作失败')
|
|
|
}
|
|
|
@@ -329,16 +434,18 @@ export default {
|
|
|
this.$refs.downFormRef.validate((valid) => {
|
|
|
if (!valid) return
|
|
|
this.loading = true
|
|
|
- walletdebiting({
|
|
|
+ manualAuditDebit({
|
|
|
+ request_id: this.genUUID(),
|
|
|
member_id: this.downForm.member_id,
|
|
|
- remark: this.downForm.remark,
|
|
|
- amount: this.downForm.amount
|
|
|
+ amount: this.downForm.amount,
|
|
|
+ remark: this.downForm.remark
|
|
|
}).then((res) => {
|
|
|
this.loading = false
|
|
|
if (res.code === 0) {
|
|
|
this.$message.success('操作成功')
|
|
|
this.$refs.downDialog.close()
|
|
|
- this.handleSearch()
|
|
|
+ this.$refs.viewDialog.close()
|
|
|
+ this.fetchList()
|
|
|
} else {
|
|
|
this.$message.error(res.msg || '操作失败')
|
|
|
}
|
|
|
@@ -346,6 +453,22 @@ export default {
|
|
|
this.loading = false
|
|
|
})
|
|
|
})
|
|
|
+ },
|
|
|
+ // 生成 UUID(幂等请求 ID)
|
|
|
+ genUUID() {
|
|
|
+ if (window.crypto && crypto.randomUUID) {
|
|
|
+ return crypto.randomUUID()
|
|
|
+ }
|
|
|
+ // 降级方案
|
|
|
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
|
+ const r = Math.random() * 16 | 0
|
|
|
+ const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
|
|
+ return v.toString(16)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
}
|
|
|
}
|
|
|
}
|