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