整合全局设置store,优化PWA模式检测

This commit is contained in:
jxxghp
2025-07-04 16:19:50 +08:00
parent e45919cac1
commit 85780917c2
31 changed files with 294 additions and 116 deletions
+1 -1
View File
@@ -317,4 +317,4 @@
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>
+2 -2
View File
@@ -1,6 +1,6 @@
#loading-bg { #loading-bg {
position: fixed; position: fixed;
z-index: 9999; z-index: 99999;
display: block; display: block;
background: var(--initial-loader-bg, #fff); background: var(--initial-loader-bg, #fff);
block-size: 100vh; block-size: 100vh;
@@ -94,4 +94,4 @@
opacity: 1; opacity: 1;
transform: rotate(1turn); transform: rotate(1turn);
} }
} }
+10 -12
View File
@@ -3,7 +3,7 @@ import { useTheme } from 'vuetify'
import { checkPrefersColorSchemeIsDark } from '@/@core/utils' import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
import { ensureRenderComplete, removeEl } from './@core/utils/dom' import { ensureRenderComplete, removeEl } from './@core/utils/dom'
import api from '@/api' import api from '@/api'
import { useAuthStore } from '@/stores/auth' import { useAuthStore, useGlobalSettingsStore } from '@/stores'
import { getBrowserLocale, setI18nLanguage } from './plugins/i18n' import { getBrowserLocale, setI18nLanguage } from './plugins/i18n'
import { SupportedLocale } from '@/types/i18n' import { SupportedLocale } from '@/types/i18n'
import { checkAndEmitUnreadMessages } from '@/utils/badge' import { checkAndEmitUnreadMessages } from '@/utils/badge'
@@ -19,13 +19,13 @@ globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
const localeValue = getBrowserLocale() const localeValue = getBrowserLocale()
setI18nLanguage(localeValue as SupportedLocale) setI18nLanguage(localeValue as SupportedLocale)
// 显示状态
const show = ref(false)
// 检查是否登录 // 检查是否登录
const authStore = useAuthStore() const authStore = useAuthStore()
const isLogin = computed(() => authStore.token) const isLogin = computed(() => authStore.token)
// 全局设置store
const globalSettingsStore = useGlobalSettingsStore()
// 生成背景图片key // 生成背景图片key
const loginStateKey = computed(() => (isLogin.value ? 'logged-in' : 'logged-out')) const loginStateKey = computed(() => (isLogin.value ? 'logged-in' : 'logged-out'))
@@ -130,9 +130,7 @@ function animateAndRemoveLoader() {
removeEl('#loading-bg') removeEl('#loading-bg')
// 将background属性从html的style中移除 // 将background属性从html的style中移除
document.documentElement.style.removeProperty('background') document.documentElement.style.removeProperty('background')
// 显示页面 }, 500)
show.value = true
}, 500) // 与CSS动画持续时间匹配
} }
} }
@@ -155,6 +153,9 @@ async function loadBackgroundImages(retryCount = 0) {
} }
onMounted(async () => { onMounted(async () => {
// 初始化全局设置
await globalSettingsStore.initialize()
// 配置 ApexCharts // 配置 ApexCharts
configureApexCharts() configureApexCharts()
@@ -172,11 +173,8 @@ onMounted(async () => {
}, },
) )
// 默认隐藏页面
show.value = false
// 加载背景图片 // 加载背景图片
loadBackgroundImages() await loadBackgroundImages()
// 移除加载动画 // 移除加载动画
ensureRenderComplete(() => { ensureRenderComplete(() => {
@@ -213,7 +211,7 @@ onUnmounted(() => {
<div v-if="isLogin && isTransparentTheme" class="global-blur-layer"></div> <div v-if="isLogin && isTransparentTheme" class="global-blur-layer"></div>
</div> </div>
<!-- 页面内容 --> <!-- 页面内容 -->
<VApp v-show="show" :class="{ 'transparent-app': isTransparentTheme }"> <VApp :class="{ 'transparent-app': isTransparentTheme }">
<RouterView /> <RouterView />
</VApp> </VApp>
</div> </div>
+3 -1
View File
@@ -5,6 +5,7 @@ import FileNavigator from './filebrowser/FileNavigator.vue'
import type { EndPoints, FileItem, StorageConf } from '@/api/types' import type { EndPoints, FileItem, StorageConf } from '@/api/types'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { storageIconDict } from '@/api/constants' import { storageIconDict } from '@/api/constants'
import { usePWA } from '@/composables/usePWA'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
@@ -33,7 +34,8 @@ const emit = defineEmits(['pathchanged'])
const display = useDisplay() const display = useDisplay()
// APP // APP
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
const fileIcons = { const fileIcons = {
// 压缩包 // 压缩包
+4 -2
View File
@@ -9,7 +9,7 @@ import { formatSeason, formatRating } from '@/@core/utils/formatters'
import { doneNProgress, startNProgress } from '@/api/nprogress' import { doneNProgress, startNProgress } from '@/api/nprogress'
import type { MediaInfo, Subscribe, MediaSeason, Site } from '@/api/types' import type { MediaInfo, Subscribe, MediaSeason, Site } from '@/api/types'
import router from '@/router' import router from '@/router'
import { useUserStore } from '@/stores' import { useUserStore, useGlobalSettingsStore } from '@/stores'
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue' import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
import SearchSiteDialog from '@/components/dialog/SearchSiteDialog.vue' import SearchSiteDialog from '@/components/dialog/SearchSiteDialog.vue'
import SubscribeSeasonDialog from '../dialog/SubscribeSeasonDialog.vue' import SubscribeSeasonDialog from '../dialog/SubscribeSeasonDialog.vue'
@@ -28,7 +28,9 @@ const props = defineProps({
}) })
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 用户 Store // 用户 Store
const userStore = useUserStore() const userStore = useUserStore()
+4 -1
View File
@@ -2,6 +2,7 @@
import personIcon from '@images/misc/person-icon.png' import personIcon from '@images/misc/person-icon.png'
import type { Person } from '@/api/types' import type { Person } from '@/api/types'
import router from '@/router' import router from '@/router'
import { useGlobalSettingsStore } from '@/stores'
const personProps = defineProps({ const personProps = defineProps({
person: Object as PropType<Person>, person: Object as PropType<Person>,
@@ -10,7 +11,9 @@ const personProps = defineProps({
}) })
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 当前人物 // 当前人物
const personInfo = ref(personProps.person) const personInfo = ref(personProps.person)
+4 -1
View File
@@ -10,6 +10,7 @@ import type { Subscribe } from '@/api/types'
import router from '@/router' import router from '@/router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useGlobalSettingsStore } from '@/stores'
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
@@ -23,7 +24,9 @@ const props = defineProps({
}) })
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 定义触发的自定义事件 // 定义触发的自定义事件
const emit = defineEmits(['remove', 'save']) const emit = defineEmits(['remove', 'save'])
+4 -1
View File
@@ -4,6 +4,7 @@ import type { SubscribeShare } from '@/api/types'
import router from '@/router' import router from '@/router'
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue' import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
import ForkSubscribeDialog from '../dialog/ForkSubscribeDialog.vue' import ForkSubscribeDialog from '../dialog/ForkSubscribeDialog.vue'
import { useGlobalSettingsStore } from '@/stores'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
@@ -14,7 +15,9 @@ const props = defineProps({
const emit = defineEmits(['delete']) const emit = defineEmits(['delete'])
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 图片是否加载完成 // 图片是否加载完成
const imageLoaded = ref(false) const imageLoaded = ref(false)
@@ -6,6 +6,7 @@ import router from '@/router'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { VBtn } from 'vuetify/lib/components/index.mjs' import { VBtn } from 'vuetify/lib/components/index.mjs'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useGlobalSettingsStore } from '@/stores'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
@@ -19,7 +20,9 @@ const props = defineProps({
const emit = defineEmits(['fork', 'delete', 'close']) const emit = defineEmits(['fork', 'delete', 'close'])
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 提示框 // 提示框
const $toast = useToast() const $toast = useToast()
+3 -1
View File
@@ -4,6 +4,7 @@ import type { Plugin } from '@/api/types'
import PageRender from '@/components/render/PageRender.vue' import PageRender from '@/components/render/PageRender.vue'
import api from '@/api' import api from '@/api'
import { loadRemoteComponent } from '@/utils/federationLoader' import { loadRemoteComponent } from '@/utils/federationLoader'
import { usePWA } from '@/composables/usePWA'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
@@ -22,7 +23,8 @@ const emit = defineEmits(['close', 'save', 'switch'])
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
// APP // APP
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
// 是否刷新 // 是否刷新
const isRefreshed = ref(false) const isRefreshed = ref(false)
+5 -2
View File
@@ -8,6 +8,7 @@ import { useDisplay } from 'vuetify'
import ProgressDialog from './ProgressDialog.vue' import ProgressDialog from './ProgressDialog.vue'
import { FileItem, StorageConf, TransferDirectoryConf, TransferForm } from '@/api/types' import { FileItem, StorageConf, TransferDirectoryConf, TransferForm } from '@/api/types'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useGlobalSettingsStore } from '@/stores'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
@@ -24,10 +25,12 @@ const props = defineProps({
}) })
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 当前识别类型 // 当前识别类型
const mediaSource = ref(globalSettings.data?.RECOGNIZE_SOURCE || 'themoviedb') const mediaSource = ref(globalSettings.RECOGNIZE_SOURCE || 'themoviedb')
// 定义事件 // 定义事件
const emit = defineEmits(['done', 'close']) const emit = defineEmits(['done', 'close'])
@@ -4,6 +4,7 @@ import { MediaInfo, MediaSeason, NotExistMediaInfo } from '@/api/types'
import { PropType } from 'vue' import { PropType } from 'vue'
import NoDataFound from '@/components/NoDataFound.vue' import NoDataFound from '@/components/NoDataFound.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useGlobalSettingsStore } from '@/stores'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
@@ -17,7 +18,9 @@ const props = defineProps({
}) })
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 季详情 // 季详情
const seasonInfos = ref<MediaSeason[]>([]) const seasonInfos = ref<MediaSeason[]>([])
+57
View File
@@ -0,0 +1,57 @@
import { ref, computed, onMounted } from 'vue'
import { useDisplay } from 'vuetify'
import { isPWA } from '@/@core/utils/navigator'
// 全局PWA状态,确保只初始化一次
const globalPwaMode = ref<boolean | null>(null)
const globalLoading = ref(false)
let initPromise: Promise<void> | null = null
// 全局初始化函数
async function initializePWAGlobally() {
if (initPromise) return initPromise
if (globalPwaMode.value !== null || globalLoading.value) return Promise.resolve()
initPromise = new Promise(async (resolve, reject) => {
globalLoading.value = true
try {
globalPwaMode.value = await isPWA()
resolve()
} catch (error) {
console.error('Failed to detect PWA mode', error)
globalPwaMode.value = false
reject(error)
} finally {
globalLoading.value = false
}
})
return initPromise
}
export function usePWA() {
const display = useDisplay()
const appMode = computed(() => {
return globalPwaMode.value && display.mdAndDown.value
})
// 自动初始化PWA检测
onMounted(() => {
initializePWAGlobally().catch(console.error)
})
// 如果是在服务端或首次调用,立即开始初始化
if (typeof window !== 'undefined' && globalPwaMode.value === null && !globalLoading.value) {
initializePWAGlobally().catch(console.error)
}
return {
pwaMode: globalPwaMode,
appMode,
loading: globalLoading,
// 保留手动初始化方法以防需要
initializePWA: initializePWAGlobally,
}
}
+42 -13
View File
@@ -1,6 +1,7 @@
import { ref, computed, onMounted, onBeforeUnmount, inject, readonly } from 'vue' import { ref, computed, onMounted, onBeforeUnmount, readonly, watch } from 'vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { usePWA } from './usePWA'
// 下拉手势配置类型 // 下拉手势配置类型
export interface PullDownConfig { export interface PullDownConfig {
@@ -37,8 +38,7 @@ const DEFAULT_CONFIG: PullDownConfig = {
export function usePullDownGesture(options: PullDownOptions = {}) { export function usePullDownGesture(options: PullDownOptions = {}) {
const display = useDisplay() const display = useDisplay()
const route = useRoute() const { appMode } = usePWA()
const appMode = inject('pwaMode')
// 合并配置 // 合并配置
const config = { ...DEFAULT_CONFIG, ...options.config } const config = { ...DEFAULT_CONFIG, ...options.config }
@@ -126,7 +126,7 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
// 事件处理函数 // 事件处理函数
const handleTouchStart = (event: TouchEvent) => { const handleTouchStart = (event: TouchEvent) => {
if (!appMode || !display.mdAndDown.value || !options.enabled) return if (!appMode.value || !display.mdAndDown.value || !options.enabled) return
// 检查是否可以使用下拉手势 // 检查是否可以使用下拉手势
if (options.canUsePullGesture && !options.canUsePullGesture()) return if (options.canUsePullGesture && !options.canUsePullGesture()) return
@@ -149,7 +149,7 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
} }
const handleTouchMove = (event: TouchEvent) => { const handleTouchMove = (event: TouchEvent) => {
if (!appMode || !display.mdAndDown.value || !options.enabled) return if (!appMode.value || !display.mdAndDown.value || !options.enabled) return
// 检查是否可以使用下拉手势 // 检查是否可以使用下拉手势
if (options.canUsePullGesture && !options.canUsePullGesture()) return if (options.canUsePullGesture && !options.canUsePullGesture()) return
@@ -192,7 +192,7 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
} }
const handleTouchEnd = () => { const handleTouchEnd = () => {
if (!appMode || !display.mdAndDown.value || !options.enabled) return if (!appMode.value || !display.mdAndDown.value || !options.enabled) return
// 检查是否可以使用下拉手势 // 检查是否可以使用下拉手势
if (options.canUsePullGesture && !options.canUsePullGesture()) return if (options.canUsePullGesture && !options.canUsePullGesture()) return
@@ -217,20 +217,49 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
} }
// 生命周期管理 // 生命周期管理
onMounted(() => { let eventsAdded = false
if (appMode && display.mdAndDown.value) {
const addEventListeners = () => {
if (!eventsAdded && appMode.value && display.mdAndDown.value) {
document.addEventListener('touchstart', handleTouchStart, { passive: false }) document.addEventListener('touchstart', handleTouchStart, { passive: false })
document.addEventListener('touchmove', handleTouchMove, { passive: false }) document.addEventListener('touchmove', handleTouchMove, { passive: false })
document.addEventListener('touchend', handleTouchEnd, { passive: true }) document.addEventListener('touchend', handleTouchEnd, { passive: true })
eventsAdded = true
}
}
const removeEventListeners = () => {
if (eventsAdded) {
document.removeEventListener('touchstart', handleTouchStart)
document.removeEventListener('touchmove', handleTouchMove)
document.removeEventListener('touchend', handleTouchEnd)
eventsAdded = false
}
}
// PWA状态确定后,一次性决定是否添加事件监听器
onMounted(() => {
// 如果PWA已经检测完成,直接添加事件监听器
if (appMode.value !== null) {
addEventListeners()
} else {
// 等待PWA检测完成(从null变为boolean
const stopWatcher = watch(
appMode,
newValue => {
if (newValue !== null) {
addEventListeners()
// PWA状态确定后停止监听
stopWatcher()
}
},
{ immediate: true },
)
} }
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (appMode && display.mdAndDown.value) { removeEventListeners()
document.removeEventListener('touchstart', handleTouchStart)
document.removeEventListener('touchmove', handleTouchMove)
document.removeEventListener('touchend', handleTouchEnd)
}
}) })
return { return {
+3 -1
View File
@@ -18,9 +18,11 @@ import { filterMenusByPermission } from '@/utils/permission'
import { onUnreadMessage } from '@/utils/badge' import { onUnreadMessage } from '@/utils/badge'
import { usePullDownGesture } from '@/composables/usePullDownGesture' import { usePullDownGesture } from '@/composables/usePullDownGesture'
import { useScrollLockWithWatch } from '@/composables/useScrollLock' import { useScrollLockWithWatch } from '@/composables/useScrollLock'
import { usePWA } from '@/composables/usePWA'
const display = useDisplay() const display = useDisplay()
const appMode = inject('pwaMode') // PWA模式检测
const { appMode } = usePWA()
const { t } = useI18n() const { t } = useI18n()
const route = useRoute() const route = useRoute()
+3 -1
View File
@@ -5,9 +5,11 @@ import { NavMenu } from '@/@layouts/types'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { filterMenusByPermission } from '@/utils/permission' import { filterMenusByPermission } from '@/utils/permission'
import { usePWA } from '@/composables/usePWA'
const display = useDisplay() const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
const { t, locale } = useI18n() const { t, locale } = useI18n()
// 判断当前是否为英文环境 // 判断当前是否为英文环境
+3 -1
View File
@@ -4,6 +4,7 @@ import useDragAndDrop from '@core/utils/workflow'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { actionStepDict } from '@/api/constants' import { actionStepDict } from '@/api/constants'
import { usePWA } from '@/composables/usePWA'
interface ActionItem { interface ActionItem {
name: string name: string
@@ -13,7 +14,8 @@ interface ActionItem {
const display = useDisplay() const display = useDisplay()
// APP // APP
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
const { t } = useI18n() const { t } = useI18n()
const { onDragStart } = useDragAndDrop() const { onDragStart } = useDragAndDrop()
+39 -57
View File
@@ -18,9 +18,7 @@ import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'
import { CronVuetify } from '@vue-js-cron/vuetify' import { CronVuetify } from '@vue-js-cron/vuetify'
// 4. 工具函数和其他辅助模块 // 4. 工具函数和其他辅助模块
import { isPWA } from './@core/utils/navigator'
import { loadRemoteComponents } from './utils/federationLoader' import { loadRemoteComponents } from './utils/federationLoader'
import { fetchGlobalSettings } from './utils/globalSetting'
// 5. 其他插件和功能模块 // 5. 其他插件和功能模块
import Toast from 'vue-toastification' import Toast from 'vue-toastification'
@@ -51,59 +49,43 @@ const app = createApp(App)
// 注册pinia // 注册pinia
app.use(pinia) app.use(pinia)
// 初始化配置 // 异步加载远程组件(不阻塞启动)
async function initializeApp() { loadRemoteComponents().catch(error => {
try { console.error('Failed to load remote components', error)
// 是否为PWA
const pwaMode = await isPWA()
app.provide('pwaMode', pwaMode)
// 全局设置
const globalSettings = await fetchGlobalSettings()
app.provide('globalSettings', globalSettings)
// 加载并注册远程联邦组件
await loadRemoteComponents()
} catch (error) {
console.error('Failed to initialize app', error)
}
}
// 注册全局组件
initializeApp().then(() => {
// 1. 注册 UI 框架
app.use(vuetify)
// 2. 注册路由
app.use(router)
// 3. 注册全局组件
app
.component('VAceEditor', VAceEditor)
.component('VApexChart', VueApexCharts)
.component('VCronVuetify', CronVuetify)
.component('VDialogCloseBtn', DialogCloseBtn)
.component('VScrollToTopBtn', ScrollToTopBtn)
.component('VMediaCard', MediaCard)
.component('VPosterCard', PosterCard)
.component('VBackdropCard', BackdropCard)
.component('VPersonCard', PersonCard)
.component('VMediaInfoCard', MediaInfoCard)
.component('VTorrentCard', TorrentCard)
.component('VMediaIdSelector', MediaIdSelector)
.component('VCronField', CronField)
.component('VPathField', PathField)
.component('VHeaderTab', HeaderTab)
.component('VPageContentTitle', PageContentTitle)
// 5. 注册其他插件
app
.use(PerfectScrollbarPlugin)
.use(Toast, {
position: 'bottom-right',
hideProgressBar: true,
})
.use(ConfirmDialog)
.use(i18n)
.mount('#app')
}) })
// 1. 注册 UI 框架
app.use(vuetify)
// 2. 注册路由
app.use(router)
// 3. 注册全局组件
app
.component('VAceEditor', VAceEditor)
.component('VApexChart', VueApexCharts)
.component('VCronVuetify', CronVuetify)
.component('VDialogCloseBtn', DialogCloseBtn)
.component('VScrollToTopBtn', ScrollToTopBtn)
.component('VMediaCard', MediaCard)
.component('VPosterCard', PosterCard)
.component('VBackdropCard', BackdropCard)
.component('VPersonCard', PersonCard)
.component('VMediaInfoCard', MediaInfoCard)
.component('VTorrentCard', TorrentCard)
.component('VMediaIdSelector', MediaIdSelector)
.component('VCronField', CronField)
.component('VPathField', PathField)
.component('VHeaderTab', HeaderTab)
.component('VPageContentTitle', PageContentTitle)
// 4. 注册其他插件
app
.use(PerfectScrollbarPlugin)
.use(Toast, {
position: 'bottom-right',
hideProgressBar: true,
})
.use(ConfirmDialog)
.use(i18n)
.mount('#app')
+3 -1
View File
@@ -9,13 +9,15 @@ import { useDisplay } from 'vuetify'
import { useDynamicButton } from '@/composables/useDynamicButton' import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { VCardActions } from 'vuetify/components' import { VCardActions } from 'vuetify/components'
import { usePWA } from '@/composables/usePWA'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
// APP // APP
const display = useDisplay() const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
// 从用户 Store 中获取superuser信息 // 从用户 Store 中获取superuser信息
const superUser = useUserStore().superUser const superUser = useUserStore().superUser
+51
View File
@@ -0,0 +1,51 @@
import { defineStore } from 'pinia'
import type { globalSettingsState } from '@/stores/types'
import { fetchGlobalSettings } from '@/utils/globalSetting'
export const useGlobalSettingsStore = defineStore('globalSettings', {
state: (): globalSettingsState => ({
data: {},
initialized: false,
loading: false,
}),
actions: {
async initialize() {
if (this.initialized || this.loading) return
this.loading = true
try {
const result = await fetchGlobalSettings()
this.data = result || {}
this.initialized = true
} catch (error) {
console.error('Failed to initialize global settings', error)
} finally {
this.loading = false
}
},
setData(data: { [key: string]: any }) {
this.data = data
this.initialized = true
},
get(key: string) {
return this.data[key]
},
reset() {
this.data = {}
this.initialized = false
this.loading = false
},
},
getters: {
isInitialized: state => state.initialized,
isLoading: state => state.loading,
getData: state => state.data,
// 直接返回data对象,避免使用.value
globalSettings: state => state.data,
},
})
+2 -1
View File
@@ -12,5 +12,6 @@ export default pinia
// 所有的 store // 所有的 store
import { useAuthStore } from './auth' import { useAuthStore } from './auth'
import { useUserStore } from './user' import { useUserStore } from './user'
import { useGlobalSettingsStore } from './globalSettings'
export { useAuthStore, useUserStore } export { useAuthStore, useUserStore, useGlobalSettingsStore }
+9
View File
@@ -21,3 +21,12 @@ export interface userState {
// 权限 // 权限
permissions: { [key: string]: any } permissions: { [key: string]: any }
} }
export interface globalSettingsState {
// 全局设置数据
data: { [key: string]: any }
// 是否已初始化
initialized: boolean
// 是否正在加载
loading: boolean
}
+4 -1
View File
@@ -15,6 +15,7 @@ import SearchSiteDialog from '@/components/dialog/SearchSiteDialog.vue'
import { useTheme } from 'vuetify' import { useTheme } from 'vuetify'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { hasPermission } from '@/utils/permission' import { hasPermission } from '@/utils/permission'
import { useGlobalSettingsStore } from '@/stores'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
@@ -28,7 +29,9 @@ const mediaProps = defineProps({
}) })
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 用户 Store // 用户 Store
const userStore = useUserStore() const userStore = useUserStore()
+4 -1
View File
@@ -5,6 +5,7 @@ import personIcon from '@images/misc/person.png'
import type { Person } from '@/api/types' import type { Person } from '@/api/types'
import NoDataFound from '@/components/NoDataFound.vue' import NoDataFound from '@/components/NoDataFound.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useGlobalSettingsStore } from '@/stores'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
@@ -17,7 +18,9 @@ const personProps = defineProps({
}) })
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') // 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 媒体详情 // 媒体详情
const personDetail = ref<Person>({} as Person) const personDetail = ref<Person>({} as Person)
+3 -1
View File
@@ -13,6 +13,7 @@ import PluginMarketSettingDialog from '@/components/dialog/PluginMarketSettingDi
import { useDynamicButton } from '@/composables/useDynamicButton' import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import PluginMixedSortCard from '@/components/cards/PluginMixedSortCard.vue' import PluginMixedSortCard from '@/components/cards/PluginMixedSortCard.vue'
import { usePWA } from '@/composables/usePWA'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
@@ -23,7 +24,8 @@ const route = useRoute()
const display = useDisplay() const display = useDisplay()
// APP // APP
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
// 当前标签 // 当前标签
const activeTab = ref('installed') const activeTab = ref('installed')
+3 -1
View File
@@ -11,13 +11,15 @@ import router from '@/router'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { formatFileSize } from '@/@core/utils/formatters' import { formatFileSize } from '@/@core/utils/formatters'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePWA } from '@/composables/usePWA'
// i18n // i18n
const { t } = useI18n() const { t } = useI18n()
// APP // APP
const display = useDisplay() const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
// 提示框 // 提示框
const $toast = useToast() const $toast = useToast()
+8 -5
View File
@@ -3,18 +3,21 @@ import { useToast } from 'vue-toastification'
import api from '@/api' import api from '@/api'
import type { TorrentCacheData, TorrentCacheItem } from '@/api/types' import type { TorrentCacheData, TorrentCacheItem } from '@/api/types'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify'
import { formatFileSize, formatDateDifference } from '@core/utils/formatters' import { formatFileSize, formatDateDifference } from '@core/utils/formatters'
import { useConfirm } from '@/composables/useConfirm' import { useConfirm } from '@/composables/useConfirm'
import { useGlobalSettingsStore } from '@/stores'
import { usePWA } from '@/composables/usePWA'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value
// 从 provide 中获取全局设置 // PWA模式检测
const globalSettings: any = inject('globalSettings') const { appMode } = usePWA()
// 全局设置
const globalSettingsStore = useGlobalSettingsStore()
const globalSettings = globalSettingsStore.globalSettings
// 确认框 // 确认框
const createConfirm = useConfirm() const createConfirm = useConfirm()
+3 -1
View File
@@ -8,13 +8,15 @@ import SiteAddEditDialog from '@/components/dialog/SiteAddEditDialog.vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useDynamicButton } from '@/composables/useDynamicButton' import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePWA } from '@/composables/usePWA'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
// APP // APP
const display = useDisplay() const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
// 站点列表 // 站点列表
const siteList = ref<Site[]>([]) const siteList = ref<Site[]>([])
+3 -1
View File
@@ -9,13 +9,15 @@ import { useUserStore } from '@/stores'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useDynamicButton } from '@/composables/useDynamicButton' import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePWA } from '@/composables/usePWA'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
// APP // APP
const display = useDisplay() const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
// 用户 Store // 用户 Store
const userStore = useUserStore() const userStore = useUserStore()
+3 -1
View File
@@ -7,13 +7,15 @@ import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useDynamicButton } from '@/composables/useDynamicButton' import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePWA } from '@/composables/usePWA'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
// APP // APP
const display = useDisplay() const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value // PWA模式检测
const { appMode } = usePWA()
// 是否刷新过 // 是否刷新过
const isRefreshed = ref(false) const isRefreshed = ref(false)
+3 -3
View File
@@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import api from '@/api' import api from '@/api'
import { Workflow } from '@/api/types' import { Workflow } from '@/api/types'
import { useDisplay } from 'vuetify'
import WorkflowAddEditDialog from '@/components/dialog/WorkflowAddEditDialog.vue' import WorkflowAddEditDialog from '@/components/dialog/WorkflowAddEditDialog.vue'
import WorkflowTaskCard from '@/components/cards/WorkflowTaskCard.vue' import WorkflowTaskCard from '@/components/cards/WorkflowTaskCard.vue'
import NoDataFound from '@/components/NoDataFound.vue' import NoDataFound from '@/components/NoDataFound.vue'
import { useDynamicButton } from '@/composables/useDynamicButton' import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePWA } from '@/composables/usePWA'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
// APP // APP
const display = useDisplay() // PWA模式检测
const appMode = inject('pwaMode') && display.mdAndDown.value const { appMode } = usePWA()
// 是否刷新 // 是否刷新
const isRefreshed = ref(false) const isRefreshed = ref(false)