feat(用户余额): 添加用户余额修改功能

- 新增用户余额实体和API接口
- 在用户详情页添加余额修改按钮和弹窗
- 实现金额单位转换和表单验证
- 添加相关文档说明
This commit is contained in:
dzq 2025-11-24 17:57:28 +08:00
parent 040fb4acaa
commit 1a30197960
5 changed files with 232 additions and 3 deletions

130
src/api/ab98/balance.ts Normal file
View File

@ -0,0 +1,130 @@
import { http } from "@/utils/http";
export interface UserBalance {
/** 主键ID */
userBalanceId: number;
/** 企业微信id */
corpid: string;
/** openid */
openid: string;
/** 汇邦云用户ID */
ab98UserId: number;
/** 企业用户id */
qyUserId: number;
/** 用户余额(单位:分) */
balance: number;
/** 用户余额(单位:分) */
useBalance: number;
/** 用户余额(单位:分) */
balanceLimit: number;
/** 创建者ID */
creatorId: number;
/** 更新者ID */
updaterId: number;
}
export interface UserBalanceListParams {
pageNum?: number;
pageSize?: number;
corpid?: string;
openid?: string;
ab98UserId?: number;
qyUserId?: number;
}
export interface UserBalanceListResponse {
rows: UserBalance[];
total: number;
}
// 新增用户余额请求参数
export interface AddUserBalanceCommand {
/** 企业微信ID */
corpid: string;
/** 微信openid */
openid?: string;
/** 汇邦云用户ID */
ab98UserId: number;
/** 企业用户ID */
qyUserId?: number;
/** 用户余额(分) */
balance?: number;
/** 可用余额(分) */
useBalance?: number;
/** 余额限制(分) */
balanceLimit?: number;
}
// 修改用户余额请求参数
export interface UpdateUserBalanceCommand extends AddUserBalanceCommand { }
// 余额增减操作请求参数
export interface ChangeBalanceCommand {
/** 企业微信ID */
corpid: string;
/** 汇邦云用户ID */
ab98UserId: number;
/** 金额(分) */
amount: number;
}
// 根据企业微信ID和汇邦云用户ID查询参数
export interface QueryByCorpidAb98Params {
corpid: string;
ab98UserId: number;
}
// 根据openid查询参数
export interface QueryByOpenidParams {
openid: string;
}
// 批量删除参数
export interface BatchDeleteParams {
ids: number[];
}
// 用户余额相关API
export const getUserBalanceListApi = (params: UserBalanceListParams) => {
return http.request<ResponseData<PageDTO<UserBalance>>>("get", "/ab98/balance", { params });
};
export const getUserBalanceDetailApi = (id: number) => {
return http.request<ResponseData<UserBalance>>("get", `/ab98/balance/${id}`);
};
export const getUserBalanceByCorpidAb98Api = (params: QueryByCorpidAb98Params) => {
return http.request<ResponseData<UserBalance>>("get", "/ab98/balance/byCorpidAb98", { params });
};
export const getUserBalanceByOpenidApi = (params: QueryByOpenidParams) => {
return http.request<ResponseData<UserBalance>>("get", "/ab98/balance/byOpenid", { params });
};
export const addUserBalanceApi = (data: AddUserBalanceCommand) => {
return http.request<ResponseData<UserBalance>>("post", "/ab98/balance", { data });
};
export const updateUserBalanceApi = (id: number, data: UpdateUserBalanceCommand) => {
return http.request<ResponseData<UserBalance>>("put", `/ab98/balance/${id}`, { data });
};
export const deleteUserBalanceApi = (id: number) => {
return http.request<ResponseData<void>>("delete", `/ab98/balance/${id}`);
};
export const batchDeleteUserBalanceApi = (ids: number[]) => {
return http.request<ResponseData<void>>("delete", `/ab98/balance/batch/${ids.join(',')}`);
};
export const addUserBalanceAmountApi = (data: ChangeBalanceCommand) => {
return http.request<ResponseData<string>>("post", "/ab98/balance/addBalance", { data });
};
export const subtractUserBalanceAmountApi = (data: ChangeBalanceCommand) => {
return http.request<ResponseData<string>>("post", "/ab98/balance/subtractBalance", { data });
};
export const insertOrUpdateUserBalanceApi = (data: AddUserBalanceCommand) => {
return http.request<ResponseData<UserBalance>>("post", "/ab98/balance/insertOrUpdate", { data });
};

View File

@ -1,4 +1,5 @@
import { http } from "@/utils/http";
import { UserBalance } from "./balance";
export interface Ab98UserDTO {
/** 主键ID */
@ -64,7 +65,8 @@ export interface Ab98UserDetailDTO {
useBalance?: number;
/** 借呗额度(单位:分) */
balanceLimit?: number;
/** 用户余额实体 */
userBalanceEntity?: UserBalance;
}
export interface Ab98UserQuery extends BasePageQuery {

View File

@ -0,0 +1,87 @@
<script setup lang="ts">
import { ref, reactive, watch } from "vue";
import { ElMessage } from "element-plus";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { insertOrUpdateUserBalanceApi } from "@/api/ab98/balance";
import { useWxStore } from "@/store/modules/wx";
import Confirm from "@iconify-icons/ep/check";
import type { FormRules } from 'element-plus';
const props = defineProps({
visible: {
type: Boolean,
default: false
},
row: {
type: Object,
default: null
}
});
const emit = defineEmits(["update:visible", "refresh"]);
const wxStore = useWxStore();
const formRef = ref();
const formData = reactive({
balanceLimit: 0
});
const rules = reactive<FormRules>({
balanceLimit: [
{ required: true, message: "请输入金额", trigger: "blur" },
{ type: 'number', message: '必须为数字类型' },
{ validator: (_, v, cb) => v >= 0 ? cb() : cb(new Error('金额不能小于0')), trigger: 'blur' }
]
});
const handleConfirm = async () => {
try {
await formRef.value.validate();
//
const balanceLimitInFen = Math.round(formData.balanceLimit * 100);
await insertOrUpdateUserBalanceApi({
corpid: wxStore.corpid,
ab98UserId: props.row.ab98UserId,
balanceLimit: balanceLimitInFen
});
ElMessage.success("借呗额度修改成功");
emit("refresh");
closeDialog();
} catch (error) {
console.error("表单提交失败", error);
}
};
const closeDialog = () => {
formRef.value.resetFields();
emit("update:visible", false);
};
watch(() => props.row, (val) => {
if (val) {
//
formData.balanceLimit = val.balanceLimit ? val.balanceLimit / 100 : 0;
}
}, { immediate: true });
</script>
<template>
<el-dialog title="修改借呗额度" :model-value="visible" width="500px" @close="closeDialog">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="金额" prop="balanceLimit">
<el-input-number v-model="formData.balanceLimit" :precision="2" :step="0.1" :min="0" controls-position="right"
class="!w-[200px]" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" :icon="useRenderIcon(Confirm)" @click="handleConfirm">
确认
</el-button>
</template>
</el-dialog>
</template>

View File

@ -8,6 +8,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
import { PureTableBar } from "@/components/RePureTableBar";
import { useWxStore } from "@/store/modules/wx";
import { formatFenToYuan } from "@/utils/currency";
import BalanceEditModal from "./BalanceEditModal.vue";
defineOptions({
name: "Ab98UserDetail"
@ -23,6 +24,7 @@ const showAddTagDialog = ref(false);
const addTagForm = ref({
tagName: ''
});
const balanceVisible = ref(false);
//
const basicInfo = ref({
@ -130,6 +132,10 @@ async function handleDeleteTag(tagId: number) {
onMounted(() => {
fetchUserDetail();
});
async function handleModifyBalance() {
balanceVisible.value = true;
}
</script>
<template>
@ -169,8 +175,11 @@ onMounted(() => {
<el-descriptions-item label="会员ID">{{ userInfo.ab98UserId }}</el-descriptions-item>
<el-descriptions-item label="微信openid">{{ userInfo.openid }}</el-descriptions-item>
<el-descriptions-item label="注册时间" :span="2">{{ userInfo.createTime }}</el-descriptions-item>
<el-descriptions-item label="借呗额度" :span="2">{{ formatFenToYuan(userInfo.balanceLimit)
}}</el-descriptions-item>
<el-descriptions-item label="借呗额度" :span="2">{{ formatFenToYuan(userInfo.balanceLimit) }}
<el-button type="primary" size="small" @click="handleModifyBalance">
修改
</el-button>
</el-descriptions-item>
<el-descriptions-item label="剩余借呗">{{ formatFenToYuan(userInfo.balance) }}</el-descriptions-item>
<el-descriptions-item label="已使用借呗">{{ formatFenToYuan(userInfo.useBalance) }}</el-descriptions-item>
<el-descriptions-item label="标签" :span="2">
@ -257,6 +266,7 @@ onMounted(() => {
<el-button type="primary" @click="handleAddTag">确定</el-button>
</template>
</el-dialog>
<BalanceEditModal v-model:visible="balanceVisible" :row="userInfo" @refresh="fetchUserDetail" />
</div>
</template>