mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-06 20:43:03 +08:00
整合全局设置store,优化PWA模式检测
This commit is contained in:
@@ -317,4 +317,4 @@
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
#loading-bg {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
z-index: 99999;
|
||||
display: block;
|
||||
background: var(--initial-loader-bg, #fff);
|
||||
block-size: 100vh;
|
||||
@@ -94,4 +94,4 @@
|
||||
opacity: 1;
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/App.vue
22
src/App.vue
@@ -3,7 +3,7 @@ import { useTheme } from 'vuetify'
|
||||
import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
|
||||
import { ensureRenderComplete, removeEl } from './@core/utils/dom'
|
||||
import api from '@/api'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useAuthStore, useGlobalSettingsStore } from '@/stores'
|
||||
import { getBrowserLocale, setI18nLanguage } from './plugins/i18n'
|
||||
import { SupportedLocale } from '@/types/i18n'
|
||||
import { checkAndEmitUnreadMessages } from '@/utils/badge'
|
||||
@@ -19,13 +19,13 @@ globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
|
||||
const localeValue = getBrowserLocale()
|
||||
setI18nLanguage(localeValue as SupportedLocale)
|
||||
|
||||
// 显示状态
|
||||
const show = ref(false)
|
||||
|
||||
// 检查是否登录
|
||||
const authStore = useAuthStore()
|
||||
const isLogin = computed(() => authStore.token)
|
||||
|
||||
// 全局设置store
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
|
||||
// 生成背景图片key
|
||||
const loginStateKey = computed(() => (isLogin.value ? 'logged-in' : 'logged-out'))
|
||||
|
||||
@@ -130,9 +130,7 @@ function animateAndRemoveLoader() {
|
||||
removeEl('#loading-bg')
|
||||
// 将background属性从html的style中移除
|
||||
document.documentElement.style.removeProperty('background')
|
||||
// 显示页面
|
||||
show.value = true
|
||||
}, 500) // 与CSS动画持续时间匹配
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +153,9 @@ async function loadBackgroundImages(retryCount = 0) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化全局设置
|
||||
await globalSettingsStore.initialize()
|
||||
|
||||
// 配置 ApexCharts
|
||||
configureApexCharts()
|
||||
|
||||
@@ -172,11 +173,8 @@ onMounted(async () => {
|
||||
},
|
||||
)
|
||||
|
||||
// 默认隐藏页面
|
||||
show.value = false
|
||||
|
||||
// 加载背景图片
|
||||
loadBackgroundImages()
|
||||
await loadBackgroundImages()
|
||||
|
||||
// 移除加载动画
|
||||
ensureRenderComplete(() => {
|
||||
@@ -213,7 +211,7 @@ onUnmounted(() => {
|
||||
<div v-if="isLogin && isTransparentTheme" class="global-blur-layer"></div>
|
||||
</div>
|
||||
<!-- 页面内容 -->
|
||||
<VApp v-show="show" :class="{ 'transparent-app': isTransparentTheme }">
|
||||
<VApp :class="{ 'transparent-app': isTransparentTheme }">
|
||||
<RouterView />
|
||||
</VApp>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import FileNavigator from './filebrowser/FileNavigator.vue'
|
||||
import type { EndPoints, FileItem, StorageConf } from '@/api/types'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { storageIconDict } from '@/api/constants'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -33,7 +34,8 @@ const emit = defineEmits(['pathchanged'])
|
||||
const display = useDisplay()
|
||||
|
||||
// APP
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
const fileIcons = {
|
||||
// 压缩包
|
||||
|
||||
@@ -9,7 +9,7 @@ import { formatSeason, formatRating } from '@/@core/utils/formatters'
|
||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||
import type { MediaInfo, Subscribe, MediaSeason, Site } from '@/api/types'
|
||||
import router from '@/router'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { useUserStore, useGlobalSettingsStore } from '@/stores'
|
||||
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
|
||||
import SearchSiteDialog from '@/components/dialog/SearchSiteDialog.vue'
|
||||
import SubscribeSeasonDialog from '../dialog/SubscribeSeasonDialog.vue'
|
||||
@@ -28,7 +28,9 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 用户 Store
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import personIcon from '@images/misc/person-icon.png'
|
||||
import type { Person } from '@/api/types'
|
||||
import router from '@/router'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
const personProps = defineProps({
|
||||
person: Object as PropType<Person>,
|
||||
@@ -10,7 +11,9 @@ const personProps = defineProps({
|
||||
})
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 当前人物
|
||||
const personInfo = ref(personProps.person)
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { Subscribe } from '@/api/types'
|
||||
import router from '@/router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
@@ -23,7 +24,9 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 定义触发的自定义事件
|
||||
const emit = defineEmits(['remove', 'save'])
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { SubscribeShare } from '@/api/types'
|
||||
import router from '@/router'
|
||||
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
|
||||
import ForkSubscribeDialog from '../dialog/ForkSubscribeDialog.vue'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -14,7 +15,9 @@ const props = defineProps({
|
||||
const emit = defineEmits(['delete'])
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 图片是否加载完成
|
||||
const imageLoaded = ref(false)
|
||||
|
||||
@@ -6,6 +6,7 @@ import router from '@/router'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { VBtn } from 'vuetify/lib/components/index.mjs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -19,7 +20,9 @@ const props = defineProps({
|
||||
const emit = defineEmits(['fork', 'delete', 'close'])
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Plugin } from '@/api/types'
|
||||
import PageRender from '@/components/render/PageRender.vue'
|
||||
import api from '@/api'
|
||||
import { loadRemoteComponent } from '@/utils/federationLoader'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -22,7 +23,8 @@ const emit = defineEmits(['close', 'save', 'switch'])
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
// APP
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 是否刷新
|
||||
const isRefreshed = ref(false)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useDisplay } from 'vuetify'
|
||||
import ProgressDialog from './ProgressDialog.vue'
|
||||
import { FileItem, StorageConf, TransferDirectoryConf, TransferForm } from '@/api/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -24,10 +25,12 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
// 从 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'])
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MediaInfo, MediaSeason, NotExistMediaInfo } from '@/api/types'
|
||||
import { PropType } from 'vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -17,7 +18,9 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 季详情
|
||||
const seasonInfos = ref<MediaSeason[]>([])
|
||||
|
||||
57
src/composables/usePWA.ts
Normal file
57
src/composables/usePWA.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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 { useRoute } from 'vue-router'
|
||||
import { usePWA } from './usePWA'
|
||||
|
||||
// 下拉手势配置类型
|
||||
export interface PullDownConfig {
|
||||
@@ -37,8 +38,7 @@ const DEFAULT_CONFIG: PullDownConfig = {
|
||||
|
||||
export function usePullDownGesture(options: PullDownOptions = {}) {
|
||||
const display = useDisplay()
|
||||
const route = useRoute()
|
||||
const appMode = inject('pwaMode')
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 合并配置
|
||||
const config = { ...DEFAULT_CONFIG, ...options.config }
|
||||
@@ -126,7 +126,7 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
|
||||
|
||||
// 事件处理函数
|
||||
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
|
||||
@@ -149,7 +149,7 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -192,7 +192,7 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -217,20 +217,49 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
|
||||
}
|
||||
|
||||
// 生命周期管理
|
||||
onMounted(() => {
|
||||
if (appMode && display.mdAndDown.value) {
|
||||
let eventsAdded = false
|
||||
|
||||
const addEventListeners = () => {
|
||||
if (!eventsAdded && appMode.value && display.mdAndDown.value) {
|
||||
document.addEventListener('touchstart', handleTouchStart, { passive: false })
|
||||
document.addEventListener('touchmove', handleTouchMove, { passive: false })
|
||||
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(() => {
|
||||
if (appMode && display.mdAndDown.value) {
|
||||
document.removeEventListener('touchstart', handleTouchStart)
|
||||
document.removeEventListener('touchmove', handleTouchMove)
|
||||
document.removeEventListener('touchend', handleTouchEnd)
|
||||
}
|
||||
removeEventListeners()
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -18,9 +18,11 @@ import { filterMenusByPermission } from '@/utils/permission'
|
||||
import { onUnreadMessage } from '@/utils/badge'
|
||||
import { usePullDownGesture } from '@/composables/usePullDownGesture'
|
||||
import { useScrollLockWithWatch } from '@/composables/useScrollLock'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode')
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ import { NavMenu } from '@/@layouts/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { filterMenusByPermission } from '@/utils/permission'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 判断当前是否为英文环境
|
||||
|
||||
@@ -4,6 +4,7 @@ import useDragAndDrop from '@core/utils/workflow'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { actionStepDict } from '@/api/constants'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
interface ActionItem {
|
||||
name: string
|
||||
@@ -13,7 +14,8 @@ interface ActionItem {
|
||||
|
||||
const display = useDisplay()
|
||||
// APP
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
const { t } = useI18n()
|
||||
|
||||
const { onDragStart } = useDragAndDrop()
|
||||
|
||||
96
src/main.ts
96
src/main.ts
@@ -18,9 +18,7 @@ import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'
|
||||
import { CronVuetify } from '@vue-js-cron/vuetify'
|
||||
|
||||
// 4. 工具函数和其他辅助模块
|
||||
import { isPWA } from './@core/utils/navigator'
|
||||
import { loadRemoteComponents } from './utils/federationLoader'
|
||||
import { fetchGlobalSettings } from './utils/globalSetting'
|
||||
|
||||
// 5. 其他插件和功能模块
|
||||
import Toast from 'vue-toastification'
|
||||
@@ -51,59 +49,43 @@ const app = createApp(App)
|
||||
// 注册pinia
|
||||
app.use(pinia)
|
||||
|
||||
// 初始化配置
|
||||
async function initializeApp() {
|
||||
try {
|
||||
// 是否为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')
|
||||
// 异步加载远程组件(不阻塞启动)
|
||||
loadRemoteComponents().catch(error => {
|
||||
console.error('Failed to load remote components', error)
|
||||
})
|
||||
|
||||
// 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')
|
||||
|
||||
@@ -9,13 +9,15 @@ import { useDisplay } from 'vuetify'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { VCardActions } from 'vuetify/components'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 从用户 Store 中获取superuser信息
|
||||
const superUser = useUserStore().superUser
|
||||
|
||||
51
src/stores/globalSettings.ts
Normal file
51
src/stores/globalSettings.ts
Normal 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,
|
||||
},
|
||||
})
|
||||
@@ -12,5 +12,6 @@ export default pinia
|
||||
// 所有的 store
|
||||
import { useAuthStore } from './auth'
|
||||
import { useUserStore } from './user'
|
||||
import { useGlobalSettingsStore } from './globalSettings'
|
||||
|
||||
export { useAuthStore, useUserStore }
|
||||
export { useAuthStore, useUserStore, useGlobalSettingsStore }
|
||||
|
||||
@@ -21,3 +21,12 @@ export interface userState {
|
||||
// 权限
|
||||
permissions: { [key: string]: any }
|
||||
}
|
||||
|
||||
export interface globalSettingsState {
|
||||
// 全局设置数据
|
||||
data: { [key: string]: any }
|
||||
// 是否已初始化
|
||||
initialized: boolean
|
||||
// 是否正在加载
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import SearchSiteDialog from '@/components/dialog/SearchSiteDialog.vue'
|
||||
import { useTheme } from 'vuetify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -28,7 +29,9 @@ const mediaProps = defineProps({
|
||||
})
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 用户 Store
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -5,6 +5,7 @@ import personIcon from '@images/misc/person.png'
|
||||
import type { Person } from '@/api/types'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -17,7 +18,9 @@ const personProps = defineProps({
|
||||
})
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 媒体详情
|
||||
const personDetail = ref<Person>({} as Person)
|
||||
|
||||
@@ -13,6 +13,7 @@ import PluginMarketSettingDialog from '@/components/dialog/PluginMarketSettingDi
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import PluginMixedSortCard from '@/components/cards/PluginMixedSortCard.vue'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -23,7 +24,8 @@ const route = useRoute()
|
||||
const display = useDisplay()
|
||||
|
||||
// APP
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 当前标签
|
||||
const activeTab = ref('installed')
|
||||
|
||||
@@ -11,13 +11,15 @@ import router from '@/router'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { formatFileSize } from '@/@core/utils/formatters'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// i18n
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
@@ -3,18 +3,21 @@ import { useToast } from 'vue-toastification'
|
||||
import api from '@/api'
|
||||
import type { TorrentCacheData, TorrentCacheItem } from '@/api/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { formatFileSize, formatDateDifference } from '@core/utils/formatters'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useGlobalSettingsStore } from '@/stores'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
|
||||
// 从 provide 中获取全局设置
|
||||
const globalSettings: any = inject('globalSettings')
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 全局设置
|
||||
const globalSettingsStore = useGlobalSettingsStore()
|
||||
const globalSettings = globalSettingsStore.globalSettings
|
||||
|
||||
// 确认框
|
||||
const createConfirm = useConfirm()
|
||||
|
||||
@@ -8,13 +8,15 @@ import SiteAddEditDialog from '@/components/dialog/SiteAddEditDialog.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 站点列表
|
||||
const siteList = ref<Site[]>([])
|
||||
|
||||
@@ -9,13 +9,15 @@ import { useUserStore } from '@/stores'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 用户 Store
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -7,13 +7,15 @@ import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 是否刷新过
|
||||
const isRefreshed = ref(false)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import api from '@/api'
|
||||
import { Workflow } from '@/api/types'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import WorkflowAddEditDialog from '@/components/dialog/WorkflowAddEditDialog.vue'
|
||||
import WorkflowTaskCard from '@/components/cards/WorkflowTaskCard.vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
// PWA模式检测
|
||||
const { appMode } = usePWA()
|
||||
|
||||
// 是否刷新
|
||||
const isRefreshed = ref(false)
|
||||
|
||||
Reference in New Issue
Block a user