fix: 取消vue原生钩子函数,对接系统管理页面中的接口
This commit is contained in:
@@ -38,7 +38,7 @@ export async function getRoleList(
|
||||
const limit = Number(params.limit) || 10;
|
||||
return { code: 200, msg: 'success', data: buildMockRoleListPage(page, limit) };
|
||||
}
|
||||
return get('/sys/role/page', params as Record<string, any>);
|
||||
return get('/admin/sys/role/page', params as Record<string, any>);
|
||||
}
|
||||
|
||||
/** GET /sys/role/usageUserPage — 角色下用户列表分页 */
|
||||
|
||||
@@ -41,16 +41,16 @@ export async function getUserList(
|
||||
return get('/admin/sys/user/page', params as Record<string, any>);
|
||||
}
|
||||
|
||||
/** POST /sys/user/save — 新增用户 */
|
||||
/** POST /admin/sys/user/save — 新增用户 */
|
||||
export function saveUser(params: UserSaveParams): Promise<ApiResult<Record<string, never>>> {
|
||||
if (USE_MOCK) return delay(MOCK_DELAY).then(() => ({ code: 200, msg: 'success', data: {} }));
|
||||
return post('/sys/user/save', params);
|
||||
return post('/admin/sys/user/save', params);
|
||||
}
|
||||
|
||||
/** POST /sys/user/update — 更新用户 */
|
||||
/** POST /admin/sys/user/update — 更新用户 */
|
||||
export function updateUser(params: UserUpdateParams): Promise<ApiResult<Record<string, never>>> {
|
||||
if (USE_MOCK) return delay(MOCK_DELAY).then(() => ({ code: 200, msg: 'success', data: {} }));
|
||||
return post('/sys/user/update', params);
|
||||
return post('/admin/sys/user/update', params);
|
||||
}
|
||||
|
||||
/** POST /sys/user/active — 启用/禁用(直接调接口) */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
@@ -171,10 +171,6 @@ export default defineComponent({
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
<div class={pageStyles.filter}>
|
||||
@@ -237,9 +233,9 @@ export default defineComponent({
|
||||
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { computed, reactive, toRef, Ref, h } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { StatusTag, type StatusTagTone } from '@/components';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { useEffect } from '@/hooks/useEffect';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import {
|
||||
getBannerList,
|
||||
delBanner,
|
||||
@@ -61,21 +64,32 @@ export function useBannerModel() {
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 表格状态(API 返回全量,前端分页) =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [allData, setAllData] = useState<TournamentAdminBannerListVO[]>([]);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
// ===== 数据请求(API 返回全量,前端分页) =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest<TournamentAdminBannerListVO[]>(() => getBannerList(buildQueryParams()), {
|
||||
refreshDeps: [],
|
||||
formatResult: (res) => (res.code == 200 ? res.data : []),
|
||||
});
|
||||
|
||||
const allData = computed(() => data.value || []);
|
||||
|
||||
// ===== 前端分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
/** 当前页数据(客户端切片) */
|
||||
const dataSource = computed(() => {
|
||||
const start = (pagination.value.current - 1) * pagination.value.pageSize;
|
||||
return allData.value.slice(start, start + pagination.value.pageSize);
|
||||
const start = ((pagination as any).current.value - 1) * (pagination as any).pageSize.value;
|
||||
return allData.value.slice(start, start + (pagination as any).pageSize.value);
|
||||
});
|
||||
|
||||
// 同步前端分页 total
|
||||
useEffect(() => {
|
||||
pagination.setTotal(allData.value.length);
|
||||
}, [allData]);
|
||||
|
||||
// ===== 弹窗状态 =====
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
const [editingRecord, setEditingRecord] = useState<any>(null);
|
||||
@@ -119,37 +133,21 @@ export function useBannerModel() {
|
||||
// ===== 方法 =====
|
||||
|
||||
/** 查询 */
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
console.log('Banner列表查询参数:', queryParams);
|
||||
const res = await getBannerList(queryParams);
|
||||
|
||||
if (res.code == 200) {
|
||||
setAllData(res.data);
|
||||
setPagination({ current: 1, pageSize: 10, total: res.data.length });
|
||||
} else {
|
||||
message.error(res.msg || '查询失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Banner查询失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
/** 重置 */
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.title = '';
|
||||
filterForm.status = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setAllData([]);
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
}, 500);
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
};
|
||||
|
||||
/** 打开新增弹窗 */
|
||||
@@ -214,39 +212,33 @@ export function useBannerModel() {
|
||||
|
||||
/** 删除 */
|
||||
const handleDelete = useThrottleFn(async (record: any) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await delBanner(record.id);
|
||||
if (res.code == 200) {
|
||||
message.success('删除成功');
|
||||
handleSearch();
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.msg || '删除失败');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('Banner删除失败:', e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
/** 启用/禁用切换 */
|
||||
const handleToggleStatus = useThrottleFn(async (record: any) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const newStatus = record.status === '1' ? '0' : '1';
|
||||
const actionText = newStatus === '1' ? '启用' : '禁用';
|
||||
const res = await toggleBannerActive({ id: record.id, status: newStatus });
|
||||
if (res.code == 200) {
|
||||
message.success(`${actionText}成功`);
|
||||
handleSearch();
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.msg || '操作失败');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('Banner状态切换失败:', e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { defineComponent, ref, nextTick } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from 'ant-design-vue';
|
||||
import { useLogModel, ACTION_TYPE_OPTIONS, ACTION_SOURCE_OPTIONS } from './model/useLogModel';
|
||||
import { useLogColumns } from './model/useLogColumns';
|
||||
import { useState, useContainerSize } from '@/hooks';
|
||||
import { useState, useEffect, useContainerSize } from '@/hooks';
|
||||
import pageStyles from '@/assets/styles/pageLayout.module.less';
|
||||
import './index.module.less';
|
||||
|
||||
@@ -29,15 +29,17 @@ const LogContentCell = defineComponent({
|
||||
if (el) setOverflow(el.scrollHeight > el.clientHeight);
|
||||
};
|
||||
let observer: ResizeObserver | null = null;
|
||||
onMounted(() => {
|
||||
nextTick(checkOverflow);
|
||||
useEffect(() => {
|
||||
const el = textRef.value;
|
||||
if (el) {
|
||||
observer = new ResizeObserver(checkOverflow);
|
||||
observer.observe(el);
|
||||
}
|
||||
});
|
||||
onUnmounted(() => observer?.disconnect());
|
||||
if (!el) return;
|
||||
nextTick(checkOverflow);
|
||||
observer = new ResizeObserver(checkOverflow);
|
||||
observer.observe(el);
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
observer = null;
|
||||
};
|
||||
}, [() => textRef.value]);
|
||||
return () => {
|
||||
const raw = props.text;
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Button, Input, Table, Form, Space, Pagination, Select, DatePicker } from 'ant-design-vue';
|
||||
import { usePaymentsModel } from './model/usePaymentsModel';
|
||||
import { useContainerSize } from '@/hooks';
|
||||
@@ -24,10 +24,6 @@ export default defineComponent({
|
||||
|
||||
const { containerRef, height } = useContainerSize({ headerOffset: 55 });
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
<div class={pageStyles.filter}>
|
||||
@@ -101,9 +97,9 @@ export default defineComponent({
|
||||
</div>
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { computed, reactive, toRef, Ref } from 'vue';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import { useDebounce, useThrottleFn, useRequest, usePagination, useEffect } from '@/hooks';
|
||||
import { getPaymentFlowList, type PaymentFlowVO, type PaymentFlowQueryParams } from './services';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -40,13 +40,50 @@ export function usePaymentsModel() {
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 汇总 =====
|
||||
const [summary, setSummary] = useState({ totalAmount: '0.00', totalRefundAmount: '0.00' });
|
||||
// ===== 分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<PaymentFlowVO[]>([]);
|
||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): PaymentFlowQueryParams => {
|
||||
const params: PaymentFlowQueryParams = {
|
||||
page: String((pagination as any).current.value),
|
||||
limit: String((pagination as any).pageSize.value),
|
||||
};
|
||||
if (filterForm.timeRange) {
|
||||
const [start, end] = filterForm.timeRange;
|
||||
if (start) params.createDateBegin = dayjs(start).format('YYYY-MM-DD');
|
||||
if (end) params.createDateEnd = dayjs(end).format('YYYY-MM-DD');
|
||||
}
|
||||
if (debouncedNickname.value.trim()) params.nickname = debouncedNickname.value.trim();
|
||||
if (debouncedPhone.value.trim()) params.phone = debouncedPhone.value.trim();
|
||||
if (filterForm.type) params.type = filterForm.type;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest(() => getPaymentFlowList(buildQueryParams()), {
|
||||
refreshDeps: [],
|
||||
formatResult: (res) => res,
|
||||
});
|
||||
|
||||
const listData = computed(() => (data.value as any)?.data);
|
||||
const dataSource = computed(() => listData.value?.list || []);
|
||||
|
||||
// 汇总:从 listData(即 res.data,包含 exData)派生
|
||||
const summary = computed(() => ({
|
||||
totalAmount: listData.value?.exData?.totalAmount ?? '0.00',
|
||||
totalRefundAmount: listData.value?.exData?.totalRefundAmount ?? '0.00',
|
||||
}));
|
||||
|
||||
// 同步 total 到 pagination
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 表格列(dataIndex 对齐 API) =====
|
||||
const columns = [
|
||||
@@ -81,59 +118,25 @@ export function usePaymentsModel() {
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): PaymentFlowQueryParams => {
|
||||
const params: PaymentFlowQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
};
|
||||
if (filterForm.timeRange) {
|
||||
const [start, end] = filterForm.timeRange;
|
||||
if (start) params.createDateBegin = dayjs(start).format('YYYY-MM-DD');
|
||||
if (end) params.createDateEnd = dayjs(end).format('YYYY-MM-DD');
|
||||
}
|
||||
if (debouncedNickname.value.trim()) params.nickname = debouncedNickname.value.trim();
|
||||
if (debouncedPhone.value.trim()) params.phone = debouncedPhone.value.trim();
|
||||
if (filterForm.type) params.type = filterForm.type;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
const res = await getPaymentFlowList(queryParams);
|
||||
if (res.code == 200) {
|
||||
setDataSource(res.data.list);
|
||||
setPagination({ ...pagination.value, total: res.data.total });
|
||||
setSummary({
|
||||
totalAmount: res.data.exData?.totalAmount ?? '0.00',
|
||||
totalRefundAmount: res.data.exData?.totalRefundAmount ?? '0.00',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// 网络层已统一提示
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.timeRange = null;
|
||||
filterForm.nickname = '';
|
||||
filterForm.phone = '';
|
||||
filterForm.type = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
setSummary({ totalAmount: '0.00', totalRefundAmount: '0.00' });
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
}, 500);
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { defineComponent, ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
|
||||
import { defineComponent, ref, nextTick } from 'vue';
|
||||
import { Select, Table } from 'ant-design-vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { useReportsModel } from './model/useReportsModel';
|
||||
import { useEffect } from '@/hooks';
|
||||
import styles from './index.module.less';
|
||||
|
||||
/**
|
||||
@@ -140,23 +141,23 @@ export default defineComponent({
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
renderChart();
|
||||
});
|
||||
// ECharts 挂载/销毁 + 窗口 resize 监听
|
||||
useEffect(() => {
|
||||
const el = chartRef.value;
|
||||
if (!el) return;
|
||||
nextTick(() => renderChart());
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chartInstance?.dispose();
|
||||
chartInstance = null;
|
||||
};
|
||||
}, [() => chartRef.value]);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
chartInstance?.dispose();
|
||||
chartInstance = null;
|
||||
});
|
||||
|
||||
// 时间范围切换时刷新图表(接入 API 后生效)
|
||||
watch(range, () => {
|
||||
// 时间范围切换时刷新图表
|
||||
useEffect(() => {
|
||||
renderChart();
|
||||
});
|
||||
}, [range]);
|
||||
|
||||
return () => {
|
||||
/** 根据增长率构建箭头 + 颜色(正值 ↑ 绿,负值 ↓ 红,null 时固定文本) */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Button, Input, Table, Form, Space, Pagination } from 'ant-design-vue';
|
||||
import { WalletOutlined, LockOutlined, RiseOutlined } from '@ant-design/icons-vue';
|
||||
import { useWalletModel } from './model/useWalletModel';
|
||||
@@ -68,10 +68,6 @@ export default defineComponent({
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => {
|
||||
const statCards: StatCardItem[] = [
|
||||
{
|
||||
@@ -206,9 +202,9 @@ export default defineComponent({
|
||||
</div>
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { computed, reactive, toRef, Ref } from 'vue';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import {
|
||||
useState,
|
||||
useDebounce,
|
||||
useThrottleFn,
|
||||
useRequest,
|
||||
usePagination,
|
||||
useEffect,
|
||||
} from '@/hooks';
|
||||
import { getWalletList, type WalletSummaryVO, type WalletQueryParams } from './services';
|
||||
|
||||
// ============================================================
|
||||
@@ -58,17 +65,58 @@ export function useWalletModel() {
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 顶部统计(汇总值从接口返回的第一条记录中提取) =====
|
||||
const [summary, setSummary] = useState({
|
||||
totalBalance: '0.00',
|
||||
frozenAmount: '0.00',
|
||||
totalWithdraw: '0.00',
|
||||
// ===== 分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): WalletQueryParams => {
|
||||
// 余额筛选:若起始 > 截止,互换两值
|
||||
const min = Number(filterForm.minBalance);
|
||||
const max = Number(filterForm.maxBalance);
|
||||
if (filterForm.minBalance && filterForm.maxBalance && min > max) {
|
||||
filterForm.minBalance = filterForm.maxBalance;
|
||||
filterForm.maxBalance = String(min);
|
||||
}
|
||||
|
||||
const params: WalletQueryParams = {
|
||||
page: String((pagination as any).current.value),
|
||||
limit: String((pagination as any).pageSize.value),
|
||||
};
|
||||
if (debouncedNickname.value.trim()) params.nickname = debouncedNickname.value.trim();
|
||||
if (debouncedPhone.value.trim()) params.phone = debouncedPhone.value.trim();
|
||||
if (filterForm.minBalance) params.amountBegin = filterForm.minBalance;
|
||||
if (filterForm.maxBalance) params.amountEnd = filterForm.maxBalance;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest(() => getWalletList(buildQueryParams()), {
|
||||
refreshDeps: [],
|
||||
formatResult: (res) => res,
|
||||
});
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<WalletSummaryVO[]>([]);
|
||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||
const listData = computed(() => (data.value as any)?.data);
|
||||
const dataSource = computed(() => listData.value?.list || []);
|
||||
|
||||
// 汇总:从第一条记录提取
|
||||
const summary = computed(() => {
|
||||
const first = dataSource.value?.[0] as any;
|
||||
return {
|
||||
totalBalance: first?.totalAmount ?? '0.00',
|
||||
frozenAmount: first?.totalFrozenAmount ?? '0.00',
|
||||
totalWithdraw: first?.totalWithdrawalAmount ?? '0.00',
|
||||
};
|
||||
});
|
||||
|
||||
// 同步 total 到 pagination
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 详情弹窗状态 =====
|
||||
const [detailVisible, setDetailVisible] = useState<boolean>(false);
|
||||
@@ -112,70 +160,25 @@ export function useWalletModel() {
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): WalletQueryParams => {
|
||||
const params: WalletQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
};
|
||||
if (debouncedNickname.value.trim()) params.nickname = debouncedNickname.value.trim();
|
||||
if (debouncedPhone.value.trim()) params.phone = debouncedPhone.value.trim();
|
||||
if (filterForm.minBalance) params.amountBegin = filterForm.minBalance;
|
||||
if (filterForm.maxBalance) params.amountEnd = filterForm.maxBalance;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 余额筛选:若起始 > 截止,互换两值
|
||||
const min = Number(filterForm.minBalance);
|
||||
const max = Number(filterForm.maxBalance);
|
||||
if (filterForm.minBalance && filterForm.maxBalance && min > max) {
|
||||
filterForm.minBalance = filterForm.maxBalance;
|
||||
filterForm.maxBalance = String(min);
|
||||
}
|
||||
|
||||
const params = buildQueryParams();
|
||||
console.log('钱包列表查询参数:', params);
|
||||
const res = await getWalletList(params);
|
||||
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 });
|
||||
// 从第一条记录提取汇总值
|
||||
if (res.data.list.length > 0) {
|
||||
const first = res.data.list[0];
|
||||
setSummary({
|
||||
totalBalance: first.totalAmount || '0.00',
|
||||
frozenAmount: first.totalFrozenAmount || '0.00',
|
||||
totalWithdraw: first.totalWithdrawalAmount || '0.00',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 网络层已统一提示
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.nickname = '';
|
||||
filterForm.phone = '';
|
||||
filterForm.minBalance = '';
|
||||
filterForm.maxBalance = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
setSummary({ totalBalance: '0.00', frozenAmount: '0.00', totalWithdraw: '0.00' });
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
}, 500);
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
const handleViewDetail = (record: any) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Button, Input, Table, Form, Space, Pagination, Select } from 'ant-design-vue';
|
||||
import { useWithdrawModel, renderAuditStatus, renderPayStatus } from './model/useWithdrawModel';
|
||||
import { useContainerSize } from '@/hooks';
|
||||
@@ -82,11 +82,6 @@ export default defineComponent({
|
||||
},
|
||||
];
|
||||
|
||||
// 首次进入自动加载
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
{/* ===== 筛选区 ===== */}
|
||||
@@ -175,9 +170,9 @@ export default defineComponent({
|
||||
{/* 独立分页,右下方 */}
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { computed, reactive, toRef, Ref, h } from 'vue';
|
||||
import { StatusTag, type StatusTagTone } from '@/components';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import {
|
||||
useState,
|
||||
useDebounce,
|
||||
useThrottleFn,
|
||||
useRequest,
|
||||
usePagination,
|
||||
useEffect,
|
||||
} from '@/hooks';
|
||||
import { getWithdrawList, type WithdrawVO, type WithdrawQueryParams } from './services';
|
||||
|
||||
// ============================================================
|
||||
@@ -93,10 +100,41 @@ export function useWithdrawModel() {
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<WithdrawVO[]>([]);
|
||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||
// ===== 分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): WithdrawQueryParams => {
|
||||
const params: WithdrawQueryParams = {
|
||||
page: String((pagination as any).current.value),
|
||||
limit: String((pagination as any).pageSize.value),
|
||||
};
|
||||
if (debouncedNickname.value.trim()) params.nickname = debouncedNickname.value.trim();
|
||||
if (debouncedPhone.value.trim()) params.phone = debouncedPhone.value.trim();
|
||||
if (debouncedRealName.value.trim()) params.realName = debouncedRealName.value.trim();
|
||||
if (filterForm.auditStatus) params.auditStatus = filterForm.auditStatus;
|
||||
if (filterForm.payStatus) params.payStatus = filterForm.payStatus;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest(() => getWithdrawList(buildQueryParams()), {
|
||||
refreshDeps: [],
|
||||
formatResult: (res) => res,
|
||||
});
|
||||
|
||||
const listData = computed(() => (data.value as any)?.data);
|
||||
const dataSource = computed(() => listData.value?.list || []);
|
||||
|
||||
// 同步 total 到 pagination
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 审核弹窗状态 =====
|
||||
const [auditVisible, setAuditVisible] = useState<boolean>(false);
|
||||
@@ -139,40 +177,9 @@ export function useWithdrawModel() {
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): WithdrawQueryParams => {
|
||||
const params: WithdrawQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
};
|
||||
if (debouncedNickname.value.trim()) params.nickname = debouncedNickname.value.trim();
|
||||
if (debouncedPhone.value.trim()) params.phone = debouncedPhone.value.trim();
|
||||
if (debouncedRealName.value.trim()) params.realName = debouncedRealName.value.trim();
|
||||
if (filterForm.auditStatus) params.auditStatus = filterForm.auditStatus;
|
||||
if (filterForm.payStatus) params.payStatus = filterForm.payStatus;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
/** 查询(节流 500ms) */
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
console.log('提现列表查询参数:', queryParams);
|
||||
const res = await getWithdrawList(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 });
|
||||
}
|
||||
} catch {
|
||||
// 网络层已统一提示
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
/** 重置(重置后自动查询) */
|
||||
const handleReset = useThrottleFn(() => {
|
||||
@@ -181,14 +188,16 @@ export function useWithdrawModel() {
|
||||
filterForm.realName = '';
|
||||
filterForm.auditStatus = '';
|
||||
filterForm.payStatus = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
}, 500);
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
/** 打开审核弹窗 */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Button, Input, Table, DatePicker, Form, Space, Select, Pagination } from 'ant-design-vue';
|
||||
import { useLogModel, ACTION_TYPE_OPTIONS, ACTION_TYPE_MAP } from './model/useLogModel';
|
||||
import { useContainerSize } from '@/hooks';
|
||||
@@ -35,11 +35,6 @@ export default defineComponent({
|
||||
|
||||
const { containerRef, height } = useContainerSize({ headerOffset: 55 });
|
||||
|
||||
// 首次进入自动加载
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
{/* ===== 筛选区 ===== */}
|
||||
@@ -102,9 +97,9 @@ export default defineComponent({
|
||||
{/* 独立分页 */}
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { computed, reactive, toRef, Ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import { useDebounce, useThrottleFn } from '@/hooks';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { useEffect } from '@/hooks/useEffect';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import {
|
||||
getOperationLogList,
|
||||
type SysOperationLogVO,
|
||||
type OperationLogQueryParams,
|
||||
type PageData,
|
||||
type ApiResult,
|
||||
} from './services';
|
||||
|
||||
// ============================================================
|
||||
@@ -49,33 +54,14 @@ export function useLogModel() {
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<SysOperationLogVO[]>([]);
|
||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||
|
||||
// ===== 表格列配置(字段名对齐 API) =====
|
||||
const columns = [
|
||||
{ title: '操作类型', dataIndex: 'type', key: 'type', width: 140 },
|
||||
{ title: '操作人', dataIndex: 'nickname', key: 'nickname', width: 140 },
|
||||
{ title: '操作内容', dataIndex: 'content', key: 'content', minWidth: 360 },
|
||||
{ title: '操作时间', dataIndex: 'createDate', key: 'createDate', width: 180 },
|
||||
];
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const hasFilter = computed(() => {
|
||||
return (
|
||||
filterForm.actionTimeRange !== null ||
|
||||
filterForm.actionType !== '' ||
|
||||
debouncedOperator.value.trim() !== ''
|
||||
);
|
||||
});
|
||||
// ===== 分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): OperationLogQueryParams => {
|
||||
const params: OperationLogQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
page: String((pagination as any).current.value),
|
||||
limit: String((pagination as any).pageSize.value),
|
||||
};
|
||||
|
||||
// 时间范围 → dateBegin / dateEnd
|
||||
@@ -98,38 +84,62 @@ export function useLogModel() {
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 数据请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest<ApiResult<PageData<SysOperationLogVO>>>(
|
||||
() => getOperationLogList(buildQueryParams()),
|
||||
{ refreshDeps: [], formatResult: (res) => res },
|
||||
);
|
||||
|
||||
const dataSource = computed(() => {
|
||||
const res = data.value;
|
||||
return res ? (res as any).data?.list || [] : [];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 表格列配置(字段名对齐 API) =====
|
||||
const columns = [
|
||||
{ title: '操作类型', dataIndex: 'type', key: 'type', width: 140 },
|
||||
{ title: '操作人', dataIndex: 'nickname', key: 'nickname', width: 140 },
|
||||
{ title: '操作内容', dataIndex: 'content', key: 'content', minWidth: 360 },
|
||||
{ title: '操作时间', dataIndex: 'createDate', key: 'createDate', width: 180 },
|
||||
];
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const hasFilter = computed(() => {
|
||||
return (
|
||||
filterForm.actionTimeRange !== null ||
|
||||
filterForm.actionType !== '' ||
|
||||
debouncedOperator.value.trim() !== ''
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
/** 查询(节流 500ms) */
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
const res = await getOperationLogList(queryParams);
|
||||
if (res.code == 200) {
|
||||
setDataSource(res.data.list);
|
||||
setPagination({ ...pagination.value, total: res.data.total });
|
||||
}
|
||||
} catch {
|
||||
// 网络层已统一提示
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
/** 重置(重置后自动查询) */
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.actionTimeRange = null;
|
||||
filterForm.actionType = '';
|
||||
filterForm.searchOperator = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
}, 500);
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -143,7 +143,6 @@ export default defineComponent({
|
||||
columns={columns}
|
||||
dataSource={dataSource.value}
|
||||
loading={loading.value}
|
||||
scroll={{ y: 300 }}
|
||||
size="small"
|
||||
pagination={false}
|
||||
bordered
|
||||
@@ -164,7 +163,6 @@ export default defineComponent({
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
size="small"
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Button, Input, Table, Form, Space, Select, Modal, Pagination } from 'ant-design-vue';
|
||||
import type { ModalProps } from 'ant-design-vue';
|
||||
import { useRoleModel } from './model/useRoleModel';
|
||||
@@ -114,11 +114,6 @@ export default defineComponent({
|
||||
},
|
||||
];
|
||||
|
||||
// ===== 初始化 =====
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
{/* ===== 筛选区 ===== */}
|
||||
@@ -173,9 +168,9 @@ export default defineComponent({
|
||||
{/* 分页 */}
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { computed, reactive, toRef, Ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { useEffect } from '@/hooks/useEffect';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import {
|
||||
getRoleList,
|
||||
saveRole,
|
||||
@@ -8,6 +11,8 @@ import {
|
||||
deleteRole,
|
||||
type TournamentAdminRolePageVO,
|
||||
type RoleListQueryParams,
|
||||
type PageData,
|
||||
type ApiResult,
|
||||
} from './services';
|
||||
|
||||
// ============================================================
|
||||
@@ -27,10 +32,38 @@ export function useRoleModel() {
|
||||
delay: 300,
|
||||
});
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<TournamentAdminRolePageVO[]>([]);
|
||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||
// ===== 分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): RoleListQueryParams => {
|
||||
const params: RoleListQueryParams = {
|
||||
page: String((pagination as any).current.value),
|
||||
limit: String((pagination as any).pageSize.value),
|
||||
};
|
||||
if (filterForm.name.trim()) params.name = filterForm.name.trim();
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 数据请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest<ApiResult<PageData<TournamentAdminRolePageVO>>>(
|
||||
() => getRoleList(buildQueryParams()),
|
||||
{ refreshDeps: [], formatResult: (res) => res },
|
||||
);
|
||||
|
||||
const dataSource = computed(() => {
|
||||
const res = data.value;
|
||||
return res ? (res as any).data?.list || [] : [];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 弹窗状态 =====
|
||||
const [formVisible, setFormVisible] = useState<boolean>(false);
|
||||
@@ -55,53 +88,27 @@ export function useRoleModel() {
|
||||
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 200 },
|
||||
];
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): RoleListQueryParams => {
|
||||
const params: RoleListQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
};
|
||||
if (filterForm.name.trim()) params.name = filterForm.name.trim();
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const hasFilter = computed(() => debouncedName.value.trim() !== '');
|
||||
const isEdit = computed(() => editingRecord.value !== null);
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
/** 查询(节流 500ms) */
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
console.log('角色列表查询参数:', queryParams);
|
||||
const res = await getRoleList(queryParams);
|
||||
if (res.code == 200) {
|
||||
setDataSource(res.data.list);
|
||||
setPagination({ ...pagination.value, total: res.data.total });
|
||||
} else {
|
||||
message.error(res.msg || '查询失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('角色列表查询失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
/** 重置(重置后自动查询) */
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.name = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
}, 500);
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
/** 新增 */
|
||||
@@ -146,7 +153,7 @@ export function useRoleModel() {
|
||||
if (res.code == 200) {
|
||||
message.success(isEdit.value ? '编辑成功' : '新增成功');
|
||||
handleCloseForm();
|
||||
handleSearch();
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.msg || '操作失败');
|
||||
}
|
||||
@@ -159,19 +166,16 @@ export function useRoleModel() {
|
||||
|
||||
/** 删除 */
|
||||
const handleDelete = useThrottleFn(async (record: any) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await deleteRole(record.roleId);
|
||||
if (res.code == 200) {
|
||||
setDataSource(dataSource.value.filter((item: any) => item.roleId !== record.roleId));
|
||||
message.success('删除成功');
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.msg || '删除失败');
|
||||
}
|
||||
} catch {
|
||||
// 网络层已统一提示,不再重复 message.error
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { defineComponent, ref, reactive } from 'vue';
|
||||
import { useEffect, useState } from '@/hooks';
|
||||
import { Modal, Form, Input, Select, Button } from 'ant-design-vue';
|
||||
import { getRoleList, type TournamentAdminRolePageVO } from '../../roles/model/services';
|
||||
import {
|
||||
ROLE_FORM_OPTIONS,
|
||||
PASSWORD_PLACEHOLDER,
|
||||
realNameRules,
|
||||
phoneRules,
|
||||
@@ -55,12 +55,84 @@ export default defineComponent({
|
||||
/** 编辑模式下是否处于"重置密码"状态 */
|
||||
const [isResetting, setIsResetting] = useState(false);
|
||||
|
||||
// ---- 角色搜索状态 ----
|
||||
const roleKeyword = ref('');
|
||||
const [roleOptions, setRoleOptions] = useState<{ value: string; label: string }[]>([]);
|
||||
const [roleSearchLoading, setRoleSearchLoading] = useState(false);
|
||||
/** 手动防抖计时器 */
|
||||
let roleSearchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
/** 已加载的全量角色缓存 */
|
||||
const [allRoleOptions, setAllRoleOptions] = useState<{ value: string; label: string }[]>([]);
|
||||
|
||||
// ---- 角色搜索 API 调用(用户主动搜索,带 loading) ----
|
||||
const doRoleSearch = async (keyword: string) => {
|
||||
setRoleSearchLoading(true);
|
||||
try {
|
||||
const params: Record<string, string> = { page: '1', limit: '999', name: keyword.trim() };
|
||||
const res = await getRoleList(params);
|
||||
if (res.code == 200) {
|
||||
const list: TournamentAdminRolePageVO[] = res.data?.list || [];
|
||||
setRoleOptions(
|
||||
list.map((item) => ({
|
||||
value: String(item.roleId),
|
||||
label: item.roleName,
|
||||
})),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[UserForm] 搜索角色失败:', e);
|
||||
} finally {
|
||||
setRoleSearchLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** 初始加载全量角色(弹窗打开时调用,不带 loading,不覆盖已选值) */
|
||||
const fetchInitialRoles = async () => {
|
||||
try {
|
||||
const res = await getRoleList({ page: '1', limit: '999' });
|
||||
if (res.code == 200) {
|
||||
const list: TournamentAdminRolePageVO[] = res.data?.list || [];
|
||||
const opts = list.map((item) => ({
|
||||
value: String(item.roleId),
|
||||
label: item.roleName,
|
||||
}));
|
||||
// 确保已选角色在列表中
|
||||
const selectedId = formData.roleId;
|
||||
if (selectedId && !opts.some((o) => o.value === selectedId)) {
|
||||
const cached = allRoleOptions.value.find((o) => o.value === selectedId);
|
||||
if (cached) opts.unshift(cached);
|
||||
}
|
||||
setAllRoleOptions(opts);
|
||||
setRoleOptions(opts);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[UserForm] 加载角色列表失败:', e);
|
||||
}
|
||||
};
|
||||
|
||||
/** 角色搜索输入(带 350ms 手动防抖) */
|
||||
const onRoleSearch = (value: string) => {
|
||||
roleKeyword.value = value;
|
||||
if (roleSearchTimer) clearTimeout(roleSearchTimer);
|
||||
if (!value.trim()) {
|
||||
// 清空搜索:恢复全量角色
|
||||
setRoleOptions(allRoleOptions.value);
|
||||
return;
|
||||
}
|
||||
roleSearchTimer = setTimeout(() => {
|
||||
doRoleSearch(value);
|
||||
}, 350);
|
||||
};
|
||||
|
||||
/** 根据 record 初始化表单 */
|
||||
const initFormFromRecord = (record: any) => {
|
||||
if (!record) {
|
||||
// 新增:预填默认密码 + 确认密码
|
||||
Object.assign(formData, getAddForm());
|
||||
setIsResetting(false);
|
||||
roleKeyword.value = '';
|
||||
fetchInitialRoles();
|
||||
return;
|
||||
}
|
||||
const fresh = getDefaultForm();
|
||||
@@ -71,6 +143,22 @@ export default defineComponent({
|
||||
roleId: record.roleId || undefined,
|
||||
});
|
||||
setIsResetting(false);
|
||||
|
||||
// 编辑模式:先回显已选角色,再静默加载全量角色
|
||||
roleKeyword.value = '';
|
||||
if (record.roleId) {
|
||||
const selectedOpt = {
|
||||
value: String(record.roleId),
|
||||
label: record.roleName || String(record.roleId),
|
||||
};
|
||||
setAllRoleOptions([selectedOpt]);
|
||||
setRoleOptions([selectedOpt]);
|
||||
} else {
|
||||
setAllRoleOptions([]);
|
||||
setRoleOptions([]);
|
||||
}
|
||||
// 后台静默加载全量角色列表(API 返回后合并到选项,loading 为 false 不影响显示)
|
||||
fetchInitialRoles();
|
||||
};
|
||||
|
||||
/** 监听 visible 变化重置表单 */
|
||||
@@ -82,6 +170,14 @@ export default defineComponent({
|
||||
}
|
||||
}, [() => props.visible]);
|
||||
|
||||
/** 关闭时清理防抖计时器 */
|
||||
useEffect(() => {
|
||||
if (!props.visible && roleSearchTimer) {
|
||||
clearTimeout(roleSearchTimer);
|
||||
roleSearchTimer = null;
|
||||
}
|
||||
}, [() => props.visible]);
|
||||
|
||||
/** 点击"重置密码" */
|
||||
const handleResetPassword = () => {
|
||||
setIsResetting(true);
|
||||
@@ -172,9 +268,19 @@ export default defineComponent({
|
||||
</Form.Item>
|
||||
<Form.Item label="角色" name="roleId" rules={roleRules} class={styles.col}>
|
||||
<Select
|
||||
options={ROLE_FORM_OPTIONS as any}
|
||||
placeholder="请选择角色"
|
||||
v-model:value={formData.roleId}
|
||||
showSearch
|
||||
filterOption={false}
|
||||
allowClear={!roleSearchLoading.value}
|
||||
loading={roleSearchLoading.value}
|
||||
placeholder="请输入关键字搜索角色"
|
||||
options={roleOptions.value as any}
|
||||
onSearch={onRoleSearch}
|
||||
onFocus={() => {
|
||||
if (formData.roleId && roleOptions.value.length === 0) {
|
||||
doRoleSearch(formData.roleId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
@@ -210,9 +316,19 @@ export default defineComponent({
|
||||
{/* 角色:单独一行 */}
|
||||
<Form.Item label="角色" name="roleId" rules={roleRules}>
|
||||
<Select
|
||||
options={ROLE_FORM_OPTIONS as any}
|
||||
placeholder="请选择角色"
|
||||
v-model:value={formData.roleId}
|
||||
showSearch
|
||||
filterOption={false}
|
||||
allowClear={!roleSearchLoading.value}
|
||||
loading={roleSearchLoading.value}
|
||||
placeholder="请输入关键字搜索角色"
|
||||
options={roleOptions.value as any}
|
||||
onSearch={onRoleSearch}
|
||||
onFocus={() => {
|
||||
if (formData.roleId && roleOptions.value.length === 0) {
|
||||
doRoleSearch(formData.roleId);
|
||||
}
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -14,14 +14,6 @@ import { isChinaPhone, isValidPassword, isValidRealName } from '@/utils/form';
|
||||
/** 密码占位符(写死长度,避免暴露真实密码长度) */
|
||||
export const PASSWORD_PLACEHOLDER = '********';
|
||||
|
||||
/** 角色下拉选项(表单使用,value 对应 API roleId) */
|
||||
export const ROLE_FORM_OPTIONS = [
|
||||
{ value: '1', label: '超级管理员' },
|
||||
{ value: '2', label: '赛事管理员' },
|
||||
{ value: '3', label: '裁判' },
|
||||
{ value: '4', label: '财务' },
|
||||
] as const;
|
||||
|
||||
// ============================================================
|
||||
// 真实姓名规则
|
||||
// ============================================================
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Pagination,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
import { useUserModel, ROLE_OPTIONS, USER_STATUS_OPTIONS } from './model/useUserModel';
|
||||
import { useUserModel, USER_STATUS_OPTIONS } from './model/useUserModel';
|
||||
import { toggleUserActive, saveUser, updateUser } from './model/services';
|
||||
import { useState, useThrottleFn, useContainerSize } from '@/hooks';
|
||||
import UserFormModal from './components/UserFormModal';
|
||||
@@ -60,6 +60,7 @@ export default defineComponent({
|
||||
dataSource,
|
||||
columns,
|
||||
pagination,
|
||||
roleOptions,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
@@ -175,11 +176,6 @@ export default defineComponent({
|
||||
},
|
||||
];
|
||||
|
||||
// ===== 初始化 =====
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
{/* ===== 筛选区 ===== */}
|
||||
@@ -198,7 +194,7 @@ export default defineComponent({
|
||||
<Form.Item label="角色" name="roleId">
|
||||
<Select
|
||||
value={filterForm.roleId}
|
||||
options={ROLE_OPTIONS as any}
|
||||
options={roleOptions.value as any}
|
||||
style={{ width: '140px' }}
|
||||
allowClear
|
||||
placeholder="全部"
|
||||
@@ -252,9 +248,9 @@ export default defineComponent({
|
||||
{/* 独立分页 */}
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -29,9 +29,9 @@ export type {
|
||||
// URL 常量
|
||||
// ============================================================
|
||||
const userPage = '/admin/sys/user/page';
|
||||
const userSave = '/sys/user/save';
|
||||
const userUpdate = '/sys/user/update';
|
||||
const userActive = '/sys/user/active';
|
||||
const userSave = '/admin/sys/user/save';
|
||||
const userUpdate = '/admin/sys/user/update';
|
||||
const userActive = '/admin/sys/user/active';
|
||||
const userUpdatePwd = '/sys/user/updatepwd';
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { computed, reactive, toRef, Ref, h } from 'vue';
|
||||
import { computed, reactive, toRef, Ref, h, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { StatusTag, type StatusTagTone } from '@/components';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import { getUserList, type TournamentAdminUserVO, type UserListQueryParams } from './services';
|
||||
import { useDebounce, useThrottleFn } from '@/hooks';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { useEffect } from '@/hooks/useEffect';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import {
|
||||
getUserList,
|
||||
type TournamentAdminUserVO,
|
||||
type UserListQueryParams,
|
||||
type PageData,
|
||||
type ApiResult,
|
||||
} from './services';
|
||||
import { getRoleList, type TournamentAdminRolePageVO } from '../../roles/model/services';
|
||||
|
||||
// ============================================================
|
||||
// 常量
|
||||
// ============================================================
|
||||
|
||||
/** 角色选项(筛选) */
|
||||
export const ROLE_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '1', label: '超级管理员' },
|
||||
{ value: '2', label: '赛事管理员' },
|
||||
{ value: '3', label: '裁判' },
|
||||
{ value: '4', label: '财务' },
|
||||
] as const;
|
||||
|
||||
/** 状态选项(筛选) */
|
||||
export const USER_STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
@@ -45,20 +46,66 @@ export function useUserModel() {
|
||||
status: '' as '' | '0' | '1',
|
||||
});
|
||||
|
||||
// ===== 动态角色选项 =====
|
||||
const roleOptions = ref<{ value: string; label: string }[]>([{ value: '', label: '全部' }]);
|
||||
|
||||
/** 加载全部角色列表(用于筛选下拉) */
|
||||
const fetchRoleOptions = async () => {
|
||||
try {
|
||||
const res = await getRoleList({ page: '1', limit: '999' });
|
||||
if (res.code == 200) {
|
||||
const list: TournamentAdminRolePageVO[] = res.data?.list || [];
|
||||
const opts = list.map((item) => ({
|
||||
value: String(item.roleId),
|
||||
label: item.roleName,
|
||||
}));
|
||||
roleOptions.value = [{ value: '', label: '全部' }, ...opts];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[UserModel] 加载角色列表失败:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// 文本筛选防抖 300ms
|
||||
const { debouncedValue: debouncedText } = useDebounce(toRef(filterForm, 'text') as Ref<string>, {
|
||||
delay: 300,
|
||||
});
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<TournamentAdminUserVO[]>([]);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
// ===== 分页 =====
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): UserListQueryParams => {
|
||||
const params: UserListQueryParams = {
|
||||
page: String((pagination as any).current.value),
|
||||
limit: String((pagination as any).pageSize.value),
|
||||
};
|
||||
if (filterForm.text.trim()) params.text = filterForm.text.trim();
|
||||
if (filterForm.roleId) params.roleId = filterForm.roleId;
|
||||
if (filterForm.status) params.status = filterForm.status;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 数据请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest<ApiResult<PageData<TournamentAdminUserVO>>>(
|
||||
() => getUserList(buildQueryParams()),
|
||||
{ refreshDeps: [], formatResult: (res) => res },
|
||||
);
|
||||
|
||||
const dataSource = computed(() => {
|
||||
const res = data.value;
|
||||
return res ? (res as any).data?.list || [] : [];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 表格列配置 =====
|
||||
const columns = [
|
||||
{ title: '姓名', dataIndex: 'realName', key: 'realName', width: 120 },
|
||||
@@ -78,18 +125,6 @@ export function useUserModel() {
|
||||
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 },
|
||||
];
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): UserListQueryParams => {
|
||||
const params: UserListQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
};
|
||||
if (filterForm.text.trim()) params.text = filterForm.text.trim();
|
||||
if (filterForm.roleId) params.roleId = filterForm.roleId;
|
||||
if (filterForm.status) params.status = filterForm.status;
|
||||
return params;
|
||||
};
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const hasFilter = computed(() => {
|
||||
return (
|
||||
@@ -99,49 +134,37 @@ export function useUserModel() {
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
/** 查询(节流 500ms) */
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
console.log('用户列表查询参数:', queryParams);
|
||||
const res = await getUserList(queryParams);
|
||||
if (res.code == 200) {
|
||||
setDataSource(res.data.list);
|
||||
setPagination({ ...pagination.value, total: res.data.total });
|
||||
} else {
|
||||
message.error(res.msg || '查询失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('用户列表查询失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
/** 重置(节流 500ms,重置后自动查询) */
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.text = '';
|
||||
filterForm.roleId = '';
|
||||
filterForm.status = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
pagination.reset();
|
||||
// 等待 debounce(300ms) 生效后自动查询
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
setTimeout(fetchList, 350);
|
||||
}, 500);
|
||||
|
||||
/** 分页变更 */
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
// ===== 初始化:加载角色选项 =====
|
||||
fetchRoleOptions();
|
||||
|
||||
return {
|
||||
filterForm,
|
||||
loading,
|
||||
dataSource,
|
||||
columns,
|
||||
pagination,
|
||||
roleOptions,
|
||||
hasFilter,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
|
||||
Reference in New Issue
Block a user