新增:用户管理增加充值功能与钱包流水查询

- 操作列新增"充值""流水信息"按钮,权限与修改一致(isOperation 控制)
- 新增充值弹窗 rechargeUser.vue:展示账户名称(只读),金额单位元(校验大于0、最多两位小数),提交 userId/userName/amount/orderNo/description
- 新增流水弹窗 walletLog.vue:展示充值与扣费明细(income/expense),金额元,后端分页(pageNum/pageSize)
- 新增 recharge、getWalletLogs 接口,路径 shop-user-trade/account/controller/*
- other.scss 补充 op-btn-recharge 固定绿色样式(含 dark 主题)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-28 15:28:03 +08:00
co-authored by Claude
parent 43d4392873
commit c2244f50b1
5 changed files with 242 additions and 2 deletions
+16
View File
@@ -70,6 +70,22 @@ export function deleteUser(ids: number[]) {
});
}
export function recharge(data: object) {
return request({
url: '/shop-user-trade/account/controller/recharge',
method: 'post',
data: data,
});
}
export function getWalletLogs(userId: number, pageNum: number, pageSize: number) {
return request({
url: '/shop-user-trade/account/controller/logs',
method: 'get',
params: { userId, pageNum, pageSize },
});
}
export function checkIsSuperAdmin() {
return request({
url: '/admin-go/api/v1/system/user/checkIsSuperAdmin',
+8
View File
@@ -31,6 +31,10 @@
color: #f56c6c !important;
}
.op-btn-recharge {
color: #67c23a !important; // 充值 绿色
}
[data-theme='dark'] {
// textarea - css vars
--w-e-textarea-bg-color: var(--el-color-white) !important;
@@ -49,6 +53,10 @@
color: #f56c6c !important; // 删除 红色
}
.op-btn-recharge {
color: #67c23a !important; // 充值 绿色
}
// toolbar - css vars
--w-e-toolbar-color: var(--el-text-color-primary) !important;
--w-e-toolbar-bg-color: var(--el-color-white) !important;
@@ -0,0 +1,111 @@
<template>
<div class="system-recharge-user-container">
<el-dialog title="用户充值" v-model="isShowDialog" width="520px">
<el-form ref="formRef" :model="ruleForm" :rules="rules" size="default" label-width="110px">
<el-form-item label="账户名称" prop="userName">
<el-input v-model="ruleForm.userName" disabled placeholder="请输入账户名称"></el-input>
</el-form-item>
<el-form-item label="充值金额" prop="amount">
<el-input v-model="ruleForm.amount" placeholder="请输入充值金额(单位:元)" clearable></el-input>
</el-form-item>
<el-form-item label="外部订单号" prop="orderNo">
<el-input v-model="ruleForm.orderNo" placeholder="请输入外部订单号(选填)" clearable></el-input>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="ruleForm.description" type="textarea" placeholder="请输入描述(选填)" maxlength="150"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
import { ElMessage } from 'element-plus';
import { recharge } from '/@/api/system/user';
export default defineComponent({
name: 'systemRechargeUser',
setup() {
const formRef = ref<HTMLElement | null>(null);
const state = reactive({
isShowDialog: false,
ruleForm: {
userId: 0,
userName: '',
amount: '',
orderNo: '',
description: '',
},
//表单校验
rules: {
amount: [
{ required: true, message: '充值金额不能为空', trigger: 'blur' },
{
validator: (_rule: any, value: any, callback: any) => {
if (!/^\d+(\.\d{1,2})?$/.test(value) || Number(value) <= 0) {
callback(new Error('请输入大于 0 的金额(单位:元,最多两位小数)'));
} else {
callback();
}
},
trigger: 'blur',
},
],
},
});
// 打开弹窗
const openDialog = (row: any) => {
state.ruleForm = {
userId: row.id,
userName: row.userName,
amount: '',
orderNo: '',
description: '',
};
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 提交
const onSubmit = () => {
const formWrap = unref(formRef) as any;
if (!formWrap) return;
formWrap.validate((valid: boolean) => {
if (valid) {
recharge({
userId: state.ruleForm.userId,
userName: state.ruleForm.userName,
amount: Number(state.ruleForm.amount),
orderNo: state.ruleForm.orderNo,
description: state.ruleForm.description,
}).then(() => {
ElMessage.success('充值成功');
closeDialog();
});
}
});
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
formRef,
...toRefs(state),
};
},
});
</script>
@@ -0,0 +1,68 @@
<template>
<div class="system-wallet-log-container">
<el-dialog title="钱包流水信息" v-model="isShowDialog" width="1000px">
<el-table :data="logs" style="width: 100%" :max-height="420">
<el-table-column prop="orderNo" label="业务单据号" show-overflow-tooltip></el-table-column>
<el-table-column prop="transactionNo" label="账务流水号" show-overflow-tooltip></el-table-column>
<el-table-column prop="type" label="类型" width="80" align="center">
<template #default="scope">
<el-tag :type="scope.row.type === 'income' ? 'success' : 'danger'">{{ scope.row.type === 'income' ? '充值' : '扣费' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="amount" label="变动金额(元)" width="120" align="right"></el-table-column>
<el-table-column prop="balanceBefore" label="变动前余额(元)" width="130" align="right"></el-table-column>
<el-table-column prop="balanceAfter" label="变动后余额(元)" width="130" align="right"></el-table-column>
<el-table-column prop="currency" label="货币" width="80" align="center"></el-table-column>
<el-table-column prop="description" label="描述" show-overflow-tooltip></el-table-column>
<el-table-column prop="createdAt" label="创建时间" width="170"></el-table-column>
</el-table>
<div class="system-wallet-log-pagination">
<pagination :total="total" v-model:page="pageNum" v-model:limit="pageSize" @pagination="handlePagination" />
</div>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, defineComponent } from 'vue';
import { getWalletLogs } from '/@/api/system/user';
export default defineComponent({
name: 'systemWalletLog',
setup() {
const state = reactive({
isShowDialog: false,
userId: 0,
logs: [] as any[],
total: 0,
pageNum: 1,
pageSize: 10,
});
// 请求流水数据(后端分页)
const getLogs = () => {
getWalletLogs(state.userId, state.pageNum, state.pageSize).then((res: any) => {
state.logs = res.data.logs ?? [];
state.total = res.data.total ?? 0;
});
};
// 打开弹窗并加载流水
const openDialog = (userId: number) => {
state.userId = userId;
state.logs = [];
state.total = 0;
state.pageNum = 1;
state.isShowDialog = true;
getLogs();
};
// 分页事件(pageNum/pageSize 已由 v-model 更新,重新请求当前页)
const handlePagination = () => {
getLogs();
};
return {
openDialog,
handlePagination,
...toRefs(state),
};
},
});
</script>
+39 -2
View File
@@ -117,7 +117,7 @@
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="200">
<el-table-column label="操作" width="320">
<template #default="scope">
<el-button
size="small"
@@ -140,6 +140,25 @@
>删除</el-button
>
<el-button size="small" text type="primary" v-debounce @click="handleResetPwd(scope.row)">重置</el-button>
<el-button
size="small"
text
type="primary"
class="op-btn-recharge"
:disabled="!scope.row.isOperation"
v-debounce
@click="onOpenRecharge(scope.row)"
>充值</el-button
>
<el-button
size="small"
text
type="primary"
:disabled="!scope.row.isOperation"
v-debounce
@click="onOpenWalletLog(scope.row)"
>流水信息</el-button
>
</template>
</el-table-column>
</el-table>
@@ -154,6 +173,8 @@
</el-col>
</el-row>
<EditUser ref="editUserRef" :dept-data="deptData" :gender-data="sys_user_sex" @getUserList="userList" />
<RechargeUser ref="rechargeUserRef" />
<WalletLog ref="walletLogRef" />
</div>
</template>
@@ -162,6 +183,8 @@ import { toRefs, reactive, onMounted, ref, defineComponent, watch, getCurrentIns
import { ElMessageBox, ElMessage, ElTree, FormInstance } from 'element-plus';
import { Search } from '@element-plus/icons-vue';
import EditUser from '/@/views/system/user/component/editUser.vue';
import RechargeUser from '/@/views/system/user/component/rechargeUser.vue';
import WalletLog from '/@/views/system/user/component/walletLog.vue';
import { getUserList, getDeptTree, resetUserPwd, changeUserStatus, deleteUser } from '/@/api/system/user/index';
interface TableDataState {
@@ -186,11 +209,13 @@ interface TableDataState {
export default defineComponent({
name: 'systemUser',
components: { EditUser },
components: { EditUser, RechargeUser, WalletLog },
setup() {
const { proxy } = <any>getCurrentInstance();
const { sys_user_sex } = proxy.useDict('sys_user_sex');
const editUserRef = ref();
const rechargeUserRef = ref();
const walletLogRef = ref();
const queryRef = ref();
const filterText = ref('');
const treeRef = ref<InstanceType<typeof ElTree>>();
@@ -259,6 +284,14 @@ export default defineComponent({
const onOpenEditUser = (row: any) => {
editUserRef.value.openDialog(row);
};
// 打开充值弹窗
const onOpenRecharge = (row: any) => {
rechargeUserRef.value.openDialog(row);
};
// 打开流水信息弹窗
const onOpenWalletLog = (row: any) => {
walletLogRef.value.openDialog(row.id);
};
// 删除用户
const onRowDel = (row: any) => {
let msg = '你确定要删除所选用户?';
@@ -358,8 +391,12 @@ export default defineComponent({
return {
queryRef,
editUserRef,
rechargeUserRef,
walletLogRef,
onOpenAddUser,
onOpenEditUser,
onOpenRecharge,
onOpenWalletLog,
onRowDel,
onHandleSizeChange,
onHandleCurrentChange,