mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-11 15:32:47 +08:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30351a02ee | ||
|
|
7f918408a6 | ||
|
|
82f69bcad0 | ||
|
|
83b25eabbb | ||
|
|
47da6db51a | ||
|
|
eee092a7fd | ||
|
|
4c0f65fcbc | ||
|
|
acbd979569 | ||
|
|
52b68c18bf | ||
|
|
c6a74a75da | ||
|
|
e39eb62f52 | ||
|
|
4ecec4865d | ||
|
|
589007a22a | ||
|
|
4d4c9516c6 | ||
|
|
8491f26617 | ||
|
|
fcb3768a76 | ||
|
|
966bb769df | ||
|
|
dc8f7caab0 | ||
|
|
683346d652 | ||
|
|
f5fe39b2d2 | ||
|
|
51beb53f51 | ||
|
|
9d3f03c83a | ||
|
|
3eda1e4ef7 | ||
|
|
7181f83d66 | ||
|
|
fffad6e1b8 | ||
|
|
7f3906e5cb | ||
|
|
f836d175f0 | ||
|
|
f49cafc0cc | ||
|
|
a3ecad3436 | ||
|
|
a019dbd44e | ||
|
|
b316f960a1 | ||
|
|
d049b26825 | ||
|
|
852579c6ee | ||
|
|
5adcfa1877 | ||
|
|
f74458629e | ||
|
|
798f9249f8 | ||
|
|
6b4383643f | ||
|
|
256e8d0452 | ||
|
|
4112214c1f | ||
|
|
c183158ffe | ||
|
|
d523790c0f | ||
|
|
615ce34a72 | ||
|
|
1d59b3566c | ||
|
|
8071b90a2b | ||
|
|
8966584ca0 | ||
|
|
822711a530 | ||
|
|
1fe8aeb9e1 | ||
|
|
f021ba8a98 | ||
|
|
e4af05cd56 | ||
|
|
43d1cdb91c | ||
|
|
ed3f66681f | ||
|
|
c718d57e77 | ||
|
|
ce2e88a532 | ||
|
|
e60015a477 | ||
|
|
761e3ac76d | ||
|
|
2cf5535376 | ||
|
|
1a3d76d7b9 | ||
|
|
942a536289 | ||
|
|
fb1f6abf2e | ||
|
|
61ecb421e6 | ||
|
|
0098f9db2f | ||
|
|
2a348a7f18 | ||
|
|
838dff4758 | ||
|
|
7fb78a86ba | ||
|
|
07c815e943 | ||
|
|
9a4392eceb | ||
|
|
dc25e457eb | ||
|
|
d65ed9725c |
327
auto-imports.d.ts
vendored
327
auto-imports.d.ts
vendored
@@ -3,6 +3,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
// Generated by unplugin-auto-import
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
@@ -66,6 +67,7 @@ declare global {
|
|||||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
|
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
|
||||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||||
@@ -77,6 +79,7 @@ declare global {
|
|||||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
|
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||||
const provide: typeof import('vue')['provide']
|
const provide: typeof import('vue')['provide']
|
||||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||||
@@ -190,6 +193,7 @@ declare global {
|
|||||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||||
|
const useId: typeof import('vue')['useId']
|
||||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||||
const useImage: typeof import('@vueuse/core')['useImage']
|
const useImage: typeof import('@vueuse/core')['useImage']
|
||||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||||
@@ -209,6 +213,7 @@ declare global {
|
|||||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||||
const useMin: typeof import('@vueuse/math')['useMin']
|
const useMin: typeof import('@vueuse/math')['useMin']
|
||||||
|
const useModel: typeof import('vue')['useModel']
|
||||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||||
@@ -234,6 +239,7 @@ declare global {
|
|||||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||||
|
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
|
||||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||||
const useProjection: typeof import('@vueuse/math')['useProjection']
|
const useProjection: typeof import('@vueuse/math')['useProjection']
|
||||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||||
@@ -242,6 +248,7 @@ declare global {
|
|||||||
const useRound: typeof import('@vueuse/math')['useRound']
|
const useRound: typeof import('@vueuse/math')['useRound']
|
||||||
const useRoute: typeof import('vue-router')['useRoute']
|
const useRoute: typeof import('vue-router')['useRoute']
|
||||||
const useRouter: typeof import('vue-router')['useRouter']
|
const useRouter: typeof import('vue-router')['useRouter']
|
||||||
|
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
|
||||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||||
@@ -261,6 +268,7 @@ declare global {
|
|||||||
const useSum: typeof import('@vueuse/math')['useSum']
|
const useSum: typeof import('@vueuse/math')['useSum']
|
||||||
const useSupported: typeof import('@vueuse/core')['useSupported']
|
const useSupported: typeof import('@vueuse/core')['useSupported']
|
||||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||||
|
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||||
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
||||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||||
@@ -313,9 +321,10 @@ declare global {
|
|||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
import('vue')
|
import('vue')
|
||||||
}
|
}
|
||||||
|
|
||||||
// for vue template auto import
|
// for vue template auto import
|
||||||
import { UnwrapRef } from 'vue'
|
import { UnwrapRef } from 'vue'
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
@@ -382,6 +391,7 @@ declare module 'vue' {
|
|||||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||||
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
||||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||||
|
readonly onElementRemoval: UnwrapRef<typeof import('@vueuse/core')['onElementRemoval']>
|
||||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||||
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
||||||
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
||||||
@@ -393,6 +403,7 @@ declare module 'vue' {
|
|||||||
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
|
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
|
||||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
|
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
||||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
@@ -506,6 +517,7 @@ declare module 'vue' {
|
|||||||
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
||||||
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
||||||
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
||||||
|
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
||||||
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
||||||
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
||||||
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
||||||
@@ -525,6 +537,7 @@ declare module 'vue' {
|
|||||||
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
|
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
|
||||||
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
|
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
|
||||||
readonly useMin: UnwrapRef<typeof import('@vueuse/math')['useMin']>
|
readonly useMin: UnwrapRef<typeof import('@vueuse/math')['useMin']>
|
||||||
|
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
|
||||||
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
|
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
|
||||||
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
|
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
|
||||||
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
||||||
@@ -550,6 +563,7 @@ declare module 'vue' {
|
|||||||
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
|
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
|
||||||
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
||||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||||
|
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
|
||||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||||
readonly useProjection: UnwrapRef<typeof import('@vueuse/math')['useProjection']>
|
readonly useProjection: UnwrapRef<typeof import('@vueuse/math')['useProjection']>
|
||||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||||
@@ -558,6 +572,7 @@ declare module 'vue' {
|
|||||||
readonly useRound: UnwrapRef<typeof import('@vueuse/math')['useRound']>
|
readonly useRound: UnwrapRef<typeof import('@vueuse/math')['useRound']>
|
||||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||||
|
readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
|
||||||
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
|
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
|
||||||
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
|
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
|
||||||
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
||||||
@@ -577,315 +592,7 @@ declare module 'vue' {
|
|||||||
readonly useSum: UnwrapRef<typeof import('@vueuse/math')['useSum']>
|
readonly useSum: UnwrapRef<typeof import('@vueuse/math')['useSum']>
|
||||||
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
|
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
|
||||||
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
||||||
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
||||||
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
|
||||||
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
|
||||||
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
|
|
||||||
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
|
|
||||||
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
|
||||||
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
|
||||||
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
|
||||||
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
|
||||||
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
|
||||||
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
|
||||||
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
|
|
||||||
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
|
|
||||||
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
|
|
||||||
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
|
|
||||||
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
|
|
||||||
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
|
||||||
readonly useTrunc: UnwrapRef<typeof import('@vueuse/math')['useTrunc']>
|
|
||||||
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
|
||||||
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
|
|
||||||
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
|
|
||||||
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
|
||||||
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
|
||||||
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
|
||||||
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
|
||||||
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
|
||||||
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
|
||||||
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
|
||||||
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
|
|
||||||
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
|
|
||||||
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
|
|
||||||
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
|
|
||||||
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
|
||||||
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
|
|
||||||
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
|
|
||||||
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
|
|
||||||
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
|
|
||||||
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
|
||||||
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
|
|
||||||
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
|
|
||||||
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
|
|
||||||
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
|
|
||||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
|
||||||
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
|
||||||
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
|
|
||||||
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
|
|
||||||
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
|
|
||||||
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
declare module '@vue/runtime-core' {
|
|
||||||
interface GlobalComponents {}
|
|
||||||
interface ComponentCustomProperties {
|
|
||||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
|
||||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
|
||||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
|
||||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
|
||||||
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
|
||||||
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
|
||||||
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
|
|
||||||
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
|
|
||||||
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
|
||||||
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
|
||||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
|
||||||
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
|
|
||||||
readonly createGenericProjection: UnwrapRef<typeof import('@vueuse/math')['createGenericProjection']>
|
|
||||||
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
|
|
||||||
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
|
|
||||||
readonly createLogger: UnwrapRef<typeof import('vuex')['createLogger']>
|
|
||||||
readonly createNamespacedHelpers: UnwrapRef<typeof import('vuex')['createNamespacedHelpers']>
|
|
||||||
readonly createProjection: UnwrapRef<typeof import('@vueuse/math')['createProjection']>
|
|
||||||
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
|
||||||
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
|
|
||||||
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
|
||||||
readonly createStore: UnwrapRef<typeof import('vuex')['createStore']>
|
|
||||||
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
|
|
||||||
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
|
|
||||||
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
|
||||||
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
|
|
||||||
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
|
|
||||||
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
|
||||||
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
|
||||||
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
|
|
||||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
|
||||||
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
|
||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
|
||||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
|
||||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
|
||||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
|
||||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
|
||||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
|
||||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
|
||||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
|
||||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
|
||||||
readonly logicAnd: UnwrapRef<typeof import('@vueuse/math')['logicAnd']>
|
|
||||||
readonly logicNot: UnwrapRef<typeof import('@vueuse/math')['logicNot']>
|
|
||||||
readonly logicOr: UnwrapRef<typeof import('@vueuse/math')['logicOr']>
|
|
||||||
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
|
||||||
readonly mapActions: UnwrapRef<typeof import('vuex')['mapActions']>
|
|
||||||
readonly mapGetters: UnwrapRef<typeof import('vuex')['mapGetters']>
|
|
||||||
readonly mapMutations: UnwrapRef<typeof import('vuex')['mapMutations']>
|
|
||||||
readonly mapState: UnwrapRef<typeof import('vuex')['mapState']>
|
|
||||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
|
||||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
|
||||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
|
||||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
|
||||||
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
|
||||||
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
|
|
||||||
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
|
||||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
|
||||||
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
|
||||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
|
||||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
|
||||||
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
|
||||||
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
|
||||||
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
|
||||||
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
|
||||||
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
|
||||||
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
|
||||||
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
|
||||||
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
|
|
||||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
|
||||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
|
||||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
|
||||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
|
||||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
|
||||||
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
|
|
||||||
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
|
|
||||||
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
|
|
||||||
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
|
||||||
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
|
||||||
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
|
|
||||||
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
|
|
||||||
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
|
|
||||||
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
|
||||||
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
|
||||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
|
||||||
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
|
||||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
|
||||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
|
||||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
|
||||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
|
||||||
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
|
|
||||||
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
|
|
||||||
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
|
|
||||||
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
|
|
||||||
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
|
|
||||||
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
|
||||||
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
|
|
||||||
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
|
||||||
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
|
||||||
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
|
||||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
|
||||||
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
|
|
||||||
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
|
|
||||||
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
|
|
||||||
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
|
|
||||||
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
|
|
||||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
|
||||||
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
|
||||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
|
||||||
readonly useAbs: UnwrapRef<typeof import('@vueuse/math')['useAbs']>
|
|
||||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
|
||||||
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
|
||||||
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
|
||||||
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
|
||||||
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
|
||||||
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
|
|
||||||
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
|
|
||||||
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
|
|
||||||
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
|
|
||||||
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
|
|
||||||
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
|
|
||||||
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
|
|
||||||
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
|
|
||||||
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
|
|
||||||
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
|
|
||||||
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
|
|
||||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
|
||||||
readonly useAverage: UnwrapRef<typeof import('@vueuse/math')['useAverage']>
|
|
||||||
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
|
|
||||||
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
|
|
||||||
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
|
|
||||||
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
|
|
||||||
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
|
|
||||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
|
||||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
|
||||||
readonly useCeil: UnwrapRef<typeof import('@vueuse/math')['useCeil']>
|
|
||||||
readonly useClamp: UnwrapRef<typeof import('@vueuse/math')['useClamp']>
|
|
||||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
|
||||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
|
||||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
|
||||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
|
||||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
|
||||||
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
|
||||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
|
||||||
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
|
||||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
|
||||||
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
|
||||||
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
|
||||||
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
|
||||||
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
|
||||||
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
|
|
||||||
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
|
|
||||||
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
|
|
||||||
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
|
|
||||||
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
|
|
||||||
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
|
|
||||||
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
|
|
||||||
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
|
|
||||||
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
|
|
||||||
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
|
|
||||||
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
|
|
||||||
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
|
|
||||||
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
|
|
||||||
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
|
|
||||||
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
|
|
||||||
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
|
|
||||||
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
|
|
||||||
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
|
|
||||||
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
|
|
||||||
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
|
|
||||||
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
|
|
||||||
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
|
|
||||||
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
|
|
||||||
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
|
|
||||||
readonly useFloor: UnwrapRef<typeof import('@vueuse/math')['useFloor']>
|
|
||||||
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
|
|
||||||
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
|
|
||||||
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
|
|
||||||
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
|
||||||
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
|
||||||
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
|
||||||
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
|
||||||
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
|
||||||
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
|
||||||
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
|
|
||||||
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
|
|
||||||
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
|
|
||||||
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
|
||||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
|
||||||
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
|
|
||||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
|
||||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
|
||||||
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
|
|
||||||
readonly useMath: UnwrapRef<typeof import('@vueuse/math')['useMath']>
|
|
||||||
readonly useMax: UnwrapRef<typeof import('@vueuse/math')['useMax']>
|
|
||||||
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
|
|
||||||
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
|
|
||||||
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
|
|
||||||
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
|
|
||||||
readonly useMin: UnwrapRef<typeof import('@vueuse/math')['useMin']>
|
|
||||||
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
|
|
||||||
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
|
|
||||||
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
|
||||||
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
|
|
||||||
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
|
|
||||||
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
|
|
||||||
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
|
|
||||||
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
|
|
||||||
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
|
|
||||||
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
|
|
||||||
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
|
|
||||||
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
|
|
||||||
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
|
|
||||||
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
|
|
||||||
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
|
|
||||||
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
|
|
||||||
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
|
|
||||||
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
|
|
||||||
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
|
|
||||||
readonly usePrecision: UnwrapRef<typeof import('@vueuse/math')['usePrecision']>
|
|
||||||
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
|
|
||||||
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
|
|
||||||
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
|
|
||||||
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
|
||||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
|
||||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
|
||||||
readonly useProjection: UnwrapRef<typeof import('@vueuse/math')['useProjection']>
|
|
||||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
|
||||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
|
||||||
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
|
||||||
readonly useRound: UnwrapRef<typeof import('@vueuse/math')['useRound']>
|
|
||||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
|
||||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
|
||||||
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
|
|
||||||
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
|
|
||||||
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
|
||||||
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
|
|
||||||
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
|
|
||||||
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
|
||||||
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
|
||||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
|
||||||
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
|
|
||||||
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
|
|
||||||
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
|
|
||||||
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
|
|
||||||
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
|
|
||||||
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
|
|
||||||
readonly useStore: UnwrapRef<typeof import('vuex')['useStore']>
|
|
||||||
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
|
|
||||||
readonly useSum: UnwrapRef<typeof import('@vueuse/math')['useSum']>
|
|
||||||
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
|
|
||||||
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
|
||||||
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
||||||
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
||||||
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
||||||
|
|||||||
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/* prettier-ignore */
|
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default']
|
DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default']
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en"
|
||||||
|
style="overflow: hidden auto; background: var(--initial-loader-bg, #fff); min-block-size: calc(100% + env(safe-area-inset-top) + env(safe-area-inset-bottom));">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="pragma" content="no-cache">
|
<meta http-equiv="pragma" content="no-cache">
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
<link rel="stylesheet" type="text/css" href="/loader.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body style="margin: 0;">
|
||||||
<div id="loading-bg">
|
<div id="loading-bg">
|
||||||
<div class="loading-logo">
|
<div class="loading-logo">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
@@ -145,17 +146,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
<script>
|
<script>
|
||||||
const loaderColor = localStorage.getItem('materio-initial-loader-bg') || '#FFFFFF'
|
const loaderColor = localStorage.getItem('materio-initial-loader-bg') || '#FFFFFF'
|
||||||
const primaryColor = localStorage.getItem('materio-initial-loader-color') || '#9155FD'
|
const primaryColor = localStorage.getItem('materio-initial-loader-color') || '#9155FD'
|
||||||
|
|
||||||
if (loaderColor)
|
if (loaderColor)
|
||||||
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
|
document.documentElement.style.setProperty('--initial-loader-bg', loaderColor)
|
||||||
|
|
||||||
if (primaryColor)
|
if (primaryColor)
|
||||||
document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
|
document.documentElement.style.setProperty('--initial-loader-color', primaryColor)
|
||||||
</script>
|
</script>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
101
package.json
101
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "2.2.1",
|
"version": "2.2.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"bin": "dist/service.js",
|
"bin": "dist/service.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -19,89 +19,86 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fullcalendar/core": "^6.1.8",
|
"@fullcalendar/core": "^6.1.15",
|
||||||
"@fullcalendar/daygrid": "^6.1.8",
|
"@fullcalendar/daygrid": "^6.1.15",
|
||||||
"@fullcalendar/interaction": "^6.1.7",
|
"@fullcalendar/interaction": "^6.1.15",
|
||||||
"@fullcalendar/list": "^6.1.7",
|
"@fullcalendar/list": "^6.1.15",
|
||||||
"@fullcalendar/timegrid": "^6.1.7",
|
"@fullcalendar/timegrid": "^6.1.15",
|
||||||
"@fullcalendar/vue3": "^6.1.8",
|
"@fullcalendar/vue3": "^6.1.15",
|
||||||
"@iconify/utils": "^2.1.22",
|
"@iconify/utils": "^2.2.1",
|
||||||
"@vue-js-cron/vuetify": "^5.0.9",
|
"@vue-js-cron/vuetify": "^5.0.9",
|
||||||
"@vueuse/core": "^10.1.2",
|
"@vueuse/core": "^12.4.0",
|
||||||
"@vueuse/math": "^10.1.2",
|
"@vueuse/math": "^12.4.0",
|
||||||
"ace-builds": "^1.32.6",
|
"ace-builds": "^1.37.4",
|
||||||
"apexcharts-clevision": "^3.28.5",
|
"apexcharts": "^4.0.0",
|
||||||
"axios": "1.6.8",
|
"axios": "^1.7.9",
|
||||||
"colorthief": "^2.4.0",
|
"colorthief": "^2.6.0",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.13",
|
||||||
"express": "^4.18.2",
|
"express": "^4.21.2",
|
||||||
"express-http-proxy": "^2.0.0",
|
"express-http-proxy": "^2.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.6.0",
|
||||||
"sass": "^1.59.3",
|
"sass": "^1.83.4",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^ 3.4.17",
|
||||||
"unplugin-vue-define-options": "^1.3.5",
|
"vue": "^3.5.13",
|
||||||
"vue": "^3.3.2",
|
"vue-router": "^4.5.0",
|
||||||
"vue-router": "^4.2.0",
|
"vue-toast-notification": "^3.1.3",
|
||||||
"vue-toast-notification": "^3",
|
|
||||||
"vue3-ace-editor": "^2.2.4",
|
"vue3-ace-editor": "^2.2.4",
|
||||||
"vue3-apexcharts": "^1.4.1",
|
"vue3-apexcharts": "^1.8.0",
|
||||||
"vue3-perfect-scrollbar": "^2.0.0",
|
"vue3-perfect-scrollbar": "^2.0.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"vuetify": "3.6.8",
|
"vuetify": "3.7.3",
|
||||||
"vuetify-use-dialog": "^0.6.11",
|
"vuetify-use-dialog": "^0.6.11",
|
||||||
"vuex": "^4.1.0",
|
"vuex": "^4.1.0",
|
||||||
"vuex-persistedstate": "^4.1.0",
|
"vuex-persistedstate": "^4.1.0",
|
||||||
"webfontloader": "^1.6.28"
|
"webfontloader": "^1.6.28"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config-vue": "^0.43.1",
|
|
||||||
"@iconify-json/mdi": "^1.1.52",
|
"@iconify-json/mdi": "^1.1.52",
|
||||||
"@iconify/tools": "^4.0.4",
|
"@iconify/tools": "^4.0.4",
|
||||||
"@iconify/vue": "4.1.1",
|
"@iconify/vue": "^4.3.0",
|
||||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||||
"@types/lodash": "^4.14.197",
|
"@types/lodash": "^4.14.197",
|
||||||
"@types/node": "^20.1.4",
|
"@types/node": "^20.1.4",
|
||||||
"@types/webfontloader": "^1.6.34",
|
"@types/webfontloader": "^1.6.34",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
||||||
"@typescript-eslint/parser": "^7.5.0",
|
"@typescript-eslint/parser": "^8.20.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
|
||||||
"eslint-import-resolver-typescript": "^3.5.1",
|
"eslint-import-resolver-typescript": "^3.5.1",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-promise": "^6.0.1",
|
"eslint-plugin-promise": "^7.2.1",
|
||||||
"eslint-plugin-regex": "^1.10.0",
|
"eslint-plugin-regex": "^1.10.0",
|
||||||
"eslint-plugin-sonarjs": "^0.25.1",
|
"eslint-plugin-sonarjs": "^3.0.1",
|
||||||
"eslint-plugin-unicorn": "^52.0.0",
|
"eslint-plugin-unicorn": "^56.0.1",
|
||||||
"eslint-plugin-vue": "^9.12.0",
|
"eslint-plugin-vue": "^9.12.0",
|
||||||
"postcss": "8",
|
"postcss": "^8.5.1",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "^1.5.0",
|
||||||
"stylelint": "16.3.1",
|
"stylelint": "^16.13.2",
|
||||||
"stylelint-config-idiomatic-order": "10.0.0",
|
"stylelint-config-idiomatic-order": "^10.0.0",
|
||||||
"stylelint-config-standard-scss": "13.1.0",
|
"stylelint-config-standard-scss": "^14.0.0",
|
||||||
"stylelint-use-logical-spec": "5.0.1",
|
"stylelint-use-logical-spec": "5.0.1",
|
||||||
"terser": "^5.36.0",
|
"terser": "^5.36.0",
|
||||||
"type-fest": "^4.15.0",
|
"type-fest": "^4.15.0",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"unplugin-auto-import": "^0.17.5",
|
"unplugin-auto-import": "^19.0.0",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-vue-components": "^28.0.0",
|
||||||
"vite": "^5.2.8",
|
"unplugin-vue-define-options": "^1.5.3",
|
||||||
|
"vite": "^5.4.11",
|
||||||
"vite-plugin-pages": "^0.32.1",
|
"vite-plugin-pages": "^0.32.1",
|
||||||
"vite-plugin-pwa": "^0.20.0",
|
"vite-plugin-pwa": "^0.21.1",
|
||||||
"vite-plugin-vue-layouts": "^0.11.0",
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
"vite-plugin-vuetify": "2.0.3",
|
"vite-plugin-vuetify": "2.0.4",
|
||||||
"vue-shepherd": "^3.0.0",
|
"vue-shepherd": "^4.1.0",
|
||||||
"vue-tsc": "^2.0.10"
|
"vue-tsc": "^2.0.10",
|
||||||
|
"workbox-build": "^7.3.0",
|
||||||
|
"workbox-window": "^7.3.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.18",
|
"packageManager": "yarn@1.22.18"
|
||||||
"resolutions": {
|
|
||||||
"postcss": "8"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,6 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
overflow: hidden auto;
|
|
||||||
background: var(--initial-loader-bg, #fff);
|
|
||||||
min-block-size: calc(100% + env(safe-area-inset-top) + env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading-bg {
|
#loading-bg {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
z-index: 999;
|
z-index: 9999;
|
||||||
display: block;
|
display: block;
|
||||||
background: var(--initial-loader-bg, #fff);
|
background: var(--initial-loader-bg, #fff);
|
||||||
block-size: 100vh;
|
block-size: 100vh;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
@use "sass:map";
|
||||||
@use "vuetify/lib/styles/settings" as vuetify_settings;
|
@use "vuetify/lib/styles/settings" as vuetify_settings;
|
||||||
|
|
||||||
@mixin avatar-font-sizes($map: $avatar-sizes) {
|
@mixin avatar-font-sizes($map: $avatar-sizes) {
|
||||||
@each $sizeName, $multiplier in vuetify_settings.$size-scales {
|
@each $sizeName, $multiplier in vuetify_settings.$size-scales {
|
||||||
/* stylelint-disable-next-line scss/no-global-function-names */
|
/* stylelint-disable-next-line scss/no-global-function-names */
|
||||||
$size: map-get($map, $sizeName);
|
$size: map.get($map, $sizeName);
|
||||||
|
|
||||||
&.v-avatar--size-#{$sizeName} {
|
&.v-avatar--size-#{$sizeName} {
|
||||||
font-size: #{$size}px;
|
font-size: #{$size}px;
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ export function useDefer(maxFrameCount = 1) {
|
|||||||
const refreshFrameCount = () => {
|
const refreshFrameCount = () => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
frameCount.value++
|
frameCount.value++
|
||||||
if (frameCount.value < maxFrameCount)
|
if (frameCount.value < maxFrameCount) refreshFrameCount()
|
||||||
refreshFrameCount()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
refreshFrameCount()
|
refreshFrameCount()
|
||||||
@@ -19,3 +18,9 @@ export function useDefer(maxFrameCount = 1) {
|
|||||||
return frameCount.value >= showInFrameCount
|
return frameCount.value >= showInFrameCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureRenderComplete(callback: () => void) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(callback)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
1
src/@iconify/tsconfig.tsbuildinfo
Normal file
1
src/@iconify/tsconfig.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./build-icons.ts"],"version":"5.7.3"}
|
||||||
29
src/App.vue
29
src/App.vue
@@ -1,15 +1,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTheme } from 'vuetify'
|
import { useTheme } from 'vuetify'
|
||||||
import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
|
import { checkPrefersColorSchemeIsDark } from '@/@core/utils'
|
||||||
|
import { ensureRenderComplete, removeEl } from './@core/utils/dom'
|
||||||
const { global: globalTheme } = useTheme()
|
|
||||||
|
|
||||||
// 生效主题
|
// 生效主题
|
||||||
async function setTheme() {
|
const { global: globalTheme } = useTheme()
|
||||||
let themeValue = localStorage.getItem('theme') || 'light'
|
let themeValue = localStorage.getItem('theme') || 'light'
|
||||||
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
const autoTheme = checkPrefersColorSchemeIsDark() ? 'dark' : 'light'
|
||||||
globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
|
globalTheme.name.value = themeValue === 'auto' ? autoTheme : themeValue
|
||||||
}
|
|
||||||
|
// 显示状态
|
||||||
|
const show = ref(false)
|
||||||
|
|
||||||
// ApexCharts 全局配置
|
// ApexCharts 全局配置
|
||||||
declare global {
|
declare global {
|
||||||
@@ -41,14 +42,20 @@ if (window.Apex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面加载时,加载当前用户数据
|
onMounted(() => {
|
||||||
onBeforeMount(async () => {
|
ensureRenderComplete(() => {
|
||||||
setTheme()
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
removeEl('#loading-bg')
|
||||||
|
show.value = true
|
||||||
|
}, 1500)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VApp>
|
<VApp v-show="show">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</VApp>
|
</VApp>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ export interface SubscribeShare {
|
|||||||
share_comment?: string
|
share_comment?: string
|
||||||
// 分享人
|
// 分享人
|
||||||
share_user?: string
|
share_user?: string
|
||||||
|
// 分享人唯一ID
|
||||||
|
share_uid?: string
|
||||||
// 订阅名称
|
// 订阅名称
|
||||||
name?: string
|
name?: string
|
||||||
// 订阅年份
|
// 订阅年份
|
||||||
@@ -184,7 +186,7 @@ export interface TransferHistory {
|
|||||||
export interface MediaInfo {
|
export interface MediaInfo {
|
||||||
// 来源:themoviedb、douban、bangumi
|
// 来源:themoviedb、douban、bangumi
|
||||||
source?: string
|
source?: string
|
||||||
// 类型 电影、电视剧
|
// 类型 电影、电视剧、合集
|
||||||
type?: string
|
type?: string
|
||||||
// 媒体标题
|
// 媒体标题
|
||||||
title?: string
|
title?: string
|
||||||
@@ -204,6 +206,8 @@ export interface MediaInfo {
|
|||||||
douban_id?: string
|
douban_id?: string
|
||||||
// Bangumi ID
|
// Bangumi ID
|
||||||
bangumi_id?: string
|
bangumi_id?: string
|
||||||
|
// 合集ID
|
||||||
|
collection_id?: number
|
||||||
// 媒体原语种
|
// 媒体原语种
|
||||||
original_language?: string
|
original_language?: string
|
||||||
// 媒体原发行标题
|
// 媒体原发行标题
|
||||||
|
|||||||
BIN
src/assets/images/logos/bangumi_title.png
Normal file
BIN
src/assets/images/logos/bangumi_title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src/assets/images/logos/douban_title.png
Normal file
BIN
src/assets/images/logos/douban_title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
1
src/assets/images/logos/tmdb_title.svg
Normal file
1
src/assets/images/logos/tmdb_title.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 273.42 35.52"><defs><style>.cls-1{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" y1="17.76" x2="273.42" y2="17.76" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#90cea1"/><stop offset="0.56" stop-color="#3cbec9"/><stop offset="1" stop-color="#00b3e5"/></linearGradient></defs><title>Asset 3</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M191.85,35.37h63.9A17.67,17.67,0,0,0,273.42,17.7h0A17.67,17.67,0,0,0,255.75,0h-63.9A17.67,17.67,0,0,0,174.18,17.7h0A17.67,17.67,0,0,0,191.85,35.37ZM10.1,35.42h7.8V6.92H28V0H0v6.9H10.1Zm28.1,0H46V8.25h.1L55.05,35.4h6L70.3,8.25h.1V35.4h7.8V0H66.45l-8.2,23.1h-.1L50,0H38.2ZM89.14.12h11.7a33.56,33.56,0,0,1,8.08,1,18.52,18.52,0,0,1,6.67,3.08,15.09,15.09,0,0,1,4.53,5.52,18.5,18.5,0,0,1,1.67,8.25,16.91,16.91,0,0,1-1.62,7.58,16.3,16.3,0,0,1-4.38,5.5,19.24,19.24,0,0,1-6.35,3.37,24.53,24.53,0,0,1-7.55,1.15H89.14Zm7.8,28.2h4a21.66,21.66,0,0,0,5-.55A10.58,10.58,0,0,0,110,26a8.73,8.73,0,0,0,2.68-3.35,11.9,11.9,0,0,0,1-5.08,9.87,9.87,0,0,0-1-4.52,9.17,9.17,0,0,0-2.63-3.18A11.61,11.61,0,0,0,106.22,8a17.06,17.06,0,0,0-4.68-.63h-4.6ZM133.09.12h13.2a32.87,32.87,0,0,1,4.63.33,12.66,12.66,0,0,1,4.17,1.3,7.94,7.94,0,0,1,3,2.72,8.34,8.34,0,0,1,1.15,4.65,7.48,7.48,0,0,1-1.67,5,9.13,9.13,0,0,1-4.43,2.82V17a10.28,10.28,0,0,1,3.18,1,8.51,8.51,0,0,1,2.45,1.85,7.79,7.79,0,0,1,1.57,2.62,9.16,9.16,0,0,1,.55,3.2,8.52,8.52,0,0,1-1.2,4.68,9.32,9.32,0,0,1-3.1,3A13.38,13.38,0,0,1,152.32,35a22.5,22.5,0,0,1-4.73.5h-14.5Zm7.8,14.15h5.65a7.65,7.65,0,0,0,1.78-.2,4.78,4.78,0,0,0,1.57-.65,3.43,3.43,0,0,0,1.13-1.2,3.63,3.63,0,0,0,.42-1.8A3.3,3.3,0,0,0,151,8.6a3.42,3.42,0,0,0-1.23-1.13A6.07,6.07,0,0,0,148,6.9a9.9,9.9,0,0,0-1.85-.18h-5.3Zm0,14.65h7a8.27,8.27,0,0,0,1.83-.2,4.67,4.67,0,0,0,1.67-.7,3.93,3.93,0,0,0,1.23-1.3,3.8,3.8,0,0,0,.47-1.95,3.16,3.16,0,0,0-.62-2,4,4,0,0,0-1.58-1.18,8.23,8.23,0,0,0-2-.55,15.12,15.12,0,0,0-2.05-.15h-5.9Z"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TransferDirectoryConf } from '@/api/types'
|
import type { TransferDirectoryConf } from '@/api/types'
|
||||||
import PathInput from '@/components/input/PathInput.vue'
|
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import { storageOptions } from '@/api/constants'
|
import { storageOptions } from '@/api/constants'
|
||||||
@@ -20,12 +19,6 @@ const props = defineProps({
|
|||||||
height: String,
|
height: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 下载路径
|
|
||||||
const downloadPath = ref<string>('')
|
|
||||||
|
|
||||||
// 媒体库路径
|
|
||||||
const libraryPath = ref<string>('')
|
|
||||||
|
|
||||||
// 卡版是否折叠状态
|
// 卡版是否折叠状态
|
||||||
const isCollapsed = ref(true)
|
const isCollapsed = ref(true)
|
||||||
|
|
||||||
@@ -210,16 +203,12 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="8">
|
<VCol cols="8">
|
||||||
<PathInput v-model="props.directory.download_path" :storage="props.directory.storage">
|
<VPathField
|
||||||
<template #activator="{ menuprops }">
|
v-model="props.directory.download_path"
|
||||||
<VTextField
|
:storage="props.directory.storage"
|
||||||
:model-value="props.directory.download_path"
|
variant="underlined"
|
||||||
v-bind="menuprops"
|
label="下载目录/源目录"
|
||||||
variant="underlined"
|
/>
|
||||||
label="下载目录/源目录"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</PathInput>
|
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
||||||
<VSwitch v-model="props.directory.download_type_folder" label="按类型分类"></VSwitch>
|
<VSwitch v-model="props.directory.download_type_folder" label="按类型分类"></VSwitch>
|
||||||
@@ -257,16 +246,12 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="8">
|
<VCol cols="8">
|
||||||
<PathInput v-model="props.directory.library_path" :storage="props.directory.library_storage">
|
<VPathField
|
||||||
<template #activator="{ menuprops }">
|
v-model="props.directory.library_path"
|
||||||
<VTextField
|
:storage="props.directory.library_storage"
|
||||||
:modelValue="props.directory.library_path"
|
variant="underlined"
|
||||||
v-bind="menuprops"
|
label="媒体库目录"
|
||||||
variant="underlined"
|
/>
|
||||||
label="媒体库目录"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</PathInput>
|
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="4">
|
<VCol cols="4">
|
||||||
<VSelect
|
<VSelect
|
||||||
|
|||||||
@@ -91,7 +91,17 @@ async function drawImages(imageList: string[]) {
|
|||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.setAttribute('crossorigin', 'anonymous')
|
img.setAttribute('crossorigin', 'anonymous')
|
||||||
img.src = imgSrc
|
img.src = imgSrc
|
||||||
await new Promise(resolve => (img.onload = resolve))
|
try {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
img.onload = () => resolve()
|
||||||
|
img.onerror = () => reject(new Error(`Failed to load image: ${imgSrc}`))
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ctx.fillStyle = '#e5e7eb'
|
||||||
|
ctx.fillRect(MARGIN_WIDTH * index + POSTER_WIDTH * (index - 1), MARGIN_HEIGHT, POSTER_WIDTH, POSTER_HEIGHT)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const x = MARGIN_WIDTH * index + POSTER_WIDTH * (index - 1)
|
const x = MARGIN_WIDTH * index + POSTER_WIDTH * (index - 1)
|
||||||
const y = MARGIN_HEIGHT
|
const y = MARGIN_HEIGHT
|
||||||
|
|||||||
@@ -59,11 +59,7 @@ const seasonInfos = ref<TmdbSeason[]>([])
|
|||||||
|
|
||||||
// 选中的订阅季
|
// 选中的订阅季
|
||||||
const seasonsSelected = ref<TmdbSeason[]>([])
|
const seasonsSelected = ref<TmdbSeason[]>([])
|
||||||
let abortController: AbortController | null = null;
|
|
||||||
|
|
||||||
abortController = new AbortController();
|
|
||||||
registerAbortController(abortController);
|
|
||||||
const { signal } = abortController;
|
|
||||||
// 来源角标字典
|
// 来源角标字典
|
||||||
const sourceIconDict: { [key: string]: any } = {
|
const sourceIconDict: { [key: string]: any } = {
|
||||||
themoviedb: tmdbImage,
|
themoviedb: tmdbImage,
|
||||||
@@ -100,7 +96,6 @@ function getChipColor(type: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加订阅处理
|
// 添加订阅处理
|
||||||
|
|
||||||
async function handleAddSubscribe() {
|
async function handleAddSubscribe() {
|
||||||
if (props.media?.type === '电视剧' && props.media?.tmdb_id) {
|
if (props.media?.type === '电视剧' && props.media?.tmdb_id) {
|
||||||
// TMDB电视剧
|
// TMDB电视剧
|
||||||
@@ -219,7 +214,6 @@ async function removeSubscribe() {
|
|||||||
// 查询当前媒体是否已订阅
|
// 查询当前媒体是否已订阅
|
||||||
async function handleCheckSubscribe() {
|
async function handleCheckSubscribe() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const result = await checkSubscribe(props.media?.season)
|
const result = await checkSubscribe(props.media?.season)
|
||||||
if (result) isSubscribed.value = true
|
if (result) isSubscribed.value = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -230,7 +224,9 @@ async function handleCheckSubscribe() {
|
|||||||
// 查询当前媒体是否已入库
|
// 查询当前媒体是否已入库
|
||||||
async function handleCheckExists() {
|
async function handleCheckExists() {
|
||||||
try {
|
try {
|
||||||
|
const abortController = new AbortController()
|
||||||
|
registerAbortController(abortController)
|
||||||
|
const { signal } = abortController
|
||||||
const result: { [key: string]: any } = await api.get('mediaserver/exists', {
|
const result: { [key: string]: any } = await api.get('mediaserver/exists', {
|
||||||
params: {
|
params: {
|
||||||
tmdbid: props.media?.tmdb_id,
|
tmdbid: props.media?.tmdb_id,
|
||||||
@@ -239,7 +235,7 @@ async function handleCheckExists() {
|
|||||||
season: props.media?.season,
|
season: props.media?.season,
|
||||||
mtype: props.media?.type,
|
mtype: props.media?.type,
|
||||||
},
|
},
|
||||||
signal
|
signal,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) isExists.value = true
|
if (result.success) isExists.value = true
|
||||||
@@ -251,15 +247,16 @@ async function handleCheckExists() {
|
|||||||
// 调用API检查是否已订阅,电视剧需要指定季
|
// 调用API检查是否已订阅,电视剧需要指定季
|
||||||
async function checkSubscribe(season = 0) {
|
async function checkSubscribe(season = 0) {
|
||||||
try {
|
try {
|
||||||
|
const abortController = new AbortController()
|
||||||
|
registerAbortController(abortController)
|
||||||
|
const { signal } = abortController
|
||||||
const mediaid = getMediaId()
|
const mediaid = getMediaId()
|
||||||
|
|
||||||
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
||||||
params: {
|
params: {
|
||||||
season,
|
season,
|
||||||
title: props.media?.title,
|
title: props.media?.title,
|
||||||
},
|
},
|
||||||
signal
|
signal,
|
||||||
})
|
})
|
||||||
|
|
||||||
return result.id || null
|
return result.id || null
|
||||||
@@ -282,7 +279,6 @@ async function checkSeasonsNotExists() {
|
|||||||
let state = 0
|
let state = 0
|
||||||
if (item.episodes.length === 0) state = 2
|
if (item.episodes.length === 0) state = 2
|
||||||
else if (item.episodes.length < item.total_episode) state = 1
|
else if (item.episodes.length < item.total_episode) state = 1
|
||||||
|
|
||||||
seasonsNotExisted.value[item.season] = state
|
seasonsNotExisted.value[item.season] = state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -351,13 +347,24 @@ function getExistText(season: number) {
|
|||||||
// 打开详情页
|
// 打开详情页
|
||||||
function goMediaDetail(isHovering = false) {
|
function goMediaDetail(isHovering = false) {
|
||||||
if (isHovering) {
|
if (isHovering) {
|
||||||
router.push({
|
if (props.media?.collection_id) {
|
||||||
path: '/media',
|
// 跳转到合集列表
|
||||||
query: {
|
router.push({
|
||||||
mediaid: getMediaId(),
|
path: `/browse/tmdb/collection/${props.media?.collection_id}`,
|
||||||
type: props.media?.type,
|
query: {
|
||||||
},
|
title: props.media?.title,
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 跳转到媒体详情页
|
||||||
|
router.push({
|
||||||
|
path: '/media',
|
||||||
|
query: {
|
||||||
|
mediaid: getMediaId(),
|
||||||
|
type: props.media?.type,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,6 +383,9 @@ function handleSearch() {
|
|||||||
|
|
||||||
// 懒加载检查
|
// 懒加载检查
|
||||||
function handleCheckLazy() {
|
function handleCheckLazy() {
|
||||||
|
if (props.media?.collection_id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
handleCheckSubscribe()
|
handleCheckSubscribe()
|
||||||
handleCheckExists()
|
handleCheckExists()
|
||||||
}
|
}
|
||||||
@@ -453,13 +463,25 @@ function onRemoveSubscribe() {
|
|||||||
<VHover>
|
<VHover>
|
||||||
<template #default="hover">
|
<template #default="hover">
|
||||||
<div ref="mediaCardRef">
|
<div ref="mediaCardRef">
|
||||||
<VCard v-bind="hover.props" :height="props.height" :width="props.width"
|
<VCard
|
||||||
class="outline-none shadow ring-gray-500 rounded-lg" :class="{
|
v-bind="hover.props"
|
||||||
|
:height="props.height"
|
||||||
|
:width="props.width"
|
||||||
|
class="outline-none shadow ring-gray-500 rounded-lg"
|
||||||
|
:class="{
|
||||||
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
||||||
'ring-1': isImageLoaded,
|
'ring-1': isImageLoaded,
|
||||||
}" @click.stop="goMediaDetail(hover.isHovering ?? false)">
|
}"
|
||||||
<VImg aspect-ratio="2/3" :src="getImgUrl" class="object-cover aspect-w-2 aspect-h-3" cover
|
@click.stop="goMediaDetail(hover.isHovering ?? false)"
|
||||||
@load="isImageLoaded = true" @error="imageLoadError = true">
|
>
|
||||||
|
<VImg
|
||||||
|
aspect-ratio="2/3"
|
||||||
|
:src="getImgUrl"
|
||||||
|
class="object-cover aspect-w-2 aspect-h-3"
|
||||||
|
cover
|
||||||
|
@load="isImageLoaded = true"
|
||||||
|
@error="imageLoadError = true"
|
||||||
|
>
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||||
@@ -467,9 +489,11 @@ function onRemoveSubscribe() {
|
|||||||
</template>
|
</template>
|
||||||
</VImg>
|
</VImg>
|
||||||
<!-- 详情 -->
|
<!-- 详情 -->
|
||||||
<VCardText v-show="hover.isHovering || imageLoadError"
|
<VCardText
|
||||||
|
v-show="hover.isHovering || imageLoadError"
|
||||||
class="w-full h-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2"
|
class="w-full h-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2"
|
||||||
style="background: linear-gradient(rgba(45, 55, 72, 40%) 0%, rgba(45, 55, 72, 90%) 100%)">
|
style="background: linear-gradient(rgba(45, 55, 72, 40%) 0%, rgba(45, 55, 72, 90%) 100%)"
|
||||||
|
>
|
||||||
<span class="font-bold">{{ props.media?.year }}</span>
|
<span class="font-bold">{{ props.media?.year }}</span>
|
||||||
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
||||||
{{ props.media?.title }}
|
{{ props.media?.title }}
|
||||||
@@ -477,27 +501,42 @@ function onRemoveSubscribe() {
|
|||||||
<p class="leading-4 line-clamp-4 overflow-hidden text-ellipsis ...">
|
<p class="leading-4 line-clamp-4 overflow-hidden text-ellipsis ...">
|
||||||
{{ props.media?.overview }}
|
{{ props.media?.overview }}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex align-center justify-between">
|
<div v-if="props.media?.collection_id" class="mb-3" @click.stop=""></div>
|
||||||
|
<div v-else class="flex align-center justify-between">
|
||||||
<IconBtn icon="mdi-magnify" color="white" @click.stop="handleSearch" />
|
<IconBtn icon="mdi-magnify" color="white" @click.stop="handleSearch" />
|
||||||
<IconBtn icon="mdi-heart" :color="isSubscribed ? 'error' : 'white'" @click.stop="handleSubscribe" />
|
<IconBtn icon="mdi-heart" :color="isSubscribed ? 'error' : 'white'" @click.stop="handleSubscribe" />
|
||||||
</div>
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<!-- 类型角标 -->
|
<!-- 类型角标 -->
|
||||||
<VChip v-show="isImageLoaded" variant="elevated" size="small" :class="getChipColor(props.media?.type || '')"
|
<VChip
|
||||||
class="absolute left-2 top-2 bg-opacity-80 shadow-md text-white font-bold">
|
v-show="isImageLoaded"
|
||||||
|
variant="elevated"
|
||||||
|
size="small"
|
||||||
|
:class="getChipColor(props.media?.type || '')"
|
||||||
|
class="absolute left-2 top-2 bg-opacity-80 shadow-md text-white font-bold"
|
||||||
|
>
|
||||||
{{ props.media?.type }}
|
{{ props.media?.type }}
|
||||||
</VChip>
|
</VChip>
|
||||||
<!-- 本地存在标识 -->
|
<!-- 本地存在标识 -->
|
||||||
<ExistIcon v-if="isExists && !hover.isHovering" />
|
<ExistIcon v-if="isExists && !hover.isHovering" />
|
||||||
<!-- 评分角标 -->
|
<!-- 评分角标 -->
|
||||||
<VChip v-if="isImageLoaded && props.media?.vote_average && !(isExists && !hover.isHovering)"
|
<VChip
|
||||||
variant="elevated" size="small" :class="getChipColor('rating')"
|
v-if="isImageLoaded && props.media?.vote_average && !(isExists && !hover.isHovering)"
|
||||||
class="absolute right-2 top-2 bg-opacity-80 shadow-md text-white font-bold">
|
variant="elevated"
|
||||||
|
size="small"
|
||||||
|
:class="getChipColor('rating')"
|
||||||
|
class="absolute right-2 top-2 bg-opacity-80 shadow-md text-white font-bold"
|
||||||
|
>
|
||||||
{{ props.media?.vote_average }}
|
{{ props.media?.vote_average }}
|
||||||
</VChip>
|
</VChip>
|
||||||
<!--来源图标-->
|
<!--来源图标-->
|
||||||
<VAvatar size="24" density="compact" class="absolute bottom-1 right-1" tile
|
<VAvatar
|
||||||
v-if="!hover.isHovering && isImageLoaded && props.media?.source">
|
size="24"
|
||||||
|
density="compact"
|
||||||
|
class="absolute bottom-1 right-1"
|
||||||
|
tile
|
||||||
|
v-if="!hover.isHovering && isImageLoaded && props.media?.source && !imageLoadError"
|
||||||
|
>
|
||||||
<VImg cover :src="sourceIconDict[props.media?.source]" class="shadow-lg" />
|
<VImg cover :src="sourceIconDict[props.media?.source]" class="shadow-lg" />
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
</VCard>
|
</VCard>
|
||||||
@@ -516,8 +555,14 @@ function onRemoveSubscribe() {
|
|||||||
<VList v-model:selected="seasonsSelected" lines="three" select-strategy="classic">
|
<VList v-model:selected="seasonsSelected" lines="three" select-strategy="classic">
|
||||||
<VListItem v-for="(item, i) in seasonInfos" :key="i" :value="item">
|
<VListItem v-for="(item, i) in seasonInfos" :key="i" :value="item">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VImg height="90" width="60" :src="getSeasonPoster(item.poster_path || '')" aspect-ratio="2/3"
|
<VImg
|
||||||
class="object-cover rounded shadow ring-gray-500 me-3" cover>
|
height="90"
|
||||||
|
width="60"
|
||||||
|
:src="getSeasonPoster(item.poster_path || '')"
|
||||||
|
aspect-ratio="2/3"
|
||||||
|
class="object-cover rounded shadow ring-gray-500 me-3"
|
||||||
|
cover
|
||||||
|
>
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||||
@@ -556,6 +601,12 @@ function onRemoveSubscribe() {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VBottomSheet>
|
</VBottomSheet>
|
||||||
<!-- 订阅编辑弹窗 -->
|
<!-- 订阅编辑弹窗 -->
|
||||||
<SubscribeEditDialog v-if="subscribeEditDialog" v-model="subscribeEditDialog" :subid="subscribeId"
|
<SubscribeEditDialog
|
||||||
@close="subscribeEditDialog = false" @save="subscribeEditDialog = false" @remove="onRemoveSubscribe" />
|
v-if="subscribeEditDialog"
|
||||||
|
v-model="subscribeEditDialog"
|
||||||
|
:subid="subscribeId"
|
||||||
|
@close="subscribeEditDialog = false"
|
||||||
|
@save="subscribeEditDialog = false"
|
||||||
|
@remove="onRemoveSubscribe"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -532,14 +532,14 @@ watch(
|
|||||||
<VDivider />
|
<VDivider />
|
||||||
<VersionHistory :history="props.plugin?.history" />
|
<VersionHistory :history="props.plugin?.history" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardItem>
|
||||||
<VBtn @click="updatePlugin" block>
|
<VBtn @click="updatePlugin" block>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon icon="mdi-arrow-up-circle-outline" />
|
<VIcon icon="mdi-arrow-up-circle-outline" />
|
||||||
</template>
|
</template>
|
||||||
更新到最新版本
|
更新到最新版本
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCardText>
|
</VCardItem>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const getImgUrl = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 跳转播放
|
// 跳转播放
|
||||||
function goPlay(isHovering = false) {
|
function goPlay(isHovering: boolean | null = false) {
|
||||||
if (props.media?.link && isHovering) window.open(props.media?.link, '_blank')
|
if (props.media?.link && isHovering) window.open(props.media?.link, '_blank')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -48,13 +48,11 @@ function goPlay(isHovering = false) {
|
|||||||
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
||||||
'ring-1': isImageLoaded,
|
'ring-1': isImageLoaded,
|
||||||
}"
|
}"
|
||||||
@click.stop="goPlay(hover.isHovering)"
|
|
||||||
>
|
>
|
||||||
<VImg
|
<VImg
|
||||||
aspect-ratio="2/3"
|
aspect-ratio="2/3"
|
||||||
:src="getImgUrl"
|
:src="getImgUrl"
|
||||||
class="object-cover aspect-w-2 aspect-h-3"
|
class="object-cover aspect-w-2 aspect-h-3"
|
||||||
:class="hover.isHovering ? 'on-hover' : ''"
|
|
||||||
cover
|
cover
|
||||||
@load="isImageLoaded = true"
|
@load="isImageLoaded = true"
|
||||||
@error="imageLoadError = true"
|
@error="imageLoadError = true"
|
||||||
@@ -78,7 +76,9 @@ function goPlay(isHovering = false) {
|
|||||||
<!-- 详情 -->
|
<!-- 详情 -->
|
||||||
<VCardText
|
<VCardText
|
||||||
v-show="hover.isHovering || imageLoadError"
|
v-show="hover.isHovering || imageLoadError"
|
||||||
class="w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2"
|
class="w-full h-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2 pb-5"
|
||||||
|
style="background: linear-gradient(rgba(45, 55, 72, 40%) 0%, rgba(45, 55, 72, 90%) 100%)"
|
||||||
|
@click.stop="goPlay(hover.isHovering)"
|
||||||
>
|
>
|
||||||
<span class="font-bold">{{ props.media?.subtitle }}</span>
|
<span class="font-bold">{{ props.media?.subtitle }}</span>
|
||||||
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
||||||
@@ -89,9 +89,3 @@ function goPlay(isHovering = false) {
|
|||||||
</template>
|
</template>
|
||||||
</VHover>
|
</VHover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.on-hover img {
|
|
||||||
@apply brightness-50;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ const props = defineProps({
|
|||||||
media: Object as PropType<SubscribeShare>,
|
media: Object as PropType<SubscribeShare>,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 定义删除事件
|
||||||
|
const emit = defineEmits(['delete'])
|
||||||
|
|
||||||
// 从 provide 中获取全局设置
|
// 从 provide 中获取全局设置
|
||||||
const globalSettings: any = inject('globalSettings')
|
const globalSettings: any = inject('globalSettings')
|
||||||
|
|
||||||
@@ -70,8 +73,18 @@ function showForkSubscribe() {
|
|||||||
// 完成复用订阅
|
// 完成复用订阅
|
||||||
function finishForkSubscribe(subid: number) {
|
function finishForkSubscribe(subid: number) {
|
||||||
subscribeId.value = subid
|
subscribeId.value = subid
|
||||||
|
forkSubscribeDialog.value=false
|
||||||
subscribeEditDialog.value = true
|
subscribeEditDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除订阅分享时处理
|
||||||
|
function doDelete() {
|
||||||
|
forkSubscribeDialog.value=false
|
||||||
|
// 通知父组件刷新
|
||||||
|
emit('delete')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -153,7 +166,8 @@ function finishForkSubscribe(subid: number) {
|
|||||||
v-model="forkSubscribeDialog"
|
v-model="forkSubscribeDialog"
|
||||||
:media="props.media"
|
:media="props.media"
|
||||||
@close="forkSubscribeDialog = false"
|
@close="forkSubscribeDialog = false"
|
||||||
@done="finishForkSubscribe"
|
@fork="finishForkSubscribe"
|
||||||
|
@delete="doDelete"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits(['close', 'done'])
|
const emit = defineEmits(['fork', 'delete', 'close'])
|
||||||
|
|
||||||
// 从 provide 中获取全局设置
|
// 从 provide 中获取全局设置
|
||||||
const globalSettings: any = inject('globalSettings')
|
const globalSettings: any = inject('globalSettings')
|
||||||
@@ -23,6 +23,61 @@ const $toast = useToast()
|
|||||||
// 处理中
|
// 处理中
|
||||||
const processing = ref(false)
|
const processing = ref(false)
|
||||||
|
|
||||||
|
// 删除中
|
||||||
|
const deleting = ref(false)
|
||||||
|
|
||||||
|
// 是否折叠
|
||||||
|
const isExpanded = ref(false)
|
||||||
|
|
||||||
|
// follow用户列表
|
||||||
|
const followUsers = ref<string[]>([])
|
||||||
|
|
||||||
|
// 当前用户是否已follow
|
||||||
|
const isFollowed = computed(() => followUsers.value.includes(props.media?.share_uid || ''))
|
||||||
|
|
||||||
|
// 折叠展开
|
||||||
|
function toggleExpand() {
|
||||||
|
isExpanded.value = !isExpanded.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载follow用户列表
|
||||||
|
async function queryFollowUsers() {
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.get('system/setting/FollowSubscribers')
|
||||||
|
followUsers.value = result.data?.value ?? []
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// follow用户
|
||||||
|
async function followUser() {
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.post(`subscribe/follow?share_uid=${props.media?.share_uid}`)
|
||||||
|
if (result.success) {
|
||||||
|
queryFollowUsers()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfollow用户
|
||||||
|
async function unfollowUser() {
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.delete('subscribe/follow', {
|
||||||
|
params: {
|
||||||
|
share_uid: props.media?.share_uid,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
queryFollowUsers()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计算海报图片地址
|
// 计算海报图片地址
|
||||||
const posterUrl = computed(() => {
|
const posterUrl = computed(() => {
|
||||||
const url = props.media?.poster
|
const url = props.media?.poster
|
||||||
@@ -55,7 +110,7 @@ async function doFork() {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success(`${props.media?.share_title} 添加订阅成功!`)
|
$toast.success(`${props.media?.share_title} 添加订阅成功!`)
|
||||||
// 完成
|
// 完成
|
||||||
emit('done', result.data.id)
|
emit('fork', result.data.id)
|
||||||
} else {
|
} else {
|
||||||
$toast.error(`${props.media?.share_title} 添加订阅失败:${result.message}!`)
|
$toast.error(`${props.media?.share_title} 添加订阅失败:${result.message}!`)
|
||||||
}
|
}
|
||||||
@@ -66,9 +121,41 @@ async function doFork() {
|
|||||||
doneNProgress()
|
doneNProgress()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除订阅分享
|
||||||
|
async function doDelete() {
|
||||||
|
// 开始处理
|
||||||
|
startNProgress()
|
||||||
|
try {
|
||||||
|
deleting.value = true
|
||||||
|
// 请求API
|
||||||
|
const result: { [key: string]: any } = await api.delete(`subscribe/share/${props.media?.id}`, {
|
||||||
|
params: {
|
||||||
|
share_uid: globalSettings.USER_UNIQUE_ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// 订阅状态
|
||||||
|
if (result.success) {
|
||||||
|
$toast.success(`${props.media?.share_title} 取消分享成功!`)
|
||||||
|
// 完成
|
||||||
|
emit('delete', result.data.id)
|
||||||
|
} else {
|
||||||
|
$toast.error(`${props.media?.share_title} 取消分享失败:${result.message}!`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
deleting.value = false
|
||||||
|
doneNProgress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queryFollowUsers()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VDialog max-width="40rem">
|
<VDialog max-width="40rem" scrollable>
|
||||||
<VCard>
|
<VCard>
|
||||||
<DialogCloseBtn @click="emit('close')" />
|
<DialogCloseBtn @click="emit('close')" />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
@@ -92,11 +179,13 @@ async function doFork() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle class="text-center text-md-left">
|
<VCardTitle
|
||||||
|
class="text-center text-md-left break-words whitespace-break-spaces line-clamp-2 overflow-hidden text-ellipsis"
|
||||||
|
>
|
||||||
{{ props.media?.share_title }}
|
{{ props.media?.share_title }}
|
||||||
</VCardTitle>
|
</VCardTitle>
|
||||||
<VCardSubtitle
|
<VCardSubtitle
|
||||||
class="text-center text-md-left break-words whitespace-break-spaces line-clamp-2 overflow-hidden text-ellipsis ..."
|
class="text-center text-md-left break-words whitespace-break-spaces line-clamp-4 overflow-hidden text-ellipsis"
|
||||||
>
|
>
|
||||||
{{ props.media?.share_comment }}
|
{{ props.media?.share_comment }}
|
||||||
</VCardSubtitle>
|
</VCardSubtitle>
|
||||||
@@ -113,9 +202,12 @@ async function doFork() {
|
|||||||
<span class="text-body-1"> {{ media?.keyword }}</span>
|
<span class="text-body-1"> {{ media?.keyword }}</span>
|
||||||
</VListItemTitle>
|
</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
<VListItem class="ps-0" v-if="media?.custom_words">
|
<VListItem class="ps-0" v-if="media?.custom_words" @click.stop="toggleExpand">
|
||||||
<VListItemTitle
|
<VListItemTitle
|
||||||
class="text-center text-md-left break-words whitespace-break-spaces line-clamp-10 overflow-hidden text-ellipsis ..."
|
class="text-center text-md-left break-words whitespace-break-spaces"
|
||||||
|
:class="{
|
||||||
|
'line-clamp-4 overflow-hidden text-ellipsis': !isExpanded,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<span class="font-weight-medium">识别词:</span>
|
<span class="font-weight-medium">识别词:</span>
|
||||||
<span class="text-body-1"> {{ media?.custom_words }}</span>
|
<span class="text-body-1"> {{ media?.custom_words }}</span>
|
||||||
@@ -123,15 +215,46 @@ async function doFork() {
|
|||||||
</VListItem>
|
</VListItem>
|
||||||
</VList>
|
</VList>
|
||||||
<div class="text-center text-md-left">
|
<div class="text-center text-md-left">
|
||||||
<VBtn
|
<div>
|
||||||
color="primary"
|
<VBtn
|
||||||
:disabled="processing"
|
color="primary"
|
||||||
@click="doFork"
|
:disabled="processing"
|
||||||
prepend-icon="mdi-heart"
|
@click="doFork"
|
||||||
:loading="processing"
|
prepend-icon="mdi-heart"
|
||||||
>
|
:loading="processing"
|
||||||
添加到我的订阅
|
>
|
||||||
</VBtn>
|
订阅
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
v-if="props.media?.share_uid && props.media?.share_uid === globalSettings.USER_UNIQUE_ID"
|
||||||
|
color="error"
|
||||||
|
:disabled="deleting"
|
||||||
|
@click="doDelete"
|
||||||
|
prepend-icon="mdi-delete"
|
||||||
|
:loading="deleting"
|
||||||
|
class="ms-2"
|
||||||
|
>
|
||||||
|
取消分享
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
v-else-if="isFollowed && props.media?.share_uid"
|
||||||
|
color="warning"
|
||||||
|
@click="unfollowUser"
|
||||||
|
prepend-icon="mdi-account-remove"
|
||||||
|
class="ms-2"
|
||||||
|
>
|
||||||
|
取消关注
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
v-else-if="props.media?.share_uid"
|
||||||
|
@click="followUser"
|
||||||
|
color="info"
|
||||||
|
prepend-icon="mdi-account-plus"
|
||||||
|
class="ms-2"
|
||||||
|
>
|
||||||
|
关注
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
<div class="text-xs mt-2" v-if="props.media?.count">
|
<div class="text-xs mt-2" v-if="props.media?.count">
|
||||||
<VIcon icon="mdi-fire" />共 {{ props.media?.count?.toLocaleString() }} 次复用
|
<VIcon icon="mdi-fire" />共 {{ props.media?.count?.toLocaleString() }} 次复用
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const dialogTitle = computed(() => {
|
|||||||
// 禁用指定集数
|
// 禁用指定集数
|
||||||
const disableEpisodeDetail = computed(() => {
|
const disableEpisodeDetail = computed(() => {
|
||||||
if (props.items) {
|
if (props.items) {
|
||||||
|
if (transferForm.episode_format) return false
|
||||||
return !(props.items.length === 1 && props.items[0].type !== 'dir')
|
return !(props.items.length === 1 && props.items[0].type !== 'dir')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,17 +67,18 @@ async function updateSiteCookie() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VDialog max-width="50rem">
|
<VDialog max-width="30rem">
|
||||||
<!-- Dialog Content -->
|
<!-- Dialog Content -->
|
||||||
<VCard title="更新站点Cookie & UA">
|
<VCard title="更新站点Cookie & UA">
|
||||||
<DialogCloseBtn @click="emit('close')" />
|
<DialogCloseBtn @click="emit('close')" />
|
||||||
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VForm @submit.prevent="() => {}">
|
<VForm @submit.prevent="() => {}">
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12" md="4">
|
<VCol cols="12">
|
||||||
<VTextField v-model="userPwForm.username" label="用户名" :rules="[requiredValidator]" />
|
<VTextField v-model="userPwForm.username" label="用户名" :rules="[requiredValidator]" />
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="4">
|
<VCol cols="12">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="userPwForm.password"
|
v-model="userPwForm.password"
|
||||||
label="密码"
|
label="密码"
|
||||||
@@ -88,19 +89,19 @@ async function updateSiteCookie() {
|
|||||||
@keydown.enter="updateSiteCookie"
|
@keydown.enter="updateSiteCookie"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="4">
|
<VCol cols="12">
|
||||||
<VTextField v-model="userPwForm.code" label="两步验证" />
|
<VTextField v-model="userPwForm.code" label="两步验证" />
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
</VForm>
|
</VForm>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
<VCardActions class="mx-auto">
|
||||||
<VCardActions>
|
|
||||||
<VSpacer />
|
|
||||||
<VBtn
|
<VBtn
|
||||||
|
size="large"
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
@click="updateSiteCookie"
|
@click="updateSiteCookie"
|
||||||
:disabled="updateButtonDisable"
|
:disabled="updateButtonDisable"
|
||||||
|
:loading="updateButtonDisable"
|
||||||
prepend-icon="mdi-refresh"
|
prepend-icon="mdi-refresh"
|
||||||
class="px-5"
|
class="px-5"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VDialog max-width="80rem" scrollable z-index="1010" :fullscreen="!display.mdAndUp.value">
|
<VDialog max-width="80rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||||
<VCard :title="`浏览 - ${props.site?.name}`">
|
<VCard :title="`浏览 - ${props.site?.name}`">
|
||||||
<DialogCloseBtn @click="emit('close')" />
|
<DialogCloseBtn @click="emit('close')" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
|
|||||||
@@ -132,59 +132,63 @@ onBeforeMount(() => {
|
|||||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
||||||
<VWindowItem value="download">
|
<VWindowItem value="download">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<VDataTable
|
<div>
|
||||||
items-per-page="50"
|
<VDataTable
|
||||||
:headers="downloadHeaders"
|
items-per-page="50"
|
||||||
:items="downloadInfos"
|
:headers="downloadHeaders"
|
||||||
:items-length="totalCount"
|
:items="downloadInfos"
|
||||||
density="compact"
|
:items-length="totalCount"
|
||||||
item-value="title"
|
density="compact"
|
||||||
return-object
|
item-value="title"
|
||||||
fixed-header
|
return-object
|
||||||
hover
|
fixed-header
|
||||||
items-per-page-text="每页条数"
|
hover
|
||||||
page-text="{0}-{1} 共 {2} 条"
|
items-per-page-text="每页条数"
|
||||||
loading-text="加载中..."
|
page-text="{0}-{1} 共 {2} 条"
|
||||||
>
|
loading-text="加载中..."
|
||||||
<template #item.episode_number="{ item }">
|
>
|
||||||
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
|
<template #item.episode_number="{ item }">
|
||||||
</template>
|
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
|
||||||
<template #item.torrent_title="{ item }">
|
</template>
|
||||||
<div class="text-xs" v-for="file in item.download">
|
<template #item.torrent_title="{ item }">
|
||||||
【{{ file.site_name }}】{{ file.torrent_title }}
|
<div class="text-xs" v-for="file in item.download">
|
||||||
</div>
|
【{{ file.site_name }}】{{ file.torrent_title }}
|
||||||
</template>
|
</div>
|
||||||
<template #item.file_path="{ item }">
|
</template>
|
||||||
<div class="text-xs" v-for="file in item.download">{{ file.file_path }}</div>
|
<template #item.file_path="{ item }">
|
||||||
</template>
|
<div class="text-xs" v-for="file in item.download">{{ file.file_path }}</div>
|
||||||
<template #no-data> 没有数据 </template>
|
</template>
|
||||||
</VDataTable>
|
<template #no-data> 没有数据 </template>
|
||||||
|
</VDataTable>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
<VWindowItem value="library">
|
<VWindowItem value="library">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<VDataTable
|
<div>
|
||||||
items-per-page="50"
|
<VDataTable
|
||||||
:headers="libraryHeaders"
|
items-per-page="50"
|
||||||
:items="libraryInfos"
|
:headers="libraryHeaders"
|
||||||
:items-length="totalCount"
|
:items="libraryInfos"
|
||||||
density="compact"
|
:items-length="totalCount"
|
||||||
item-value="title"
|
density="compact"
|
||||||
return-object
|
item-value="title"
|
||||||
fixed-header
|
return-object
|
||||||
hover
|
fixed-header
|
||||||
items-per-page-text="每页条数"
|
hover
|
||||||
page-text="{0}-{1} 共 {2} 条"
|
items-per-page-text="每页条数"
|
||||||
loading-text="加载中..."
|
page-text="{0}-{1} 共 {2} 条"
|
||||||
>
|
loading-text="加载中..."
|
||||||
<template #item.episode_number="{ item }">
|
>
|
||||||
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
|
<template #item.episode_number="{ item }">
|
||||||
</template>
|
<div class="text-high-emphasis pt-1">{{ item.episode_number }}. {{ item.title }}</div>
|
||||||
<template #item.file_path="{ item }">
|
</template>
|
||||||
<div class="text-xs" v-for="file in item.library">{{ file.file_path }}</div>
|
<template #item.file_path="{ item }">
|
||||||
</template>
|
<div class="text-xs" v-for="file in item.library">{{ file.file_path }}</div>
|
||||||
<template #no-data> 没有数据 </template>
|
</template>
|
||||||
</VDataTable>
|
<template #no-data> 没有数据 </template>
|
||||||
|
</VDataTable>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
</VWindow>
|
</VWindow>
|
||||||
|
|||||||
@@ -138,14 +138,7 @@ const dropdownItems = ref([
|
|||||||
<VCardTitle>{{ props.type + '订阅历史' }}</VCardTitle>
|
<VCardTitle>{{ props.type + '订阅历史' }}</VCardTitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<DialogCloseBtn
|
<DialogCloseBtn @click="emit('close')" />
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit('close')
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<!-- <VList lines="two" v-if="historyList.length > 0"> -->
|
|
||||||
<VList lines="two">
|
<VList lines="two">
|
||||||
<VInfiniteScroll mode="intersect" side="end" :items="historyList" class="overflow-hidden" @load="loadHistory">
|
<VInfiniteScroll mode="intersect" side="end" :items="historyList" class="overflow-hidden" @load="loadHistory">
|
||||||
<template #loading>
|
<template #loading>
|
||||||
@@ -207,7 +200,7 @@ const dropdownItems = ref([
|
|||||||
</template>
|
</template>
|
||||||
</VInfiniteScroll>
|
</VInfiniteScroll>
|
||||||
</VList>
|
</VList>
|
||||||
<VCardText v-if="historyList.length === 0" class="text-center"> 没有已完成的订阅 </VCardText>
|
<VCardText v-if="historyList.length === 0 && isRefreshed" class="text-center"> 没有已完成的订阅 </VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<!-- 进度框 -->
|
<!-- 进度框 -->
|
||||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
|
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ onMounted(() => {
|
|||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VDivider class="my-10">
|
<VDivider class="my-10">
|
||||||
<span>消息账号绑定</span>
|
<span>账号绑定</span>
|
||||||
</VDivider>
|
</VDivider>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
@@ -395,6 +395,9 @@ onMounted(() => {
|
|||||||
label="SynologyChat用户"
|
label="SynologyChat用户"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.settings.douban_userid" density="comfortable" clearable label="豆瓣用户" />
|
||||||
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
</VForm>
|
</VForm>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
|||||||
49
src/components/field/PathField.vue
Normal file
49
src/components/field/PathField.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import PathInput from '@/components/input/PathInput.vue'
|
||||||
|
|
||||||
|
const attrs = useAttrs()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '* * * * *',
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
type: String,
|
||||||
|
default: 'local',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const innerValue = ref(props.modelValue)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
value => {
|
||||||
|
innerValue.value = value
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const propsWithoutModelValue = computed(() => {
|
||||||
|
const { modelValue, ...rest } = props
|
||||||
|
return { ...rest, ...attrs }
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateModelValue(value: string) {
|
||||||
|
innerValue.value = value
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PathInput v-model="innerValue" :storage="props.storage" @update:modelValue="updateModelValue">
|
||||||
|
<template #activator="{ menuprops }">
|
||||||
|
<VTextField
|
||||||
|
:modelValue="innerValue"
|
||||||
|
@update:modelValue="updateModelValue"
|
||||||
|
v-bind="{ ...menuprops, ...propsWithoutModelValue }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</PathInput>
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from '@/api'
|
|
||||||
import { FileItem } from '@/api/types'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import api from '@/api'
|
|||||||
import { FileItem } from '@/api/types'
|
import { FileItem } from '@/api/types'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '/',
|
||||||
|
},
|
||||||
root: {
|
root: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '/',
|
default: '/',
|
||||||
@@ -16,9 +20,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const activedDirs = ref<string[]>([])
|
const menuVisible = ref(false)
|
||||||
const openedDirs = ref<string[]>([])
|
|
||||||
const isUserAction = ref(false) // 标志:是否为用户主动操作
|
|
||||||
|
|
||||||
const treeItems = ref<FileItem[]>([
|
const treeItems = ref<FileItem[]>([
|
||||||
{
|
{
|
||||||
@@ -31,29 +33,103 @@ const treeItems = ref<FileItem[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const activedDirs = ref<FileItem[]>([])
|
||||||
|
|
||||||
|
const openedDirs = ref<FileItem[]>([])
|
||||||
|
|
||||||
|
// 调用API查询子目录
|
||||||
async function fetchDirs(item: any) {
|
async function fetchDirs(item: any) {
|
||||||
return api
|
return api
|
||||||
.post('/storage/list', item)
|
.post('/storage/list', item)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
data = data.filter((i: any) => i.type === 'dir')
|
data = data.filter((i: any) => i.type === 'dir')
|
||||||
item.children.push(...data)
|
item.children?.push(...data)
|
||||||
})
|
})
|
||||||
.catch(err => console.warn(err))
|
.catch(err => console.warn(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 递归查询路径
|
||||||
|
function findPath(item: FileItem, path: string): FileItem | null {
|
||||||
|
if (item.path === path) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
for (const child of item.children) {
|
||||||
|
const res: FileItem | null = findPath(child, path)
|
||||||
|
if (res) {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据路径展开所有子目录
|
||||||
|
async function expandDirs(path: string) {
|
||||||
|
// 分割路径
|
||||||
|
const paths = path.split('/').filter(i => i)
|
||||||
|
// 展开根目录
|
||||||
|
const root_item = treeItems.value[0]
|
||||||
|
await fetchDirs(root_item)
|
||||||
|
openedDirs.value.push(root_item)
|
||||||
|
// 逐级展开
|
||||||
|
let currentPath = '/'
|
||||||
|
for (const p of paths) {
|
||||||
|
currentPath += `${p}/`
|
||||||
|
// 查询当前目录
|
||||||
|
const item = findPath(root_item, currentPath)
|
||||||
|
if (!item) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 加载子目录
|
||||||
|
if (item.children?.length === 0) {
|
||||||
|
await fetchDirs(item)
|
||||||
|
}
|
||||||
|
// 打开当前目录
|
||||||
|
if (!openedDirs.value.includes(item) && path != currentPath) {
|
||||||
|
openedDirs.value.push(item)
|
||||||
|
}
|
||||||
|
// 选中当前目录
|
||||||
|
if (path == currentPath) {
|
||||||
|
activedDirs.value = [item]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前选中项
|
||||||
const selectedPath = computed(() => {
|
const selectedPath = computed(() => {
|
||||||
if (activedDirs.value.length > 0) {
|
if (activedDirs.value.length > 0) {
|
||||||
return activedDirs.value[0]
|
return activedDirs.value[0].path
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(activedDirs, newVal => {
|
watch(activedDirs, newVal => {
|
||||||
if (!newVal.length || !isUserAction.value) return
|
if (!newVal.length) return
|
||||||
emit('update:modelValue', selectedPath.value)
|
emit('update:modelValue', selectedPath.value)
|
||||||
isUserAction.value = false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => menuVisible.value,
|
||||||
|
async visible => {
|
||||||
|
if (visible) {
|
||||||
|
treeItems.value = [
|
||||||
|
{
|
||||||
|
name: '/',
|
||||||
|
path: props.root,
|
||||||
|
children: [],
|
||||||
|
type: 'dir',
|
||||||
|
basename: props.root,
|
||||||
|
storage: props.storage,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
openedDirs.value = []
|
||||||
|
activedDirs.value = []
|
||||||
|
await expandDirs(props.modelValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.storage,
|
() => props.storage,
|
||||||
async newVal => {
|
async newVal => {
|
||||||
@@ -67,19 +143,15 @@ watch(
|
|||||||
storage: newVal,
|
storage: newVal,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
openedDirs.value = []
|
|
||||||
activedDirs.value = []
|
activedDirs.value = []
|
||||||
|
openedDirs.value = []
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
function handleUserSelect() {
|
|
||||||
isUserAction.value = true
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VMenu :close-on-content-click="false" content-class="cursor-default">
|
<VMenu v-model="menuVisible" :close-on-content-click="false" content-class="cursor-default">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<slot name="activator" :menuprops="props" />
|
<slot name="activator" :menuprops="props" />
|
||||||
</template>
|
</template>
|
||||||
@@ -91,13 +163,11 @@ function handleUserSelect() {
|
|||||||
item-key="path"
|
item-key="path"
|
||||||
item-title="name"
|
item-title="name"
|
||||||
item-value="path"
|
item-value="path"
|
||||||
item-type="unknown"
|
|
||||||
activatable
|
activatable
|
||||||
return-object
|
return-object
|
||||||
max-height="20rem"
|
max-height="20rem"
|
||||||
expand-icon="mdi-folder"
|
expand-icon="mdi-folder"
|
||||||
collapse-icon="mdi-folder-open"
|
collapse-icon="mdi-folder-open"
|
||||||
@update:activated="handleUserSelect"
|
|
||||||
/>
|
/>
|
||||||
</VMenu>
|
</VMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,10 +6,21 @@ import { type PropType } from 'vue'
|
|||||||
const elementProps = defineProps({
|
const elementProps = defineProps({
|
||||||
config: Object as PropType<RenderProps>,
|
config: Object as PropType<RenderProps>,
|
||||||
})
|
})
|
||||||
|
// key
|
||||||
|
const componentKey = ref(0)
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
componentKey.value++
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Component :is="elementProps.config?.component" v-if="!elementProps.config?.html" v-bind="elementProps.config?.props">
|
<Component
|
||||||
|
:key="componentKey"
|
||||||
|
:is="elementProps.config?.component"
|
||||||
|
v-if="!elementProps.config?.html"
|
||||||
|
v-bind="elementProps.config?.props"
|
||||||
|
>
|
||||||
{{ elementProps.config?.text }}
|
{{ elementProps.config?.text }}
|
||||||
<template v-for="(content, name) in elementProps.config?.slots || []" :key="name" v-slot:[name]="{ _props }">
|
<template v-for="(content, name) in elementProps.config?.slots || []" :key="name" v-slot:[name]="{ _props }">
|
||||||
<slot :name="name" v-bind="_props">
|
<slot :name="name" v-bind="_props">
|
||||||
@@ -23,6 +34,7 @@ const elementProps = defineProps({
|
|||||||
/>
|
/>
|
||||||
</Component>
|
</Component>
|
||||||
<Component
|
<Component
|
||||||
|
:key="componentKey"
|
||||||
:is="elementProps.config?.component"
|
:is="elementProps.config?.component"
|
||||||
v-if="elementProps.config?.html"
|
v-if="elementProps.config?.html"
|
||||||
v-bind="elementProps.config?.props"
|
v-bind="elementProps.config?.props"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RenderProps } from '@/api/types'
|
import { RenderProps } from '@/api/types'
|
||||||
import { h, resolveComponent, defineProps } from 'vue'
|
|
||||||
|
|
||||||
// 定义 props
|
// 定义 props
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ onMounted(() => {
|
|||||||
<VIcon icon="mdi-menu" />
|
<VIcon icon="mdi-menu" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<!-- 👉 Back Button -->
|
<!-- 👉 Back Button -->
|
||||||
<IconBtn v-if="appMode && display.mdAndDown.value" class="ms-n2" @click="goBack">
|
<IconBtn v-if="appMode" class="ms-n2" @click="goBack">
|
||||||
<VIcon icon="mdi-arrow-left" size="32" />
|
<VIcon icon="mdi-arrow-left" size="32" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<!-- 👉 Search Bar -->
|
<!-- 👉 Search Bar -->
|
||||||
|
|||||||
@@ -64,4 +64,3 @@ const activeState = computed(() => {
|
|||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ const progressDialog = ref(false)
|
|||||||
// 站点认证对话框
|
// 站点认证对话框
|
||||||
const siteAuthDialog = ref(false)
|
const siteAuthDialog = ref(false)
|
||||||
|
|
||||||
|
// 重启确认对话框
|
||||||
|
const restartDialog = ref(false)
|
||||||
|
|
||||||
// 执行注销操作
|
// 执行注销操作
|
||||||
function logout() {
|
function logout() {
|
||||||
// 清除登录状态信息
|
// 清除登录状态信息
|
||||||
@@ -33,13 +36,8 @@ function logout() {
|
|||||||
|
|
||||||
// 执行重启操作
|
// 执行重启操作
|
||||||
async function restart() {
|
async function restart() {
|
||||||
// 弹出提示
|
{
|
||||||
const confirmed = await createConfirm({
|
restartDialog.value = false
|
||||||
title: '确认',
|
|
||||||
content: '确认重启系统吗?',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (confirmed) {
|
|
||||||
// 调用API重启
|
// 调用API重启
|
||||||
try {
|
try {
|
||||||
// 显示等待框
|
// 显示等待框
|
||||||
@@ -60,6 +58,11 @@ async function restart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示重启确认对话框
|
||||||
|
async function showRestartDialog() {
|
||||||
|
restartDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 显示站点认证对话框
|
// 显示站点认证对话框
|
||||||
function showSiteAuthDialog() {
|
function showSiteAuthDialog() {
|
||||||
siteAuthDialog.value = true
|
siteAuthDialog.value = true
|
||||||
@@ -130,7 +133,7 @@ const userLevel = computed(() => store.state.auth.level)
|
|||||||
<VDivider v-if="superUser" class="my-2" />
|
<VDivider v-if="superUser" class="my-2" />
|
||||||
|
|
||||||
<!-- 👉 restart -->
|
<!-- 👉 restart -->
|
||||||
<VListItem v-if="superUser" @click="restart">
|
<VListItem v-if="superUser" @click="showRestartDialog">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon class="me-2" icon="mdi-restart" size="22" />
|
<VIcon class="me-2" icon="mdi-restart" size="22" />
|
||||||
</template>
|
</template>
|
||||||
@@ -152,4 +155,25 @@ const userLevel = computed(() => store.state.auth.level)
|
|||||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在重启 ..." />
|
<ProgressDialog v-if="progressDialog" v-model="progressDialog" text="正在重启 ..." />
|
||||||
<!-- 用户认证对话框 -->
|
<!-- 用户认证对话框 -->
|
||||||
<UserAuthDialog v-if="siteAuthDialog" v-model="siteAuthDialog" @done="siteAuthDone" @close="siteAuthDialog = false" />
|
<UserAuthDialog v-if="siteAuthDialog" v-model="siteAuthDialog" @done="siteAuthDone" @close="siteAuthDialog = false" />
|
||||||
|
<!-- 重启确认对话框 -->
|
||||||
|
<VDialog v-model="restartDialog" max-width="25rem">
|
||||||
|
<VCard>
|
||||||
|
<VCardItem>
|
||||||
|
<div class="flex items-center justify-center mt-3">
|
||||||
|
<VAvatar color="warning" variant="text" size="x-large">
|
||||||
|
<VIcon size="x-large" icon="mdi-alert" />
|
||||||
|
</VAvatar>
|
||||||
|
<div class="ms-3">
|
||||||
|
<p class="font-bold text-xl text-high-emphasis">确认重启系统吗?</p>
|
||||||
|
<p>重启后,您将被注销并需要重新登录。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCardItem>
|
||||||
|
<VCardActions class="mx-auto">
|
||||||
|
<VBtn variant="elevated" color="error" @click="restart" prepend-icon="mdi-restart" class="px-5"> 确定 </VBtn>
|
||||||
|
<VBtn variant="tonal" color="secondary" class="px-5" @click="restartDialog = false">取消</VBtn>
|
||||||
|
</VCardActions>
|
||||||
|
<DialogCloseBtn @click="restartDialog = false" />
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
32
src/main.ts
32
src/main.ts
@@ -1,23 +1,31 @@
|
|||||||
|
// 1. 配置与兼容性
|
||||||
import './ace-config'
|
import './ace-config'
|
||||||
import '@/@core/utils/compatibility'
|
import '@/@core/utils/compatibility'
|
||||||
import '@/@iconify/icons-bundle'
|
import '@/@iconify/icons-bundle'
|
||||||
import '@/plugins/webfontloader'
|
import '@/plugins/webfontloader'
|
||||||
|
|
||||||
|
// 2. 核心插件和 UI 框架
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { VAceEditor } from 'vue3-ace-editor'
|
|
||||||
import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'
|
|
||||||
import { CronVuetify } from '@vue-js-cron/vuetify'
|
|
||||||
import { removeEl } from './@core/utils/dom'
|
|
||||||
import { fetchGlobalSettings } from './api'
|
|
||||||
import { isPWA } from './@core/utils/navigator'
|
|
||||||
|
|
||||||
import App from '@/App.vue'
|
|
||||||
import vuetify from '@/plugins/vuetify'
|
import vuetify from '@/plugins/vuetify'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
|
|
||||||
|
// 3. 全局组件
|
||||||
|
import App from '@/App.vue'
|
||||||
|
import { VAceEditor } from 'vue3-ace-editor'
|
||||||
|
import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar'
|
||||||
|
import { CronVuetify } from '@vue-js-cron/vuetify'
|
||||||
|
|
||||||
|
// 4. 工具函数和其他辅助模块
|
||||||
|
import { fetchGlobalSettings } from './api'
|
||||||
|
import { isPWA } from './@core/utils/navigator'
|
||||||
|
|
||||||
|
// 5. 其他插件和功能模块
|
||||||
import ToastPlugin from 'vue-toast-notification'
|
import ToastPlugin from 'vue-toast-notification'
|
||||||
import VuetifyUseDialog from 'vuetify-use-dialog'
|
import VuetifyUseDialog from 'vuetify-use-dialog'
|
||||||
import VueApexCharts from 'vue3-apexcharts'
|
import VueApexCharts from 'vue3-apexcharts'
|
||||||
|
|
||||||
|
// 6. 注册自定义组件
|
||||||
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
|
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
|
||||||
import MediaCard from './components/cards/MediaCard.vue'
|
import MediaCard from './components/cards/MediaCard.vue'
|
||||||
import PosterCard from './components/cards/PosterCard.vue'
|
import PosterCard from './components/cards/PosterCard.vue'
|
||||||
@@ -27,13 +35,17 @@ import MediaInfoCard from './components/cards/MediaInfoCard.vue'
|
|||||||
import TorrentCard from './components/cards/TorrentCard.vue'
|
import TorrentCard from './components/cards/TorrentCard.vue'
|
||||||
import MediaIdSelector from './components/misc/MediaIdSelector.vue'
|
import MediaIdSelector from './components/misc/MediaIdSelector.vue'
|
||||||
import CronField from './components/field/CronField.vue'
|
import CronField from './components/field/CronField.vue'
|
||||||
|
import PathField from './components/field/PathField.vue'
|
||||||
|
|
||||||
|
// 7. 样式文件
|
||||||
|
import '@core/scss/template/libs/vuetify/index.scss'
|
||||||
|
import 'vuetify/styles'
|
||||||
import '@core/scss/template/index.scss'
|
import '@core/scss/template/index.scss'
|
||||||
import '@layouts/styles/index.scss'
|
import '@layouts/styles/index.scss'
|
||||||
import '@styles/styles.scss'
|
|
||||||
import 'vue-toast-notification/dist/theme-bootstrap.css'
|
import 'vue-toast-notification/dist/theme-bootstrap.css'
|
||||||
import 'vue3-perfect-scrollbar/style.css'
|
import 'vue3-perfect-scrollbar/style.css'
|
||||||
import '@vue-js-cron/vuetify/dist/vuetify.css'
|
import '@vue-js-cron/vuetify/dist/vuetify.css'
|
||||||
|
import '@styles/styles.scss'
|
||||||
|
|
||||||
// 创建Vue实例
|
// 创建Vue实例
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
@@ -70,6 +82,7 @@ initializeApp().then(() => {
|
|||||||
.component('VTorrentCard', TorrentCard)
|
.component('VTorrentCard', TorrentCard)
|
||||||
.component('VMediaIdSelector', MediaIdSelector)
|
.component('VMediaIdSelector', MediaIdSelector)
|
||||||
.component('VCronField', CronField)
|
.component('VCronField', CronField)
|
||||||
|
.component('VPathField', PathField)
|
||||||
|
|
||||||
// 注册插件
|
// 注册插件
|
||||||
app
|
app
|
||||||
@@ -100,5 +113,4 @@ initializeApp().then(() => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
.$nextTick(() => removeEl('#loading-bg'))
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from '@/api'
|
|
||||||
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
||||||
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
||||||
|
|
||||||
|
|||||||
50
src/pages/discover.vue
Normal file
50
src/pages/discover.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { DiscoverTabs } from '@/router/menu'
|
||||||
|
import router from '@/router'
|
||||||
|
import TheMovieDbView from '@/views/discover/TheMovieDbView.vue'
|
||||||
|
import DoubanView from '@/views/discover/DoubanView.vue'
|
||||||
|
import BangumiView from '@/views/discover/BangumiView.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const activeTab = ref(route.query.tab)
|
||||||
|
|
||||||
|
function jumpTab(tab: string) {
|
||||||
|
router.push('/subscribe/discover?tab=' + tab)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VTabs v-model="activeTab" show-arrows>
|
||||||
|
<VTab v-for="item in DiscoverTabs" :value="item.tab" @to="jumpTab(item.tab)">
|
||||||
|
<div class="min-w-24">
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
</VTab>
|
||||||
|
</VTabs>
|
||||||
|
|
||||||
|
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
||||||
|
<VWindowItem value="themoviedb">
|
||||||
|
<transition name="fade-slide" appear>
|
||||||
|
<div>
|
||||||
|
<TheMovieDbView />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</VWindowItem>
|
||||||
|
<VWindowItem value="douban">
|
||||||
|
<transition name="fade-slide" appear>
|
||||||
|
<div>
|
||||||
|
<DoubanView />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</VWindowItem>
|
||||||
|
<VWindowItem value="bangumi">
|
||||||
|
<transition name="fade-slide" appear>
|
||||||
|
<div>
|
||||||
|
<BangumiView />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</VWindowItem>
|
||||||
|
</VWindow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -35,14 +35,16 @@ onMounted(async () => {
|
|||||||
<div v-if="downloaders.length > 0">
|
<div v-if="downloaders.length > 0">
|
||||||
<VTabs v-model="activeTab">
|
<VTabs v-model="activeTab">
|
||||||
<VTab v-for="item in downloaders" :value="item.name" @to="jumpTab(item.name)">
|
<VTab v-for="item in downloaders" :value="item.name" @to="jumpTab(item.name)">
|
||||||
<span class="mx-5">{{ item.name }}</span>
|
<span class="min-w-24">{{ item.name }}</span>
|
||||||
</VTab>
|
</VTab>
|
||||||
</VTabs>
|
</VTabs>
|
||||||
|
|
||||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
||||||
<VWindowItem v-for="item in downloaders" :value="item.name">
|
<VWindowItem v-for="item in downloaders" :value="item.name">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<DownloadingListView :name="item.name" />
|
<div>
|
||||||
|
<DownloadingListView :name="item.name" />
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
</VWindow>
|
</VWindow>
|
||||||
|
|||||||
@@ -17,16 +17,6 @@ const viewList = reactive<{ apipath: string; linkurl: string; title: string }[]>
|
|||||||
linkurl: '/browse/bangumi/calendar?title=Bangumi每日放送',
|
linkurl: '/browse/bangumi/calendar?title=Bangumi每日放送',
|
||||||
title: 'Bangumi每日放送',
|
title: 'Bangumi每日放送',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
apipath: 'tmdb/movies',
|
|
||||||
linkurl: '/browse/tmdb/movies?title=TMDB热门电影',
|
|
||||||
title: 'TMDB热门电影',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apipath: 'tmdb/tvs?with_original_language=zh|en|ja|ko',
|
|
||||||
linkurl: '/browse/tmdb/tvs??with_original_language=zh|en|ja|ko&title=TMDB热门电视剧',
|
|
||||||
title: 'TMDB热门电视剧',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
apipath: 'douban/movie_hot',
|
apipath: 'douban/movie_hot',
|
||||||
linkurl: '/browse/douban/movie_hot?title=豆瓣热门电影',
|
linkurl: '/browse/douban/movie_hot?title=豆瓣热门电影',
|
||||||
@@ -42,16 +32,6 @@ const viewList = reactive<{ apipath: string; linkurl: string; title: string }[]>
|
|||||||
linkurl: '/browse/douban/tv_animation?title=豆瓣热门动漫',
|
linkurl: '/browse/douban/tv_animation?title=豆瓣热门动漫',
|
||||||
title: '豆瓣热门动漫',
|
title: '豆瓣热门动漫',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
apipath: 'douban/movies',
|
|
||||||
linkurl: '/browse/douban/movies?title=豆瓣最新电影',
|
|
||||||
title: '豆瓣最新电影',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apipath: 'douban/tvs',
|
|
||||||
linkurl: '/browse/douban/tvs?title=豆瓣最新电视剧',
|
|
||||||
title: '豆瓣最新电视剧',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
apipath: 'douban/movie_top250',
|
apipath: 'douban/movie_top250',
|
||||||
linkurl: '/browse/douban/movie_top250?title=电影TOP250',
|
linkurl: '/browse/douban/movie_top250?title=电影TOP250',
|
||||||
@@ -68,7 +48,6 @@ const viewList = reactive<{ apipath: string; linkurl: string; title: string }[]>
|
|||||||
title: '豆瓣全球剧集榜',
|
title: '豆瓣全球剧集榜',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import NoDataFound from '@/components/NoDataFound.vue'
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Context } from '@/api/types'
|
import type { Context } from '@/api/types'
|
||||||
import TorrentCardListView from '@/views/discover/TorrentCardListView.vue'
|
import TorrentCardListView from '@/views/torrent/TorrentCardListView.vue'
|
||||||
import TorrentRowListView from '@/views/discover/TorrentRowListView.vue'
|
import TorrentRowListView from '@/views/torrent/TorrentRowListView.vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
// APP
|
// APP
|
||||||
@@ -139,27 +139,28 @@ onUnmounted(() => {
|
|||||||
<TorrentCardListView v-else :items="dataList" />
|
<TorrentCardListView v-else :items="dataList" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 视图切换 -->
|
<!-- 视图切换 -->
|
||||||
<VFab
|
<div v-if="isRefreshed">
|
||||||
v-if="viewType === 'list'"
|
<VFab
|
||||||
class="mb-12"
|
v-if="viewType === 'list'"
|
||||||
icon="mdi-view-grid"
|
icon="mdi-view-grid"
|
||||||
location="bottom"
|
location="bottom"
|
||||||
size="x-large"
|
size="x-large"
|
||||||
absolute
|
absolute
|
||||||
app
|
app
|
||||||
appear
|
appear
|
||||||
@click="setViewType('card')"
|
@click="setViewType('card')"
|
||||||
:class="{ 'mb-12': appMode }"
|
:class="{ 'mb-12': appMode }"
|
||||||
/>
|
/>
|
||||||
<VFab
|
<VFab
|
||||||
v-else
|
v-else
|
||||||
icon="mdi-view-list"
|
icon="mdi-view-list"
|
||||||
location="bottom"
|
location="bottom"
|
||||||
size="x-large"
|
size="x-large"
|
||||||
fixed
|
fixed
|
||||||
app
|
app
|
||||||
appear
|
appear
|
||||||
@click="setViewType('list')"
|
@click="setViewType('list')"
|
||||||
:class="{ 'mb-12': appMode }"
|
:class="{ 'mb-12': appMode }"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ function jumpTab(tab: string) {
|
|||||||
<!-- 规则 -->
|
<!-- 规则 -->
|
||||||
<VWindowItem value="rule">
|
<VWindowItem value="rule">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<AccountSettingRule />
|
<div>
|
||||||
|
<AccountSettingRule />
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
|
|
||||||
|
|||||||
@@ -18,29 +18,41 @@ function jumpTab(tab: string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VTabs v-model="activeTab">
|
<VTabs v-model="activeTab" show-arrows>
|
||||||
<VTab v-if="subType == '电影'" v-for="item in SubscribeMovieTabs" :value="item.tab" @to="jumpTab(item.tab)">
|
<VTab v-if="subType == '电影'" v-for="item in SubscribeMovieTabs" :value="item.tab" @to="jumpTab(item.tab)">
|
||||||
<span class="mx-5">{{ item.title }}</span>
|
<div class="flex align-center min-w-24">
|
||||||
|
<VIcon size="20" start :icon="item.icon" />
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
</VTab>
|
</VTab>
|
||||||
<VTab v-if="subType == '电视剧'" v-for="item in SubscribeTvTabs" :value="item.tab" @to="jumpTab(item.tab)">
|
<VTab v-if="subType == '电视剧'" v-for="item in SubscribeTvTabs" :value="item.tab" @to="jumpTab(item.tab)">
|
||||||
<span class="mx-5">{{ item.title }}</span>
|
<div class="flex align-center min-w-24">
|
||||||
|
<VIcon size="20" start :icon="item.icon" />
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
</VTab>
|
</VTab>
|
||||||
</VTabs>
|
</VTabs>
|
||||||
|
|
||||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
||||||
<VWindowItem value="mysub">
|
<VWindowItem value="mysub">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<SubscribeListView :type="subType" :subid="subId" />
|
<div>
|
||||||
|
<SubscribeListView :type="subType" :subid="subId" />
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
<VWindowItem value="popular">
|
<VWindowItem value="popular">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<SubscribePopularView :type="subType" />
|
<div>
|
||||||
|
<SubscribePopularView :type="subType" />
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
<VWindowItem value="share">
|
<VWindowItem value="share">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<SubscribeShareView />
|
<div>
|
||||||
|
<SubscribeShareView />
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
</VWindow>
|
</VWindow>
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ import defaults from './defaults'
|
|||||||
import { icons } from './icons'
|
import { icons } from './icons'
|
||||||
import theme from './theme'
|
import theme from './theme'
|
||||||
|
|
||||||
// Styles
|
|
||||||
import '@core/scss/template/libs/vuetify/index.scss'
|
|
||||||
import 'vuetify/styles'
|
|
||||||
|
|
||||||
export default createVuetify({
|
export default createVuetify({
|
||||||
aliases: {
|
aliases: {
|
||||||
IconBtn: VBtn,
|
IconBtn: VBtn,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ configureNProgress()
|
|||||||
// Router
|
// Router
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to: any, from: any, savedPosition: any) {
|
||||||
// 如果页面有缓存那么恢复其位置, 否则始终滚动到顶部
|
// 如果页面有缓存那么恢复其位置, 否则始终滚动到顶部
|
||||||
if (to.meta.keepAlive && savedPosition) return savedPosition
|
if (to.meta.keepAlive && savedPosition) return savedPosition
|
||||||
return { top: 0 }
|
return { top: 0 }
|
||||||
@@ -35,6 +35,14 @@ const router = createRouter({
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/discover',
|
||||||
|
component: () => import('../pages/discover.vue'),
|
||||||
|
meta: {
|
||||||
|
keepAlive: true,
|
||||||
|
requiresAuth: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/resource',
|
path: '/resource',
|
||||||
component: () => import('../pages/resource.vue'),
|
component: () => import('../pages/resource.vue'),
|
||||||
@@ -189,31 +197,36 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const abortControllers = new Set<AbortController>()
|
const abortControllers = new Set<AbortController>()
|
||||||
|
|
||||||
|
// 注册中止控制器
|
||||||
function registerAbortController(controller: AbortController) {
|
function registerAbortController(controller: AbortController) {
|
||||||
abortControllers.add(controller)
|
abortControllers.add(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 中止所有组件的任务
|
||||||
function abortAllControllers() {
|
function abortAllControllers() {
|
||||||
for (const controller of abortControllers) {
|
for (const controller of abortControllers) {
|
||||||
controller.abort()
|
controller.abort()
|
||||||
}
|
}
|
||||||
abortControllers.clear()
|
abortControllers.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由导航守卫
|
// 路由导航守卫
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to: any, from: any, next: any) => {
|
||||||
// 总是记录非login路由
|
// 总是记录非login路由
|
||||||
if (to.fullPath != '/login') store.state.auth.originalPath = to.fullPath
|
if (to.fullPath != '/login') store.state.auth.originalPath = to.fullPath
|
||||||
const isAuthenticated = store.state.auth.token !== null
|
const isAuthenticated = store.state.auth.token !== null
|
||||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||||
next('/login')
|
next('/login')
|
||||||
} else {
|
} else {
|
||||||
abortAllControllers() // 中止所有组件的任务
|
abortAllControllers()
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router // 导出默认对象
|
// 导出默认对象
|
||||||
export { registerAbortController } // 另行导出其他功能
|
export default router
|
||||||
|
// 另行导出其他功能
|
||||||
|
export { registerAbortController }
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ export const SystemNavMenus = [
|
|||||||
admin: false,
|
admin: false,
|
||||||
footer: true,
|
footer: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '搜索结果',
|
||||||
|
icon: 'mdi-magnify',
|
||||||
|
to: '/resource',
|
||||||
|
header: '开始',
|
||||||
|
admin: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '推荐',
|
title: '推荐',
|
||||||
icon: 'mdi-star-outline',
|
icon: 'mdi-star-outline',
|
||||||
@@ -17,11 +24,12 @@ export const SystemNavMenus = [
|
|||||||
footer: true,
|
footer: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '资源搜索',
|
title: '探索',
|
||||||
icon: 'mdi-magnify',
|
icon: 'mdi-apple-safari',
|
||||||
to: '/resource',
|
to: '/discover',
|
||||||
header: '发现',
|
header: '发现',
|
||||||
admin: false,
|
admin: false,
|
||||||
|
footer: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '电影',
|
title: '电影',
|
||||||
@@ -169,12 +177,12 @@ export const SubscribeMovieTabs = [
|
|||||||
{
|
{
|
||||||
title: '我的订阅',
|
title: '我的订阅',
|
||||||
tab: 'mysub',
|
tab: 'mysub',
|
||||||
icon: 'mdi-movie-open-outline',
|
icon: 'mdi-heart',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '热门订阅',
|
title: '热门订阅',
|
||||||
tab: 'popular',
|
tab: 'popular',
|
||||||
icon: 'mdi-movie-open-outline',
|
icon: 'mdi-fire',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -183,17 +191,17 @@ export const SubscribeTvTabs = [
|
|||||||
{
|
{
|
||||||
title: '我的订阅',
|
title: '我的订阅',
|
||||||
tab: 'mysub',
|
tab: 'mysub',
|
||||||
icon: 'mdi-television',
|
icon: 'mdi-heart',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '热门订阅',
|
title: '热门订阅',
|
||||||
tab: 'popular',
|
tab: 'popular',
|
||||||
icon: 'mdi-television',
|
icon: 'mdi-fire',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '订阅分享',
|
title: '订阅分享',
|
||||||
tab: 'share',
|
tab: 'share',
|
||||||
icon: 'mdi-television',
|
icon: 'mdi-share-variant',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -210,3 +218,22 @@ export const PluginTabs = [
|
|||||||
icon: 'mdi-store',
|
icon: 'mdi-store',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 发现标签页
|
||||||
|
export const DiscoverTabs = [
|
||||||
|
{
|
||||||
|
title: 'TheMovieDb',
|
||||||
|
tab: 'themoviedb',
|
||||||
|
icon: 'themoviedb',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '豆瓣',
|
||||||
|
tab: 'douban',
|
||||||
|
icon: 'douban',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Bangumi',
|
||||||
|
tab: 'bangumi',
|
||||||
|
icon: 'bangumi',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createHandlerBoundToURL, cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'
|
import { createHandlerBoundToURL, cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'
|
||||||
import { NavigationRoute, registerRoute } from 'workbox-routing'
|
import { NavigationRoute, registerRoute } from 'workbox-routing'
|
||||||
import { clientsClaim } from 'workbox-core'
|
|
||||||
|
|
||||||
declare let self: ServiceWorkerGlobalScope
|
declare let self: ServiceWorkerGlobalScope
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
html.v-overlay-scroll-blocked {
|
html.v-overlay-scroll-blocked {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
--v-body-scroll-y: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nprogress .bar {
|
#nprogress .bar {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const variableTheme = controlledComputed(
|
|||||||
() => vuetifyTheme.current.value.variables,
|
() => vuetifyTheme.current.value.variables,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const chartKey = ref(0)
|
||||||
|
|
||||||
// 定时器
|
// 定时器
|
||||||
let refreshTimer: NodeJS.Timeout | null = null
|
let refreshTimer: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
@@ -129,6 +131,10 @@ onUnmounted(() => {
|
|||||||
refreshTimer = null
|
refreshTimer = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
chartKey.value += 1
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -142,7 +148,7 @@ onUnmounted(() => {
|
|||||||
<VCardTitle>CPU</VCardTitle>
|
<VCardTitle>CPU</VCardTitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VApexChart type="line" :options="chartOptions" :series="series" :height="150" />
|
<VApexChart :key="chartKey" type="line" :options="chartOptions" :series="series" :height="150" />
|
||||||
<p class="text-center font-weight-medium mb-0">当前:{{ current }}%</p>
|
<p class="text-center font-weight-medium mb-0">当前:{{ current }}%</p>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const variableTheme = controlledComputed(
|
|||||||
() => vuetifyTheme.current.value.variables,
|
() => vuetifyTheme.current.value.variables,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const chartKey = ref(0)
|
||||||
|
|
||||||
// 定时器
|
// 定时器
|
||||||
let refreshTimer: NodeJS.Timeout | null = null
|
let refreshTimer: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
@@ -135,6 +137,10 @@ onUnmounted(() => {
|
|||||||
refreshTimer = null
|
refreshTimer = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
chartKey.value += 1
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -148,7 +154,7 @@ onUnmounted(() => {
|
|||||||
<VCardTitle>内存</VCardTitle>
|
<VCardTitle>内存</VCardTitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VApexChart type="area" :options="chartOptions" :series="series" :height="150" />
|
<VApexChart :key="chartKey" type="area" :options="chartOptions" :series="series" :height="150" />
|
||||||
<p class="text-center font-weight-medium mb-0">当前:{{ formatBytes(usedMemory) }}</p>
|
<p class="text-center font-weight-medium mb-0">当前:{{ formatBytes(usedMemory) }}</p>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
107
src/views/discover/BangumiView.vue
Normal file
107
src/views/discover/BangumiView.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
||||||
|
|
||||||
|
// 过滤参数
|
||||||
|
const filterParams = reactive({
|
||||||
|
'type': 2,
|
||||||
|
'cat': null,
|
||||||
|
'sort': 'rank', // date/rank
|
||||||
|
'year': null,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bangumi cat字典
|
||||||
|
/**
|
||||||
|
* 0 为 其他
|
||||||
|
1 为 TV
|
||||||
|
2 为 OVA
|
||||||
|
3 为 Movie
|
||||||
|
5 为 WEB
|
||||||
|
*/
|
||||||
|
const bangumiCatDict = {
|
||||||
|
'0': '其他',
|
||||||
|
'1': 'TV',
|
||||||
|
'2': 'OVA',
|
||||||
|
'3': 'Movie',
|
||||||
|
'5': 'WEB',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bangumi排序字典
|
||||||
|
const bangumiSortDict = {
|
||||||
|
'rank': '排名',
|
||||||
|
'date': '日期',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 年份字典,自动生成最近10年
|
||||||
|
const yearDict: Record<number, number> = {}
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
yearDict[currentYear - i] = currentYear - i
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前Key
|
||||||
|
const currentKey = ref(0)
|
||||||
|
|
||||||
|
// 类型和过滤参数变化后重新刷新列表
|
||||||
|
watch([filterParams], () => {
|
||||||
|
currentKey.value++
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="px-3">
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>类别</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="filterParams.cat">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.cat == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in bangumiCatDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>排序</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="filterParams.sort">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.sort == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in bangumiSortDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>年份</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="filterParams.year">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.year == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in yearDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MediaCardListView :key="currentKey" apipath="bangumi/subjects" :params="filterParams" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
202
src/views/discover/DoubanView.vue
Normal file
202
src/views/discover/DoubanView.vue
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
||||||
|
|
||||||
|
// 电影或者电视剧 movies/tvs
|
||||||
|
const type = ref('movies')
|
||||||
|
|
||||||
|
// 过滤参数
|
||||||
|
const filterParams = reactive({
|
||||||
|
'sort': 'U',
|
||||||
|
'tags': '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 豆瓣风格类型
|
||||||
|
const doubanCategory = ref('')
|
||||||
|
|
||||||
|
// 地区
|
||||||
|
const doubanZone = ref('')
|
||||||
|
|
||||||
|
// 年代
|
||||||
|
const doubanYear = ref('')
|
||||||
|
|
||||||
|
// 豆瓣风格字典
|
||||||
|
const categoryDict = {
|
||||||
|
'喜剧': '喜剧',
|
||||||
|
'爱情': '爱情',
|
||||||
|
'动作': '动作',
|
||||||
|
'科幻': '科幻',
|
||||||
|
'动画': '动画',
|
||||||
|
'悬疑': '悬疑',
|
||||||
|
'犯罪': '犯罪',
|
||||||
|
'惊悚': '惊悚',
|
||||||
|
'冒险': '冒险',
|
||||||
|
'音乐': '音乐',
|
||||||
|
'历史': '历史',
|
||||||
|
'奇幻': '奇幻',
|
||||||
|
'恐怖': '恐怖',
|
||||||
|
'战争': '战争',
|
||||||
|
'传记': '传记',
|
||||||
|
'歌舞': '歌舞',
|
||||||
|
'武侠': '武侠',
|
||||||
|
'情色': '情色',
|
||||||
|
'灾难': '灾难',
|
||||||
|
'西部': '西部',
|
||||||
|
'纪录片': '纪录片',
|
||||||
|
'短片': '短片',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地区字典
|
||||||
|
const zoneDict = {
|
||||||
|
'华语': '华语',
|
||||||
|
'欧美': '欧美',
|
||||||
|
'韩国': '韩国',
|
||||||
|
'日本': '日本',
|
||||||
|
'中国大陆': '中国大陆',
|
||||||
|
'美国': '美国',
|
||||||
|
'中国香港': '中国香港',
|
||||||
|
'中国台湾': '中国台湾',
|
||||||
|
'英国': '英国',
|
||||||
|
'法国': '法国',
|
||||||
|
'德国': '德国',
|
||||||
|
'意大利': '意大利',
|
||||||
|
'西班牙': '西班牙',
|
||||||
|
'印度': '印度',
|
||||||
|
'泰国': '泰国',
|
||||||
|
'俄罗斯': '俄罗斯',
|
||||||
|
'加拿大': '加拿大',
|
||||||
|
'澳大利亚': '澳大利亚',
|
||||||
|
'爱尔兰': '爱尔兰',
|
||||||
|
'瑞典': '瑞典',
|
||||||
|
'巴西': '巴西',
|
||||||
|
'丹麦': '丹麦',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 年代字典
|
||||||
|
const yearDict: Record<string, string> = {
|
||||||
|
'2020年代': '2020年代',
|
||||||
|
'2010年代': '2010年代',
|
||||||
|
'2000年代': '2000年代',
|
||||||
|
'90年代': '90年代',
|
||||||
|
'80年代': '80年代',
|
||||||
|
'70年代': '70年代',
|
||||||
|
'60年代': '60年代',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 往年代字典中追加当前年份及往前5年的字典
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
yearDict[`${currentYear - i}`] = `${currentYear - i}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 豆瓣过滤参数
|
||||||
|
const doubanSortDict = {
|
||||||
|
'U': '综合排序',
|
||||||
|
'R': '首播时间',
|
||||||
|
'T': '近期热度',
|
||||||
|
'S': '高分优先',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 风格、年代、地区变化时,以,分隔拼接到tags参数
|
||||||
|
watch([doubanCategory, doubanZone, doubanYear], () => {
|
||||||
|
filterParams.tags = [doubanCategory.value, doubanZone.value, doubanYear.value].filter(Boolean).join(',')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前Key
|
||||||
|
const currentKey = ref(0)
|
||||||
|
|
||||||
|
// 类型和过滤参数变化后重新刷新列表
|
||||||
|
watch([type, filterParams], () => {
|
||||||
|
if (!type.value) {
|
||||||
|
type.value = 'movies'
|
||||||
|
}
|
||||||
|
if (!filterParams.sort) {
|
||||||
|
filterParams.sort = 'U'
|
||||||
|
}
|
||||||
|
currentKey.value++
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="px-3">
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>类型</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="type">
|
||||||
|
<VChip :color="type == 'movies' ? 'primary' : ''" filter tile value="movies">电影</VChip>
|
||||||
|
<VChip :color="type == 'tvs' ? 'primary' : ''" filter tile value="tvs">电视剧</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>排序</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="filterParams.sort">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.sort == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in doubanSortDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>风格</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="doubanCategory">
|
||||||
|
<VChip
|
||||||
|
:color="doubanCategory == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in categoryDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>地区</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="doubanZone">
|
||||||
|
<VChip
|
||||||
|
:color="doubanZone == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in zoneDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>年代</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="doubanYear">
|
||||||
|
<VChip
|
||||||
|
:color="doubanYear == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in yearDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MediaCardListView :key="currentKey" :apipath="`douban/${type}`" :params="filterParams" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -27,6 +27,7 @@ const isRefreshed = ref(false)
|
|||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<MediaInfo[]>([])
|
const dataList = ref<MediaInfo[]>([])
|
||||||
const currData = ref<MediaInfo[]>([])
|
const currData = ref<MediaInfo[]>([])
|
||||||
|
|
||||||
// 拼装参数
|
// 拼装参数
|
||||||
function getParams() {
|
function getParams() {
|
||||||
let params = {
|
let params = {
|
||||||
@@ -77,7 +78,6 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
} else {
|
} else {
|
||||||
// 加载一次
|
// 加载一次
|
||||||
// 设置加载中
|
// 设置加载中
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
// 请求API
|
// 请求API
|
||||||
currData.value = await api.get(props.apipath, {
|
currData.value = await api.get(props.apipath, {
|
||||||
@@ -115,7 +115,11 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
<div v-if="dataList.length > 0" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
<div v-if="dataList.length > 0" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
||||||
<MediaCard v-for="data in dataList" :key="data.tmdb_id || data.douban_id" :media="data" />
|
<MediaCard v-for="data in dataList" :key="data.tmdb_id || data.douban_id" :media="data" />
|
||||||
</div>
|
</div>
|
||||||
<NoDataFound v-if="dataList.length === 0 && isRefreshed" error-code="404" error-title="没有数据"
|
<NoDataFound
|
||||||
error-description="无法获取到媒体信息。" />
|
v-if="dataList.length === 0 && isRefreshed"
|
||||||
|
error-code="404"
|
||||||
|
error-title="没有数据"
|
||||||
|
error-description="无法获取到媒体信息。"
|
||||||
|
/>
|
||||||
</VInfiniteScroll>
|
</VInfiniteScroll>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import api from '@/api'
|
|||||||
import type { MediaInfo } from '@/api/types'
|
import type { MediaInfo } from '@/api/types'
|
||||||
import MediaCard from '@/components/cards/MediaCard.vue'
|
import MediaCard from '@/components/cards/MediaCard.vue'
|
||||||
import SlideView from '@/components/slide/SlideView.vue'
|
import SlideView from '@/components/slide/SlideView.vue'
|
||||||
import { registerAbortController } from "@/router";
|
import { registerAbortController } from '@/router'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -11,8 +11,8 @@ const props = defineProps({
|
|||||||
linkurl: String,
|
linkurl: String,
|
||||||
title: String,
|
title: String,
|
||||||
})
|
})
|
||||||
let abortController: AbortController | null = null;
|
|
||||||
|
|
||||||
|
// 提供给子组件的属性
|
||||||
provide('rankingPropsKey', reactive({ ...props }))
|
provide('rankingPropsKey', reactive({ ...props }))
|
||||||
|
|
||||||
// 组件加载完成
|
// 组件加载完成
|
||||||
@@ -24,30 +24,26 @@ const dataList = ref<MediaInfo[]>([])
|
|||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
if (!props.apipath)
|
if (!props.apipath) return
|
||||||
return
|
const abortController = new AbortController()
|
||||||
abortController = new AbortController();
|
registerAbortController(abortController)
|
||||||
registerAbortController(abortController);
|
const { signal } = abortController
|
||||||
const { signal } = abortController;
|
|
||||||
dataList.value = await api.get(props.apipath, { signal })
|
dataList.value = await api.get(props.apipath, { signal })
|
||||||
if (dataList.value.length > 0)
|
if (dataList.value.length > 0) componentLoaded.value = true
|
||||||
componentLoaded.value = true
|
} catch (error) {
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载时获取数据
|
// 加载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchData();
|
fetchData()
|
||||||
});
|
})
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
if (dataList.value.length == 0) {
|
if (dataList.value.length == 0) {
|
||||||
fetchData();
|
fetchData()
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
178
src/views/discover/TheMovieDbView.vue
Normal file
178
src/views/discover/TheMovieDbView.vue
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
||||||
|
|
||||||
|
// 电影或者电视剧 movies/tvs
|
||||||
|
const type = ref('movies')
|
||||||
|
|
||||||
|
// 过滤参数
|
||||||
|
const filterParams = reactive({
|
||||||
|
sort_by: 'popularity.desc',
|
||||||
|
with_genres: '',
|
||||||
|
with_original_language: '',
|
||||||
|
with_keywords: '',
|
||||||
|
with_watch_providers: '',
|
||||||
|
vote_average: 0,
|
||||||
|
vote_count: 10,
|
||||||
|
release_date: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// TMDB 电影排序字典
|
||||||
|
const tmdbSortDict = {
|
||||||
|
'popularity.desc': '热度降序',
|
||||||
|
'popularity.asc': '热度升序',
|
||||||
|
'release_date.desc': '上映日期降序',
|
||||||
|
'release_date.asc': '上映日期升序',
|
||||||
|
'vote_average.desc': '评分降序',
|
||||||
|
'vote_average.asc': '评分升序',
|
||||||
|
}
|
||||||
|
|
||||||
|
// TMDB 电视剧排序字典
|
||||||
|
const tmdbTvSortDict = {
|
||||||
|
'popularity.desc': '热度降序',
|
||||||
|
'popularity.asc': '热度升序',
|
||||||
|
'first_air_date.desc': '首播日期降序',
|
||||||
|
'first_air_date.asc': '首播日期升序',
|
||||||
|
'vote_average.desc': '评分降序',
|
||||||
|
'vote_average.asc': '评分升序',
|
||||||
|
}
|
||||||
|
|
||||||
|
// TMDB风格字典
|
||||||
|
const tmdbGenreDict = {
|
||||||
|
'28': '动作',
|
||||||
|
'12': '冒险',
|
||||||
|
'16': '动画',
|
||||||
|
'35': '喜剧',
|
||||||
|
'80': '犯罪',
|
||||||
|
'99': '纪录片',
|
||||||
|
'18': '剧情',
|
||||||
|
'10751': '家庭',
|
||||||
|
'14': '奇幻',
|
||||||
|
'36': '历史',
|
||||||
|
'27': '恐怖',
|
||||||
|
'10402': '音乐',
|
||||||
|
'9648': '悬疑',
|
||||||
|
'10749': '爱情',
|
||||||
|
'878': '科幻',
|
||||||
|
'10770': '电视电影',
|
||||||
|
'53': '惊悚',
|
||||||
|
'10752': '战争',
|
||||||
|
'37': '西部',
|
||||||
|
}
|
||||||
|
|
||||||
|
// TMDB原始语言字典(主要语言)
|
||||||
|
const tmdbLanguageDict = {
|
||||||
|
'zh': '中文',
|
||||||
|
'en': '英语',
|
||||||
|
'ja': '日语',
|
||||||
|
'ko': '韩语',
|
||||||
|
'fr': '法语',
|
||||||
|
'de': '德语',
|
||||||
|
'es': '西班牙语',
|
||||||
|
'it': '意大利语',
|
||||||
|
'ru': '俄语',
|
||||||
|
'pt': '葡萄牙语',
|
||||||
|
'ar': '阿拉伯语',
|
||||||
|
'hi': '印地语',
|
||||||
|
'th': '泰语',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前Key
|
||||||
|
const currentKey = ref(0)
|
||||||
|
|
||||||
|
// 类型和过滤参数变化后重新刷新列表
|
||||||
|
watch([type, filterParams], () => {
|
||||||
|
if (!type.value) {
|
||||||
|
type.value = 'movies'
|
||||||
|
}
|
||||||
|
if (!filterParams.sort_by) {
|
||||||
|
filterParams.sort_by = 'popularity.desc'
|
||||||
|
}
|
||||||
|
currentKey.value++
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="px-3">
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>类型</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="type">
|
||||||
|
<VChip :color="type == 'movies' ? 'primary' : ''" filter tile value="movies">电影</VChip>
|
||||||
|
<VChip :color="type == 'tvs' ? 'primary' : ''" filter tile value="tvs">电视剧</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>排序</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="filterParams.sort_by">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.sort_by == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in type == 'movies' ? tmdbSortDict : tmdbTvSortDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>风格</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="filterParams.with_genres">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.with_genres == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in tmdbGenreDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>语言</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup v-model="filterParams.with_original_language">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.with_original_language == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in tmdbLanguageDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>评分</VLabel>
|
||||||
|
</div>
|
||||||
|
<VSlider v-model="filterParams.vote_average" thumb-label max="10" min="0" class="align-center" hide-details>
|
||||||
|
<template v-slot:append>
|
||||||
|
<VTextField
|
||||||
|
width="5rem"
|
||||||
|
v-model="filterParams.vote_count"
|
||||||
|
density="compact"
|
||||||
|
type="number"
|
||||||
|
hide-details
|
||||||
|
single-line
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VSlider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<MediaCardListView :key="currentKey" :apipath="`tmdb/${type}`" :params="filterParams" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -397,9 +397,12 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VTabs v-model="activeTab">
|
<VTabs v-model="activeTab" show-arrows>
|
||||||
<VTab v-for="item in PluginTabs" :value="item.tab">
|
<VTab v-for="item in PluginTabs" :value="item.tab">
|
||||||
<span class="mx-5">{{ item.title }}</span>
|
<div class="flex align-center min-w-24">
|
||||||
|
<VIcon size="20" start :icon="item.icon" />
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
</VTab>
|
</VTab>
|
||||||
</VTabs>
|
</VTabs>
|
||||||
|
|
||||||
@@ -507,7 +510,7 @@ onMounted(async () => {
|
|||||||
</VWindow>
|
</VWindow>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div v-if="isRefreshed">
|
||||||
<!-- 插件搜索图标 -->
|
<!-- 插件搜索图标 -->
|
||||||
<VFab
|
<VFab
|
||||||
icon="mdi-magnify"
|
icon="mdi-magnify"
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ const progressText = ref('请稍候 ...')
|
|||||||
// 进度值
|
// 进度值
|
||||||
const progressValue = ref(0)
|
const progressValue = ref(0)
|
||||||
|
|
||||||
|
// 是否已刷新
|
||||||
|
const isRefreshed = ref(false)
|
||||||
|
|
||||||
// 删除确认对话框
|
// 删除确认对话框
|
||||||
const deleteConfirmDialog = ref(false)
|
const deleteConfirmDialog = ref(false)
|
||||||
|
|
||||||
@@ -160,7 +163,8 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 搜索监听
|
// 搜索监听
|
||||||
watch([() => search.value, () => isComposing.value],
|
watch(
|
||||||
|
[() => search.value, () => isComposing.value],
|
||||||
debounce(async () => {
|
debounce(async () => {
|
||||||
if (!isComposing.value) {
|
if (!isComposing.value) {
|
||||||
console.log('search: ' + search.value)
|
console.log('search: ' + search.value)
|
||||||
@@ -181,7 +185,7 @@ async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
|
|||||||
title: search.value,
|
title: search.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
isRefreshed.value = true
|
||||||
dataList.value = result.data?.list
|
dataList.value = result.data?.list
|
||||||
totalItems.value = result.data?.total
|
totalItems.value = result.data?.total
|
||||||
searchHintList.value = ['失败', '成功', ...new Set(dataList.value.map(item => item.title || ''))].filter(
|
searchHintList.value = ['失败', '成功', ...new Set(dataList.value.map(item => item.title || ''))].filter(
|
||||||
@@ -502,7 +506,7 @@ onMounted(fetchData)
|
|||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<!-- 底部操作按钮 -->
|
<!-- 底部操作按钮 -->
|
||||||
<div>
|
<div v-if="isRefreshed">
|
||||||
<VFab
|
<VFab
|
||||||
v-if="selected.length > 0"
|
v-if="selected.length > 0"
|
||||||
icon="mdi-trash-can-outline"
|
icon="mdi-trash-can-outline"
|
||||||
|
|||||||
@@ -52,6 +52,17 @@ async function loadSystemSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重载系统生效配置
|
||||||
|
async function reloadSystem() {
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.get('system/reload')
|
||||||
|
if (result.success) $toast.success('系统配置已生效')
|
||||||
|
else $toast.error('重载系统失败!')
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 移动结束
|
// 移动结束
|
||||||
function orderDirectoryCards() {
|
function orderDirectoryCards() {
|
||||||
// 更新所有目录的优先级
|
// 更新所有目录的优先级
|
||||||
@@ -109,6 +120,7 @@ async function saveDirectories() {
|
|||||||
const result: { [key: string]: any } = await api.post('system/setting/Directories', directories.value)
|
const result: { [key: string]: any } = await api.post('system/setting/Directories', directories.value)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success('目录设置保存成功')
|
$toast.success('目录设置保存成功')
|
||||||
|
await reloadSystem()
|
||||||
} else $toast.error('目录设置保存失败!')
|
} else $toast.error('目录设置保存失败!')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -129,6 +141,7 @@ function addDirectory() {
|
|||||||
monitor_type: '',
|
monitor_type: '',
|
||||||
media_type: '',
|
media_type: '',
|
||||||
media_category: '',
|
media_category: '',
|
||||||
|
transfer_type: ''
|
||||||
})
|
})
|
||||||
orderDirectoryCards()
|
orderDirectoryCards()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ onActivated(() => {
|
|||||||
/>
|
/>
|
||||||
<!-- 新增站点按钮 -->
|
<!-- 新增站点按钮 -->
|
||||||
<VFab
|
<VFab
|
||||||
|
v-if="isRefreshed"
|
||||||
icon="mdi-plus"
|
icon="mdi-plus"
|
||||||
location="bottom"
|
location="bottom"
|
||||||
size="x-large"
|
size="x-large"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import { VPullToRefresh } from 'vuetify/labs/VPullToRefresh'
|
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Subscribe } from '@/api/types'
|
import type { Subscribe } from '@/api/types'
|
||||||
import NoDataFound from '@/components/NoDataFound.vue'
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
@@ -110,12 +109,6 @@ async function fetchData() {
|
|||||||
// 刷新状态
|
// 刷新状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 下拉刷新
|
|
||||||
async function onRefresh({ done }: { done: any }) {
|
|
||||||
await fetchData()
|
|
||||||
done('ok')
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSubscribeOrderConfig()
|
await loadSubscribeOrderConfig()
|
||||||
await fetchData()
|
await fetchData()
|
||||||
@@ -138,29 +131,27 @@ onActivated(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||||
<VPullToRefresh v-model="loading" @load="onRefresh">
|
<draggable
|
||||||
<draggable
|
v-if="displayList.length > 0"
|
||||||
v-if="displayList.length > 0"
|
v-model="displayList"
|
||||||
v-model="displayList"
|
@end="saveSubscribeOrder"
|
||||||
@end="saveSubscribeOrder"
|
handle=".cursor-move"
|
||||||
handle=".cursor-move"
|
item-key="id"
|
||||||
item-key="id"
|
tag="div"
|
||||||
tag="div"
|
:component-data="{ class: 'mx-3 grid gap-4 grid-subscribe-card p-1' }"
|
||||||
:component-data="{ class: 'mx-3 grid gap-4 grid-subscribe-card p-1' }"
|
>
|
||||||
>
|
<template #item="{ element }">
|
||||||
<template #item="{ element }">
|
<SubscribeCard :key="element.id" :media="element" @remove="fetchData" @save="fetchData" />
|
||||||
<SubscribeCard :key="element.id" :media="element" @remove="fetchData" @save="fetchData" />
|
</template>
|
||||||
</template>
|
</draggable>
|
||||||
</draggable>
|
<NoDataFound
|
||||||
<NoDataFound
|
v-if="displayList.length === 0 && isRefreshed"
|
||||||
v-if="displayList.length === 0 && isRefreshed"
|
error-code="404"
|
||||||
error-code="404"
|
error-title="没有订阅"
|
||||||
error-title="没有订阅"
|
error-description="请通过搜索添加电影、电视剧订阅。"
|
||||||
error-description="请通过搜索添加电影、电视剧订阅。"
|
/>
|
||||||
/>
|
|
||||||
</VPullToRefresh>
|
|
||||||
<!-- 底部操作按钮 -->
|
<!-- 底部操作按钮 -->
|
||||||
<div>
|
<div v-if="isRefreshed">
|
||||||
<VFab
|
<VFab
|
||||||
v-if="store.state.auth.superUser"
|
v-if="store.state.auth.superUser"
|
||||||
icon="mdi-clipboard-edit"
|
icon="mdi-clipboard-edit"
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
done('error')
|
done('error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将数据从列表中移除
|
||||||
|
function removeData(id: number) {
|
||||||
|
dataList.value = dataList.value.filter(item => item.id !== id)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -106,7 +111,7 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
<template #empty />
|
<template #empty />
|
||||||
<div v-if="dataList.length > 0" class="grid gap-4 grid-subscribe-card mx-3" tabindex="0">
|
<div v-if="dataList.length > 0" class="grid gap-4 grid-subscribe-card mx-3" tabindex="0">
|
||||||
<div v-for="data in dataList" :key="data.id">
|
<div v-for="data in dataList" :key="data.id">
|
||||||
<SubscribeShareCard :media="data" />
|
<SubscribeShareCard :media="data" @delete="removeData(data.id || 0)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NoDataFound
|
<NoDataFound
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// 日志列表
|
// 已解析的日志列表
|
||||||
const logs = ref<string[]>([])
|
const parsedLogs = ref<{ level: string; time: string; program: string; content: string }[]>([])
|
||||||
|
|
||||||
// 表头
|
// 表头
|
||||||
const headers = [
|
const headers = [
|
||||||
@@ -13,55 +13,56 @@ const headers = [
|
|||||||
// SSE消息对象
|
// SSE消息对象
|
||||||
let eventSource: EventSource | null = null
|
let eventSource: EventSource | null = null
|
||||||
|
|
||||||
|
// 日志颜色映射表
|
||||||
|
const logColorMap: Record<string, string> = {
|
||||||
|
DEBUG: 'secondary',
|
||||||
|
INFO: 'info',
|
||||||
|
WARNING: 'warning',
|
||||||
|
ERROR: 'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取日志颜色
|
||||||
|
function getLogColor(level: string): string {
|
||||||
|
return logColorMap[level] || 'secondary'
|
||||||
|
}
|
||||||
|
|
||||||
// SSE持续获取日志
|
// SSE持续获取日志
|
||||||
function startSSELogging() {
|
function startSSELogging() {
|
||||||
eventSource = new EventSource(`${import.meta.env.VITE_API_BASE_URL}system/logging`)
|
eventSource = new EventSource(`${import.meta.env.VITE_API_BASE_URL}system/logging`)
|
||||||
|
const buffer: string[] = []
|
||||||
|
let timeoutId: number | null = null
|
||||||
|
|
||||||
eventSource.addEventListener('message', event => {
|
eventSource.addEventListener('message', event => {
|
||||||
const message = event.data
|
const message = event.data
|
||||||
if (message) logs.value.push(message)
|
if (message) {
|
||||||
|
buffer.push(message)
|
||||||
|
if (!timeoutId) {
|
||||||
|
timeoutId = window.setTimeout(() => {
|
||||||
|
// 解析新日志
|
||||||
|
const newParsedLogs = buffer
|
||||||
|
.map(log => {
|
||||||
|
const logPattern = /^【(.*?)】[0-9\-:]*\s(.*?)\s-\s(.*?)\s-\s(.*)$/
|
||||||
|
const matches = log.match(logPattern)
|
||||||
|
if (matches) {
|
||||||
|
const [, level, time, program, content] = matches
|
||||||
|
return { level, time, program, content }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
// 倒序后插入parsedLogs顶部
|
||||||
|
parsedLogs.value.unshift(...(newParsedLogs.reverse() as any[]))
|
||||||
|
// 保留最新的200条日志
|
||||||
|
parsedLogs.value = parsedLogs.value.slice(0, 200)
|
||||||
|
// 重置buffer
|
||||||
|
buffer.length = 0
|
||||||
|
timeoutId = null
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从日志中提取日志详情
|
|
||||||
function extractLogDetailsFromLogs(
|
|
||||||
logs: string[],
|
|
||||||
): { level: string; time: string; program: string; content: string }[] {
|
|
||||||
const logDetails: { level: string; time: string; program: string; content: string }[] = []
|
|
||||||
|
|
||||||
const logPattern = /^【(.*?)】[0-9\-:]*\s(.*?)\s-\s(.*?)\s-\s(.*)$/
|
|
||||||
|
|
||||||
for (const log of logs) {
|
|
||||||
const matches = RegExp(logPattern).exec(log)
|
|
||||||
if (matches && matches.length === 5) {
|
|
||||||
const [_, level, time, program, content] = matches
|
|
||||||
logDetails.unshift({ level, time, program, content })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return logDetails
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算日志颜色
|
|
||||||
function getLogColor(level: string): string {
|
|
||||||
switch (level) {
|
|
||||||
case 'DEBUG':
|
|
||||||
return 'primary'
|
|
||||||
case 'INFO':
|
|
||||||
return 'secondary'
|
|
||||||
case 'WARNING':
|
|
||||||
return 'warning'
|
|
||||||
case 'ERROR':
|
|
||||||
return 'error'
|
|
||||||
default:
|
|
||||||
return 'secondary'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 拆分日志数据计算属性
|
|
||||||
const extractLogDetails = computed(() => {
|
|
||||||
return extractLogDetailsFromLogs(logs.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
startSSELogging()
|
startSSELogging()
|
||||||
})
|
})
|
||||||
@@ -72,13 +73,13 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LoadingBanner v-if="logs.length === 0" class="mt-12" text="正在刷新 ..." />
|
<LoadingBanner v-if="parsedLogs.length === 0" class="mt-12" text="正在刷新 ..." />
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<VTable class="table-rounded" hide-default-footer disable-sort>
|
<VTable class="table-rounded" hide-default-footer disable-sort>
|
||||||
<tbody>
|
<tbody>
|
||||||
<VDataTableVirtual
|
<VDataTableVirtual
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:items="extractLogDetails"
|
:items="parsedLogs"
|
||||||
height="100%"
|
height="100%"
|
||||||
density="compact"
|
density="compact"
|
||||||
hover
|
hover
|
||||||
@@ -94,7 +95,7 @@ onBeforeUnmount(() => {
|
|||||||
<h6 class="text-sm font-weight-medium">{{ item.program }}</h6>
|
<h6 class="text-sm font-weight-medium">{{ item.program }}</h6>
|
||||||
</template>
|
</template>
|
||||||
<template #item.content="{ item }">
|
<template #item.content="{ item }">
|
||||||
<span class="text-sm">
|
<span class="text-sm" :class="`text-${getLogColor(item.level)}`">
|
||||||
{{ item.content }}
|
{{ item.content }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -272,6 +272,24 @@ onMounted(() => {
|
|||||||
</VListItem>
|
</VListItem>
|
||||||
</template>
|
</template>
|
||||||
</VHover>
|
</VHover>
|
||||||
|
<VHover>
|
||||||
|
<template #default="hover">
|
||||||
|
<VListItem
|
||||||
|
prepend-icon="mdi-movie-filter"
|
||||||
|
density="compact"
|
||||||
|
link
|
||||||
|
v-bind="hover.props"
|
||||||
|
@click="searchMedia('collection')"
|
||||||
|
>
|
||||||
|
<VListItemTitle class="break-words whitespace-break-spaces">
|
||||||
|
搜索 <span class="font-bold">{{ searchWord }} </span> 相关的【系列合集】 ...
|
||||||
|
</VListItemTitle>
|
||||||
|
<template #append>
|
||||||
|
<VIcon v-if="hover.isHovering" icon="ri-corner-down-left-line" />
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
</template>
|
||||||
|
</VHover>
|
||||||
<VHover>
|
<VHover>
|
||||||
<template #default="hover">
|
<template #default="hover">
|
||||||
<VListItem prepend-icon="mdi-account-search" link v-bind="hover.props" @click="searchMedia('person')">
|
<VListItem prepend-icon="mdi-account-search" link v-bind="hover.props" @click="searchMedia('person')">
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ onActivated(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<VFab
|
<VFab
|
||||||
|
v-if="isRefreshed"
|
||||||
icon="mdi-plus"
|
icon="mdi-plus"
|
||||||
location="bottom"
|
location="bottom"
|
||||||
size="x-large"
|
size="x-large"
|
||||||
@@ -85,7 +86,6 @@ onActivated(() => {
|
|||||||
oper="add"
|
oper="add"
|
||||||
max-width="50rem"
|
max-width="50rem"
|
||||||
persistent
|
persistent
|
||||||
z-index="1010"
|
|
||||||
@save="onUserAdd"
|
@save="onUserAdd"
|
||||||
@close="addUserDialog = false"
|
@close="addUserDialog = false"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ watch(
|
|||||||
</VRow>
|
</VRow>
|
||||||
|
|
||||||
<VDivider class="my-10">
|
<VDivider class="my-10">
|
||||||
<span>消息账号绑定</span>
|
<span>账号绑定</span>
|
||||||
</VDivider>
|
</VDivider>
|
||||||
|
|
||||||
<VRow>
|
<VRow>
|
||||||
@@ -378,6 +378,14 @@ watch(
|
|||||||
label="SynologyChat用户"
|
label="SynologyChat用户"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model="accountInfo.settings.douban_userid"
|
||||||
|
density="comfortable"
|
||||||
|
clearable
|
||||||
|
label="豆瓣用户"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VRow>
|
<VRow>
|
||||||
<!-- 👉 Form Actions -->
|
<!-- 👉 Form Actions -->
|
||||||
@@ -395,7 +403,7 @@ watch(
|
|||||||
</VRow>
|
</VRow>
|
||||||
|
|
||||||
<!-- 双重验证弹窗 -->
|
<!-- 双重验证弹窗 -->
|
||||||
<VDialog v-model="otpDialog" max-width="45rem" persistent z-index="1010">
|
<VDialog v-model="otpDialog" max-width="45rem" persistent scrollable>
|
||||||
<!-- 开启双重验证弹窗内容 -->
|
<!-- 开启双重验证弹窗内容 -->
|
||||||
<VCard>
|
<VCard>
|
||||||
<DialogCloseBtn @click="otpDialog = false" />
|
<DialogCloseBtn @click="otpDialog = false" />
|
||||||
|
|||||||
@@ -34,10 +34,32 @@ export default defineConfig({
|
|||||||
filename: 'service-worker.ts',
|
filename: 'service-worker.ts',
|
||||||
workbox: {
|
workbox: {
|
||||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,jpg,jpeg}'],
|
globPatterns: ['**/*.{js,css,html,ico,png,svg,jpg,jpeg}'],
|
||||||
|
runtimeCaching: [
|
||||||
|
{
|
||||||
|
urlPattern: /\.(?:js|css|html)$/,
|
||||||
|
handler: 'StaleWhileRevalidate',
|
||||||
|
options: {
|
||||||
|
cacheName: 'static-resources',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /\.(?:png|jpg|jpeg|svg|ico)$/,
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'image-cache',
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 50,
|
||||||
|
maxAgeSeconds: 30 * 24 * 60 * 60, // 缓存 30 天
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
navigateFallback: '/index.html', // 确保页面路由正确加载
|
||||||
navigateFallbackDenylist: [/.*\/api\/v\d+\/system\/logging.*/],
|
navigateFallbackDenylist: [/.*\/api\/v\d+\/system\/logging.*/],
|
||||||
},
|
},
|
||||||
injectManifest: {
|
injectManifest: {
|
||||||
rollupFormat: 'iife',
|
rollupFormat: 'iife',
|
||||||
|
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||||
},
|
},
|
||||||
devOptions: {
|
devOptions: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -134,16 +156,14 @@ export default defineConfig({
|
|||||||
'@images': fileURLToPath(new URL('./src/assets/images/', import.meta.url)),
|
'@images': fileURLToPath(new URL('./src/assets/images/', import.meta.url)),
|
||||||
'@styles': fileURLToPath(new URL('./src/styles/', import.meta.url)),
|
'@styles': fileURLToPath(new URL('./src/styles/', import.meta.url)),
|
||||||
'@configured-variables': fileURLToPath(new URL('./src/styles/variables/_template.scss', import.meta.url)),
|
'@configured-variables': fileURLToPath(new URL('./src/styles/variables/_template.scss', import.meta.url)),
|
||||||
'apexcharts': fileURLToPath(new URL('node_modules/apexcharts-clevision', import.meta.url)),
|
'apexcharts': fileURLToPath(new URL('node_modules/apexcharts', import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
minify: 'terser',
|
minify: 'terser',
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
compress: {
|
compress: {
|
||||||
// 控制console.log()是否被移除,生产环境建议移除,存在内存泄漏风险
|
|
||||||
drop_console: true,
|
drop_console: true,
|
||||||
// 控制debugger是否被移除,酌情处理
|
|
||||||
drop_debugger: false,
|
drop_debugger: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -170,4 +190,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
quietDeps: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user