fix: 对接提现申请与用户钱包,支付流水页面,对接角色权限页面

This commit is contained in:
ZhuRui
2026-08-04 18:26:47 +08:00
parent bf3f380ba8
commit 7cc56af0d5
14 changed files with 180 additions and 123 deletions
+14 -3
View File
@@ -5,16 +5,27 @@
import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockPaymentFlowPage } from '@/config/mock/paymentFlow';
import type { PaymentFlowQueryParams, PaymentFlowVO, PageData, ApiResult } from './types';
import type {
PaymentFlowQueryParams,
PaymentFlowVO,
PaymentFlowPageData,
ApiResult,
} from './types';
export type { PaymentFlowVO, PaymentFlowQueryParams, PageData, ApiResult } from './types';
export type {
PaymentFlowVO,
PaymentFlowQueryParams,
PaymentFlowPageData,
PaymentFlowExData,
ApiResult,
} from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** GET /admin/finance/paymentFlow/page — 支付流水分页 */
export async function getPaymentFlowList(
params: PaymentFlowQueryParams,
): Promise<ApiResult<PageData<PaymentFlowVO>>> {
): Promise<ApiResult<PaymentFlowPageData>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const page = Number(params.page) || 1;
+16
View File
@@ -41,6 +41,22 @@ export interface PageData<T> {
total: number;
list: T[];
}
/** 支付流水分页扩展汇总(与 total 同级) */
export interface PaymentFlowExData {
/** 总支付金额 */
totalAmount: string;
/** 总退款金额 */
totalRefundAmount: string;
}
/** 支付流水分页响应 */
export interface PaymentFlowPageData {
total: number;
list: PaymentFlowVO[];
exData: PaymentFlowExData;
}
export interface ApiResult<T> {
code: number;
msg: string;
+13
View File
@@ -41,6 +41,19 @@ export interface RoleUsageUserQueryParams {
roleId?: string;
}
/** 保存角色 — POST /admin/sys/role/save */
export interface RoleSaveParams {
name: string;
menuIds: string[];
}
/** 更新角色 — POST /admin/sys/role/update */
export interface RoleUpdateParams {
id: string;
name: string;
menuIds: string[];
}
export interface PageData<T> {
total: number;
list: T[];
+2 -2
View File
@@ -28,7 +28,7 @@ export type {
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** GET /sys/user/page — 用户分页 */
/** GET /admin/sys/user/page — 用户分页 */
export async function getUserList(
params: UserListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminUserVO>>> {
@@ -38,7 +38,7 @@ export async function getUserList(
const limit = Number(params.limit) || 10;
return { code: 200, msg: 'success', data: buildMockUserListPage(page, limit) };
}
return get('/sys/user/page', params as Record<string, any>);
return get('/admin/sys/user/page', params as Record<string, any>);
}
/** POST /sys/user/save — 新增用户 */
+18 -3
View File
@@ -2,7 +2,7 @@
* 支付流水假数据
* 对应接口:GET /admin/finance/paymentFlow/page
*/
import type { PaymentFlowVO, PaymentFlowQueryParams, PageData } from '@/api/payments';
import type { PaymentFlowVO, PaymentFlowQueryParams, PaymentFlowPageData } from '@/api/payments';
const MOCK_LIST: PaymentFlowVO[] = [
{
@@ -127,7 +127,7 @@ export function buildMockPaymentFlowPage(
page: number,
limit: number,
params: PaymentFlowQueryParams,
): PageData<PaymentFlowVO> {
): PaymentFlowPageData {
let filtered = [...MOCK_LIST];
if (params.nickname) {
@@ -143,7 +143,22 @@ export function buildMockPaymentFlowPage(
inDateRange(item.createDate, params.createDateBegin, params.createDateEnd),
);
let totalAmount = 0;
let totalRefundAmount = 0;
for (const item of filtered) {
const amt = Number(item.amount) || 0;
if (item.type === 1) totalAmount += amt;
else if (item.type === 2) totalRefundAmount += amt;
}
const total = filtered.length;
const start = (page - 1) * limit;
return { total, list: filtered.slice(start, start + limit) };
return {
total,
list: filtered.slice(start, start + limit),
exData: {
totalAmount: totalAmount.toFixed(2),
totalRefundAmount: totalRefundAmount.toFixed(2),
},
};
}
+2 -2
View File
@@ -83,11 +83,11 @@ export default defineComponent({
<div class={pageStyles.summary}>
<span class={pageStyles.summaryItem}>
<span class={pageStyles.summaryLabel}></span>
<span class={pageStyles.summaryValue}>{summary.value.totalPay}</span>
<span class={pageStyles.summaryValue}>{summary.value.totalAmount}</span>
</span>
<span class={pageStyles.summaryItem}>
<span class={pageStyles.summaryLabel}>退</span>
<span class={pageStyles.summaryValue}>{summary.value.totalRefund}</span>
<span class={pageStyles.summaryValue}>{summary.value.totalRefundAmount}</span>
</span>
</div>
<div ref={containerRef} class={pageStyles.tableBody}>
+5 -4
View File
@@ -8,21 +8,22 @@ import { buildMockPaymentFlowPage } from '@/config/mock/paymentFlow';
import type {
PaymentFlowQueryParams,
PaymentFlowVO,
PageData,
PaymentFlowPageData,
ApiResult,
} from '@/api/payments/types';
export type {
PaymentFlowVO,
PaymentFlowQueryParams,
PageData,
PaymentFlowPageData,
PaymentFlowExData,
ApiResult,
} from '@/api/payments/types';
// ============================================================
// URL 常量
// ============================================================
const paymentFlowList = '/finance/paymentFlow/page';
const paymentFlowList = '/admin/finance/paymentFlow/page';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
@@ -33,7 +34,7 @@ const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** 支付流水分页 */
export async function getPaymentFlowList(
params: PaymentFlowQueryParams,
): Promise<ApiResult<PageData<PaymentFlowVO>>> {
): Promise<ApiResult<PaymentFlowPageData>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const page = Number(params.page) || 1;
@@ -41,7 +41,7 @@ export function usePaymentsModel() {
);
// ===== 汇总 =====
const [summary, setSummary] = useState({ totalPay: '0.00', totalRefund: '0.00' });
const [summary, setSummary] = useState({ totalAmount: '0.00', totalRefundAmount: '0.00' });
// ===== 表格状态 =====
const [loading, setLoading] = useState<boolean>(false);
@@ -104,21 +104,14 @@ export function usePaymentsModel() {
setLoading(true);
try {
const queryParams = buildQueryParams();
console.log('支付流水查询参数:', queryParams);
const res = await getPaymentFlowList(queryParams);
if (res.code == 200) {
console.log('支付流水查询结果:', { total: res.data.total, count: res.data.list.length });
setDataSource(res.data.list);
setPagination({ ...pagination.value, total: res.data.total });
// 汇总:type=1 金额为收入,type=2 金额为退款
let totalPay = 0;
let totalRefund = 0;
for (const item of res.data.list) {
const amt = Number(item.amount) || 0;
if (item.type === 1) totalPay += amt;
else if (item.type === 2) totalRefund += amt;
}
setSummary({ totalPay: totalPay.toFixed(2), totalRefund: totalRefund.toFixed(2) });
setSummary({
totalAmount: res.data.exData?.totalAmount ?? '0.00',
totalRefundAmount: res.data.exData?.totalRefundAmount ?? '0.00',
});
}
} catch {
// 网络层已统一提示
@@ -134,7 +127,7 @@ export function usePaymentsModel() {
filterForm.type = '';
setPagination({ current: 1, pageSize: 10, total: 0 });
setDataSource([]);
setSummary({ totalPay: '0.00', totalRefund: '0.00' });
setSummary({ totalAmount: '0.00', totalRefundAmount: '0.00' });
setTimeout(() => handleSearch(), 350);
}, 500);
@@ -2,7 +2,7 @@ import { defineComponent, reactive, ref, watch } from 'vue';
import { Modal, Radio, Input, Button, Upload, Image, Spin, message } from 'ant-design-vue';
import { PlusOutlined } from '@ant-design/icons-vue';
import { getWithdrawInfo, postWithdrawAudit, type WithdrawInfoVO } from '../model/services';
import { AUDIT_STATUS_MAP, PAY_STATUS_MAP } from '../model/useWithdrawModel';
import { getAuditStatusText, getPayStatusText } from '../model/useWithdrawModel';
import styles from './WithdrawDetailModal.module.less';
const { TextArea } = Input;
@@ -135,8 +135,8 @@ export default defineComponent({
{renderItem('费率:', `${(Number(d.feeRate) * 100).toFixed(2)}%`)}
{renderItem('手续费:', `¥${d.feeAmount}`)}
{renderItem('到账金额:', `¥${d.receivedAmount}`, true)}
{renderItem('审核状态:', AUDIT_STATUS_MAP[d.auditStatus] ?? '-', true)}
{renderItem('列账状态:', PAY_STATUS_MAP[d.payStatus] ?? '-')}
{renderItem('审核状态:', getAuditStatusText(d.auditStatus), true)}
{renderItem('列账状态:', getPayStatusText(d.payStatus))}
</div>
</div>
+3 -3
View File
@@ -1,6 +1,6 @@
import { defineComponent, onMounted } from 'vue';
import { Button, Input, Table, Form, Space, Pagination, Select } from 'ant-design-vue';
import { useWithdrawModel, AUDIT_STATUS_MAP, PAY_STATUS_MAP } from './model/useWithdrawModel';
import { useWithdrawModel, renderAuditStatus, renderPayStatus } from './model/useWithdrawModel';
import { useContainerSize } from '@/hooks';
import WithdrawDetailModal from './components/WithdrawDetailModal';
import pageStyles from '@/assets/styles/pageLayout.module.less';
@@ -21,10 +21,10 @@ function renderBodyCell({
onView: (record: any) => void;
}) {
if (column.key === 'auditStatus') {
return <span>{AUDIT_STATUS_MAP[record.auditStatus] || record.auditStatus || '-'}</span>;
return renderAuditStatus(record.auditStatus);
}
if (column.key === 'payStatus') {
return <span>{PAY_STATUS_MAP[record.payStatus] ?? '-'}</span>;
return renderPayStatus(record.payStatus);
}
if (column.key === 'action') {
// auditStatus=0(待审核)→ 显示「审核」,其他 → 显示「查看」
@@ -1,4 +1,5 @@
import { computed, reactive, toRef, Ref } from 'vue';
import { computed, reactive, toRef, Ref, h } from 'vue';
import { StatusTag, type StatusTagTone } from '@/components';
import { useState, useDebounce, useThrottleFn } from '@/hooks';
import { getWithdrawList, type WithdrawVO, type WithdrawQueryParams } from './services';
@@ -22,20 +23,45 @@ export const PAY_STATUS_OPTIONS = [
{ value: '2', label: '支付失败' },
] as const;
/** 审核状态文案映射 */
export const AUDIT_STATUS_MAP: Record<number, string> = {
0: '待审核',
1: '审核通过',
2: '审核驳回',
/** 审核状态文案 + 色调映射 */
const AUDIT_STATUS_MAP: Record<number, { label: string; tone: StatusTagTone }> = {
0: { label: '待审核', tone: 'warning' },
1: { label: '审核通过', tone: 'success' },
2: { label: '审核驳回', tone: 'danger' },
};
/** 到账状态文案映射 */
export const PAY_STATUS_MAP: Record<number, string> = {
0: '未支付',
1: '支付成功',
2: '支付失败',
/** 到账状态文案 + 色调映射 */
const PAY_STATUS_MAP: Record<number, { label: string; tone: StatusTagTone }> = {
0: { label: '未支付', tone: 'default' },
1: { label: '支付成功', tone: 'success' },
2: { label: '支付失败', tone: 'danger' },
};
/** 渲染审核状态 StatusTag */
export function renderAuditStatus(status: number) {
const info = AUDIT_STATUS_MAP[status] || {
label: String(status ?? '-'),
tone: 'default' as const,
};
return h(StatusTag, { label: info.label, tone: info.tone });
}
/** 渲染到账状态 StatusTag */
export function renderPayStatus(status: number) {
const info = PAY_STATUS_MAP[status] || { label: String(status ?? '-'), tone: 'default' as const };
return h(StatusTag, { label: info.label, tone: info.tone });
}
/** 获取审核状态纯文本(详情弹窗用) */
export function getAuditStatusText(status: number): string {
return AUDIT_STATUS_MAP[status]?.label ?? String(status ?? '-');
}
/** 获取到账状态纯文本(详情弹窗用) */
export function getPayStatusText(status: number): string {
return PAY_STATUS_MAP[status]?.label ?? String(status ?? '-');
}
// ============================================================
// Model
// ============================================================
+27 -34
View File
@@ -3,13 +3,11 @@
* 该页面使用的所有接口集中管理
*/
import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockRoleListPage } from '@/config/mock/roleList';
import { buildMockRoleUsageUserPage } from '@/config/mock/roleUsageUser';
import { buildMockPermissionTree } from '@/config/mock/permissionTree';
import type {
RoleListQueryParams,
RoleUsageUserQueryParams,
RoleSaveParams,
RoleUpdateParams,
TournamentAdminRolePageVO,
RoleUsageUserVO,
PermissionTreeNode,
@@ -22,6 +20,8 @@ export type {
RoleUsageUserVO,
RoleListQueryParams,
RoleUsageUserQueryParams,
RoleSaveParams,
RoleUpdateParams,
PermissionTreeNode,
PageData,
ApiResult,
@@ -30,56 +30,49 @@ export type {
// ============================================================
// URL 常量
// ============================================================
const roleList = '/sys/role/page';
const roleUsageUserList = '/sys/role/usageUserPage';
const rolePermissionList = '/sys/role/permissionList';
const roleDelete = '/sys/role/del';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
const roleList = '/admin/sys/role/page';
const roleSave = '/admin/sys/role/save';
const roleUpdate = '/admin/sys/role/update';
const roleUsageUserList = '/admin/sys/role/usageUserPage';
const rolePermissionList = '/admin/sys/role/permissionList';
const roleDelete = '/admin/sys/role/del';
// ============================================================
// API 函数
// ============================================================
/** 角色列表分页 */
export async function getRoleList(
/** 角色列表分页 — GET /admin/sys/role/page */
export function getRoleList(
params: RoleListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminRolePageVO>>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const page = Number(params.page) || 1;
const limit = Number(params.limit) || 10;
return { code: 200, msg: 'success', data: buildMockRoleListPage(page, limit) };
}
return get(roleList, params as Record<string, any>);
}
/** 角色下用户列表分页 */
export async function getRoleUsageUserList(
/** 新增角色 — POST /admin/sys/role/save */
export function saveRole(params: RoleSaveParams): Promise<ApiResult<Record<string, never>>> {
return post(roleSave, params);
}
/** 更新角色 — POST /admin/sys/role/update */
export function updateRole(params: RoleUpdateParams): Promise<ApiResult<Record<string, never>>> {
return post(roleUpdate, params);
}
/** 角色下用户列表分页 — GET /admin/sys/role/usageUserPage */
export function getRoleUsageUserList(
params: RoleUsageUserQueryParams,
): Promise<ApiResult<PageData<RoleUsageUserVO>>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const page = Number(params.page) || 1;
const limit = Number(params.limit) || 10;
return { code: 200, msg: 'success', data: buildMockRoleUsageUserPage(page, limit) };
}
return get(roleUsageUserList, params as Record<string, any>);
}
/** 角色权限树 */
export async function getPermissionTree(
/** 角色权限树 — GET /admin/sys/role/permissionList */
export function getPermissionTree(
roleId: number | string,
): Promise<ApiResult<PermissionTreeNode[]>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const id = Number(roleId) || 0;
return { code: 200, msg: 'success', data: buildMockPermissionTree(id) };
}
return get(rolePermissionList, { roleId: String(roleId) });
}
/** 删除角色 */
/** 删除角色 — POST /admin/sys/role/del */
export function deleteRole(id: number): Promise<ApiResult<null>> {
return post(roleDelete, { id });
}
+27 -5
View File
@@ -3,6 +3,8 @@ import { message } from 'ant-design-vue';
import { useState, useDebounce, useThrottleFn } from '@/hooks';
import {
getRoleList,
saveRole,
updateRole,
deleteRole,
type TournamentAdminRolePageVO,
type RoleListQueryParams,
@@ -125,11 +127,31 @@ export function useRoleModel() {
if (formSubmitting.value) return;
setFormSubmitting(true);
try {
console.log(isEdit.value ? '编辑角色' : '新增角色', formPayload);
// TODO: 替换为真实 API 调用(/sys/role/save、/sys/role/update
message.success(isEdit.value ? '编辑成功' : '新增成功');
handleCloseForm();
handleSearch();
// 从叶节点权限中提取选中的 menuIds
const menuIds: string[] = (formPayload.leafPermissions || [])
.filter((item: any) => item.selected === true)
.map((item: any) => String(item.id));
let res: any;
if (isEdit.value) {
res = await updateRole({
id: String(formPayload.roleId),
name: formPayload.roleName,
menuIds,
});
} else {
res = await saveRole({
name: formPayload.roleName,
menuIds,
});
}
if (res.code == 200) {
message.success(isEdit.value ? '编辑成功' : '新增成功');
handleCloseForm();
handleSearch();
} else {
message.error(res.msg || '操作失败');
}
} catch {
// 网络层已统一提示,不再重复 message.error
} finally {
+7 -40
View File
@@ -3,7 +3,6 @@
* 该页面使用的所有接口集中管理
*/
import { get, post } from '@/utils/request';
import { fetchPermissions } from '@/api/menu';
import type {
UserListQueryParams,
UserSaveParams,
@@ -29,6 +28,7 @@ export type {
// ============================================================
// URL 常量
// ============================================================
const userPage = '/admin/sys/user/page';
const userSave = '/sys/user/save';
const userUpdate = '/sys/user/update';
const userActive = '/sys/user/active';
@@ -39,47 +39,14 @@ const userUpdatePwd = '/sys/user/updatepwd';
// ============================================================
/**
* 用户列表 - 通过 fetchPermissions 接口获取用户权限列表
* 接口: GET /admin/sys/role/permissionList
* 返回权限编码列表,前端做分页处理
* 用户列表分页
* 接口: GET /admin/sys/user/page
* 参数: page, limit, text(姓名/手机号), roleId(角色ID), status(0=停用,1=正常)
*/
export async function getUserList(
_params: UserListQueryParams,
export function getUserList(
params: UserListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminUserVO>>> {
// 调用 fetchPermissions 获取用户权限数据
const list = await fetchPermissions();
// 将权限编码字符串转为类用户列表格式,支持分页
const page = Number(_params.page) || 1;
const limit = Number(_params.limit) || 10;
const start = (page - 1) * limit;
const end = start + limit;
const pagedList = list.slice(start, end);
// 将权限编码映射为用户 VO 格式(后端返回的权限码可能包含用户信息)
const userListData: TournamentAdminUserVO[] = pagedList.map((item, index) => {
// 如果 item 是对象则直接使用,如果是字符串则构造基本结构
if (typeof item === 'object' && item !== null) {
return item as unknown as TournamentAdminUserVO;
}
return {
id: String(start + index + 1),
realName: String(item),
phone: '',
roleName: '',
status: '1',
createTime: '',
} as TournamentAdminUserVO;
});
return {
code: 200,
msg: 'success',
data: {
total: list.length,
list: userListData,
},
};
return get(userPage, params as Record<string, any>);
}
/** 新增用户 */