Files
SearcjGal-frontend/src/stores/ui.ts
AdingApkgg c46517da8b feat: 重构进度条与键盘快捷键帮助功能
* 移除 `nprogress` 依赖,替换为自定义进度条,使用 `anime.js` 实现更流畅的加载效果。
* 在 `index.html` 中优化主题检测与背景样式,提升用户体验。
* 添加键盘快捷键帮助面板,增强用户交互,支持通过快捷键显示/隐藏。
* 更新多个组件以集成新的键盘帮助功能,确保一致性和可用性。
* 优化 UI 状态管理,支持会话状态的恢复与清除,提升用户体验。
2025-12-21 11:30:04 +08:00

415 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
// 持久化的 UI 状态类型
export interface PersistedUIState {
// 主题
isDarkMode: boolean
customCSS: string
// 模态框状态
isCommentsModalOpen: boolean
isVndbPanelOpen: boolean
isSettingsModalOpen: boolean
isHistoryModalOpen: boolean
isKeyboardHelpOpen: boolean
// 其他 UI 状态
showSearchHistory: boolean
showPlatformNav: boolean
// 滚动位置
scrollPosition: number
// 时间戳
lastVisitTime: number
}
const STORAGE_KEY = 'ui-state'
const SESSION_KEY = 'ui-session-state'
// 默认持久化状态
const DEFAULT_PERSISTED_STATE: PersistedUIState = {
isDarkMode: false,
customCSS: '',
isCommentsModalOpen: false,
isVndbPanelOpen: false,
isSettingsModalOpen: false,
isHistoryModalOpen: false,
isKeyboardHelpOpen: false,
showSearchHistory: true,
showPlatformNav: false,
scrollPosition: 0,
lastVisitTime: 0,
}
export const useUIStore = defineStore('ui', () => {
// 是否已初始化
const isInitialized = ref(false)
// 主题相关
const isDarkMode = ref(false)
const customCSS = ref('')
// 模态框状态
const isCommentsModalOpen = ref(false)
const isVndbPanelOpen = ref(false)
const isSettingsModalOpen = ref(false)
const isHistoryModalOpen = ref(false)
const isKeyboardHelpOpen = ref(false)
// 浮动按钮状态
const showScrollToTop = ref(false)
const showPlatformNav = ref(false)
// 搜索历史显示
const showSearchHistory = ref(true)
// 背景图片
const currentBackgroundImage = ref('')
const backgroundImageLoaded = ref(false)
// 加载状态
const isLoading = ref(false)
const loadingMessage = ref('')
// SW 更新状态
const showUpdateToast = ref(false)
// 滚动位置(用于恢复)
const scrollPosition = ref(0)
// Toast 通知
const toasts = ref<Array<{
id: string
type: 'success' | 'error' | 'info' | 'warning'
message: string
duration: number
}>>([])
// 计算属性
const hasOpenModal = computed(() =>
isCommentsModalOpen.value ||
isVndbPanelOpen.value ||
isSettingsModalOpen.value ||
isHistoryModalOpen.value,
)
const activeModalsCount = computed(() => {
let count = 0
if (isCommentsModalOpen.value) {count++}
if (isVndbPanelOpen.value) {count++}
if (isSettingsModalOpen.value) {count++}
if (isHistoryModalOpen.value) {count++}
return count
})
// 方法 - 主题
function toggleDarkMode() {
isDarkMode.value = !isDarkMode.value
document.documentElement.classList.toggle('dark', isDarkMode.value)
localStorage.setItem('darkMode', isDarkMode.value ? 'true' : 'false')
}
function setDarkMode(value: boolean) {
isDarkMode.value = value
document.documentElement.classList.toggle('dark', value)
localStorage.setItem('darkMode', value ? 'true' : 'false')
}
function setCustomCSS(css: string) {
customCSS.value = css
}
// 方法 - 模态框
function toggleCommentsModal() {
isCommentsModalOpen.value = !isCommentsModalOpen.value
}
function toggleVndbPanel() {
isVndbPanelOpen.value = !isVndbPanelOpen.value
}
function toggleSettingsModal() {
isSettingsModalOpen.value = !isSettingsModalOpen.value
}
function closeAllModals() {
isCommentsModalOpen.value = false
isVndbPanelOpen.value = false
isSettingsModalOpen.value = false
isHistoryModalOpen.value = false
isKeyboardHelpOpen.value = false
}
function toggleHistoryModal() {
isHistoryModalOpen.value = !isHistoryModalOpen.value
}
// 方法 - 浮动按钮
function setShowScrollToTop(show: boolean) {
showScrollToTop.value = show
}
function togglePlatformNav() {
showPlatformNav.value = !showPlatformNav.value
}
function closePlatformNav() {
showPlatformNav.value = false
}
// 方法 - 搜索历史
function toggleSearchHistory() {
showSearchHistory.value = !showSearchHistory.value
}
// 方法 - 背景图片
function setBackgroundImage(url: string) {
currentBackgroundImage.value = url
}
function setBackgroundImageLoaded(loaded: boolean) {
backgroundImageLoaded.value = loaded
}
// 方法 - 加载状态
function setLoading(loading: boolean, message = '') {
isLoading.value = loading
loadingMessage.value = message
}
// 方法 - Toast 通知
function showToast(
type: 'success' | 'error' | 'info' | 'warning',
message: string,
duration = 3000,
) {
const id = `toast-${Date.now()}-${Math.random()}`
toasts.value.push({ id, type, message, duration })
// 自动移除
setTimeout(() => {
removeToast(id)
}, duration)
return id
}
function removeToast(id: string) {
const index = toasts.value.findIndex(t => t.id === id)
if (index !== -1) {
toasts.value.splice(index, 1)
}
}
function clearToasts() {
toasts.value = []
}
// 从 localStorage 加载持久化状态(长期偏好)
function loadPersistedState() {
try {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
const parsed: Partial<PersistedUIState> = JSON.parse(saved)
isDarkMode.value = parsed.isDarkMode ?? DEFAULT_PERSISTED_STATE.isDarkMode
customCSS.value = parsed.customCSS ?? DEFAULT_PERSISTED_STATE.customCSS
showSearchHistory.value = parsed.showSearchHistory ?? DEFAULT_PERSISTED_STATE.showSearchHistory
}
} catch {
// 解析失败,使用默认值
}
}
// 从 sessionStorage 加载会话状态(刷新恢复)
function loadSessionState() {
try {
const saved = sessionStorage.getItem(SESSION_KEY)
if (saved) {
const parsed: Partial<PersistedUIState> = JSON.parse(saved)
// 恢复模态框状态
isCommentsModalOpen.value = parsed.isCommentsModalOpen ?? false
isVndbPanelOpen.value = parsed.isVndbPanelOpen ?? false
isSettingsModalOpen.value = parsed.isSettingsModalOpen ?? false
isHistoryModalOpen.value = parsed.isHistoryModalOpen ?? false
isKeyboardHelpOpen.value = parsed.isKeyboardHelpOpen ?? false
// 恢复其他状态
showPlatformNav.value = parsed.showPlatformNav ?? false
scrollPosition.value = parsed.scrollPosition ?? 0
}
} catch {
// 解析失败,使用默认值
}
}
// 保存持久化状态到 localStorage长期偏好
function savePersistedState() {
try {
const state: Partial<PersistedUIState> = {
isDarkMode: isDarkMode.value,
customCSS: customCSS.value,
showSearchHistory: showSearchHistory.value,
lastVisitTime: Date.now(),
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
} catch {
// 保存失败,静默处理
}
}
// 保存会话状态到 sessionStorage刷新恢复
function saveSessionState() {
try {
const state: Partial<PersistedUIState> = {
isCommentsModalOpen: isCommentsModalOpen.value,
isVndbPanelOpen: isVndbPanelOpen.value,
isSettingsModalOpen: isSettingsModalOpen.value,
isHistoryModalOpen: isHistoryModalOpen.value,
isKeyboardHelpOpen: isKeyboardHelpOpen.value,
showPlatformNav: showPlatformNav.value,
scrollPosition: window.scrollY,
}
sessionStorage.setItem(SESSION_KEY, JSON.stringify(state))
} catch {
// 保存失败,静默处理
}
}
// 监听需要持久化的状态变化localStorage - 长期偏好)
watch(
[isDarkMode, customCSS, showSearchHistory],
() => {
if (isInitialized.value) {
savePersistedState()
}
},
)
// 监听需要保存到会话的状态变化sessionStorage - 刷新恢复)
watch(
[
isCommentsModalOpen,
isVndbPanelOpen,
isSettingsModalOpen,
isHistoryModalOpen,
isKeyboardHelpOpen,
showPlatformNav,
],
() => {
if (isInitialized.value) {
saveSessionState()
}
},
)
// 初始化
function init() {
// 加载持久化状态(长期偏好)
loadPersistedState()
// 加载会话状态(刷新恢复)
loadSessionState()
// 如果没有保存的主题偏好,跟随系统
const saved = localStorage.getItem(STORAGE_KEY)
if (!saved) {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
isDarkMode.value = prefersDark
}
// 应用主题
document.documentElement.classList.toggle('dark', isDarkMode.value)
isInitialized.value = true
// 恢复滚动位置
if (scrollPosition.value > 0) {
requestAnimationFrame(() => {
window.scrollTo(0, scrollPosition.value)
})
}
// 监听页面卸载,保存滚动位置
window.addEventListener('beforeunload', saveSessionState)
// 定期保存滚动位置(防止意外关闭)
let scrollSaveTimer: number | null = null
window.addEventListener('scroll', () => {
if (scrollSaveTimer) {
clearTimeout(scrollSaveTimer)
}
scrollSaveTimer = window.setTimeout(() => {
if (isInitialized.value) {
saveSessionState()
}
}, 500)
}, { passive: true })
}
// 显示 SW 更新提示
function setShowUpdateToast(show: boolean) {
showUpdateToast.value = show
}
// 清除会话状态(用于完全重置)
function clearSessionState() {
sessionStorage.removeItem(SESSION_KEY)
}
return {
// 状态
isInitialized,
isDarkMode,
customCSS,
isCommentsModalOpen,
isVndbPanelOpen,
isSettingsModalOpen,
isHistoryModalOpen,
showScrollToTop,
showPlatformNav,
showSearchHistory,
currentBackgroundImage,
backgroundImageLoaded,
isLoading,
loadingMessage,
showUpdateToast,
toasts,
isKeyboardHelpOpen,
scrollPosition,
// 计算属性
hasOpenModal,
activeModalsCount,
// 方法
toggleDarkMode,
setDarkMode,
setCustomCSS,
toggleCommentsModal,
toggleVndbPanel,
toggleSettingsModal,
toggleHistoryModal,
closeAllModals,
setShowScrollToTop,
togglePlatformNav,
closePlatformNav,
toggleSearchHistory,
setBackgroundImage,
setBackgroundImageLoaded,
setLoading,
setShowUpdateToast,
showToast,
removeToast,
clearToasts,
loadPersistedState,
savePersistedState,
loadSessionState,
saveSessionState,
clearSessionState,
init,
}
})