feat(admin): 用户列表新增「调余额」人工充值/扣减入口
- 新接口 POST /api/admin/users/:id/balance(requireAdmin):事务锁行、扣减不可为负、单次上限 100 万、写 finance_records 并推送站内通知 - 后台用户列表每行新增「调余额」按钮与弹窗(当前余额/方向/金额/备注) - 新增 admin-balance-adjust 护栏;113 套件/737 用例全绿
这个提交包含在:
@@ -226,6 +226,11 @@
|
||||
详情
|
||||
</a-button>
|
||||
|
||||
<a-button type="outline" size="small" @click.stop="openBalanceModal(record)">
|
||||
<template #icon><icon-plus /></template>
|
||||
调余额
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
:content="record.status === '正常' ? `确定要封禁用户 [${record.nickname}] 吗?封禁后将限制发单与接单。` : `确定要解除用户 [${record.nickname}] 的封禁状态吗?`"
|
||||
ok-text="确认"
|
||||
@@ -601,6 +606,53 @@
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
||||
<!-- 人工调整钱包余额(运营/测试) -->
|
||||
<a-modal
|
||||
v-model:visible="balanceModalVisible"
|
||||
title="调整用户钱包余额"
|
||||
:width="480"
|
||||
:ok-loading="submittingBalance"
|
||||
ok-text="确认调整"
|
||||
cancel-text="取消"
|
||||
@ok="submitBalanceAdjust"
|
||||
>
|
||||
<a-form :model="balanceForm" layout="vertical">
|
||||
<a-form-item label="用户">
|
||||
<a-input :model-value="`${balanceTarget.nickname || ''}(UID: ${balanceTarget.id || ''})`" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="当前余额">
|
||||
<a-input :model-value="`¥${Number(balanceTarget.balance || 0).toFixed(2)}`" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item label="调整方向">
|
||||
<a-radio-group v-model="balanceForm.direction">
|
||||
<a-radio value="IN">充值(增加余额)</a-radio>
|
||||
<a-radio value="OUT">扣减(减少余额)</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="金额(元)" required>
|
||||
<a-input-number
|
||||
v-model="balanceForm.amount"
|
||||
:min="0.01"
|
||||
:max="100000"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
placeholder="请输入调整金额"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注(写入用户财务流水)">
|
||||
<a-input
|
||||
v-model="balanceForm.reason"
|
||||
placeholder="如:测试充值 / 活动赠送 / 人工补偿"
|
||||
:max-length="80"
|
||||
show-word-limit
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-alert type="warning">
|
||||
该操作会真实修改用户余额并写入财务流水;扣减时不能超过当前余额,请谨慎操作。
|
||||
</a-alert>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -622,6 +674,52 @@
|
||||
const currentUser = ref<any>(null);
|
||||
const userDetail = ref<any>(null);
|
||||
|
||||
// 人工调整余额
|
||||
const balanceModalVisible = ref(false);
|
||||
const balanceTarget = ref<any>({});
|
||||
const balanceForm = ref({ direction: 'IN', amount: 100, reason: '' });
|
||||
const submittingBalance = ref(false);
|
||||
|
||||
const openBalanceModal = (record: any) => {
|
||||
balanceTarget.value = { ...(record || {}) };
|
||||
balanceForm.value = { direction: 'IN', amount: 100, reason: '' };
|
||||
balanceModalVisible.value = true;
|
||||
};
|
||||
|
||||
const submitBalanceAdjust = async () => {
|
||||
const amount = Number(balanceForm.value.amount);
|
||||
if (!amount || amount <= 0) {
|
||||
Message.warning('请输入有效的调整金额');
|
||||
return;
|
||||
}
|
||||
if (!balanceTarget.value.id) {
|
||||
Message.error('用户信息缺失,请刷新后重试');
|
||||
return;
|
||||
}
|
||||
submittingBalance.value = true;
|
||||
try {
|
||||
const res: any = await axios.post(`/api/admin/users/${balanceTarget.value.id}/balance`, {
|
||||
amount,
|
||||
direction: balanceForm.value.direction,
|
||||
reason: balanceForm.value.reason,
|
||||
});
|
||||
if (res && res.data && res.data.code === 20000) {
|
||||
Message.success(res.data.message || '余额调整成功');
|
||||
balanceModalVisible.value = false;
|
||||
await fetchUsers();
|
||||
if (detailDrawerVisible.value && currentUser.value) {
|
||||
fetchUserDetail(currentUser.value.id);
|
||||
}
|
||||
} else {
|
||||
Message.error((res && res.data && res.data.message) || '余额调整失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
Message.error((err && err.message) || '余额调整失败');
|
||||
} finally {
|
||||
submittingBalance.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchUsers = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
|
||||
在新工单中引用
屏蔽一个用户