feat: 优化路由 搭建赛事管理页面基础 处理部分样式问题 完成赛事列表页面搭建

This commit is contained in:
ZhuRui
2026-07-24 15:57:20 +08:00
parent 5523695518
commit 03f1b6c220
35 changed files with 6599 additions and 117 deletions
+2
View File
@@ -7,4 +7,6 @@ export * from './useAuth';
export * from './useWebSocket';
export * from './useBasicLayout';
export * from './useBreadcrumb';
export * from './useThrottleFn';
export * from './useContainerSize';
export * from './useContext';
+54
View File
@@ -0,0 +1,54 @@
import { ref, onMounted, onUnmounted, type Ref } from 'vue';
/**
* 监听容器尺寸变化,返回动态 width/height 给 Table 的 scroll 属性使用。
*
* 基于 ResizeObserver,窗口缩放 / 侧边栏收起展开 / 筛选区折叠 等任何导致
* 容器尺寸变化的行为都会自动触发重新计算,无需额外处理。
*
* 用法:
* ```ts
* const { containerRef, width, height } = useContainerSize();
* // <div ref={containerRef}>
* // <Table scroll={{ x: width.value, y: height.value }} />
* // </div>
* ```
*
* @param headerOffset 从容器高度中扣除的表头偏移量,默认 45
*/
export function useContainerSize(options?: { headerOffset?: number }) {
const { headerOffset = 45 } = options ?? {};
const containerRef = ref<HTMLElement | null>(null) as Ref<HTMLElement | null>;
const width = ref(0);
const height = ref(0);
let observer: ResizeObserver | null = null;
const updateSize = () => {
const el = containerRef.value;
if (!el) return;
width.value = el.clientWidth;
height.value = Math.max(el.clientHeight - headerOffset, 200);
};
onMounted(() => {
updateSize();
const el = containerRef.value;
if (!el) return;
observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const w = entry.contentRect.width;
const h = entry.contentRect.height;
if (w !== width.value) width.value = w;
if (h !== height.value) height.value = Math.max(h - headerOffset, 200);
}
});
observer.observe(el);
});
onUnmounted(() => observer?.disconnect());
return { containerRef, width, height };
}
+3 -6
View File
@@ -1,5 +1,5 @@
// src/hooks/useDebounce.ts
import { watch, onUnmounted } from 'vue';
import { watch, onUnmounted, Ref } from 'vue';
import { useState } from './useState';
export interface UseDebounceOptions {
@@ -8,14 +8,11 @@ export interface UseDebounceOptions {
maxWait?: number;
}
export function useDebounce<T>(
source: any, // Ref<T>
options: number | UseDebounceOptions = {},
) {
export function useDebounce<T>(source: Ref<T>, options: number | UseDebounceOptions = {}) {
const config = typeof options === 'number' ? { delay: options } : options;
const { delay = 300, immediate = false, maxWait } = config;
const [debouncedValue, setDebouncedValue] = useState<T>(source.value);
const [debouncedValue, setDebouncedValue] = useState<T>(source.value as T);
const [isPending, setIsPending] = useState(false);
let timer: any = null;
+46
View File
@@ -0,0 +1,46 @@
import { ref } from 'vue';
/**
* 节流函数封装
* @param fn 需要节流的函数
* @param delay 节流间隔时间(ms),默认 500
* @returns 返回节流后的函数,连续调用时只会在每个 delay 周期内执行一次
*/
export function useThrottleFn<T extends (...args: any[]) => any>(
fn: T,
delay: number = 500,
): T & { cancel: () => void } {
const lastTime = ref<number>(0);
let timer: any = null;
const throttled = function (this: any, ...args: any[]) {
const now = Date.now();
const remaining = delay - (now - lastTime.value);
if (remaining <= 0) {
// 冷却时间已过,立即执行
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime.value = now;
fn.apply(this, args);
} else if (!timer) {
// 冷却时间未过,延后执行
timer = setTimeout(() => {
lastTime.value = Date.now();
timer = null;
fn.apply(this, args);
}, remaining);
}
} as T & { cancel: () => void };
throttled.cancel = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
return throttled;
}