feat: 处理 ignore 干扰问题 优化文件依赖

This commit is contained in:
ZhuRui
2026-07-30 17:23:23 +08:00
parent 6b7a0fd284
commit 7606bb7d8d
10 changed files with 845 additions and 19 deletions
+5 -1
View File
@@ -1,5 +1,5 @@
# Logs
logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
@@ -40,3 +40,7 @@ coverage
.cache
.temp
.tmp
# Easy LESS 误生成的编译产物(本项目为 Vue PC 端,样式源文件为 .less,由 Vite 编译)
*.wxss
*.module.css
+66
View File
@@ -0,0 +1,66 @@
/**
* 操作日志 API
*/
import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockOperationLogPage } from '@/config/mock/operationLogs';
import { buildMockEventOperationLogPage } from '@/config/mock/eventOperationLogs';
import type {
OperationLogQueryParams,
SysOperationLogVO,
TournamentAdminOperationPageVO,
PageData,
ApiResult,
} from './types';
export type {
SysOperationLogVO,
TournamentAdminOperationPageVO,
OperationLogQueryParams,
PageData,
ApiResult,
} from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
function pickFilters(params: OperationLogQueryParams): OperationLogQueryParams {
const { page: _page, limit: _limit, ...filters } = params;
return filters;
}
/** GET /admin/sys/operation/page — 系统操作日志分页 */
export async function getOperationLogList(
params: OperationLogQueryParams,
): Promise<ApiResult<PageData<SysOperationLogVO>>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const page = Number(params.page) || 1;
const limit = Number(params.limit) || 10;
return {
code: 200,
msg: 'success',
data: buildMockOperationLogPage(page, limit, pickFilters(params)),
};
}
return get('/admin/sys/operation/page', params as Record<string, any>);
}
/** GET /admin/tournament/operation/page — 赛事操作日志分页 */
export async function getOperationLogs(
params: OperationLogQueryParams,
): Promise<ApiResult<PageData<TournamentAdminOperationPageVO>>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const page = Number(params.page) || 1;
const limit = Number(params.limit) || 10;
return {
code: 200,
msg: 'success',
data: buildMockEventOperationLogPage(page, limit, pickFilters(params)),
};
}
return get('/admin/tournament/operation/page', params as Record<string, any>);
}
/** @deprecated 使用 getOperationLogList */
export const getOperationLogPage = getOperationLogList;
+60
View File
@@ -0,0 +1,60 @@
/**
* 操作日志 — 类型定义
*/
/** 系统操作日志列表项(GET /admin/sys/operation/page */
export interface SysOperationLogVO {
/** 操作类型:1=下架赛事、2=上架赛事、3=禁用用户、4=启用用户 */
type: number;
/** 操作人昵称 */
nickname: string;
/** 操作时间 */
createDate: string;
/** 操作内容 */
content: string;
}
/** 赛事操作日志列表项 */
export interface TournamentAdminOperationPageVO {
opName: string;
source: string;
nickname: string;
phone: string;
tournamentName: string;
obj: string;
content: string;
createDate: string;
}
/** 操作日志列表查询参数 */
export interface OperationLogQueryParams {
page?: string;
limit?: string;
/** 操作类型 */
type?: string;
/** 系统日志:操作人昵称 */
nickname?: string;
/** 赛事日志:操作来源 1=PC,2=小程序 */
source?: string;
/** 赛事日志:操作人昵称 */
sourceNickname?: string;
/** 赛事日志:操作人手机号 */
sourcePhone?: string;
/** 赛事日志:赛事名称 */
tournamentName?: string;
/** 开始日期 yyyy-MM-dd */
dateBegin?: string;
/** 结束日期 yyyy-MM-dd */
dateEnd?: string;
}
export interface PageData<T> {
total: number;
list: T[];
}
export interface ApiResult<T> {
code: number;
msg: string;
data: T;
}
+27 -8
View File
@@ -1,13 +1,32 @@
/**
* 赛事操作日志假数据
*
* 类型内联,避免与 @/api/logs 循环引用。
*/
import type {
OperationLogQueryParams,
PageData,
TournamentAdminOperationPageVO,
} from '@/api/logs/types';
interface MockTournamentAdminOperationPageVO {
opName: string;
source: string;
nickname: string;
phone: string;
tournamentName: string;
obj: string;
content: string;
createDate: string;
}
const MOCK_EVENT_LOGS: TournamentAdminOperationPageVO[] = [
interface MockEventOperationLogQueryParams {
source?: string;
sourceNickname?: string;
sourcePhone?: string;
tournamentName?: string;
}
interface MockPageData<T> {
total: number;
list: T[];
}
const MOCK_EVENT_LOGS: MockTournamentAdminOperationPageVO[] = [
{
opName: '创建赛事',
source: 'PC',
@@ -33,8 +52,8 @@ const MOCK_EVENT_LOGS: TournamentAdminOperationPageVO[] = [
export function buildMockEventOperationLogPage(
page: number,
limit: number,
params: OperationLogQueryParams,
): PageData<TournamentAdminOperationPageVO> {
params: MockEventOperationLogQueryParams,
): MockPageData<MockTournamentAdminOperationPageVO> {
let filtered = [...MOCK_EVENT_LOGS];
if (params.source) {
+23 -10
View File
@@ -2,12 +2,28 @@
* 操作日志假数据
* 对应接口:GET /admin/sys/operation/page
*
* 支持按 type、nickname、dateBegin/dateEnd 前端筛选,
* 模拟后端分页行为。
* 类型内联,避免与 @/api/logs 循环引用。
*/
import type { SysOperationLogVO, OperationLogQueryParams, PageData } from '@/api/logs/types';
interface MockSysOperationLogVO {
type: number;
nickname: string;
createDate: string;
content: string;
}
const MOCK_LOGS: SysOperationLogVO[] = [
interface MockOperationLogQueryParams {
type?: string;
nickname?: string;
dateBegin?: string;
dateEnd?: string;
}
interface MockPageData<T> {
total: number;
list: T[];
}
const MOCK_LOGS: MockSysOperationLogVO[] = [
{
type: 1,
nickname: '张三',
@@ -133,7 +149,7 @@ const MOCK_LOGS: SysOperationLogVO[] = [
/** 按日期范围过滤(dateBegin / dateEnd 格式 yyyy-MM-dd */
function inDateRange(dateStr: string, begin?: string, end?: string): boolean {
if (!begin && !end) return true;
const d = dateStr.slice(0, 10); // yyyy-MM-dd
const d = dateStr.slice(0, 10);
if (begin && d < begin) return false;
if (end && d > end) return false;
return true;
@@ -142,21 +158,18 @@ function inDateRange(dateStr: string, begin?: string, end?: string): boolean {
export function buildMockOperationLogPage(
page: number,
limit: number,
params: OperationLogQueryParams,
): PageData<SysOperationLogVO> {
params: MockOperationLogQueryParams,
): MockPageData<MockSysOperationLogVO> {
let filtered = [...MOCK_LOGS];
// 按操作类型筛选(API 传 '1'/'2'/'3'/'4'
if (params.type) {
filtered = filtered.filter((item) => item.type === Number(params.type));
}
// 按操作人昵称模糊筛选
if (params.nickname) {
filtered = filtered.filter((item) => item.nickname.includes(params.nickname!));
}
// 按日期范围筛选
filtered = filtered.filter((item) =>
inDateRange(item.createDate, params.dateBegin, params.dateEnd),
);
+36
View File
@@ -0,0 +1,36 @@
// 操作内容单元格:文字最多 2 行省略,末尾带"查看"链接
:global {
.log-content-cell {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
cursor: default;
}
.log-content-text {
flex: 1;
min-width: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.5;
word-break: break-all;
color: rgba(0, 0, 0, 0.88);
}
.log-content-view {
flex-shrink: 0;
color: #1677ff;
cursor: pointer;
user-select: none;
line-height: 1.5;
white-space: nowrap;
&:hover {
opacity: 0.85;
}
}
}
+210
View File
@@ -0,0 +1,210 @@
import { defineComponent, ref, onMounted, onUnmounted, nextTick } from 'vue';
import {
Button,
Input,
Table,
DatePicker,
Form,
Space,
Select,
Tooltip,
Pagination,
} from 'ant-design-vue';
import {
useLogModel,
ACTION_TYPE_OPTIONS,
ACTION_SOURCE_OPTIONS,
ACTION_SOURCE_MAP,
} from './model/useLogModel';
import { useState, useContainerSize } from '@/hooks';
import pageStyles from '@/assets/styles/pageLayout.module.less';
import './index.module.less';
const { RangePicker } = DatePicker;
// ============================================================
// LogContentCell: 操作内容单元格
// ============================================================
const LogContentCell = defineComponent({
name: 'LogContentCell',
props: { text: { type: String, required: true } },
setup(props) {
const textRef = ref<HTMLElement | null>(null);
const [overflow, setOverflow] = useState(false);
const checkOverflow = () => {
const el = textRef.value;
if (el) setOverflow(el.scrollHeight > el.clientHeight);
};
let observer: ResizeObserver | null = null;
onMounted(() => {
nextTick(checkOverflow);
const el = textRef.value;
if (el) {
observer = new ResizeObserver(checkOverflow);
observer.observe(el);
}
});
onUnmounted(() => observer?.disconnect());
return () => {
const raw = props.text;
return (
<div class="log-content-cell">
<div ref={textRef} class="log-content-text">
{raw}
</div>
{overflow.value ? (
<Tooltip title={raw} placement="topLeft">
<span class="log-content-view"></span>
</Tooltip>
) : null}
</div>
);
};
},
});
// ============================================================
// bodyCell 渲染
// ============================================================
function renderBodyCell({ column, text }: { column: any; text: any }) {
const value = text || '-';
if (column.key === 'source') return <span>{ACTION_SOURCE_MAP[text] || value}</span>;
return <span>{value}</span>;
}
export default defineComponent({
name: 'EventLogs',
setup() {
const {
filterForm,
loading,
dataSource,
columns,
pagination,
handleSearch,
handleReset,
handlePageChange,
} = useLogModel();
const { containerRef, height } = useContainerSize({ headerOffset: 55 });
onMounted(() => {
handleSearch();
});
return () => (
<div class={pageStyles.containerMain}>
<div class={pageStyles.filter}>
<Form style={{ rowGap: '10px' }} layout="inline" model={filterForm}>
<Form.Item label="操作时间" name="dateRange">
<RangePicker
value={filterForm.dateRange as any}
format="YYYY-MM-DD"
valueFormat="YYYY-MM-DD"
style={{ width: '280px' }}
allowClear
onUpdate:value={(val: any) => (filterForm.dateRange = val)}
/>
</Form.Item>
<Form.Item label="操作类型" name="type">
<Select
value={filterForm.type}
options={ACTION_TYPE_OPTIONS as any}
style={{ width: '180px' }}
allowClear
placeholder="全部"
onUpdate:value={(val: any) => (filterForm.type = val || '')}
/>
</Form.Item>
<Form.Item label="操作来源" name="source">
<Select
value={filterForm.source}
options={ACTION_SOURCE_OPTIONS as any}
style={{ width: '120px' }}
allowClear
placeholder="全部"
onUpdate:value={(val: any) => (filterForm.source = val || '')}
/>
</Form.Item>
<Form.Item label="操作人昵称" name="sourceNickname">
<Input
value={filterForm.sourceNickname}
placeholder="请输入"
style={{ width: '160px' }}
allowClear
onUpdate:value={(val: any) =>
(filterForm.sourceNickname = val.target?.value ?? val ?? '')
}
onPressEnter={handleSearch}
/>
</Form.Item>
<Form.Item label="手机号" name="sourcePhone">
<Input
value={filterForm.sourcePhone}
placeholder="请输入"
style={{ width: '160px' }}
allowClear
onUpdate:value={(val: any) =>
(filterForm.sourcePhone = val.target?.value ?? val ?? '')
}
onPressEnter={handleSearch}
/>
</Form.Item>
<Form.Item label="赛事名称" name="tournamentName">
<Input
value={filterForm.tournamentName}
placeholder="请输入"
style={{ width: '200px' }}
allowClear
onUpdate:value={(val: any) =>
(filterForm.tournamentName = val.target?.value ?? val ?? '')
}
onPressEnter={handleSearch}
/>
</Form.Item>
<Form.Item>
<Space>
<Button onClick={handleReset}></Button>
<Button type="primary" onClick={handleSearch} loading={loading.value}>
</Button>
</Space>
</Form.Item>
</Form>
</div>
<div class={pageStyles.table}>
<div ref={containerRef} class={pageStyles.tableBody}>
<Table
columns={columns}
dataSource={dataSource.value}
loading={loading.value}
scroll={{ x: 'max-content', y: height.value }}
pagination={false}
>
{{
bodyCell: (args: any) => {
if (args.column.key === 'content') {
const raw = args.text || '';
return raw ? <LogContentCell text={raw} /> : <span>-</span>;
}
return renderBodyCell(args);
},
}}
</Table>
</div>
<div class={pageStyles.pagination}>
<Pagination
current={pagination.value.current}
pageSize={pagination.value.pageSize}
total={pagination.value.total}
showSizeChanger
showTotal={(total: number) => `${total}`}
onChange={handlePageChange}
onShowSizeChange={handlePageChange}
/>
</div>
</div>
</div>
);
},
});
+154
View File
@@ -0,0 +1,154 @@
import { computed, reactive, toRef, Ref } from 'vue';
import { message } from 'ant-design-vue';
import { useState, useDebounce, useThrottleFn } from '@/hooks';
import {
getOperationLogs,
type TournamentAdminOperationPageVO,
type OperationLogQueryParams,
} from '@/api/logs';
// ============================================================
// 常量
// ============================================================
/** 操作类型选项(TODO: API type 字段待定意,暂时留空) */
export const ACTION_TYPE_OPTIONS = [{ value: '', label: '全部' }] as const;
/** 操作来源选项(value 对应 API source: 1=PC2=小程序) */
export const ACTION_SOURCE_OPTIONS = [
{ value: '', label: '全部' },
{ value: '1', label: 'PC' },
{ value: '2', label: '小程序' },
] as const;
/** 操作来源文案映射 */
export const ACTION_SOURCE_MAP: Record<string, string> = {
'1': 'PC',
'2': '小程序',
};
// ============================================================
// Model
// ============================================================
export function useLogModel() {
// ===== 筛选条件(key 名对齐 API 查询参数) =====
const filterForm = reactive({
dateRange: null as [string, string] | null,
type: '',
source: '',
sourceNickname: '',
sourcePhone: '',
tournamentName: '',
});
const { debouncedValue: debouncedNickname } = useDebounce(
toRef(filterForm, 'sourceNickname') as Ref<string>,
{ delay: 300 },
);
const { debouncedValue: debouncedPhone } = useDebounce(
toRef(filterForm, 'sourcePhone') as Ref<string>,
{ delay: 300 },
);
const { debouncedValue: debouncedTournamentName } = useDebounce(
toRef(filterForm, 'tournamentName') as Ref<string>,
{ delay: 300 },
);
// ===== 表格状态 =====
const [loading, setLoading] = useState<boolean>(false);
const [dataSource, setDataSource] = useState<TournamentAdminOperationPageVO[]>([]);
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
// ===== 表格列配置(dataIndex 对齐 TournamentAdminOperationPageVO =====
const columns = [
{ title: '操作类型', dataIndex: 'opName', key: 'opName', width: 140 },
{ title: '操作来源', dataIndex: 'source', key: 'source', width: 100 },
{ title: '操作人昵称', dataIndex: 'nickname', key: 'nickname', width: 120 },
{ title: '操作人手机号', dataIndex: 'phone', key: 'phone', width: 140 },
{ title: '赛事名称', dataIndex: 'tournamentName', key: 'tournamentName', width: 200 },
{ title: '操作对象', dataIndex: 'obj', key: 'obj', width: 240 },
{ title: '操作内容', dataIndex: 'content', key: 'content', width: 360 },
{ title: '操作时间', dataIndex: 'createDate', key: 'createDate', width: 170 },
];
// ===== 构建 API 查询参数 =====
const buildQueryParams = (): OperationLogQueryParams => {
const params: OperationLogQueryParams = {
page: String(pagination.value.current),
limit: String(pagination.value.pageSize),
};
if (filterForm.dateRange) {
params.dateBegin = filterForm.dateRange[0];
params.dateEnd = filterForm.dateRange[1];
}
if (filterForm.type) params.type = filterForm.type;
if (filterForm.source) params.source = filterForm.source;
if (filterForm.sourceNickname.trim()) params.sourceNickname = filterForm.sourceNickname.trim();
if (filterForm.sourcePhone.trim()) params.sourcePhone = filterForm.sourcePhone.trim();
if (filterForm.tournamentName.trim()) params.tournamentName = filterForm.tournamentName.trim();
return params;
};
// ===== 计算属性 =====
const hasFilter = computed(() => {
return (
filterForm.dateRange !== null ||
filterForm.type !== '' ||
filterForm.source !== '' ||
debouncedNickname.value.trim() !== '' ||
debouncedPhone.value.trim() !== '' ||
debouncedTournamentName.value.trim() !== ''
);
});
// ===== 方法 =====
const handleSearch = useThrottleFn(async () => {
setLoading(true);
try {
const params = buildQueryParams();
console.log('操作日志查询参数:', params);
const res = await getOperationLogs(params);
if (res.code === 200) {
setDataSource(res.data.list);
setPagination({ ...pagination.value, total: res.data.total });
} else {
message.error(res.msg || '查询失败');
}
} catch (e: any) {
console.error('操作日志查询失败:', e);
} finally {
setLoading(false);
}
}, 500);
const handleReset = useThrottleFn(() => {
filterForm.dateRange = null;
filterForm.type = '';
filterForm.source = '';
filterForm.sourceNickname = '';
filterForm.sourcePhone = '';
filterForm.tournamentName = '';
setPagination({ current: 1, pageSize: 10, total: 0 });
setDataSource([]);
setTimeout(() => handleSearch(), 350);
}, 500);
const handlePageChange = (page: number, pageSize: number) => {
setPagination({ ...pagination.value, current: page, pageSize });
handleSearch();
};
return {
filterForm,
loading,
dataSource,
columns,
pagination,
hasFilter,
handleSearch,
handleReset,
handlePageChange,
};
}
+118
View File
@@ -0,0 +1,118 @@
import { defineComponent, onMounted } 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';
import pageStyles from '@/assets/styles/pageLayout.module.less';
const { RangePicker } = DatePicker;
/**
* bodyCell 渲染
*/
function renderBodyCell({ column, text }: { column: any; text: any }) {
if (column.key === 'type') {
return <span>{ACTION_TYPE_MAP[text as number] || text || '-'}</span>;
}
return <span>{text || '-'}</span>;
}
/**
* 系统操作日志
*/
export default defineComponent({
name: 'SystemLogs',
setup() {
const {
filterForm,
loading,
dataSource,
columns,
pagination,
handleSearch,
handleReset,
handlePageChange,
} = useLogModel();
const { containerRef, height } = useContainerSize({ headerOffset: 55 });
// 首次进入自动加载
onMounted(() => {
handleSearch();
});
return () => (
<div class={pageStyles.containerMain}>
{/* ===== 筛选区 ===== */}
<div class={pageStyles.filter}>
<Form style={{ rowGap: '10px' }} layout="inline" model={filterForm}>
<Form.Item label="操作时间" name="actionTimeRange">
<RangePicker
value={filterForm.actionTimeRange as any}
style={{ width: '320px' }}
allowClear
onUpdate:value={(val: any) => (filterForm.actionTimeRange = val)}
/>
</Form.Item>
<Form.Item label="操作类型" name="actionType">
<Select
value={filterForm.actionType}
options={ACTION_TYPE_OPTIONS as any}
style={{ width: '140px' }}
allowClear
placeholder="全部"
onUpdate:value={(val: any) => (filterForm.actionType = val || '')}
/>
</Form.Item>
<Form.Item label="操作人" name="searchOperator">
<Input
v-model:value={filterForm.searchOperator}
placeholder="请输入"
style={{ width: '160px' }}
allowClear
onPressEnter={handleSearch}
/>
</Form.Item>
<Form.Item>
<Space>
<Button onClick={handleReset}></Button>
<Button type="primary" onClick={handleSearch} loading={loading.value}>
</Button>
</Space>
</Form.Item>
</Form>
</div>
{/* ===== 表格区 ===== */}
<div class={pageStyles.table}>
<div ref={containerRef} class={pageStyles.tableBody}>
<Table
columns={columns}
dataSource={dataSource.value}
loading={loading.value}
scroll={{ x: 'max-content', y: height.value }}
pagination={false}
>
{{
bodyCell: (args: any) => renderBodyCell(args),
}}
</Table>
</div>
{/* 独立分页 */}
<div class={pageStyles.pagination}>
<Pagination
current={pagination.value.current}
pageSize={pagination.value.pageSize}
total={pagination.value.total}
showSizeChanger
showTotal={(total: number) => `${total}`}
onChange={handlePageChange}
onShowSizeChange={handlePageChange}
/>
</div>
</div>
</div>
);
},
});
+146
View File
@@ -0,0 +1,146 @@
import { computed, reactive, toRef, Ref } from 'vue';
import dayjs from 'dayjs';
import { useState, useDebounce, useThrottleFn } from '@/hooks';
import {
getOperationLogList,
type SysOperationLogVO,
type OperationLogQueryParams,
} from '@/api/logs';
// ============================================================
// 常量
// ============================================================
/** 操作类型选项(筛选),value 对应 API type 字段 */
export const ACTION_TYPE_OPTIONS = [
{ value: '', label: '全部' },
{ value: '1', label: '下架赛事' },
{ value: '2', label: '上架赛事' },
{ value: '3', label: '禁用用户' },
{ value: '4', label: '启用用户' },
] as const;
/** 操作类型文案映射 */
export const ACTION_TYPE_MAP: Record<number, string> = {
1: '下架赛事',
2: '上架赛事',
3: '禁用用户',
4: '启用用户',
};
// ============================================================
// Model
// ============================================================
/**
* 系统操作日志页数据模型
*/
export function useLogModel() {
// ===== 筛选条件 =====
const filterForm = reactive({
actionTimeRange: null as [string, string] | null,
actionType: '',
searchOperator: '',
});
// 操作人字段防抖 300ms
const { debouncedValue: debouncedOperator } = useDebounce(
toRef(filterForm, 'searchOperator') as Ref<string>,
{ 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() !== ''
);
});
// ===== 构建 API 查询参数 =====
const buildQueryParams = (): OperationLogQueryParams => {
const params: OperationLogQueryParams = {
page: String(pagination.value.current),
limit: String(pagination.value.pageSize),
};
// 时间范围 → dateBegin / dateEnd
if (filterForm.actionTimeRange) {
const [start, end] = filterForm.actionTimeRange;
if (start) params.dateBegin = dayjs(start).format('YYYY-MM-DD');
if (end) params.dateEnd = dayjs(end).format('YYYY-MM-DD');
}
// 操作类型(仅非空时传参)
if (filterForm.actionType) {
params.type = filterForm.actionType;
}
// 操作人昵称(防抖后的值)
if (debouncedOperator.value.trim()) {
params.nickname = debouncedOperator.value.trim();
}
return params;
};
// ===== 方法 =====
/** 查询(节流 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 handleReset = useThrottleFn(() => {
filterForm.actionTimeRange = null;
filterForm.actionType = '';
filterForm.searchOperator = '';
setPagination({ current: 1, pageSize: 10, total: 0 });
setDataSource([]);
setTimeout(() => handleSearch(), 350);
}, 500);
const handlePageChange = (page: number, pageSize: number) => {
setPagination({ ...pagination.value, current: page, pageSize });
handleSearch();
};
return {
filterForm,
loading,
dataSource,
columns,
pagination,
hasFilter,
handleSearch,
handleReset,
handlePageChange,
};
}