Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1251a0be83 | |||
| 7cc56af0d5 |
@@ -5,16 +5,27 @@
|
|||||||
import { get } from '@/utils/request';
|
import { get } from '@/utils/request';
|
||||||
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
|
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
|
||||||
import { buildMockPaymentFlowPage } from '@/config/mock/paymentFlow';
|
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));
|
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
||||||
|
|
||||||
/** GET /admin/finance/paymentFlow/page — 支付流水分页 */
|
/** GET /admin/finance/paymentFlow/page — 支付流水分页 */
|
||||||
export async function getPaymentFlowList(
|
export async function getPaymentFlowList(
|
||||||
params: PaymentFlowQueryParams,
|
params: PaymentFlowQueryParams,
|
||||||
): Promise<ApiResult<PageData<PaymentFlowVO>>> {
|
): Promise<ApiResult<PaymentFlowPageData>> {
|
||||||
if (USE_MOCK) {
|
if (USE_MOCK) {
|
||||||
await delay(MOCK_DELAY);
|
await delay(MOCK_DELAY);
|
||||||
const page = Number(params.page) || 1;
|
const page = Number(params.page) || 1;
|
||||||
|
|||||||
@@ -41,6 +41,22 @@ export interface PageData<T> {
|
|||||||
total: number;
|
total: number;
|
||||||
list: T[];
|
list: T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 支付流水分页扩展汇总(与 total 同级) */
|
||||||
|
export interface PaymentFlowExData {
|
||||||
|
/** 总支付金额 */
|
||||||
|
totalAmount: string;
|
||||||
|
/** 总退款金额 */
|
||||||
|
totalRefundAmount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 支付流水分页响应 */
|
||||||
|
export interface PaymentFlowPageData {
|
||||||
|
total: number;
|
||||||
|
list: PaymentFlowVO[];
|
||||||
|
exData: PaymentFlowExData;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiResult<T> {
|
export interface ApiResult<T> {
|
||||||
code: number;
|
code: number;
|
||||||
msg: string;
|
msg: string;
|
||||||
|
|||||||
@@ -41,6 +41,19 @@ export interface RoleUsageUserQueryParams {
|
|||||||
roleId?: string;
|
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> {
|
export interface PageData<T> {
|
||||||
total: number;
|
total: number;
|
||||||
list: T[];
|
list: T[];
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export type {
|
|||||||
|
|
||||||
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
||||||
|
|
||||||
/** GET /sys/user/page — 用户分页 */
|
/** GET /admin/sys/user/page — 用户分页 */
|
||||||
export async function getUserList(
|
export async function getUserList(
|
||||||
params: UserListQueryParams,
|
params: UserListQueryParams,
|
||||||
): Promise<ApiResult<PageData<TournamentAdminUserVO>>> {
|
): Promise<ApiResult<PageData<TournamentAdminUserVO>>> {
|
||||||
@@ -38,7 +38,7 @@ export async function getUserList(
|
|||||||
const limit = Number(params.limit) || 10;
|
const limit = Number(params.limit) || 10;
|
||||||
return { code: 200, msg: 'success', data: buildMockUserListPage(page, limit) };
|
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 — 新增用户 */
|
/** POST /sys/user/save — 新增用户 */
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* 支付流水假数据
|
* 支付流水假数据
|
||||||
* 对应接口:GET /admin/finance/paymentFlow/page
|
* 对应接口: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[] = [
|
const MOCK_LIST: PaymentFlowVO[] = [
|
||||||
{
|
{
|
||||||
@@ -127,7 +127,7 @@ export function buildMockPaymentFlowPage(
|
|||||||
page: number,
|
page: number,
|
||||||
limit: number,
|
limit: number,
|
||||||
params: PaymentFlowQueryParams,
|
params: PaymentFlowQueryParams,
|
||||||
): PageData<PaymentFlowVO> {
|
): PaymentFlowPageData {
|
||||||
let filtered = [...MOCK_LIST];
|
let filtered = [...MOCK_LIST];
|
||||||
|
|
||||||
if (params.nickname) {
|
if (params.nickname) {
|
||||||
@@ -143,7 +143,22 @@ export function buildMockPaymentFlowPage(
|
|||||||
inDateRange(item.createDate, params.createDateBegin, params.createDateEnd),
|
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 total = filtered.length;
|
||||||
const start = (page - 1) * limit;
|
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),
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,11 +83,11 @@ export default defineComponent({
|
|||||||
<div class={pageStyles.summary}>
|
<div class={pageStyles.summary}>
|
||||||
<span class={pageStyles.summaryItem}>
|
<span class={pageStyles.summaryItem}>
|
||||||
<span class={pageStyles.summaryLabel}>总支付金额:</span>
|
<span class={pageStyles.summaryLabel}>总支付金额:</span>
|
||||||
<span class={pageStyles.summaryValue}>{summary.value.totalPay}元</span>
|
<span class={pageStyles.summaryValue}>{summary.value.totalAmount}元</span>
|
||||||
</span>
|
</span>
|
||||||
<span class={pageStyles.summaryItem}>
|
<span class={pageStyles.summaryItem}>
|
||||||
<span class={pageStyles.summaryLabel}>总退款金额:</span>
|
<span class={pageStyles.summaryLabel}>总退款金额:</span>
|
||||||
<span class={pageStyles.summaryValue}>{summary.value.totalRefund}元</span>
|
<span class={pageStyles.summaryValue}>{summary.value.totalRefundAmount}元</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div ref={containerRef} class={pageStyles.tableBody}>
|
<div ref={containerRef} class={pageStyles.tableBody}>
|
||||||
|
|||||||
@@ -8,21 +8,22 @@ import { buildMockPaymentFlowPage } from '@/config/mock/paymentFlow';
|
|||||||
import type {
|
import type {
|
||||||
PaymentFlowQueryParams,
|
PaymentFlowQueryParams,
|
||||||
PaymentFlowVO,
|
PaymentFlowVO,
|
||||||
PageData,
|
PaymentFlowPageData,
|
||||||
ApiResult,
|
ApiResult,
|
||||||
} from '@/api/payments/types';
|
} from '@/api/payments/types';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
PaymentFlowVO,
|
PaymentFlowVO,
|
||||||
PaymentFlowQueryParams,
|
PaymentFlowQueryParams,
|
||||||
PageData,
|
PaymentFlowPageData,
|
||||||
|
PaymentFlowExData,
|
||||||
ApiResult,
|
ApiResult,
|
||||||
} from '@/api/payments/types';
|
} from '@/api/payments/types';
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// URL 常量
|
// URL 常量
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const paymentFlowList = '/finance/paymentFlow/page';
|
const paymentFlowList = '/admin/finance/paymentFlow/page';
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
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(
|
export async function getPaymentFlowList(
|
||||||
params: PaymentFlowQueryParams,
|
params: PaymentFlowQueryParams,
|
||||||
): Promise<ApiResult<PageData<PaymentFlowVO>>> {
|
): Promise<ApiResult<PaymentFlowPageData>> {
|
||||||
if (USE_MOCK) {
|
if (USE_MOCK) {
|
||||||
await delay(MOCK_DELAY);
|
await delay(MOCK_DELAY);
|
||||||
const page = Number(params.page) || 1;
|
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);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
@@ -104,21 +104,14 @@ export function usePaymentsModel() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const queryParams = buildQueryParams();
|
const queryParams = buildQueryParams();
|
||||||
console.log('支付流水查询参数:', queryParams);
|
|
||||||
const res = await getPaymentFlowList(queryParams);
|
const res = await getPaymentFlowList(queryParams);
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
console.log('支付流水查询结果:', { total: res.data.total, count: res.data.list.length });
|
|
||||||
setDataSource(res.data.list);
|
setDataSource(res.data.list);
|
||||||
setPagination({ ...pagination.value, total: res.data.total });
|
setPagination({ ...pagination.value, total: res.data.total });
|
||||||
// 汇总:type=1 金额为收入,type=2 金额为退款
|
setSummary({
|
||||||
let totalPay = 0;
|
totalAmount: res.data.exData?.totalAmount ?? '0.00',
|
||||||
let totalRefund = 0;
|
totalRefundAmount: res.data.exData?.totalRefundAmount ?? '0.00',
|
||||||
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) });
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// 网络层已统一提示
|
// 网络层已统一提示
|
||||||
@@ -134,7 +127,7 @@ export function usePaymentsModel() {
|
|||||||
filterForm.type = '';
|
filterForm.type = '';
|
||||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||||
setDataSource([]);
|
setDataSource([]);
|
||||||
setSummary({ totalPay: '0.00', totalRefund: '0.00' });
|
setSummary({ totalAmount: '0.00', totalRefundAmount: '0.00' });
|
||||||
setTimeout(() => handleSearch(), 350);
|
setTimeout(() => handleSearch(), 350);
|
||||||
}, 500);
|
}, 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 { Modal, Radio, Input, Button, Upload, Image, Spin, message } from 'ant-design-vue';
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import { getWithdrawInfo, postWithdrawAudit, type WithdrawInfoVO } from '../model/services';
|
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';
|
import styles from './WithdrawDetailModal.module.less';
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@@ -135,8 +135,8 @@ export default defineComponent({
|
|||||||
{renderItem('费率:', `${(Number(d.feeRate) * 100).toFixed(2)}%`)}
|
{renderItem('费率:', `${(Number(d.feeRate) * 100).toFixed(2)}%`)}
|
||||||
{renderItem('手续费:', `¥${d.feeAmount}`)}
|
{renderItem('手续费:', `¥${d.feeAmount}`)}
|
||||||
{renderItem('到账金额:', `¥${d.receivedAmount}`, true)}
|
{renderItem('到账金额:', `¥${d.receivedAmount}`, true)}
|
||||||
{renderItem('审核状态:', AUDIT_STATUS_MAP[d.auditStatus] ?? '-', true)}
|
{renderItem('审核状态:', getAuditStatusText(d.auditStatus), true)}
|
||||||
{renderItem('列账状态:', PAY_STATUS_MAP[d.payStatus] ?? '-')}
|
{renderItem('列账状态:', getPayStatusText(d.payStatus))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineComponent, onMounted } from 'vue';
|
import { defineComponent, onMounted } from 'vue';
|
||||||
import { Button, Input, Table, Form, Space, Pagination, Select } from 'ant-design-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 { useContainerSize } from '@/hooks';
|
||||||
import WithdrawDetailModal from './components/WithdrawDetailModal';
|
import WithdrawDetailModal from './components/WithdrawDetailModal';
|
||||||
import pageStyles from '@/assets/styles/pageLayout.module.less';
|
import pageStyles from '@/assets/styles/pageLayout.module.less';
|
||||||
@@ -21,10 +21,10 @@ function renderBodyCell({
|
|||||||
onView: (record: any) => void;
|
onView: (record: any) => void;
|
||||||
}) {
|
}) {
|
||||||
if (column.key === 'auditStatus') {
|
if (column.key === 'auditStatus') {
|
||||||
return <span>{AUDIT_STATUS_MAP[record.auditStatus] || record.auditStatus || '-'}</span>;
|
return renderAuditStatus(record.auditStatus);
|
||||||
}
|
}
|
||||||
if (column.key === 'payStatus') {
|
if (column.key === 'payStatus') {
|
||||||
return <span>{PAY_STATUS_MAP[record.payStatus] ?? '-'}</span>;
|
return renderPayStatus(record.payStatus);
|
||||||
}
|
}
|
||||||
if (column.key === 'action') {
|
if (column.key === 'action') {
|
||||||
// auditStatus=0(待审核)→ 显示「审核」,其他 → 显示「查看」
|
// 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 { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||||
import { getWithdrawList, type WithdrawVO, type WithdrawQueryParams } from './services';
|
import { getWithdrawList, type WithdrawVO, type WithdrawQueryParams } from './services';
|
||||||
|
|
||||||
@@ -22,20 +23,45 @@ export const PAY_STATUS_OPTIONS = [
|
|||||||
{ value: '2', label: '支付失败' },
|
{ value: '2', label: '支付失败' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/** 审核状态文案映射 */
|
/** 审核状态文案 + 色调映射 */
|
||||||
export const AUDIT_STATUS_MAP: Record<number, string> = {
|
const AUDIT_STATUS_MAP: Record<number, { label: string; tone: StatusTagTone }> = {
|
||||||
0: '待审核',
|
0: { label: '待审核', tone: 'warning' },
|
||||||
1: '审核通过',
|
1: { label: '审核通过', tone: 'success' },
|
||||||
2: '审核驳回',
|
2: { label: '审核驳回', tone: 'danger' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 到账状态文案映射 */
|
/** 到账状态文案 + 色调映射 */
|
||||||
export const PAY_STATUS_MAP: Record<number, string> = {
|
const PAY_STATUS_MAP: Record<number, { label: string; tone: StatusTagTone }> = {
|
||||||
0: '未支付',
|
0: { label: '未支付', tone: 'default' },
|
||||||
1: '支付成功',
|
1: { label: '支付成功', tone: 'success' },
|
||||||
2: '支付失败',
|
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
|
// Model
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export type {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// URL 常量
|
// URL 常量
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const operationLogList = '/sys/operation/page';
|
const operationLogList = '/admin/sys/operation/page';
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,7 @@ import { defineComponent, ref, reactive, watch } from 'vue';
|
|||||||
import { Modal, Form, Input, Tree, Button, Spin, message } from 'ant-design-vue';
|
import { Modal, Form, Input, Tree, Button, Spin, message } from 'ant-design-vue';
|
||||||
import type { TreeProps } from 'ant-design-vue';
|
import type { TreeProps } from 'ant-design-vue';
|
||||||
import { getPermissionTree, type PermissionTreeNode } from '../model/services';
|
import { getPermissionTree, type PermissionTreeNode } from '../model/services';
|
||||||
import {
|
import { roleNameRules, buildTreeData, type AntdTreeNode } from '../controller';
|
||||||
roleNameRules,
|
|
||||||
buildTreeData,
|
|
||||||
extractLeafPermissions,
|
|
||||||
type AntdTreeNode,
|
|
||||||
} from '../controller';
|
|
||||||
import styles from './RoleFormModal.module.less';
|
import styles from './RoleFormModal.module.less';
|
||||||
|
|
||||||
interface RoleFormModalProps {
|
interface RoleFormModalProps {
|
||||||
@@ -110,18 +105,26 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEdit = !!props.record;
|
// 从原始权限树中收集所有叶节点 ID(children 为空或不存在)
|
||||||
// 仅提取叶节点 { id, selected } 传给后端
|
const leafNodeIds = new Set<string>();
|
||||||
const leafPermissions = extractLeafPermissions(rawApiTree.value);
|
const collectLeafIds = (nodes: PermissionTreeNode[]) => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!node.children || node.children.length === 0) {
|
||||||
|
leafNodeIds.add(String(node.id));
|
||||||
|
} else {
|
||||||
|
collectLeafIds(node.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
collectLeafIds(rawApiTree.value);
|
||||||
|
|
||||||
|
// 从用户当前勾选的 checkedKeys 中只取叶节点 ID
|
||||||
|
const menuIds = formData.checkedKeys.filter((key: string) => leafNodeIds.has(key));
|
||||||
|
|
||||||
props.onSave({
|
props.onSave({
|
||||||
roleName: formData.roleName,
|
roleName: formData.roleName,
|
||||||
roleId: props.record?.roleId,
|
roleId: props.record?.roleId,
|
||||||
// 完整选中 key 列表(含父节点),供向后兼容
|
menuIds,
|
||||||
permissions: formData.checkedKeys,
|
|
||||||
// 仅叶节点数据,后端实际需要的格式
|
|
||||||
leafPermissions,
|
|
||||||
isEdit,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -66,33 +66,3 @@ export function buildTreeData(apiTree: PermissionTreeNode[]): {
|
|||||||
const treeData = apiTree.map((node) => convertNode(node, leafKeys));
|
const treeData = apiTree.map((node) => convertNode(node, leafKeys));
|
||||||
return { treeData, checkedKeys: Array.from(leafKeys) };
|
return { treeData, checkedKeys: Array.from(leafKeys) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从后端权限树中提取叶节点数据,整理为提交格式。
|
|
||||||
*
|
|
||||||
* 只返回叶节点,格式为 `{ id, selected }` 数组。
|
|
||||||
* 父节点的勾选状态由后端根据叶节点数据推导,前端不传父节点。
|
|
||||||
*/
|
|
||||||
export function extractLeafPermissions(
|
|
||||||
apiTree: PermissionTreeNode[],
|
|
||||||
): { id: number; selected: boolean }[] {
|
|
||||||
const result: { id: number; selected: boolean }[] = [];
|
|
||||||
|
|
||||||
const walk = (nodes: PermissionTreeNode[]) => {
|
|
||||||
for (const node of nodes) {
|
|
||||||
const isLeaf = !node.children || node.children.length === 0;
|
|
||||||
if (isLeaf) {
|
|
||||||
result.push({
|
|
||||||
id: node.id,
|
|
||||||
// null 视为 false,即未授权
|
|
||||||
selected: node.selected === true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
walk(node.children!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
walk(apiTree);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,13 +3,11 @@
|
|||||||
* 该页面使用的所有接口集中管理
|
* 该页面使用的所有接口集中管理
|
||||||
*/
|
*/
|
||||||
import { get, post } from '@/utils/request';
|
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 {
|
import type {
|
||||||
RoleListQueryParams,
|
RoleListQueryParams,
|
||||||
RoleUsageUserQueryParams,
|
RoleUsageUserQueryParams,
|
||||||
|
RoleSaveParams,
|
||||||
|
RoleUpdateParams,
|
||||||
TournamentAdminRolePageVO,
|
TournamentAdminRolePageVO,
|
||||||
RoleUsageUserVO,
|
RoleUsageUserVO,
|
||||||
PermissionTreeNode,
|
PermissionTreeNode,
|
||||||
@@ -22,6 +20,8 @@ export type {
|
|||||||
RoleUsageUserVO,
|
RoleUsageUserVO,
|
||||||
RoleListQueryParams,
|
RoleListQueryParams,
|
||||||
RoleUsageUserQueryParams,
|
RoleUsageUserQueryParams,
|
||||||
|
RoleSaveParams,
|
||||||
|
RoleUpdateParams,
|
||||||
PermissionTreeNode,
|
PermissionTreeNode,
|
||||||
PageData,
|
PageData,
|
||||||
ApiResult,
|
ApiResult,
|
||||||
@@ -30,56 +30,49 @@ export type {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// URL 常量
|
// URL 常量
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const roleList = '/sys/role/page';
|
const roleList = '/admin/sys/role/page';
|
||||||
const roleUsageUserList = '/sys/role/usageUserPage';
|
const roleSave = '/admin/sys/role/save';
|
||||||
const rolePermissionList = '/sys/role/permissionList';
|
const roleUpdate = '/admin/sys/role/update';
|
||||||
const roleDelete = '/sys/role/del';
|
const roleUsageUserList = '/admin/sys/role/usageUserPage';
|
||||||
|
const rolePermissionList = '/admin/sys/role/permissionList';
|
||||||
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
const roleDelete = '/admin/sys/role/del';
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// API 函数
|
// API 函数
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
/** 角色列表分页 */
|
/** 角色列表分页 — GET /admin/sys/role/page */
|
||||||
export async function getRoleList(
|
export function getRoleList(
|
||||||
params: RoleListQueryParams,
|
params: RoleListQueryParams,
|
||||||
): Promise<ApiResult<PageData<TournamentAdminRolePageVO>>> {
|
): 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>);
|
return get(roleList, params as Record<string, any>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 角色下用户列表分页 */
|
/** 新增角色 — POST /admin/sys/role/save */
|
||||||
export async function getRoleUsageUserList(
|
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,
|
params: RoleUsageUserQueryParams,
|
||||||
): Promise<ApiResult<PageData<RoleUsageUserVO>>> {
|
): 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>);
|
return get(roleUsageUserList, params as Record<string, any>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 角色权限树 */
|
/** 角色权限树 — GET /admin/sys/role/permissionList */
|
||||||
export async function getPermissionTree(
|
export function getPermissionTree(
|
||||||
roleId: number | string,
|
roleId: number | string,
|
||||||
): Promise<ApiResult<PermissionTreeNode[]>> {
|
): 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) });
|
return get(rolePermissionList, { roleId: String(roleId) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除角色 */
|
/** 删除角色 — POST /admin/sys/role/del */
|
||||||
export function deleteRole(id: number): Promise<ApiResult<null>> {
|
export function deleteRole(id: number): Promise<ApiResult<null>> {
|
||||||
return post(roleDelete, { id });
|
return post(roleDelete, { id });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { message } from 'ant-design-vue';
|
|||||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
getRoleList,
|
getRoleList,
|
||||||
|
saveRole,
|
||||||
|
updateRole,
|
||||||
deleteRole,
|
deleteRole,
|
||||||
type TournamentAdminRolePageVO,
|
type TournamentAdminRolePageVO,
|
||||||
type RoleListQueryParams,
|
type RoleListQueryParams,
|
||||||
@@ -125,11 +127,29 @@ export function useRoleModel() {
|
|||||||
if (formSubmitting.value) return;
|
if (formSubmitting.value) return;
|
||||||
setFormSubmitting(true);
|
setFormSubmitting(true);
|
||||||
try {
|
try {
|
||||||
console.log(isEdit.value ? '编辑角色' : '新增角色', formPayload);
|
// menuIds 由 RoleFormModal 从用户勾选的 checkedKeys 中过滤叶节点后传入
|
||||||
// TODO: 替换为真实 API 调用(/sys/role/save、/sys/role/update)
|
const menuIds: string[] = formPayload.menuIds || [];
|
||||||
|
|
||||||
|
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 ? '编辑成功' : '新增成功');
|
message.success(isEdit.value ? '编辑成功' : '新增成功');
|
||||||
handleCloseForm();
|
handleCloseForm();
|
||||||
handleSearch();
|
handleSearch();
|
||||||
|
} else {
|
||||||
|
message.error(res.msg || '操作失败');
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// 网络层已统一提示,不再重复 message.error
|
// 网络层已统一提示,不再重复 message.error
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* 该页面使用的所有接口集中管理
|
* 该页面使用的所有接口集中管理
|
||||||
*/
|
*/
|
||||||
import { get, post } from '@/utils/request';
|
import { get, post } from '@/utils/request';
|
||||||
import { fetchPermissions } from '@/api/menu';
|
|
||||||
import type {
|
import type {
|
||||||
UserListQueryParams,
|
UserListQueryParams,
|
||||||
UserSaveParams,
|
UserSaveParams,
|
||||||
@@ -29,6 +28,7 @@ export type {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// URL 常量
|
// URL 常量
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
const userPage = '/admin/sys/user/page';
|
||||||
const userSave = '/sys/user/save';
|
const userSave = '/sys/user/save';
|
||||||
const userUpdate = '/sys/user/update';
|
const userUpdate = '/sys/user/update';
|
||||||
const userActive = '/sys/user/active';
|
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(
|
export function getUserList(
|
||||||
_params: UserListQueryParams,
|
params: UserListQueryParams,
|
||||||
): Promise<ApiResult<PageData<TournamentAdminUserVO>>> {
|
): Promise<ApiResult<PageData<TournamentAdminUserVO>>> {
|
||||||
// 调用 fetchPermissions 获取用户权限数据
|
return get(userPage, params as Record<string, any>);
|
||||||
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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增用户 */
|
/** 新增用户 */
|
||||||
|
|||||||
Reference in New Issue
Block a user