Spaces:
Running
Running
Upload 5 files
Browse files- 前端页面/components/图片展示测试.js +9 -16
- 前端页面/components/图片沙盒组件.js +0 -18
- 前端页面/components/用户体验_国际化.js +20 -9
- 前端页面/components/用户体验增强.js +33 -106
- 前端页面/components/视频播放器组件.js +11 -98
前端页面/components/图片展示测试.js
CHANGED
|
@@ -502,8 +502,6 @@ class CyberImageViewer {
|
|
| 502 |
</svg>
|
| 503 |
</button>
|
| 504 |
<div class="cyber-keyboard-hint">
|
| 505 |
-
<span><span class="cyber-key">←</span> ${escapeHtml(t('image.viewer.previous_short'))}</span>
|
| 506 |
-
<span><span class="cyber-key">→</span> ${escapeHtml(t('image.viewer.next_short'))}</span>
|
| 507 |
<span><span class="cyber-key">ESC</span> ${escapeHtml(t('image.viewer.close_short'))}</span>
|
| 508 |
</div>
|
| 509 |
</div>
|
|
@@ -623,6 +621,8 @@ class CyberImageViewer {
|
|
| 623 |
pauseIcon.style.display = 'none';
|
| 624 |
}
|
| 625 |
|
|
|
|
|
|
|
| 626 |
// 触发状态变化回调
|
| 627 |
this._autoPlayStateChangeCallbacks.forEach(cb => cb(this._autoPlayEnabled));
|
| 628 |
}
|
|
@@ -762,6 +762,8 @@ class CyberImageViewer {
|
|
| 762 |
_bindEvents() {
|
| 763 |
// 键盘导航
|
| 764 |
this._keydownHandler = (e) => {
|
|
|
|
|
|
|
| 765 |
if (e.key === 'Tab') {
|
| 766 |
const controls = [...this.container.querySelectorAll('button:not([disabled]), [tabindex="0"]')];
|
| 767 |
if (!controls.length) { e.preventDefault(); this.container.focus(); return; }
|
|
@@ -775,22 +777,13 @@ class CyberImageViewer {
|
|
| 775 |
return;
|
| 776 |
}
|
| 777 |
if (e.key === 'Escape') {
|
|
|
|
|
|
|
| 778 |
this.close();
|
| 779 |
-
|
| 780 |
-
// 键盘导航使用屏幕中心作为波浪起点
|
| 781 |
-
this._stopAutoPlay(); // 手动切换时停止自动播放
|
| 782 |
-
this.prev(window.innerWidth / 2, window.innerHeight / 2);
|
| 783 |
-
} else if (e.key === 'ArrowRight') {
|
| 784 |
-
// 键盘导航使用屏幕中心作为波浪起点
|
| 785 |
-
this._stopAutoPlay(); // 手动切换时停止自动播放
|
| 786 |
-
this.next(window.innerWidth / 2, window.innerHeight / 2);
|
| 787 |
-
} else if (e.key === ' ') {
|
| 788 |
-
// 空格键切换自动播放状态
|
| 789 |
-
e.preventDefault(); // 阻止页面滚动
|
| 790 |
-
this.toggleAutoPlay();
|
| 791 |
}
|
| 792 |
};
|
| 793 |
-
|
| 794 |
|
| 795 |
// 点击导航区域 - 传递点击位置用于波浪效果
|
| 796 |
this.elements.navLeft.addEventListener('click', (e) => {
|
|
@@ -1579,7 +1572,7 @@ class CyberImageViewer {
|
|
| 1579 |
this._stopAutoPlay();
|
| 1580 |
|
| 1581 |
// 清理事件监听
|
| 1582 |
-
|
| 1583 |
|
| 1584 |
// 添加关闭动画
|
| 1585 |
this.container.style.opacity = '1';
|
|
|
|
| 502 |
</svg>
|
| 503 |
</button>
|
| 504 |
<div class="cyber-keyboard-hint">
|
|
|
|
|
|
|
| 505 |
<span><span class="cyber-key">ESC</span> ${escapeHtml(t('image.viewer.close_short'))}</span>
|
| 506 |
</div>
|
| 507 |
</div>
|
|
|
|
| 621 |
pauseIcon.style.display = 'none';
|
| 622 |
}
|
| 623 |
|
| 624 |
+
btn.setAttribute('aria-label', btn.title);
|
| 625 |
+
|
| 626 |
// 触发状态变化回调
|
| 627 |
this._autoPlayStateChangeCallbacks.forEach(cb => cb(this._autoPlayEnabled));
|
| 628 |
}
|
|
|
|
| 762 |
_bindEvents() {
|
| 763 |
// 键盘导航
|
| 764 |
this._keydownHandler = (e) => {
|
| 765 |
+
if (e.defaultPrevented || e.isComposing || e.keyCode === 229
|
| 766 |
+
|| e.ctrlKey || e.metaKey || e.altKey || (e.shiftKey && e.key !== 'Tab')) return;
|
| 767 |
if (e.key === 'Tab') {
|
| 768 |
const controls = [...this.container.querySelectorAll('button:not([disabled]), [tabindex="0"]')];
|
| 769 |
if (!controls.length) { e.preventDefault(); this.container.focus(); return; }
|
|
|
|
| 777 |
return;
|
| 778 |
}
|
| 779 |
if (e.key === 'Escape') {
|
| 780 |
+
e.preventDefault();
|
| 781 |
+
e.stopPropagation();
|
| 782 |
this.close();
|
| 783 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 784 |
}
|
| 785 |
};
|
| 786 |
+
this.container.addEventListener('keydown', this._keydownHandler);
|
| 787 |
|
| 788 |
// 点击导航区域 - 传递点击位置用于波浪效果
|
| 789 |
this.elements.navLeft.addEventListener('click', (e) => {
|
|
|
|
| 1572 |
this._stopAutoPlay();
|
| 1573 |
|
| 1574 |
// 清理事件监听
|
| 1575 |
+
this.container.removeEventListener('keydown', this._keydownHandler);
|
| 1576 |
|
| 1577 |
// 添加关闭动画
|
| 1578 |
this.container.style.opacity = '1';
|
前端页面/components/图片沙盒组件.js
CHANGED
|
@@ -314,28 +314,10 @@ export function setupImageSandboxEvents(detailView) {
|
|
| 314 |
window.addEventListener('mouseup', handleMouseUp);
|
| 315 |
viewport.addEventListener('mouseleave', () => { isDragging = false; viewport.style.cursor = 'grab'; });
|
| 316 |
|
| 317 |
-
// ⌨️ 键盘左右切换
|
| 318 |
-
const handleKeydown = (e) => {
|
| 319 |
-
if (e.key === 'ArrowLeft' && imageUrls.length > 1) {
|
| 320 |
-
loadImage(currentIndex > 0 ? currentIndex - 1 : imageUrls.length - 1);
|
| 321 |
-
} else if (e.key === 'ArrowRight' && imageUrls.length > 1) {
|
| 322 |
-
loadImage(currentIndex < imageUrls.length - 1 ? currentIndex + 1 : 0);
|
| 323 |
-
}
|
| 324 |
-
};
|
| 325 |
-
|
| 326 |
-
// 当鼠标在视口内时启用键盘切换
|
| 327 |
-
viewport.addEventListener('mouseenter', () => {
|
| 328 |
-
document.addEventListener('keydown', handleKeydown);
|
| 329 |
-
});
|
| 330 |
-
viewport.addEventListener('mouseleave', () => {
|
| 331 |
-
document.removeEventListener('keydown', handleKeydown);
|
| 332 |
-
});
|
| 333 |
-
|
| 334 |
// 🔧 P3优化:注册清理函数,供组件销毁时调用
|
| 335 |
const cleanup = () => {
|
| 336 |
window.removeEventListener('mousemove', handleMouseMove);
|
| 337 |
window.removeEventListener('mouseup', handleMouseUp);
|
| 338 |
-
document.removeEventListener('keydown', handleKeydown);
|
| 339 |
};
|
| 340 |
viewport._sandboxCleanup = cleanup;
|
| 341 |
}
|
|
|
|
| 314 |
window.addEventListener('mouseup', handleMouseUp);
|
| 315 |
viewport.addEventListener('mouseleave', () => { isDragging = false; viewport.style.cursor = 'grab'; });
|
| 316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
// 🔧 P3优化:注册清理函数,供组件销毁时调用
|
| 318 |
const cleanup = () => {
|
| 319 |
window.removeEventListener('mousemove', handleMouseMove);
|
| 320 |
window.removeEventListener('mouseup', handleMouseUp);
|
|
|
|
| 321 |
};
|
| 322 |
viewport._sandboxCleanup = cleanup;
|
| 323 |
}
|
前端页面/components/用户体验_国际化.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
| 9 |
// - 默认中文
|
| 10 |
|
| 11 |
let currentLang = 'zh-CN';
|
|
|
|
| 12 |
|
| 13 |
const translations = {
|
| 14 |
'zh-CN': {
|
|
@@ -1259,8 +1260,8 @@ const translations = {
|
|
| 1259 |
'image.viewer.close': '关闭 (ESC)',
|
| 1260 |
'image.viewer.previous': '上一张 (←)',
|
| 1261 |
'image.viewer.next': '下一张 (→)',
|
| 1262 |
-
'image.viewer.autoplay': '自动播放
|
| 1263 |
-
'image.viewer.pause': '暂停自动播放
|
| 1264 |
'image.viewer.dialog': '图片查看器',
|
| 1265 |
'image.viewer.previous_short': '上一张',
|
| 1266 |
'image.viewer.next_short': '下一张',
|
|
@@ -3058,8 +3059,8 @@ const translations = {
|
|
| 3058 |
'image.viewer.close': 'Close (Esc)',
|
| 3059 |
'image.viewer.previous': 'Previous image (←)',
|
| 3060 |
'image.viewer.next': 'Next image (→)',
|
| 3061 |
-
'image.viewer.autoplay': 'Start slideshow
|
| 3062 |
-
'image.viewer.pause': 'Pause slideshow
|
| 3063 |
'image.viewer.dialog': 'Image viewer',
|
| 3064 |
'image.viewer.previous_short': 'Previous',
|
| 3065 |
'image.viewer.next_short': 'Next',
|
|
@@ -3434,9 +3435,13 @@ export function tIfExists(key, fallback = '') {
|
|
| 3434 |
* @param {string} lang - 语言代码 ('zh-CN' | 'en-US')
|
| 3435 |
*/
|
| 3436 |
export function setLanguage(lang) {
|
| 3437 |
-
if (translations
|
| 3438 |
currentLang = lang;
|
| 3439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3440 |
|
| 3441 |
// 触发语言变更事件
|
| 3442 |
document.dispatchEvent(new CustomEvent('comfy-language-change', {
|
|
@@ -3457,18 +3462,24 @@ export function getLanguage() {
|
|
| 3457 |
* 初始化 i18n
|
| 3458 |
*/
|
| 3459 |
export function initI18n() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3460 |
// 🔧 修复:优先从全局设置读取语言
|
| 3461 |
try {
|
| 3462 |
const globalSettings = JSON.parse(localStorage.getItem('ComfyCommunity_Settings') || '{}');
|
| 3463 |
-
if (globalSettings.language && translations
|
| 3464 |
currentLang = globalSettings.language;
|
| 3465 |
return;
|
| 3466 |
}
|
| 3467 |
} catch (e) {}
|
| 3468 |
|
| 3469 |
// 回退:从旧的独立存储读取
|
| 3470 |
-
|
| 3471 |
-
|
|
|
|
|
|
|
| 3472 |
currentLang = savedLang;
|
| 3473 |
} else {
|
| 3474 |
// 检测浏览器语言
|
|
|
|
| 9 |
// - 默认中文
|
| 10 |
|
| 11 |
let currentLang = 'zh-CN';
|
| 12 |
+
let pendingLanguage = null;
|
| 13 |
|
| 14 |
const translations = {
|
| 15 |
'zh-CN': {
|
|
|
|
| 1260 |
'image.viewer.close': '关闭 (ESC)',
|
| 1261 |
'image.viewer.previous': '上一张 (←)',
|
| 1262 |
'image.viewer.next': '下一张 (→)',
|
| 1263 |
+
'image.viewer.autoplay': '自动播放',
|
| 1264 |
+
'image.viewer.pause': '暂停自动播放',
|
| 1265 |
'image.viewer.dialog': '图片查看器',
|
| 1266 |
'image.viewer.previous_short': '上一张',
|
| 1267 |
'image.viewer.next_short': '下一张',
|
|
|
|
| 3059 |
'image.viewer.close': 'Close (Esc)',
|
| 3060 |
'image.viewer.previous': 'Previous image (←)',
|
| 3061 |
'image.viewer.next': 'Next image (→)',
|
| 3062 |
+
'image.viewer.autoplay': 'Start slideshow',
|
| 3063 |
+
'image.viewer.pause': 'Pause slideshow',
|
| 3064 |
'image.viewer.dialog': 'Image viewer',
|
| 3065 |
'image.viewer.previous_short': 'Previous',
|
| 3066 |
'image.viewer.next_short': 'Next',
|
|
|
|
| 3435 |
* @param {string} lang - 语言代码 ('zh-CN' | 'en-US')
|
| 3436 |
*/
|
| 3437 |
export function setLanguage(lang) {
|
| 3438 |
+
if (typeof lang === 'string' && Object.hasOwn(translations, lang)) {
|
| 3439 |
currentLang = lang;
|
| 3440 |
+
pendingLanguage = lang;
|
| 3441 |
+
try {
|
| 3442 |
+
localStorage.setItem('comfy_ranking_lang', lang);
|
| 3443 |
+
pendingLanguage = null;
|
| 3444 |
+
} catch {}
|
| 3445 |
|
| 3446 |
// 触发语言变更事件
|
| 3447 |
document.dispatchEvent(new CustomEvent('comfy-language-change', {
|
|
|
|
| 3462 |
* 初始化 i18n
|
| 3463 |
*/
|
| 3464 |
export function initI18n() {
|
| 3465 |
+
if (pendingLanguage !== null) {
|
| 3466 |
+
currentLang = pendingLanguage;
|
| 3467 |
+
return;
|
| 3468 |
+
}
|
| 3469 |
// 🔧 修复:优先从全局设置读取语言
|
| 3470 |
try {
|
| 3471 |
const globalSettings = JSON.parse(localStorage.getItem('ComfyCommunity_Settings') || '{}');
|
| 3472 |
+
if (typeof globalSettings.language === 'string' && Object.hasOwn(translations, globalSettings.language)) {
|
| 3473 |
currentLang = globalSettings.language;
|
| 3474 |
return;
|
| 3475 |
}
|
| 3476 |
} catch (e) {}
|
| 3477 |
|
| 3478 |
// 回退:从旧的独立存储读取
|
| 3479 |
+
let savedLang = null;
|
| 3480 |
+
try { savedLang = localStorage.getItem('comfy_ranking_lang'); }
|
| 3481 |
+
catch {}
|
| 3482 |
+
if (typeof savedLang === 'string' && Object.hasOwn(translations, savedLang)) {
|
| 3483 |
currentLang = savedLang;
|
| 3484 |
} else {
|
| 3485 |
// 检测浏览器语言
|
前端页面/components/用户体验增强.js
CHANGED
|
@@ -218,118 +218,48 @@ export function createAutoSaver(formId, formElement, options = {}) {
|
|
| 218 |
// ==========================================
|
| 219 |
// ⌨️ P2-3: 快捷键支持
|
| 220 |
// ==========================================
|
| 221 |
-
// 特点:
|
| 222 |
-
// - Esc 关闭弹窗
|
| 223 |
-
// - Enter 确认提交
|
| 224 |
-
// - Ctrl+S 保存
|
| 225 |
-
// - 可配置扩展
|
| 226 |
-
|
| 227 |
const keyboardShortcuts = new Map();
|
| 228 |
-
let shortcutsInitialized = false;
|
| 229 |
|
| 230 |
-
/**
|
| 231 |
-
* 注册全局快捷键
|
| 232 |
-
* @param {string} key - 按键组合 (如 'Escape', 'Enter', 'Ctrl+S')
|
| 233 |
-
* @param {Function} handler - 处理函数
|
| 234 |
-
* @param {Object} options - 配置选项
|
| 235 |
-
*/
|
| 236 |
export function registerShortcut(key, handler, options = {}) {
|
| 237 |
-
const {
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
}
|
| 254 |
|
| 255 |
-
/**
|
| 256 |
-
* 移除快捷键
|
| 257 |
-
* @param {string} key - 按键组合
|
| 258 |
-
* @param {Function} handler - 处理函数(可选,不传则移除所有)
|
| 259 |
-
*/
|
| 260 |
export function unregisterShortcut(key, handler = null) {
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
keyboardShortcuts.set(key, filtered);
|
| 266 |
-
} else {
|
| 267 |
-
keyboardShortcuts.delete(key);
|
| 268 |
-
}
|
| 269 |
-
} else {
|
| 270 |
-
keyboardShortcuts.delete(key);
|
| 271 |
}
|
|
|
|
|
|
|
| 272 |
}
|
| 273 |
|
| 274 |
-
|
| 275 |
-
* 初始化快捷键系统
|
| 276 |
-
*/
|
| 277 |
-
export function initKeyboardShortcuts() {
|
| 278 |
-
if (shortcutsInitialized) return;
|
| 279 |
-
shortcutsInitialized = true;
|
| 280 |
-
|
| 281 |
-
document.addEventListener('keydown', (e) => {
|
| 282 |
-
// 构建按键标识
|
| 283 |
-
let keyCombo = '';
|
| 284 |
-
if (e.ctrlKey || e.metaKey) keyCombo += 'Ctrl+';
|
| 285 |
-
if (e.altKey) keyCombo += 'Alt+';
|
| 286 |
-
if (e.shiftKey) keyCombo += 'Shift+';
|
| 287 |
-
keyCombo += e.key;
|
| 288 |
-
|
| 289 |
-
// 也检查单独的按键
|
| 290 |
-
const keysToCheck = [keyCombo];
|
| 291 |
-
if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
|
| 292 |
-
keysToCheck.push(e.key);
|
| 293 |
-
}
|
| 294 |
-
|
| 295 |
-
for (const key of keysToCheck) {
|
| 296 |
-
if (keyboardShortcuts.has(key)) {
|
| 297 |
-
const handlers = keyboardShortcuts.get(key);
|
| 298 |
-
for (const { handler, preventDefault } of handlers) {
|
| 299 |
-
// 跳过在输入框中的快捷键(除了 Escape)
|
| 300 |
-
const isInput = ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName);
|
| 301 |
-
if (isInput && key !== 'Escape') continue;
|
| 302 |
-
|
| 303 |
-
if (preventDefault) {
|
| 304 |
-
e.preventDefault();
|
| 305 |
-
}
|
| 306 |
-
handler(e);
|
| 307 |
-
return;
|
| 308 |
-
}
|
| 309 |
-
}
|
| 310 |
-
}
|
| 311 |
-
});
|
| 312 |
-
|
| 313 |
-
// 注册默认快捷键
|
| 314 |
-
_registerDefaultShortcuts();
|
| 315 |
-
}
|
| 316 |
-
|
| 317 |
-
function _registerDefaultShortcuts() {
|
| 318 |
-
// Esc 关闭最上层弹窗
|
| 319 |
-
registerShortcut('Escape', () => {
|
| 320 |
-
const modals = document.querySelectorAll('[data-modal="true"]');
|
| 321 |
-
if (modals.length > 0) {
|
| 322 |
-
const topModal = modals[modals.length - 1];
|
| 323 |
-
const closeBtn = topModal.querySelector('[data-close]');
|
| 324 |
-
if (closeBtn) closeBtn.click();
|
| 325 |
-
}
|
| 326 |
-
}, { description: '关闭弹窗' });
|
| 327 |
-
|
| 328 |
-
// Ctrl+S 保存(触发自定义事件)
|
| 329 |
-
registerShortcut('Ctrl+s', () => {
|
| 330 |
-
document.dispatchEvent(new CustomEvent('comfy-save-shortcut'));
|
| 331 |
-
}, { description: '保存' });
|
| 332 |
-
}
|
| 333 |
|
| 334 |
|
| 335 |
// ==========================================
|
|
@@ -574,9 +504,6 @@ export function initUXEnhancements() {
|
|
| 574 |
// 应用安全区域
|
| 575 |
applySafeAreaInsets();
|
| 576 |
|
| 577 |
-
// 初始化快捷键
|
| 578 |
-
initKeyboardShortcuts();
|
| 579 |
-
|
| 580 |
// 初始化 i18n
|
| 581 |
initI18n();
|
| 582 |
|
|
|
|
| 218 |
// ==========================================
|
| 219 |
// ⌨️ P2-3: 快捷键支持
|
| 220 |
// ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
const keyboardShortcuts = new Map();
|
|
|
|
| 222 |
|
| 223 |
+
/** 仅为明确的局部容器注册按键;不接受页面全局作用域。 */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
export function registerShortcut(key, handler, options = {}) {
|
| 225 |
+
const { scope, preventDefault = true } = options;
|
| 226 |
+
if (!scope || scope.nodeType !== 1 || scope === document.body || scope === document.documentElement
|
| 227 |
+
|| typeof handler !== 'function' || typeof key !== 'string') return () => {};
|
| 228 |
+
const listener = event => {
|
| 229 |
+
if (!scope.isConnected || !scope.contains(event.target) || event.defaultPrevented
|
| 230 |
+
|| event.isComposing || event.keyCode === 229 || event.repeat) return;
|
| 231 |
+
if (event.target.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"])')
|
| 232 |
+
&& event.key !== 'Escape') return;
|
| 233 |
+
const combo = `${event.ctrlKey || event.metaKey ? 'Ctrl+' : ''}${event.altKey ? 'Alt+' : ''}${event.shiftKey ? 'Shift+' : ''}${event.key}`;
|
| 234 |
+
if (combo !== key) return;
|
| 235 |
+
if (preventDefault) event.preventDefault();
|
| 236 |
+
event.stopPropagation();
|
| 237 |
+
handler(event);
|
| 238 |
+
};
|
| 239 |
+
const entry = { handler, scope, listener };
|
| 240 |
+
const entries = keyboardShortcuts.get(key) || [];
|
| 241 |
+
entries.push(entry);
|
| 242 |
+
keyboardShortcuts.set(key, entries);
|
| 243 |
+
scope.addEventListener('keydown', listener);
|
| 244 |
+
return () => {
|
| 245 |
+
scope.removeEventListener('keydown', listener);
|
| 246 |
+
const remaining = (keyboardShortcuts.get(key) || []).filter(item => item !== entry);
|
| 247 |
+
if (remaining.length) keyboardShortcuts.set(key, remaining);
|
| 248 |
+
else keyboardShortcuts.delete(key);
|
| 249 |
+
};
|
| 250 |
}
|
| 251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
export function unregisterShortcut(key, handler = null) {
|
| 253 |
+
const remaining = [];
|
| 254 |
+
for (const entry of keyboardShortcuts.get(key) || []) {
|
| 255 |
+
if (handler && entry.handler !== handler) remaining.push(entry);
|
| 256 |
+
else entry.scope.removeEventListener('keydown', entry.listener);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
}
|
| 258 |
+
if (remaining.length) keyboardShortcuts.set(key, remaining);
|
| 259 |
+
else keyboardShortcuts.delete(key);
|
| 260 |
}
|
| 261 |
|
| 262 |
+
export function initKeyboardShortcuts() {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
|
| 265 |
// ==========================================
|
|
|
|
| 504 |
// 应用安全区域
|
| 505 |
applySafeAreaInsets();
|
| 506 |
|
|
|
|
|
|
|
|
|
|
| 507 |
// 初始化 i18n
|
| 508 |
initI18n();
|
| 509 |
|
前端页面/components/视频播放器组件.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
// ==========================================
|
| 3 |
// 🎬 自定义视频播放器组件
|
| 4 |
// ==========================================
|
| 5 |
-
// 功能:内嵌视频播放 + 全屏沉浸式播放,支持自定义控制栏
|
| 6 |
// 接口:getVideoPlayerHTML / setupVideoPlayerEvents / cleanupVideoPlayer / openFullscreenVideo
|
| 7 |
// ==========================================
|
| 8 |
|
|
@@ -676,56 +676,6 @@ export function setupVideoPlayerEvents(container) {
|
|
| 676 |
});
|
| 677 |
}
|
| 678 |
|
| 679 |
-
// 8. 键盘快捷键
|
| 680 |
-
const handleKeydown = (e) => {
|
| 681 |
-
if (document.activeElement !== player) return;
|
| 682 |
-
switch (e.key) {
|
| 683 |
-
case ' ':
|
| 684 |
-
case 'k':
|
| 685 |
-
e.preventDefault();
|
| 686 |
-
togglePlay();
|
| 687 |
-
break;
|
| 688 |
-
case 'ArrowLeft':
|
| 689 |
-
e.preventDefault();
|
| 690 |
-
video.currentTime = Math.max(0, video.currentTime - 5);
|
| 691 |
-
break;
|
| 692 |
-
case 'ArrowRight':
|
| 693 |
-
e.preventDefault();
|
| 694 |
-
video.currentTime = Math.min(video.duration || 0, video.currentTime + 5);
|
| 695 |
-
break;
|
| 696 |
-
case 'ArrowUp':
|
| 697 |
-
e.preventDefault();
|
| 698 |
-
video.volume = Math.min(1, video.volume + 0.1);
|
| 699 |
-
if (video.volume > 0) video.muted = false;
|
| 700 |
-
if (volumeSlider) volumeSlider.value = Math.round(video.volume * 100);
|
| 701 |
-
updateVolumeIcon();
|
| 702 |
-
break;
|
| 703 |
-
case 'ArrowDown':
|
| 704 |
-
e.preventDefault();
|
| 705 |
-
video.volume = Math.max(0, video.volume - 0.1);
|
| 706 |
-
if (volumeSlider) volumeSlider.value = Math.round(video.volume * 100);
|
| 707 |
-
updateVolumeIcon();
|
| 708 |
-
break;
|
| 709 |
-
case 'f':
|
| 710 |
-
case 'F':
|
| 711 |
-
e.preventDefault();
|
| 712 |
-
if (btnFullscreen) btnFullscreen.click();
|
| 713 |
-
break;
|
| 714 |
-
case 'm':
|
| 715 |
-
case 'M':
|
| 716 |
-
e.preventDefault();
|
| 717 |
-
video.muted = !video.muted;
|
| 718 |
-
updateVolumeIcon();
|
| 719 |
-
break;
|
| 720 |
-
}
|
| 721 |
-
};
|
| 722 |
-
player.addEventListener('keydown', handleKeydown);
|
| 723 |
-
|
| 724 |
-
// 点击播放器获取焦点
|
| 725 |
-
player.addEventListener('click', () => {
|
| 726 |
-
player.focus();
|
| 727 |
-
});
|
| 728 |
-
|
| 729 |
// 9. 控制栏自动隐藏
|
| 730 |
player.addEventListener('mousemove', resetHideTimer);
|
| 731 |
player.addEventListener('mouseenter', resetHideTimer);
|
|
@@ -897,7 +847,7 @@ export function openFullscreenVideo(videoUrl, posterUrl, options = {}) {
|
|
| 897 |
if (destroyed) return;
|
| 898 |
destroyed = true;
|
| 899 |
if (hideControlsTimer) clearTimeout(hideControlsTimer);
|
| 900 |
-
|
| 901 |
document.removeEventListener('click', handleDocClickForSpeed);
|
| 902 |
window.removeEventListener('mousemove', handleMouseMove);
|
| 903 |
window.removeEventListener('mouseup', handleMouseUp);
|
|
@@ -1100,53 +1050,16 @@ export function openFullscreenVideo(videoUrl, posterUrl, options = {}) {
|
|
| 1100 |
// 关闭按钮
|
| 1101 |
closeBtn.addEventListener('click', () => destroy(video.currentTime));
|
| 1102 |
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
case ' ':
|
| 1111 |
-
case 'k':
|
| 1112 |
-
e.preventDefault();
|
| 1113 |
-
togglePlay();
|
| 1114 |
-
break;
|
| 1115 |
-
case 'ArrowLeft':
|
| 1116 |
-
e.preventDefault();
|
| 1117 |
-
video.currentTime = Math.max(0, video.currentTime - 5);
|
| 1118 |
-
break;
|
| 1119 |
-
case 'ArrowRight':
|
| 1120 |
-
e.preventDefault();
|
| 1121 |
-
video.currentTime = Math.min(video.duration || 0, video.currentTime + 5);
|
| 1122 |
-
break;
|
| 1123 |
-
case 'ArrowUp':
|
| 1124 |
-
e.preventDefault();
|
| 1125 |
-
video.volume = Math.min(1, video.volume + 0.1);
|
| 1126 |
-
if (video.volume > 0) video.muted = false;
|
| 1127 |
-
volumeSlider.value = Math.round(video.volume * 100);
|
| 1128 |
-
updateVolumeIcon();
|
| 1129 |
-
break;
|
| 1130 |
-
case 'ArrowDown':
|
| 1131 |
-
e.preventDefault();
|
| 1132 |
-
video.volume = Math.max(0, video.volume - 0.1);
|
| 1133 |
-
volumeSlider.value = Math.round(video.volume * 100);
|
| 1134 |
-
updateVolumeIcon();
|
| 1135 |
-
break;
|
| 1136 |
-
case 'f':
|
| 1137 |
-
case 'F':
|
| 1138 |
-
e.preventDefault();
|
| 1139 |
-
destroy(video.currentTime);
|
| 1140 |
-
break;
|
| 1141 |
-
case 'm':
|
| 1142 |
-
case 'M':
|
| 1143 |
-
e.preventDefault();
|
| 1144 |
-
video.muted = !video.muted;
|
| 1145 |
-
updateVolumeIcon();
|
| 1146 |
-
break;
|
| 1147 |
-
}
|
| 1148 |
};
|
| 1149 |
-
|
|
|
|
| 1150 |
|
| 1151 |
// 控制栏自动隐藏
|
| 1152 |
overlay.addEventListener('mousemove', resetHideTimer);
|
|
|
|
| 2 |
// ==========================================
|
| 3 |
// 🎬 自定义视频播放器组件
|
| 4 |
// ==========================================
|
| 5 |
+
// 功能:内嵌视频播放 + 全屏沉浸式播放,支持自定义控制栏
|
| 6 |
// 接口:getVideoPlayerHTML / setupVideoPlayerEvents / cleanupVideoPlayer / openFullscreenVideo
|
| 7 |
// ==========================================
|
| 8 |
|
|
|
|
| 676 |
});
|
| 677 |
}
|
| 678 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 679 |
// 9. 控制栏自动隐藏
|
| 680 |
player.addEventListener('mousemove', resetHideTimer);
|
| 681 |
player.addEventListener('mouseenter', resetHideTimer);
|
|
|
|
| 847 |
if (destroyed) return;
|
| 848 |
destroyed = true;
|
| 849 |
if (hideControlsTimer) clearTimeout(hideControlsTimer);
|
| 850 |
+
overlay.removeEventListener('keydown', handleKeydown);
|
| 851 |
document.removeEventListener('click', handleDocClickForSpeed);
|
| 852 |
window.removeEventListener('mousemove', handleMouseMove);
|
| 853 |
window.removeEventListener('mouseup', handleMouseUp);
|
|
|
|
| 1050 |
// 关闭按钮
|
| 1051 |
closeBtn.addEventListener('click', () => destroy(video.currentTime));
|
| 1052 |
|
| 1053 |
+
const handleKeydown = event => {
|
| 1054 |
+
if (event.defaultPrevented || event.isComposing || event.keyCode === 229
|
| 1055 |
+
|| event.ctrlKey || event.metaKey || event.altKey || event.shiftKey
|
| 1056 |
+
|| event.key !== 'Escape') return;
|
| 1057 |
+
event.preventDefault();
|
| 1058 |
+
event.stopPropagation();
|
| 1059 |
+
destroy(video.currentTime);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1060 |
};
|
| 1061 |
+
overlay.addEventListener('keydown', handleKeydown);
|
| 1062 |
+
closeBtn.focus();
|
| 1063 |
|
| 1064 |
// 控制栏自动隐藏
|
| 1065 |
overlay.addEventListener('mousemove', resetHideTimer);
|