[优化] 添加 web 端的 xbox 翻页支持 (#639)
* xbox for web supported * disable debug log * vue导入ts文件问题 * 修复 package.json 依赖问题 * 更改web.yml上游 * Bump web v3.26.022400
This commit is contained in:
@@ -19,12 +19,12 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-svg": "^2.3.1",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^13.8.0",
|
||||
"@vueuse/core": "^11.1.0",
|
||||
"@vueuse/shared": "^11.1.0",
|
||||
"axios": "^1.7.7",
|
||||
"element-plus": "^2.8.5",
|
||||
"element-plus": "2.8.5",
|
||||
"hotkeys-js": "^3.13.7",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia": "^2.2.4",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue3-virtual-scroll-list": "^0.2.1"
|
||||
@@ -32,11 +32,11 @@
|
||||
"devDependencies": {
|
||||
"sass-embedded": "^1.83.1",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/node": "^20.16.11",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue/eslint-config-prettier": "^10.0.0",
|
||||
"@vue/eslint-config-typescript": "^14.0.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-plugin-vue": "^9.29.0",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
@@ -46,6 +46,6 @@
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.8",
|
||||
"vue-tsc": "^3.0.6"
|
||||
"vue-tsc": "^2.1.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1319
-1265
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,6 @@ import API from '@api'
|
||||
import { CircleCheckFilled, Edit } from '@element-plus/icons-vue'
|
||||
import hotkeys from 'hotkeys-js'
|
||||
import { getSourceName, isInvaildSource, normalizeSource } from '../utils/souce'
|
||||
import { getSourceName, isInvaildSource } from '../utils/souce'
|
||||
|
||||
const store = useSourceStore()
|
||||
const pull = () => {
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// =====================================================
|
||||
// 类型定义
|
||||
// =====================================================
|
||||
|
||||
interface GamepadConfig {
|
||||
DEBUG: boolean;
|
||||
AXIS_THRESHOLD: number;
|
||||
AXIS_COOLDOWN: number;
|
||||
DPAD_INDEX: {
|
||||
UP: number;
|
||||
DOWN: number;
|
||||
LEFT: number;
|
||||
RIGHT: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface DPadState {
|
||||
up: boolean;
|
||||
down: boolean;
|
||||
left: boolean;
|
||||
right: boolean;
|
||||
}
|
||||
|
||||
interface GlobalState {
|
||||
lastAxisTime: number;
|
||||
lastAxisDirection: number;
|
||||
dpadPressed: DPadState;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 配置区域
|
||||
// =====================================================
|
||||
|
||||
const CONFIG: GamepadConfig = {
|
||||
DEBUG: false, // 日志总开关
|
||||
AXIS_THRESHOLD: 0.7, // 摇杆触发阈值
|
||||
AXIS_COOLDOWN: 300, // 摇杆触发冷却时间 (ms)
|
||||
DPAD_INDEX: { // Xbox 标准映射
|
||||
UP: 12,
|
||||
DOWN: 13,
|
||||
LEFT: 14,
|
||||
RIGHT: 15
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================
|
||||
// 日志工具函数
|
||||
// =====================================================
|
||||
|
||||
function log(...args: any[]): void {
|
||||
if (!CONFIG.DEBUG) return;
|
||||
console.log(...args);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 状态记录
|
||||
// =====================================================
|
||||
|
||||
const state: GlobalState = {
|
||||
lastAxisTime: 0,
|
||||
lastAxisDirection: 0,
|
||||
dpadPressed: {
|
||||
up: false,
|
||||
down: false,
|
||||
left: false,
|
||||
right: false
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================
|
||||
// 通用工具函数
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* 平滑翻页函数
|
||||
* @param direction 1 = 向下, -1 = 向上
|
||||
*/
|
||||
function scrollPage(direction: number): void {
|
||||
const offset = window.innerHeight - 110;
|
||||
const distance = direction === 1 ? offset : -offset;
|
||||
|
||||
log(direction === 1 ? "🎮 翻页:向下" : "🎮 翻页:向上");
|
||||
|
||||
window.scrollBy({
|
||||
top: distance,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 章节切换函数
|
||||
* @param direction 1 = 下一章, -1 = 上一章
|
||||
*/
|
||||
function switchChapter(direction: number): void {
|
||||
const buttons = document.querySelectorAll<HTMLElement>(".read-bar .tool-icon");
|
||||
|
||||
if (buttons.length < 2) return;
|
||||
|
||||
if (direction === 1) {
|
||||
log("🎮 切换章节:下一章");
|
||||
buttons[1].click();
|
||||
} else {
|
||||
log("🎮 切换章节:上一章");
|
||||
buttons[0].click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 边沿检测函数
|
||||
*/
|
||||
function isPressedOnce(current: boolean, previous: boolean): boolean {
|
||||
return current && !previous;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 摇杆处理
|
||||
// =====================================================
|
||||
|
||||
function handleAxis(gp: Gamepad, now: number): void {
|
||||
const axisY = gp.axes[1] || 0;
|
||||
|
||||
if (Math.abs(axisY) <= CONFIG.AXIS_THRESHOLD) {
|
||||
state.lastAxisDirection = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const direction = axisY > 0 ? 1 : -1;
|
||||
const cooldownPassed = now - state.lastAxisTime > CONFIG.AXIS_COOLDOWN;
|
||||
const directionChanged = direction !== state.lastAxisDirection;
|
||||
|
||||
if (cooldownPassed || directionChanged) {
|
||||
state.lastAxisTime = now;
|
||||
state.lastAxisDirection = direction;
|
||||
log(`🎮 摇杆触发 | axisY=${axisY.toFixed(2)}`);
|
||||
scrollPage(direction);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// DPad 处理
|
||||
// =====================================================
|
||||
|
||||
function handleDPad(gp: Gamepad): void {
|
||||
const indexes = CONFIG.DPAD_INDEX;
|
||||
|
||||
const current: DPadState = {
|
||||
up: gp.buttons[indexes.UP]?.pressed || false,
|
||||
down: gp.buttons[indexes.DOWN]?.pressed || false,
|
||||
left: gp.buttons[indexes.LEFT]?.pressed || false,
|
||||
right: gp.buttons[indexes.RIGHT]?.pressed || false
|
||||
};
|
||||
|
||||
if (isPressedOnce(current.up, state.dpadPressed.up)) {
|
||||
log("🎮 DPad 上");
|
||||
scrollPage(-1);
|
||||
}
|
||||
if (isPressedOnce(current.down, state.dpadPressed.down)) {
|
||||
log("🎮 DPad 下");
|
||||
scrollPage(1);
|
||||
}
|
||||
if (isPressedOnce(current.left, state.dpadPressed.left)) {
|
||||
log("🎮 DPad 左");
|
||||
switchChapter(-1);
|
||||
}
|
||||
if (isPressedOnce(current.right, state.dpadPressed.right)) {
|
||||
log("🎮 DPad 右");
|
||||
switchChapter(1);
|
||||
}
|
||||
|
||||
state.dpadPressed = current;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 主手柄处理入口
|
||||
// =====================================================
|
||||
|
||||
function handleGamepad(gp: Gamepad | null): void {
|
||||
if (!gp) return;
|
||||
const now = performance.now();
|
||||
handleAxis(gp, now);
|
||||
handleDPad(gp);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 主循环
|
||||
// =====================================================
|
||||
|
||||
let running = false;
|
||||
|
||||
function gamepadLoop(): void {
|
||||
const gamepads = navigator.getGamepads?.() || [];
|
||||
|
||||
for (const gp of gamepads) {
|
||||
if (gp) handleGamepad(gp);
|
||||
}
|
||||
|
||||
requestAnimationFrame(gamepadLoop);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 连接 / 断开 事件
|
||||
// =====================================================
|
||||
|
||||
window.addEventListener("gamepadconnected", (e: GamepadEvent) => {
|
||||
log("🎮 手柄已连接:", e.gamepad.id);
|
||||
if (!running) {
|
||||
running = true;
|
||||
requestAnimationFrame(gamepadLoop);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("gamepaddisconnected", (e: GamepadEvent) => {
|
||||
log("🎮 手柄已断开:", e.gamepad.id);
|
||||
});
|
||||
|
||||
export function initXboxGamepad(): void {
|
||||
if (!running) {
|
||||
running = true;
|
||||
requestAnimationFrame(gamepadLoop);
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,7 @@ import API from '@api'
|
||||
import { useLoading } from '@/hooks/loading'
|
||||
import { useThrottleFn } from '@vueuse/shared'
|
||||
import { isNullOrBlank } from '@/utils/utils'
|
||||
import { initXboxGamepad } from '@/utils/xboxGamepad'
|
||||
|
||||
const content = ref()
|
||||
// loading spinner
|
||||
@@ -519,6 +520,7 @@ onMounted(async () => {
|
||||
document.title = (name as string) + ' | ' + chapters[chapterIndex].title
|
||||
}),
|
||||
)
|
||||
initXboxGamepad()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user