feat: 添加引导页、视频播放和触摸交互功能

- 新增引导页组件(Guide.tsx)及相关资源
- 添加视频播放功能和相关资源文件
- 实现useTouch自定义hook支持触摸交互
- 添加store状态管理
- 更新多个页面组件(Content, Menu, Publicity)
- 优化图片组件和背景图片组件
- 更新项目依赖
This commit is contained in:
zhongyidian committed 2025-10-31 17:49:13 +08:00
1 parent 4ce7520066
commit 53d0eeac6b
24 files changed
+1390 -84

No files matched your search

+485
View File
@@ -0,0 +1,485 @@
import { useRef, useCallback, useState } from "react";
/**
* 触摸点信息
*/
interface TouchPoint {
x: number;
y: number;
timestamp: number;
}
/**
* 滑动方向
*/
export type SwipeDirection = "left" | "right" | "up" | "down";
/**
* 手势事件回调参数
*/
export interface TouchEventData {
/** 起始位置 */
startPoint: TouchPoint;
/** 当前位置 */
currentPoint: TouchPoint;
/** 移动距离 X */
deltaX: number;
/** 移动距离 Y */
deltaY: number;
/** 移动的总距离 */
distance: number;
/** 移动速度(像素/毫秒) */
velocity: number;
/** 移动方向 */
direction: SwipeDirection | null;
/** 原始事件 */
event: React.TouchEvent | React.MouseEvent;
}
/**
* useTouch 配置选项
*/
export interface UseTouchOptions {
/** 是否启用,默认 true */
enabled?: boolean;
// ========== 滑动配置 ==========
/** 触发滑动的最小距离(像素),默认 10 */
swipeThreshold?: number;
/** 触发快速滑动的最小速度(像素/毫秒),默认 0.5 */
swipeVelocityThreshold?: number;
// ========== 长按配置 ==========
/** 触发长按的时间(毫秒),默认 500 */
longPressDelay?: number;
/** 长按时允许的最大移动距离(像素),默认 10 */
longPressMoveThreshold?: number;
// ========== 双击配置 ==========
/** 双击的最大时间间隔(毫秒),默认 300 */
doubleTapDelay?: number;
/** 双击的最大移动距离(像素),默认 10 */
doubleTapMoveThreshold?: number;
// ========== 事件回调 ==========
/** 触摸开始 */
onTouchStart?: (data: TouchEventData) => void;
/** 触摸移动 */
onTouchMove?: (data: TouchEventData) => void;
/** 触摸结束 */
onTouchEnd?: (data: TouchEventData) => void;
/** 点击 */
onTap?: (data: TouchEventData) => void;
/** 双击 */
onDoubleTap?: (data: TouchEventData) => void;
/** 长按 */
onLongPress?: (data: TouchEventData) => void;
/** 滑动 */
onSwipe?: (data: TouchEventData) => void;
/** 向左滑动 */
onSwipeLeft?: (data: TouchEventData) => void;
/** 向右滑动 */
onSwipeRight?: (data: TouchEventData) => void;
/** 向上滑动 */
onSwipeUp?: (data: TouchEventData) => void;
/** 向下滑动 */
onSwipeDown?: (data: TouchEventData) => void;
// ========== 其他配置 ==========
/** 是否阻止默认行为,默认 false */
preventDefault?: boolean;
/** 是否阻止事件冒泡,默认 false */
stopPropagation?: boolean;
}
/**
* useTouch 返回值
*/
export interface UseTouchReturn {
/** 是否正在触摸 */
isTouching: boolean;
/** 触摸事件处理器 */
handlers: {
onTouchStart: (e: React.TouchEvent) => void;
onTouchMove: (e: React.TouchEvent) => void;
onTouchEnd: (e: React.TouchEvent) => void;
onMouseDown: (e: React.MouseEvent) => void;
};
/** 当前触摸数据 */
touchData: TouchEventData | null;
}
/**
* 计算两点之间的距离
*/
function getDistance(p1: TouchPoint, p2: TouchPoint): number {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 计算滑动方向
*/
function getDirection(deltaX: number, deltaY: number): SwipeDirection | null {
const absDeltaX = Math.abs(deltaX);
const absDeltaY = Math.abs(deltaY);
if (absDeltaX < 10 && absDeltaY < 10) {
return null;
}
if (absDeltaX > absDeltaY) {
return deltaX > 0 ? "right" : "left";
} else {
return deltaY > 0 ? "down" : "up";
}
}
/**
* 灵活的触摸手势处理 Hook
*
* @example
* ```tsx
* const { handlers, isTouching } = useTouch({
* onSwipeLeft: () => console.log('向左滑动'),
* onSwipeRight: () => console.log('向右滑动'),
* onTap: () => console.log('点击'),
* onDoubleTap: () => console.log('双击'),
* onLongPress: () => console.log('长按'),
* });
*
* return <div {...handlers}>可触摸区域</div>;
* ```
*/
export function useTouch(options: UseTouchOptions = {}): UseTouchReturn {
const {
enabled = true,
swipeThreshold = 10,
swipeVelocityThreshold = 0.5,
longPressDelay = 500,
longPressMoveThreshold = 10,
doubleTapDelay = 300,
doubleTapMoveThreshold = 10,
onTouchStart,
onTouchMove,
onTouchEnd,
onTap,
onDoubleTap,
onLongPress,
onSwipe,
onSwipeLeft,
onSwipeRight,
onSwipeUp,
onSwipeDown,
preventDefault = false,
stopPropagation = false,
} = options;
// 状态
const [isTouching, setIsTouching] = useState(false);
const [touchData, setTouchData] = useState<TouchEventData | null>(null);
// 引用
const startPointRef = useRef<TouchPoint | null>(null);
const currentPointRef = useRef<TouchPoint | null>(null);
const lastTapTimeRef = useRef<number>(0);
const longPressTimerRef = useRef<NodeJS.Timeout | null>(null);
const isLongPressTriggeredRef = useRef(false);
const isMouseDownRef = useRef(false);
/**
* 创建触摸点
*/
const createTouchPoint = useCallback(
(x: number, y: number): TouchPoint => ({
x,
y,
timestamp: Date.now(),
}),
[]
);
/**
* 创建触摸事件数据
*/
const createTouchEventData = useCallback(
(
startPoint: TouchPoint,
currentPoint: TouchPoint,
event: React.TouchEvent | React.MouseEvent
): TouchEventData => {
const deltaX = currentPoint.x - startPoint.x;
const deltaY = currentPoint.y - startPoint.y;
const distance = getDistance(startPoint, currentPoint);
const deltaTime = currentPoint.timestamp - startPoint.timestamp;
const velocity = deltaTime > 0 ? distance / deltaTime : 0;
const direction = getDirection(deltaX, deltaY);
return {
startPoint,
currentPoint,
deltaX,
deltaY,
distance,
velocity,
direction,
event,
};
},
[]
);
/**
* 清除长按定时器
*/
const clearLongPressTimer = useCallback(() => {
if (longPressTimerRef.current) {
clearTimeout(longPressTimerRef.current);
longPressTimerRef.current = null;
}
}, []);
/**
* 处理触摸开始
*/
const handleStart = useCallback(
(x: number, y: number, event: React.TouchEvent | React.MouseEvent) => {
if (!enabled) return;
if (preventDefault) event.preventDefault();
if (stopPropagation) event.stopPropagation();
const point = createTouchPoint(x, y);
startPointRef.current = point;
currentPointRef.current = point;
isLongPressTriggeredRef.current = false;
setIsTouching(true);
const data = createTouchEventData(point, point, event);
setTouchData(data);
onTouchStart?.(data);
// 设置长按定时器
if (onLongPress) {
clearLongPressTimer();
longPressTimerRef.current = setTimeout(() => {
if (startPointRef.current && currentPointRef.current) {
const distance = getDistance(
startPointRef.current,
currentPointRef.current
);
if (distance < longPressMoveThreshold) {
isLongPressTriggeredRef.current = true;
const longPressData = createTouchEventData(
startPointRef.current,
currentPointRef.current,
event
);
onLongPress(longPressData);
}
}
}, longPressDelay);
}
},
[
enabled,
preventDefault,
stopPropagation,
createTouchPoint,
createTouchEventData,
onTouchStart,
onLongPress,
longPressDelay,
longPressMoveThreshold,
clearLongPressTimer,
]
);
/**
* 处理触摸移动
*/
const handleMove = useCallback(
(x: number, y: number, event: React.TouchEvent | React.MouseEvent) => {
if (!enabled || !startPointRef.current) return;
if (preventDefault) event.preventDefault();
if (stopPropagation) event.stopPropagation();
const point = createTouchPoint(x, y);
currentPointRef.current = point;
const data = createTouchEventData(startPointRef.current, point, event);
setTouchData(data);
onTouchMove?.(data);
// 如果移动距离超过阈值,取消长按
if (data.distance > longPressMoveThreshold) {
clearLongPressTimer();
}
},
[
enabled,
preventDefault,
stopPropagation,
createTouchPoint,
createTouchEventData,
onTouchMove,
longPressMoveThreshold,
clearLongPressTimer,
]
);
/**
* 处理触摸结束
*/
const handleEnd = useCallback(
(event: React.TouchEvent | React.MouseEvent) => {
if (!enabled || !startPointRef.current || !currentPointRef.current)
return;
if (preventDefault) event.preventDefault();
if (stopPropagation) event.stopPropagation();
clearLongPressTimer();
const data = createTouchEventData(
startPointRef.current,
currentPointRef.current,
event
);
onTouchEnd?.(data);
// 判断手势类型
const { distance, velocity, direction } = data;
// 长按已触发,不处理其他手势
if (isLongPressTriggeredRef.current) {
setIsTouching(false);
setTouchData(null);
startPointRef.current = null;
currentPointRef.current = null;
return;
}
// 滑动手势
if (distance > swipeThreshold || velocity > swipeVelocityThreshold) {
onSwipe?.(data);
if (direction === "left") onSwipeLeft?.(data);
if (direction === "right") onSwipeRight?.(data);
if (direction === "up") onSwipeUp?.(data);
if (direction === "down") onSwipeDown?.(data);
}
// 点击或双击
else if (distance < doubleTapMoveThreshold) {
const now = Date.now();
const timeSinceLastTap = now - lastTapTimeRef.current;
if (timeSinceLastTap < doubleTapDelay && onDoubleTap) {
// 双击
onDoubleTap(data);
lastTapTimeRef.current = 0; // 重置,避免三击触发两次双击
} else {
// 单击
onTap?.(data);
lastTapTimeRef.current = now;
}
}
setIsTouching(false);
setTouchData(null);
startPointRef.current = null;
currentPointRef.current = null;
},
[
enabled,
preventDefault,
stopPropagation,
createTouchEventData,
onTouchEnd,
onSwipe,
onSwipeLeft,
onSwipeRight,
onSwipeUp,
onSwipeDown,
onTap,
onDoubleTap,
swipeThreshold,
swipeVelocityThreshold,
doubleTapMoveThreshold,
doubleTapDelay,
clearLongPressTimer,
]
);
// 触摸事件处理器
const handleTouchStart = useCallback(
(e: React.TouchEvent) => {
if (e.touches.length === 1) {
const touch = e.touches[0];
handleStart(touch?.clientX || 0, touch?.clientY || 0, e);
}
},
[handleStart]
);
const handleTouchMove = useCallback(
(e: React.TouchEvent) => {
if (e.touches.length === 1) {
const touch = e.touches[0];
handleMove(touch?.clientX || 0, touch?.clientY || 0, e);
}
},
[handleMove]
);
const handleTouchEnd = useCallback(
(e: React.TouchEvent) => {
handleEnd(e);
},
[handleEnd]
);
// 鼠标事件处理器(桌面端支持)
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
isMouseDownRef.current = true;
handleStart(e.clientX, e.clientY, e);
const handleMouseMove = (event: MouseEvent) => {
if (isMouseDownRef.current) {
handleMove(
event.clientX,
event.clientY,
e as unknown as React.MouseEvent
);
}
};
const handleMouseUp = (event: MouseEvent) => {
if (isMouseDownRef.current) {
isMouseDownRef.current = false;
handleEnd(e as unknown as React.MouseEvent);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
}
};
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
},
[handleStart, handleMove, handleEnd]
);
return {
isTouching,
handlers: {
onTouchStart: handleTouchStart,
onTouchMove: handleTouchMove,
onTouchEnd: handleTouchEnd,
onMouseDown: handleMouseDown,
},
touchData,
};
}