mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-26 10:49:47 +08:00
Merge pull request #416 from PKC278/v2
This commit is contained in:
103
index.html
103
index.html
@@ -250,108 +250,7 @@
|
||||
<div id="loading-bg">
|
||||
<div class="loading-logo">
|
||||
<!-- Logo -->
|
||||
<svg width="160px" height="160px" viewBox="0 0 192 192" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2">
|
||||
<g transform="matrix(1,0,0,1,-2606,-236)">
|
||||
<g id="a2-c" transform="matrix(1,0,0,1,2606,236)">
|
||||
<rect x="0" y="0" width="192" height="192" style="fill: none" />
|
||||
<g transform="matrix(-0.800798,0.462341,-0.769972,-1.33363,1869.11,-896.718)">
|
||||
<path
|
||||
d="M2241.27,-28.175C2238.86,-28.931 2236.64,-29.181 2234.48,-29.254L2159.78,-29.286L2165.01,-11.207C2167.16,-13.121 2169.64,-13.722 2172.26,-13.808L2222.12,-13.822C2223.52,-13.824 2225,-13.701 2226.78,-13.108L2241.27,-28.175Z"
|
||||
style="fill: url(#_Linear1)" />
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
|
||||
<path
|
||||
d="M2205.67,331.428L2205.67,332.25L2205.67,352.835C2205.67,354.263 2204.91,355.583 2203.67,356.298C2202.43,357.012 2200.91,357.013 2199.67,356.3L2190.78,351.174C2189.73,350.595 2188.83,350.083 2188.03,349.59L2187.45,349.257C2186.66,348.725 2185.91,348.142 2185.21,347.461C2185.08,347.331 2184.95,347.198 2184.82,347.061C2184.26,346.457 2183.75,345.778 2183.3,344.995C2182.16,343.05 2181.69,341.024 2181.68,338.948L2181.67,268.923L2209.77,274.425C2207.5,275.639 2205.68,278.3 2205.67,281.429L2205.67,331.428Z"
|
||||
style="fill: url(#_Linear2)" />
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
|
||||
<path
|
||||
d="M2295.93,363.064C2299.48,360.882 2301.46,357.55 2301.67,352.926L2301.67,277.879L2301.67,276.775L2301.67,252.515C2301.67,251.801 2302.05,251.14 2302.67,250.783C2303.29,250.426 2304.05,250.426 2304.67,250.784L2319.81,259.54C2321.59,260.617 2322.95,262.115 2324.04,263.875C2325.03,265.551 2325.56,267.37 2325.67,269.835L2325.67,339.91C2325.18,343.645 2323.51,346.705 2320.3,348.887L2295.93,363.064ZM2295.93,363.064C2295.73,363.184 2295.53,363.301 2295.32,363.414L2295.93,363.064Z"
|
||||
style="fill: rgb(141, 81, 249)" />
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
|
||||
<path
|
||||
d="M2295.93,363.064C2299.48,360.882 2301.46,357.55 2301.67,352.926L2301.67,277.879L2301.67,276.775L2301.67,252.515C2301.67,251.801 2302.05,251.14 2302.67,250.783C2303.29,250.426 2304.05,250.426 2304.67,250.784L2319.81,259.54C2321.59,260.617 2322.95,262.115 2324.04,263.875C2325.03,265.551 2325.56,267.37 2325.67,269.835L2325.67,339.91C2325.18,343.645 2323.51,346.705 2320.3,348.887L2295.93,363.064ZM2299.79,360.238C2299.79,360.238 2320.03,348.464 2320.04,348.461C2323.1,346.372 2324.69,343.444 2325.17,339.877C2325.17,339.877 2325.17,269.846 2325.17,269.839C2325.06,267.482 2324.56,265.739 2323.61,264.133C2322.56,262.445 2321.26,261.005 2319.55,259.97L2304.42,251.217C2303.96,250.949 2303.39,250.948 2302.92,251.216C2302.46,251.484 2302.17,251.979 2302.17,252.515L2302.17,276.775L2302.17,277.879L2302.17,352.926C2302.17,352.933 2302.17,352.941 2302.17,352.948C2302.04,355.861 2301.23,358.279 2299.79,360.238Z"
|
||||
style="fill: url(#_Linear3)" />
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
|
||||
<path
|
||||
d="M2253.67,223.256C2255.26,223.245 2257.02,223.56 2259.11,224.557L2275.67,234.102C2276.91,234.816 2277.67,236.138 2277.67,237.568L2277.67,259.508C2277.67,260.222 2277.29,260.882 2276.67,261.239C2276.05,261.597 2275.29,261.597 2274.67,261.24L2257.52,251.353C2256.38,250.731 2255.12,250.341 2253.67,250.347C2252.26,250.339 2250.99,250.721 2249.82,251.353L2187.87,287.04C2184.23,289.147 2181.96,292.478 2181.67,297.57L2181.68,269.865C2181.85,265.167 2183.93,261.653 2187.92,259.322L2248.23,224.557C2249.69,223.796 2251.5,223.29 2253.67,223.256Z"
|
||||
style="fill: rgb(165, 118, 255)" />
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-2157.67,-208.423)">
|
||||
<path
|
||||
d="M2253.67,223.256C2255.26,223.245 2257.02,223.56 2259.11,224.557L2275.67,234.102C2276.91,234.816 2277.67,236.138 2277.67,237.568L2277.67,259.508C2277.67,260.222 2277.29,260.882 2276.67,261.239C2276.05,261.597 2275.29,261.597 2274.67,261.24L2257.52,251.353C2256.38,250.731 2255.12,250.341 2253.67,250.347C2252.26,250.339 2250.99,250.721 2249.82,251.353L2187.87,287.04C2184.23,289.147 2181.96,292.478 2181.67,297.57L2181.68,269.865C2181.85,265.167 2183.93,261.653 2187.92,259.322L2248.23,224.557C2249.69,223.796 2251.5,223.29 2253.67,223.256ZM2253.68,223.756C2251.6,223.789 2249.87,224.269 2248.47,224.996L2188.17,259.754C2184.35,261.992 2182.35,265.367 2182.18,269.874C2182.18,269.874 2182.17,292.759 2182.17,292.757C2183.25,290.047 2185.13,288.051 2187.62,286.607L2249.57,250.919C2249.58,250.917 2249.58,250.915 2249.59,250.913C2250.83,250.243 2252.17,249.839 2253.67,249.847C2255.21,249.841 2256.54,250.253 2257.76,250.914C2257.76,250.916 2257.76,250.917 2257.76,250.919L2274.92,260.807C2275.38,261.075 2275.95,261.074 2276.42,260.806C2276.88,260.538 2277.17,260.043 2277.17,259.508L2277.17,237.568C2277.17,236.317 2276.5,235.16 2275.42,234.535C2275.42,234.535 2258.88,225 2258.87,224.996C2256.87,224.049 2255.2,223.746 2253.68,223.756Z"
|
||||
style="fill: url(#_Linear4)" />
|
||||
</g>
|
||||
<g transform="matrix(0.800798,0.462341,0.769972,-1.33363,-1677.22,-896.858)">
|
||||
<path
|
||||
d="M2241.55,-28.184C2239.1,-28.989 2236.83,-29.204 2234.68,-29.295C2234.68,-29.295 2220.82,-29.3 2215.03,-29.303C2213.48,-29.303 2212.05,-28.808 2211.28,-28.004C2208.65,-25.275 2202.56,-18.936 2199.45,-15.709C2199.07,-15.306 2199.07,-14.809 2199.46,-14.406C2199.85,-14.004 2200.57,-13.758 2201.34,-13.761C2208.36,-13.788 2222.72,-13.845 2222.72,-13.845C2223.98,-13.851 2225.44,-13.657 2227.06,-13.117L2241.55,-28.184Z"
|
||||
style="fill: rgb(141, 81, 249)" />
|
||||
</g>
|
||||
<g transform="matrix(-4.32309,0,0,12.4454,9610.35,-1450.35)">
|
||||
<path
|
||||
d="M2205.31,121.966C2205.31,121.88 2205.18,121.8 2204.96,121.757C2204.74,121.714 2204.48,121.714 2204.27,121.757C2201.75,122.263 2195.36,123.547 2192.85,124.052C2192.63,124.095 2192.5,124.174 2192.5,124.261C2192.5,124.347 2192.63,124.426 2192.85,124.469C2195.36,124.974 2201.75,126.255 2204.27,126.759C2204.48,126.802 2204.74,126.802 2204.96,126.759C2205.18,126.716 2205.31,126.636 2205.31,126.55C2205.31,125.541 2205.31,122.976 2205.31,121.966Z"
|
||||
style="fill: rgb(104, 0, 197)" />
|
||||
<clipPath id="_clip5">
|
||||
<path
|
||||
d="M2205.31,121.966C2205.31,121.88 2205.18,121.8 2204.96,121.757C2204.74,121.714 2204.48,121.714 2204.27,121.757C2201.75,122.263 2195.36,123.547 2192.85,124.052C2192.63,124.095 2192.5,124.174 2192.5,124.261C2192.5,124.347 2192.63,124.426 2192.85,124.469C2195.36,124.974 2201.75,126.255 2204.27,126.759C2204.48,126.802 2204.74,126.802 2204.96,126.759C2205.18,126.716 2205.31,126.636 2205.31,126.55C2205.31,125.541 2205.31,122.976 2205.31,121.966Z" />
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip5)">
|
||||
<g transform="matrix(0.124502,0.074907,0.206623,-0.0414384,1997.62,-7.40235)">
|
||||
<path
|
||||
d="M1726.17,-64.249L1708.16,-72.303L1708.05,-23.514L1721.88,-32.386C1722.96,-33.241 1723.09,-33.944 1723.15,-34.636L1723.15,-54.373C1723.19,-56.238 1724.96,-57.594 1726.87,-56.686L1726.17,-64.249Z"
|
||||
style="fill: url(#_Linear6)" />
|
||||
</g>
|
||||
<g transform="matrix(-0.126036,0.0767377,0.569859,0.112933,2435.01,-3.09225)">
|
||||
<path
|
||||
d="M1726.17,-45.661L1704.47,-40.254C1706.28,-40.527 1708.14,-40.212 1708.16,-39.416L1708.16,-18.976L1726.17,-18.976L1726.17,-45.661Z"
|
||||
style="fill: rgb(141, 81, 249)" />
|
||||
</g>
|
||||
<g transform="matrix(-0.126036,0.0767377,0.569859,0.112933,2435.01,-3.09225)">
|
||||
<path
|
||||
d="M1726.17,-45.661L1726.17,-18.976L1708.16,-18.976L1708.16,-39.416C1707.79,-40.732 1704.5,-40.298 1702.68,-40.025L1726.17,-45.661ZM1705.49,-40.491C1706.2,-40.507 1706.87,-40.464 1707.4,-40.327C1708.01,-40.173 1708.48,-39.899 1708.62,-39.436C1708.62,-39.429 1708.62,-39.423 1708.62,-39.416L1708.62,-19.152C1708.62,-19.152 1725.72,-19.152 1725.72,-19.152L1725.72,-45.345L1705.49,-40.491Z"
|
||||
style="fill: url(#_Radial7)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-70.0711,-0.927611,1.54482,-42.0752,2233.59,-20.1891)">
|
||||
<stop offset="0" style="stop-color: rgb(141, 81, 249); stop-opacity: 1" />
|
||||
<stop offset="1" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(4.78193e-15,-78.0949,78.0949,4.78193e-15,2195.72,354.021)">
|
||||
<stop offset="0" style="stop-color: rgb(141, 81, 249); stop-opacity: 1" />
|
||||
<stop offset="1" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(41.6089,41.5866,-41.5866,41.6089,2282.31,262.837)">
|
||||
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
|
||||
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(9.25616,16.7005,-16.7005,9.25616,2215,243.712)">
|
||||
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
|
||||
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="_Linear6" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-0.130164,-61.9937,59.4003,-0.135847,1711.63,-25.7957)">
|
||||
<stop offset="0" style="stop-color: rgb(116, 50, 223); stop-opacity: 1" />
|
||||
<stop offset="0.51" style="stop-color: rgb(110, 38, 217); stop-opacity: 1" />
|
||||
<stop offset="1" style="stop-color: rgb(91, 0, 197); stop-opacity: 1" />
|
||||
</linearGradient>
|
||||
<radialGradient id="_Radial7" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(13.8659,4.71436,-12.1609,5.37534,1708.16,-32.287)">
|
||||
<stop offset="0" style="stop-color: rgb(211, 187, 255); stop-opacity: 1" />
|
||||
<stop offset="1" style="stop-color: rgb(211, 187, 255); stop-opacity: 0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<img src="/logo.svg" alt="MoviePilot" width="160px" height="160px" />
|
||||
</div>
|
||||
<div class="loading">
|
||||
<div class="effect-1 effects"></div>
|
||||
|
||||
1
shims.d.ts
vendored
1
shims.d.ts
vendored
@@ -12,3 +12,4 @@ declare module 'vue-prism-component' {
|
||||
export default component
|
||||
}
|
||||
declare module 'vue-shepherd';
|
||||
declare module 'colorthief';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@use "sass:map";
|
||||
@use "vuetify/lib/styles/settings" as vuetify_settings;
|
||||
@use "vuetify/lib/styles/settings/_index.sass" as vuetify_settings;
|
||||
@use "@styles/variables/_vuetify.scss" as vuetify;
|
||||
|
||||
@mixin themed($property, $light-value, $dark-value) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ValidationRule } from 'vuetify/types/services/validation'
|
||||
type ValidationRule = (value: any) => string | boolean
|
||||
|
||||
// 必输校验
|
||||
export const requiredValidator: ValidationRule = (value: any) => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import FileToolbar from './filebrowser/FileToolbar.vue'
|
||||
import FileNavigator from './filebrowser/FileNavigator.vue'
|
||||
import type { EndPoints, FileItem, StorageConf } from '@/api/types'
|
||||
import { storageIconDict } from '@/api/constants'
|
||||
import type { AxiosInstance } from 'axios'
|
||||
|
||||
// LocalStorage keys
|
||||
const SORT_KEY = 'fileBrowser.sort'
|
||||
@@ -16,7 +17,7 @@ const props = defineProps({
|
||||
tree: Boolean,
|
||||
endpoints: Object as PropType<EndPoints>,
|
||||
axios: {
|
||||
type: Function,
|
||||
type: Object as PropType<AxiosInstance>,
|
||||
required: true,
|
||||
},
|
||||
axiosconfig: Object,
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { formatDateDifference } from '@/@core/utils/formatters'
|
||||
import api from '@/api'
|
||||
import { clearCachesAndServiceWorker } from '@/composables/useVersionChecker'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
declare const __APP_VERSION__: string
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
|
||||
// APP版本
|
||||
const appVersion = __APP_VERSION__
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
@@ -115,6 +121,13 @@ function releaseTime(releaseDate: string) {
|
||||
return formatDateDifference(releaseDate)
|
||||
}
|
||||
|
||||
// 强制清除缓存
|
||||
async function cleanCache() {
|
||||
await clearCachesAndServiceWorker()
|
||||
// 刷新页面
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
querySystemEnv()
|
||||
queryAllRelease()
|
||||
@@ -170,6 +183,27 @@ onMounted(() => {
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.browserVersion') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow flex flex-row items-center truncate">
|
||||
<code class="truncate">{{ appVersion }}</code>
|
||||
<VBtn
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
class="ms-2"
|
||||
@click="cleanCache"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-refresh" size="14" />
|
||||
</template>
|
||||
{{ t('setting.about.cleanCache') }}
|
||||
</VBtn>
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.authVersion') }}</dt>
|
||||
@@ -194,7 +228,7 @@ onMounted(() => {
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.configDir') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow undefined">
|
||||
<span class="flex-grow break-all">
|
||||
<code>{{ systemEnv.CONFIG_DIR }}</code>
|
||||
</span>
|
||||
</dd>
|
||||
@@ -202,7 +236,7 @@ onMounted(() => {
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.dataDir') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow undefined"
|
||||
<span class="flex-grow break-all"
|
||||
><code>{{ t('setting.about.dataDirectory') }}</code></span
|
||||
>
|
||||
</dd>
|
||||
@@ -212,7 +246,7 @@ onMounted(() => {
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.timezone') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow undefined">
|
||||
<span class="flex-grow break-all">
|
||||
<code>{{ systemEnv.TZ }}</code>
|
||||
</span>
|
||||
</dd>
|
||||
@@ -261,7 +295,7 @@ onMounted(() => {
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.documentation') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow undefined">
|
||||
<span class="flex-grow break-all">
|
||||
<a
|
||||
href="https://movie-pilot.org"
|
||||
target="_blank"
|
||||
@@ -278,7 +312,7 @@ onMounted(() => {
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.feedback') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow undefined">
|
||||
<span class="flex-grow break-all">
|
||||
<a
|
||||
href="https://github.com/jxxghp/MoviePilot/issues/new/choose"
|
||||
target="_blank"
|
||||
@@ -295,7 +329,7 @@ onMounted(() => {
|
||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="block text-sm font-bold">{{ t('setting.about.channel') }}</dt>
|
||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||
<span class="flex-grow undefined">
|
||||
<span class="flex-grow break-all">
|
||||
<a
|
||||
href="https://t.me/moviepilot_channel"
|
||||
target="_blank"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
import type { AxiosRequestConfig, AxiosInstance } from 'axios'
|
||||
import type { PropType } from 'vue'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useToast } from 'vue-toastification'
|
||||
@@ -28,7 +28,7 @@ const inProps = defineProps({
|
||||
icons: Object,
|
||||
endpoints: Object as PropType<EndPoints>,
|
||||
axios: {
|
||||
type: Function,
|
||||
type: Object as PropType<AxiosInstance>,
|
||||
required: true,
|
||||
},
|
||||
refreshpending: Boolean,
|
||||
@@ -196,7 +196,7 @@ async function list_files() {
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const data = (await inProps.axios.request(config)) ?? []
|
||||
const data = ((await inProps.axios.request<FileItem[], FileItem[]>(config))) ?? []
|
||||
// 如果当前路径已经变化,则放弃此次加载结果
|
||||
if (prevURI !== takeURISnapshot()) {
|
||||
return;
|
||||
@@ -300,7 +300,7 @@ async function download(item: FileItem) {
|
||||
responseType: 'blob',
|
||||
}
|
||||
// 加载数据
|
||||
const result: Blob = await inProps.axios.request(config)
|
||||
const result: Blob = (await inProps.axios.request<Blob, Blob>(config))
|
||||
if (result) {
|
||||
const downloadUrl = URL.createObjectURL(result)
|
||||
window.open(downloadUrl, '_blank')
|
||||
@@ -318,7 +318,7 @@ async function getImgLink(item: FileItem) {
|
||||
responseType: 'blob',
|
||||
}
|
||||
// 加载二进制数据
|
||||
const result: Blob = await inProps.axios.request(config)
|
||||
const result: Blob = (await inProps.axios.request<Blob, Blob>(config))
|
||||
if (result) {
|
||||
// 创建图片地址
|
||||
currentImgLink.value = URL.createObjectURL(result)
|
||||
@@ -395,7 +395,7 @@ async function rename() {
|
||||
method: inProps.endpoints?.rename.method || 'post',
|
||||
data: currentItem.value,
|
||||
}
|
||||
const result: { [key: string]: any } = await inProps.axios?.request(config)
|
||||
const result: { [key: string]: any } = (await inProps.axios?.request<any, { [key: string]: any }>(config))
|
||||
if (!result.success) {
|
||||
$toast.error(result.message)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { PropType } from 'vue'
|
||||
import type { FileItem } from '@/api/types'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
import type { AxiosRequestConfig, AxiosInstance } from 'axios'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
|
||||
@@ -54,7 +54,7 @@ const props = defineProps({
|
||||
},
|
||||
endpoints: Object,
|
||||
axios: {
|
||||
type: Function,
|
||||
type: Object as PropType<AxiosInstance>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
@@ -131,7 +131,7 @@ async function loadSubdirectories(path: string) {
|
||||
data: fakeItem,
|
||||
}
|
||||
|
||||
const result = await props.axios?.request(config)
|
||||
const result = (await props.axios?.request(config))
|
||||
if (result && Array.isArray(result)) {
|
||||
// 过滤出目录项
|
||||
const dirs = result.filter(item => item.type === 'dir')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
import type { AxiosRequestConfig, AxiosInstance } from 'axios'
|
||||
import type { EndPoints, FileItem } from '@/api/types'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -23,7 +23,7 @@ const inProps = defineProps({
|
||||
},
|
||||
endpoints: Object as PropType<EndPoints>,
|
||||
axios: {
|
||||
type: Function,
|
||||
type: Object as PropType<AxiosInstance>,
|
||||
required: true,
|
||||
},
|
||||
sort: {
|
||||
|
||||
58
src/components/toast/VersionUpdateToast.vue
Normal file
58
src/components/toast/VersionUpdateToast.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="version-update-toast">
|
||||
<span class="message">{{ message }}</span>
|
||||
<button class="refresh-button" @click="handleRefresh">
|
||||
{{ refreshText }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 接收 props
|
||||
interface Props {
|
||||
message: string
|
||||
refreshText: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const handleRefresh = () => {
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.version-update-toast {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.message {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
padding: 6px 16px;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.refresh-button:hover {
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.refresh-button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
</style>
|
||||
@@ -12,6 +12,16 @@ const globalPwaStatus = ref<{
|
||||
const globalLoading = ref(false)
|
||||
let initPromise: Promise<void> | null = null
|
||||
|
||||
// UI模式设置
|
||||
export type UIMode = 'auto' | 'desktop' | 'app'
|
||||
const uiMode = ref<UIMode>((localStorage.getItem('ui-mode') as UIMode) || 'auto')
|
||||
|
||||
// 设置UI模式
|
||||
function setUIMode(mode: UIMode) {
|
||||
uiMode.value = mode
|
||||
localStorage.setItem('ui-mode', mode)
|
||||
}
|
||||
|
||||
// 全局初始化函数
|
||||
async function initializePWAGlobally() {
|
||||
if (initPromise) return initPromise
|
||||
@@ -50,6 +60,8 @@ export function usePWA() {
|
||||
})
|
||||
|
||||
const appMode = computed(() => {
|
||||
if (uiMode.value === 'app') return true
|
||||
if (uiMode.value === 'desktop') return false
|
||||
return pwaMode.value && display.mdAndDown.value
|
||||
})
|
||||
|
||||
@@ -70,6 +82,8 @@ export function usePWA() {
|
||||
pwaMode,
|
||||
appMode,
|
||||
pwaStatus,
|
||||
uiMode,
|
||||
setUIMode,
|
||||
loading: globalLoading,
|
||||
initializePWA: initializePWAGlobally,
|
||||
}
|
||||
|
||||
@@ -236,16 +236,15 @@ export function usePullDownGesture(options: PullDownOptions = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// PWA状态确定后,一次性决定是否添加事件监听器
|
||||
// 监听 appMode 变化动态添加/移除事件监听器
|
||||
onMounted(() => {
|
||||
// 等待PWA检测完成后添加事件监听器
|
||||
const stopWatcher = watch(
|
||||
watch(
|
||||
appMode,
|
||||
newValue => {
|
||||
if (newValue) {
|
||||
addEventListeners()
|
||||
// PWA状态确定后停止监听
|
||||
stopWatcher()
|
||||
} else {
|
||||
removeEventListeners()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
|
||||
113
src/composables/useVersionChecker.ts
Normal file
113
src/composables/useVersionChecker.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { ref, computed, h } from 'vue'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import i18n from '@/plugins/i18n'
|
||||
import VersionUpdateToast from '@/components/toast/VersionUpdateToast.vue'
|
||||
|
||||
// 声明全局变量类型
|
||||
declare const __APP_VERSION__: string
|
||||
|
||||
// 全局状态
|
||||
const currentVersion = ref(__APP_VERSION__)
|
||||
const serverVersion = ref<string | null>(null)
|
||||
const versionChecked = ref(false)
|
||||
const needsUpdate = computed(() => {
|
||||
return serverVersion.value !== null && serverVersion.value !== currentVersion.value
|
||||
})
|
||||
|
||||
/**
|
||||
* 清除所有缓存和 Service Worker
|
||||
*/
|
||||
export const clearCachesAndServiceWorker = async (): Promise<void> => {
|
||||
try {
|
||||
// 1. 清除所有缓存
|
||||
if ('caches' in window) {
|
||||
const cacheNames = await caches.keys()
|
||||
await Promise.all(cacheNames.map(name => caches.delete(name)))
|
||||
console.log('[VersionChecker] 已清除所有缓存')
|
||||
}
|
||||
|
||||
// 2. 注销 Service Worker
|
||||
if ('serviceWorker' in navigator) {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations()
|
||||
await Promise.all(registrations.map(registration => registration.unregister()))
|
||||
console.log('[VersionChecker] 已注销所有 Service Worker')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[VersionChecker] 清除缓存失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本检查 Composable
|
||||
*
|
||||
* 功能:
|
||||
* - 检查前端版本与服务端版本是否一致
|
||||
* - 检测到版本更新时清除缓存和 Service Worker
|
||||
* - 显示持久化更新通知
|
||||
*/
|
||||
export function useVersionChecker() {
|
||||
const toast = useToast()
|
||||
|
||||
/**
|
||||
* 显示版本更新通知(带刷新按钮)
|
||||
*/
|
||||
const showUpdateNotification = (): void => {
|
||||
// 使用自定义 Vue 组件作为 toast 内容,传递翻译后的文本作为 props
|
||||
const component = h(VersionUpdateToast, {
|
||||
message: i18n.global.t('common.newVersionAvailable'),
|
||||
refreshText: i18n.global.t('common.refresh'),
|
||||
})
|
||||
|
||||
toast.info(component, {
|
||||
timeout: false, // 不自动消失
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查版本并在需要时显示更新通知
|
||||
* @param latestVersion 服务端返回的最新版本号
|
||||
*/
|
||||
const checkVersion = async (latestVersion: string): Promise<void> => {
|
||||
// 如果已经检查过,则跳过
|
||||
if (versionChecked.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新服务端版本
|
||||
serverVersion.value = latestVersion
|
||||
|
||||
// 版本不同,且尚未显示通知
|
||||
if (needsUpdate.value) {
|
||||
versionChecked.value = true
|
||||
console.log(`[VersionChecker] 检测到版本更新: ${currentVersion.value} -> ${latestVersion}`)
|
||||
|
||||
// 清除缓存和 Service Worker
|
||||
await clearCachesAndServiceWorker()
|
||||
|
||||
// 显示持久化通知
|
||||
showUpdateNotification()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置版本检查状态(用于测试或特殊场景)
|
||||
*/
|
||||
const resetVersionCheck = (): void => {
|
||||
versionChecked.value = false
|
||||
serverVersion.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
currentVersion: computed(() => currentVersion.value),
|
||||
serverVersion: computed(() => serverVersion.value),
|
||||
needsUpdate,
|
||||
versionChecked: computed(() => versionChecked.value),
|
||||
// 方法
|
||||
checkVersion,
|
||||
resetVersionCheck,
|
||||
}
|
||||
}
|
||||
@@ -328,6 +328,7 @@ const showDynamicButton = computed(() => {
|
||||
block-size: auto;
|
||||
inline-size: auto;
|
||||
min-block-size: 0;
|
||||
max-width: 60px; // 限制最大宽度以便动画
|
||||
|
||||
.footer-card-content {
|
||||
padding: 3px;
|
||||
@@ -358,14 +359,12 @@ const showDynamicButton = computed(() => {
|
||||
transition: all 0.3s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.fade-slide-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.fade-slide-enter-from,
|
||||
.fade-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
max-width: 0;
|
||||
margin-inline-start: 0 !important;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { saveLocalTheme } from '@/@core/utils/theme'
|
||||
import type { ThemeSwitcherTheme } from '@layouts/types'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { themeManager } from '@/utils/themeManager'
|
||||
import { usePWA, type UIMode } from '@/composables/usePWA'
|
||||
|
||||
// 认证 Store
|
||||
const authStore = useAuthStore()
|
||||
@@ -27,6 +28,8 @@ const globalSettingsStore = useGlobalSettingsStore()
|
||||
const { t } = useI18n()
|
||||
// 显示器
|
||||
const display = useDisplay()
|
||||
// PWA
|
||||
const { uiMode, setUIMode } = usePWA()
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
@@ -40,6 +43,9 @@ const siteAuthDialog = ref(false)
|
||||
// 自定义CSS弹窗
|
||||
const cssDialog = ref(false)
|
||||
|
||||
// UI模式菜单是否显示
|
||||
const showUIModeMenu = ref(false)
|
||||
|
||||
// 主题菜单是否显示
|
||||
const showThemeMenu = ref(false)
|
||||
|
||||
@@ -233,6 +239,37 @@ const isAdvancedMode = computed(() => {
|
||||
return globalSettingsStore.get('ADVANCED_MODE') !== false
|
||||
})
|
||||
|
||||
// UI模式相关
|
||||
const uiModes = computed(() => [
|
||||
{
|
||||
name: 'auto',
|
||||
title: t('theme.autoUI'),
|
||||
icon: 'mdi-devices',
|
||||
},
|
||||
{
|
||||
name: 'desktop',
|
||||
title: t('pwa.platforms.desktop'),
|
||||
icon: 'mdi-monitor',
|
||||
},
|
||||
{
|
||||
name: 'app',
|
||||
title: t('pwa.platforms.mobile'),
|
||||
icon: 'mdi-cellphone',
|
||||
},
|
||||
])
|
||||
|
||||
// 切换UI模式
|
||||
function changeUIMode(mode: UIMode) {
|
||||
setUIMode(mode)
|
||||
showUIModeMenu.value = false
|
||||
}
|
||||
|
||||
// 获取当前UI模式图标
|
||||
const getUIModeIcon = computed(() => {
|
||||
const mode = uiModes.value.find(m => m.name === uiMode.value)
|
||||
return mode?.icon || 'mdi-devices'
|
||||
})
|
||||
|
||||
// 主题相关功能
|
||||
const { name: themeName, global: globalTheme } = useTheme()
|
||||
const savedTheme = ref(localStorage.getItem('theme') ?? themeName)
|
||||
@@ -546,6 +583,41 @@ onUnmounted(() => {
|
||||
<VListItemTitle>{{ t('user.siteAuth') }}</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- 👉 UI模式设置 - 使用嵌套菜单 -->
|
||||
<VMenu location="end" offset-x min-width="200" v-model="showUIModeMenu" :close-on-content-click="true">
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<VListItem v-bind="menuProps" class="mb-1 rounded-lg" hover>
|
||||
<template #prepend>
|
||||
<VIcon :icon="getUIModeIcon" />
|
||||
</template>
|
||||
<VListItemTitle>{{ t('common.uiMode') }}</VListItemTitle>
|
||||
<VListItemSubtitle>
|
||||
{{ uiModes.find(m => m.name === uiMode)?.title || t('theme.autoUI') }}
|
||||
</VListItemSubtitle>
|
||||
<template #append>
|
||||
<VIcon icon="mdi-chevron-right" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</template>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="mode in uiModes"
|
||||
:key="mode.name"
|
||||
@click="changeUIMode(mode.name as UIMode)"
|
||||
:active="uiMode === mode.name"
|
||||
class="mb-1"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon :icon="mode.icon" />
|
||||
</template>
|
||||
<VListItemTitle>{{ mode.title }}</VListItemTitle>
|
||||
<template #append v-if="uiMode === mode.name">
|
||||
<VIcon icon="mdi-check" color="primary" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
|
||||
<!-- 👉 主题设置 - 使用嵌套菜单 -->
|
||||
<VMenu location="end" offset-x min-width="200" v-model="showThemeMenu" :close-on-content-click="true">
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
@@ -553,9 +625,10 @@ onUnmounted(() => {
|
||||
<template #prepend>
|
||||
<VIcon :icon="getThemeIcon" />
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
{{ themes.find(t => t.name === currentThemeName)?.title || t('common.theme') }}
|
||||
</VListItemTitle>
|
||||
<VListItemTitle>{{ t('common.theme') }}</VListItemTitle>
|
||||
<VListItemSubtitle>
|
||||
{{ themes.find(t => t.name === currentThemeName)?.title || t('theme.auto') }}
|
||||
</VListItemSubtitle>
|
||||
<template #append>
|
||||
<VIcon icon="mdi-chevron-right" size="small" />
|
||||
</template>
|
||||
|
||||
@@ -30,6 +30,7 @@ export default {
|
||||
saving: 'Saving',
|
||||
reset: 'Reset',
|
||||
theme: 'Theme',
|
||||
uiMode: 'UI Layout',
|
||||
language: 'Language',
|
||||
pleaseWait: 'Please wait...',
|
||||
viewDetails: 'View Details',
|
||||
@@ -66,6 +67,7 @@ export default {
|
||||
serviceUnavailable: 'Service Unavailable',
|
||||
status: 'Status',
|
||||
preset: 'Preset',
|
||||
refresh: 'Refresh',
|
||||
newVersionAvailable: 'New version detected, please refresh the page to get the latest features',
|
||||
},
|
||||
mediaType: {
|
||||
@@ -130,6 +132,7 @@ export default {
|
||||
light: 'Light',
|
||||
dark: 'Dark',
|
||||
auto: 'Follow System',
|
||||
autoUI: 'Auto',
|
||||
transparent: 'Transparent',
|
||||
purple: 'Purple',
|
||||
custom: 'Custom Style',
|
||||
@@ -1226,6 +1229,7 @@ export default {
|
||||
title: 'About MoviePilot',
|
||||
softwareVersion: 'Software Version',
|
||||
frontendVersion: 'Frontend Version',
|
||||
browserVersion: 'Browser Cached Version',
|
||||
authVersion: 'Auth Resource Version',
|
||||
indexerVersion: 'Indexer Resource Version',
|
||||
configDir: 'Config Directory',
|
||||
@@ -1245,6 +1249,7 @@ export default {
|
||||
dataDirectory: '/moviepilot',
|
||||
expand: 'Expand',
|
||||
collapse: 'Collapse',
|
||||
cleanCache: 'Clear Cache',
|
||||
},
|
||||
system: {
|
||||
custom: 'Custom',
|
||||
|
||||
@@ -30,6 +30,7 @@ export default {
|
||||
saving: '保存中',
|
||||
reset: '重置',
|
||||
theme: '主题',
|
||||
uiMode: '界面布局',
|
||||
language: '语言',
|
||||
pleaseWait: '请稍候...',
|
||||
viewDetails: '查看详情',
|
||||
@@ -66,6 +67,7 @@ export default {
|
||||
serviceUnavailable: '服务不可用',
|
||||
status: '状态',
|
||||
preset: '预设',
|
||||
refresh: '刷新',
|
||||
newVersionAvailable: '检测到新版本,请刷新页面以获取最新功能',
|
||||
},
|
||||
mediaType: {
|
||||
@@ -130,6 +132,7 @@ export default {
|
||||
light: '浅色',
|
||||
dark: '深色',
|
||||
auto: '跟随系统',
|
||||
autoUI: '自动',
|
||||
transparent: '透明',
|
||||
purple: '幻紫',
|
||||
custom: '附加样式',
|
||||
@@ -1223,6 +1226,7 @@ export default {
|
||||
title: '关于 MoviePilot',
|
||||
softwareVersion: '软件版本',
|
||||
frontendVersion: '前端版本',
|
||||
browserVersion: '浏览器缓存版本',
|
||||
authVersion: '认证资源版本',
|
||||
indexerVersion: '站点资源版本',
|
||||
configDir: '配置目录',
|
||||
@@ -1230,7 +1234,7 @@ export default {
|
||||
timezone: '时区',
|
||||
latest: '最新',
|
||||
supportingSites: '支持站点',
|
||||
support: '支援',
|
||||
support: '支持',
|
||||
documentation: '文档',
|
||||
feedback: '问题反馈',
|
||||
channel: '发布频道',
|
||||
@@ -1242,6 +1246,7 @@ export default {
|
||||
dataDirectory: '/moviepilot',
|
||||
expand: '展开',
|
||||
collapse: '收起',
|
||||
cleanCache: '清除缓存',
|
||||
},
|
||||
system: {
|
||||
custom: '自定义',
|
||||
|
||||
@@ -30,6 +30,7 @@ export default {
|
||||
saving: '保存中',
|
||||
reset: '重置',
|
||||
theme: '主題',
|
||||
uiMode: '界面佈局',
|
||||
language: '語言',
|
||||
pleaseWait: '請稍候...',
|
||||
viewDetails: '查看詳情',
|
||||
@@ -66,6 +67,7 @@ export default {
|
||||
serviceUnavailable: '服務不可用',
|
||||
status: '狀態',
|
||||
preset: '預設',
|
||||
refresh: '刷新',
|
||||
newVersionAvailable: '檢測到新版本,請刷新頁面以獲取最新功能',
|
||||
},
|
||||
mediaType: {
|
||||
@@ -130,6 +132,7 @@ export default {
|
||||
light: '淺色',
|
||||
dark: '深色',
|
||||
auto: '跟隨系統',
|
||||
autoUI: '自動',
|
||||
transparent: '透明',
|
||||
purple: '幻紫',
|
||||
custom: '附加樣式',
|
||||
@@ -1211,6 +1214,7 @@ export default {
|
||||
title: '關於 MoviePilot',
|
||||
softwareVersion: '軟件版本',
|
||||
frontendVersion: '前端版本',
|
||||
browserVersion: '瀏覽器緩存版本',
|
||||
authVersion: '認證資源版本',
|
||||
indexerVersion: '站點資源版本',
|
||||
configDir: '配置目錄',
|
||||
@@ -1230,6 +1234,7 @@ export default {
|
||||
dataDirectory: '/moviepilot',
|
||||
expand: '展開',
|
||||
collapse: '收起',
|
||||
cleanCache: '清除緩存',
|
||||
},
|
||||
system: {
|
||||
custom: '自定義',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { aliases } from 'vuetify/lib/iconsets/mdi'
|
||||
import { aliases } from 'vuetify/iconsets/mdi'
|
||||
|
||||
const alertTypeIcon = {
|
||||
success: 'mdi-check-circle-outline',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { globalSettingsState } from '@/stores/types'
|
||||
import { fetchGlobalSettings } from '@/utils/globalSetting'
|
||||
import { useVersionChecker } from '@/composables/useVersionChecker'
|
||||
|
||||
export const useGlobalSettingsStore = defineStore('globalSettings', {
|
||||
state: (): globalSettingsState => ({
|
||||
@@ -18,6 +19,12 @@ export const useGlobalSettingsStore = defineStore('globalSettings', {
|
||||
const result = await fetchGlobalSettings()
|
||||
this.data = result || {}
|
||||
this.initialized = true
|
||||
|
||||
// 检查版本更新
|
||||
if (result.FRONTEND_VERSION) {
|
||||
const { checkVersion } = useVersionChecker()
|
||||
await checkVersion(result.FRONTEND_VERSION)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize global settings', error)
|
||||
} finally {
|
||||
|
||||
@@ -1,58 +1,8 @@
|
||||
import api from '@/api'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import i18n from '@/plugins/i18n'
|
||||
|
||||
// 创建一个专用的AbortController,用于globalSetting请求
|
||||
const globalSettingController = new AbortController()
|
||||
|
||||
// 声明全局变量类型
|
||||
declare const __APP_VERSION__: string
|
||||
|
||||
// 当前前端版本号(由 Vite 在编译时注入)
|
||||
const CURRENT_FRONTEND_VERSION = __APP_VERSION__
|
||||
console.log(`[VersionChecker] 当前前端版本: ${CURRENT_FRONTEND_VERSION}`)
|
||||
|
||||
// 标记是否已经显示过版本更新通知
|
||||
let versionNotificationShown = false
|
||||
|
||||
/**
|
||||
* 检查版本并在需要时显示更新通知
|
||||
*/
|
||||
async function checkVersionAndNotify(serverVersion: string): Promise<void> {
|
||||
// 版本不同,且尚未显示通知
|
||||
if (serverVersion !== CURRENT_FRONTEND_VERSION && !versionNotificationShown) {
|
||||
versionNotificationShown = true
|
||||
console.log(`[VersionChecker] 检测到版本更新: ${CURRENT_FRONTEND_VERSION} -> ${serverVersion}`)
|
||||
|
||||
try {
|
||||
// 1. 清除所有缓存
|
||||
if ('caches' in window) {
|
||||
const cacheNames = await caches.keys()
|
||||
await Promise.all(cacheNames.map(name => caches.delete(name)))
|
||||
console.log('[VersionChecker] 已清除所有缓存')
|
||||
}
|
||||
|
||||
// 2. 注销Service Worker
|
||||
if ('serviceWorker' in navigator) {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations()
|
||||
await Promise.all(registrations.map(registration => registration.unregister()))
|
||||
console.log('[VersionChecker] 已注销所有 Service Worker')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[VersionChecker] 清除缓存失败:', error)
|
||||
}
|
||||
|
||||
// 3. 显示持久化通知,提示用户刷新
|
||||
const toast = useToast()
|
||||
toast.info(i18n.global.t('common.newVersionAvailable'), {
|
||||
timeout: false, // 不自动消失
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchGlobalSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/global', {
|
||||
@@ -62,15 +12,7 @@ export async function fetchGlobalSettings() {
|
||||
// 手动设置signal,防止reqestOptimizer添加可中断的controller
|
||||
signal: globalSettingController.signal,
|
||||
})
|
||||
|
||||
const data = result.data || {}
|
||||
|
||||
// 检查版本更新
|
||||
if (data.FRONTEND_VERSION) {
|
||||
await checkVersionAndNotify(data.FRONTEND_VERSION)
|
||||
}
|
||||
|
||||
return data
|
||||
return result.data || {}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch global settings', error)
|
||||
throw error
|
||||
|
||||
@@ -331,6 +331,7 @@ export default defineConfig({
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
quietDeps: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3017,9 +3017,9 @@ camelcase-css@^2.0.1:
|
||||
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||
|
||||
caniuse-lite@^1.0.30001688, caniuse-lite@^1.0.30001702:
|
||||
version "1.0.30001715"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz"
|
||||
integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==
|
||||
version "1.0.30001761"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz"
|
||||
integrity sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==
|
||||
|
||||
chalk@^4.0.0, chalk@^4.0.2:
|
||||
version "4.1.2"
|
||||
@@ -6856,6 +6856,7 @@ std-env@^3.9.0:
|
||||
integrity sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.3:
|
||||
name string-width-cjs
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -6941,6 +6942,7 @@ stringify-object@^3.3.0:
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
name strip-ansi-cjs
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
||||
Reference in New Issue
Block a user