mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 07:28:37 +08:00
Merge branch 'jxxghp:v2' into v2
This commit is contained in:
@@ -50,7 +50,7 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
federation({
|
federation({
|
||||||
name: 'MyPlugin', // 需与插件的ID保持一致
|
name: 'MyPlugin',
|
||||||
filename: 'remoteEntry.js',
|
filename: 'remoteEntry.js',
|
||||||
exposes: {
|
exposes: {
|
||||||
'./Page': './src/components/Page.vue',
|
'./Page': './src/components/Page.vue',
|
||||||
@@ -60,13 +60,18 @@ export default defineConfig({
|
|||||||
shared: {
|
shared: {
|
||||||
vue: {
|
vue: {
|
||||||
requiredVersion: false,
|
requiredVersion: false,
|
||||||
|
generate: false,
|
||||||
},
|
},
|
||||||
vuetify: {
|
vuetify: {
|
||||||
requiredVersion: false,
|
requiredVersion: false,
|
||||||
|
generate: false,
|
||||||
|
singleton: true,
|
||||||
},
|
},
|
||||||
'vuetify/styles': {
|
'vuetify/styles': {
|
||||||
requiredVersion: false,
|
requiredVersion: false,
|
||||||
}
|
generate: false,
|
||||||
|
singleton: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
format: 'esm'
|
format: 'esm'
|
||||||
})
|
})
|
||||||
@@ -74,7 +79,48 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
target: 'esnext', // 必须设置为esnext以支持顶层await
|
target: 'esnext', // 必须设置为esnext以支持顶层await
|
||||||
minify: false, // 开发阶段建议关闭混淆
|
minify: false, // 开发阶段建议关闭混淆
|
||||||
cssCodeSplit: false,
|
cssCodeSplit: true, // 改为true以便能分离样式文件
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'vuetify-lib': ['vuetify'] // 将vuetify单独分离出来
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: '/* 覆盖vuetify样式 */',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postcss: {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
postcssPlugin: 'internal:charset-removal',
|
||||||
|
AtRule: {
|
||||||
|
charset: (atRule) => {
|
||||||
|
if (atRule.name === 'charset') {
|
||||||
|
atRule.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
postcssPlugin: 'vuetify-filter',
|
||||||
|
Root(root) {
|
||||||
|
// 过滤掉所有vuetify相关的CSS
|
||||||
|
root.walkRules(rule => {
|
||||||
|
if (rule.selector && (
|
||||||
|
rule.selector.includes('.v-') ||
|
||||||
|
rule.selector.includes('.mdi-'))) {
|
||||||
|
rule.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 5001, // 使用不同于主应用的端口
|
port: 5001, // 使用不同于主应用的端口
|
||||||
@@ -82,6 +128,7 @@ export default defineConfig({
|
|||||||
origin: 'http://localhost:5001'
|
origin: 'http://localhost:5001'
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. 组件开发规范
|
## 5. 组件开发规范
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ import App from './App.vue'
|
|||||||
import { createVuetify } from 'vuetify'
|
import { createVuetify } from 'vuetify'
|
||||||
import * as components from 'vuetify/components'
|
import * as components from 'vuetify/components'
|
||||||
import * as directives from 'vuetify/directives'
|
import * as directives from 'vuetify/directives'
|
||||||
|
import defaults from './vuetify/defaults'
|
||||||
|
import theme from './vuetify/theme'
|
||||||
import 'vuetify/styles'
|
import 'vuetify/styles'
|
||||||
|
|
||||||
// 创建Vuetify实例
|
// 创建Vuetify实例
|
||||||
const vuetify = createVuetify({
|
const vuetify = createVuetify({
|
||||||
components,
|
components,
|
||||||
directives,
|
directives,
|
||||||
theme: {
|
theme,
|
||||||
defaultTheme: 'dark'
|
defaults
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建应用
|
// 创建应用
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
export default {
|
||||||
|
IconBtn: {
|
||||||
|
icon: true,
|
||||||
|
color: 'default',
|
||||||
|
variant: 'text',
|
||||||
|
VIcon: {
|
||||||
|
size: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VAlert: {
|
||||||
|
VBtn: {
|
||||||
|
color: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VAvatar: {
|
||||||
|
// ℹ️ Remove after next release
|
||||||
|
variant: 'flat',
|
||||||
|
VIcon: {
|
||||||
|
size: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VBadge: {
|
||||||
|
// set v-badge default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
VBtn: {
|
||||||
|
// set v-btn default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
|
VCard: {
|
||||||
|
elevation: 0,
|
||||||
|
rounded: 'lg',
|
||||||
|
},
|
||||||
|
VMenu: {
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
|
VChip: {
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
|
VBottomSheet: {
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
|
VDialog: {
|
||||||
|
elevation: 0,
|
||||||
|
rounded: 'lg',
|
||||||
|
},
|
||||||
|
VExpansionPanels: {
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
|
VList: {
|
||||||
|
color: 'primary',
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
|
VListItem: {
|
||||||
|
rounded: 'md',
|
||||||
|
},
|
||||||
|
VPagination: {
|
||||||
|
activeColor: 'primary',
|
||||||
|
},
|
||||||
|
VTabs: {
|
||||||
|
// set v-tabs default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
VSlideGroup: {
|
||||||
|
showArrows: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VTooltip: {
|
||||||
|
// set v-tooltip default location to top
|
||||||
|
location: 'top',
|
||||||
|
},
|
||||||
|
VCheckboxBtn: {
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VCheckbox: {
|
||||||
|
// set v-checkbox default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VRadioGroup: {
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VRadio: {
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VSelect: {
|
||||||
|
variant: 'outlined',
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
menuProps: { elevation: 0 },
|
||||||
|
},
|
||||||
|
VRangeSlider: {
|
||||||
|
// set v-range-slider default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
density: 'comfortable',
|
||||||
|
thumbLabel: true,
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VRating: {
|
||||||
|
// set v-rating default color to primary
|
||||||
|
color: 'rgba(var(--v-theme-on-background),0.23)',
|
||||||
|
activeColor: 'warning',
|
||||||
|
halfIncrements: true,
|
||||||
|
},
|
||||||
|
VProgressCircular: {
|
||||||
|
// set v-progress-circular default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
},
|
||||||
|
VSlider: {
|
||||||
|
// set v-slider default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VTextField: {
|
||||||
|
variant: 'outlined',
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VAutocomplete: {
|
||||||
|
variant: 'outlined',
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VCombobox: {
|
||||||
|
variant: 'outlined',
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
menuProps: { elevation: 0 },
|
||||||
|
},
|
||||||
|
VFileInput: {
|
||||||
|
variant: 'outlined',
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VTextarea: {
|
||||||
|
variant: 'outlined',
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
VSwitch: {
|
||||||
|
// set v-switch default color to primary
|
||||||
|
color: 'primary',
|
||||||
|
hideDetails: 'auto',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import type { VuetifyOptions } from 'vuetify'
|
||||||
|
|
||||||
|
const theme: VuetifyOptions['theme'] = {
|
||||||
|
defaultTheme: 'light',
|
||||||
|
themes: {
|
||||||
|
light: {
|
||||||
|
dark: false,
|
||||||
|
colors: {
|
||||||
|
'primary': '#9155FD',
|
||||||
|
'secondary': '#8A8D93',
|
||||||
|
'on-secondary': '#FFFFFF',
|
||||||
|
'success': '#56CA00',
|
||||||
|
'info': '#16B1FF',
|
||||||
|
'warning': '#FFB400',
|
||||||
|
'error': '#FF4C51',
|
||||||
|
'on-primary': '#FFFFFF',
|
||||||
|
'on-success': '#FFFFFF',
|
||||||
|
'on-warning': '#FFFFFF',
|
||||||
|
'background': '#F4F5FA',
|
||||||
|
'on-background': '#3A3541',
|
||||||
|
'on-surface': '#3A3541',
|
||||||
|
'grey-50': '#FAFAFA',
|
||||||
|
'grey-100': '#F0F2F8',
|
||||||
|
'grey-200': '#EEEEEE',
|
||||||
|
'grey-300': '#E0E0E0',
|
||||||
|
'grey-400': '#BDBDBD',
|
||||||
|
'grey-500': '#9E9E9E',
|
||||||
|
'grey-600': '#757575',
|
||||||
|
'grey-700': '#616161',
|
||||||
|
'grey-800': '#424242',
|
||||||
|
'grey-900': '#212121',
|
||||||
|
'perfect-scrollbar-thumb': '#DBDADE',
|
||||||
|
'skin-bordered-background': '#FFFFFF',
|
||||||
|
'skin-bordered-surface': '#FFFFFF',
|
||||||
|
},
|
||||||
|
|
||||||
|
variables: {
|
||||||
|
'code-color': '#D400FF',
|
||||||
|
'overlay-scrim-background': '#3A3541',
|
||||||
|
'overlay-scrim-opacity': 0.5,
|
||||||
|
'hover-opacity': 0.04,
|
||||||
|
'focus-opacity': 0.1,
|
||||||
|
'selected-opacity': 0.12,
|
||||||
|
'activated-opacity': 0.1,
|
||||||
|
'pressed-opacity': 0.14,
|
||||||
|
'dragged-opacity': 0.1,
|
||||||
|
'border-color': '#3A3541',
|
||||||
|
'table-header-background': '#F9FAFC',
|
||||||
|
'custom-background': '#F9F8F9',
|
||||||
|
|
||||||
|
// Shadows
|
||||||
|
'shadow-key-umbra-opacity': 'rgba(var(--v-theme-on-surface), 0.08)',
|
||||||
|
'shadow-key-penumbra-opacity': 'rgba(var(--v-theme-on-surface), 0.12)',
|
||||||
|
'shadow-key-ambient-opacity': 'rgba(var(--v-theme-on-surface), 0.04)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
dark: true,
|
||||||
|
colors: {
|
||||||
|
'primary': '#6E66ED',
|
||||||
|
'secondary': '#8A8D93',
|
||||||
|
'on-secondary': '#FFFFFF',
|
||||||
|
'success': '#56CA00',
|
||||||
|
'info': '#16B1FF',
|
||||||
|
'warning': '#FFB400',
|
||||||
|
'error': '#FF4C51',
|
||||||
|
'on-primary': '#FFFFFF',
|
||||||
|
'on-success': '#FFFFFF',
|
||||||
|
'on-warning': '#FFFFFF',
|
||||||
|
'background': '#0E1116',
|
||||||
|
'on-background': '#E7E3FC',
|
||||||
|
'surface': '#14161F',
|
||||||
|
'on-surface': '#E7E3FC',
|
||||||
|
'grey-50': '#2A2E42',
|
||||||
|
'grey-100': '#474360',
|
||||||
|
'grey-200': '#4A5072',
|
||||||
|
'grey-300': '#5E6692',
|
||||||
|
'grey-400': '#7983BB',
|
||||||
|
'grey-500': '#8692D0',
|
||||||
|
'grey-600': '#AAB3DE',
|
||||||
|
'grey-700': '#B6BEE3',
|
||||||
|
'grey-800': '#CFD3EC',
|
||||||
|
'grey-900': '#E7E9F6',
|
||||||
|
'perfect-scrollbar-thumb': '#4A5072',
|
||||||
|
'skin-bordered-background': '#312d4b',
|
||||||
|
'skin-bordered-surface': '#312d4b',
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
'code-color': '#d400ff',
|
||||||
|
'overlay-scrim-background': '#191D21',
|
||||||
|
'overlay-scrim-opacity': 0.6,
|
||||||
|
'hover-opacity': 0.04,
|
||||||
|
'focus-opacity': 0.1,
|
||||||
|
'selected-opacity': 0.12,
|
||||||
|
'activated-opacity': 0.1,
|
||||||
|
'pressed-opacity': 0.14,
|
||||||
|
'dragged-opacity': 0.1,
|
||||||
|
'border-color': '#E7E3FC',
|
||||||
|
'table-header-background': '#14161F',
|
||||||
|
'custom-background': '#373452',
|
||||||
|
// Shadows
|
||||||
|
'shadow-key-umbra-opacity': 'rgba(20, 18, 33, 0.08)',
|
||||||
|
'shadow-key-penumbra-opacity': 'rgba(20, 18, 33, 0.12)',
|
||||||
|
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
dark: true,
|
||||||
|
colors: {
|
||||||
|
'primary': '#9155FD',
|
||||||
|
'secondary': '#8A8D93',
|
||||||
|
'on-secondary': '#FFFFFF',
|
||||||
|
'success': '#56CA00',
|
||||||
|
'info': '#16B1FF',
|
||||||
|
'warning': '#FFB400',
|
||||||
|
'error': '#FF4C51',
|
||||||
|
'on-primary': '#FFFFFF',
|
||||||
|
'on-success': '#FFFFFF',
|
||||||
|
'on-warning': '#FFFFFF',
|
||||||
|
'background': '#28243D',
|
||||||
|
'on-background': '#E7E3FC',
|
||||||
|
'surface': '#312D4B',
|
||||||
|
'on-surface': '#E7E3FC',
|
||||||
|
'grey-50': '#2A2E42',
|
||||||
|
'grey-100': '#474360',
|
||||||
|
'grey-200': '#4A5072',
|
||||||
|
'grey-300': '#5E6692',
|
||||||
|
'grey-400': '#7983BB',
|
||||||
|
'grey-500': '#8692D0',
|
||||||
|
'grey-600': '#AAB3DE',
|
||||||
|
'grey-700': '#B6BEE3',
|
||||||
|
'grey-800': '#CFD3EC',
|
||||||
|
'grey-900': '#E7E9F6',
|
||||||
|
'perfect-scrollbar-thumb': '#4A5072',
|
||||||
|
'skin-bordered-background': '#312d4b',
|
||||||
|
'skin-bordered-surface': '#312d4b',
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
'code-color': '#d400ff',
|
||||||
|
'overlay-scrim-background': '#2C2942',
|
||||||
|
'overlay-scrim-opacity': 0.6,
|
||||||
|
'hover-opacity': 0.04,
|
||||||
|
'focus-opacity': 0.1,
|
||||||
|
'selected-opacity': 0.12,
|
||||||
|
'activated-opacity': 0.1,
|
||||||
|
'pressed-opacity': 0.14,
|
||||||
|
'dragged-opacity': 0.1,
|
||||||
|
'border-color': '#E7E3FC',
|
||||||
|
'table-header-background': '#3D3759',
|
||||||
|
'custom-background': '#373452',
|
||||||
|
|
||||||
|
// Shadows
|
||||||
|
'shadow-key-umbra-opacity': 'rgba(20, 18, 33, 0.08)',
|
||||||
|
'shadow-key-penumbra-opacity': 'rgba(20, 18, 33, 0.12)',
|
||||||
|
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transparent: {
|
||||||
|
dark: true,
|
||||||
|
colors: {
|
||||||
|
'primary': '#A370F7',
|
||||||
|
'secondary': '#8A8D93',
|
||||||
|
'on-secondary': '#FFFFFF',
|
||||||
|
'success': '#66BB6A',
|
||||||
|
'info': '#42A5F5',
|
||||||
|
'warning': '#FFA726',
|
||||||
|
'error': '#EF5350',
|
||||||
|
'on-primary': '#FFFFFF',
|
||||||
|
'on-success': '#FFFFFF',
|
||||||
|
'on-warning': '#FFFFFF',
|
||||||
|
'background': '#000000',
|
||||||
|
'on-background': '#E7E3FC',
|
||||||
|
'surface': 'rgba(30, 30, 30, 0.3)',
|
||||||
|
'on-surface': '#E7E3FC',
|
||||||
|
'surface-variant': 'rgba(30, 30, 30, 0.2)',
|
||||||
|
'on-surface-variant': 'rgba(255, 255, 255, 0.65)',
|
||||||
|
'grey-50': 'rgba(42, 46, 66, 0.15)',
|
||||||
|
'grey-100': 'rgba(71, 67, 96, 0.15)',
|
||||||
|
'grey-200': 'rgba(74, 80, 114, 0.15)',
|
||||||
|
'grey-300': 'rgba(94, 102, 146, 0.15)',
|
||||||
|
'grey-400': 'rgba(121, 131, 187, 0.15)',
|
||||||
|
'grey-500': 'rgba(134, 146, 208, 0.15)',
|
||||||
|
'grey-600': 'rgba(170, 179, 222, 0.15)',
|
||||||
|
'grey-700': 'rgba(182, 190, 227, 0.15)',
|
||||||
|
'grey-800': 'rgba(207, 211, 236, 0.15)',
|
||||||
|
'grey-900': 'rgba(231, 233, 246, 0.15)',
|
||||||
|
'perfect-scrollbar-thumb': 'rgba(158, 158, 190, 0.4)',
|
||||||
|
'skin-bordered-background': 'rgba(30, 30, 30, 0.3)',
|
||||||
|
'skin-bordered-surface': 'rgba(30, 30, 30, 0.3)',
|
||||||
|
'card-background': 'rgba(30, 30, 30, 0.3)',
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
'code-color': '#6D9EEB',
|
||||||
|
'overlay-scrim-background': '0, 0, 0',
|
||||||
|
'overlay-scrim-opacity': 0.7,
|
||||||
|
'hover-opacity': 0.1,
|
||||||
|
'focus-opacity': 0.15,
|
||||||
|
'selected-opacity': 0.2,
|
||||||
|
'activated-opacity': 0.15,
|
||||||
|
'pressed-opacity': 0.2,
|
||||||
|
'dragged-opacity': 0.15,
|
||||||
|
'border-color': '#E7E3FC',
|
||||||
|
'table-header-background': 'rgba(30, 30, 30, 0.3)',
|
||||||
|
'custom-background': 'rgba(30, 30, 30, 0.3)',
|
||||||
|
'card-background': 'rgba(30, 30, 30, 0.3)',
|
||||||
|
|
||||||
|
// Shadows
|
||||||
|
'shadow-key-umbra-opacity': 'rgba(0, 0, 0, 0.07)',
|
||||||
|
'shadow-key-penumbra-opacity': 'rgba(0, 0, 0, 0.1)',
|
||||||
|
'shadow-key-ambient-opacity': 'rgba(0, 0, 0, 0.05)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default theme
|
||||||
@@ -21,10 +21,12 @@ export default defineConfig({
|
|||||||
vuetify: {
|
vuetify: {
|
||||||
requiredVersion: false,
|
requiredVersion: false,
|
||||||
generate: false,
|
generate: false,
|
||||||
|
singleton: true,
|
||||||
},
|
},
|
||||||
'vuetify/styles': {
|
'vuetify/styles': {
|
||||||
requiredVersion: false,
|
requiredVersion: false,
|
||||||
generate: false,
|
generate: false,
|
||||||
|
singleton: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
format: 'esm'
|
format: 'esm'
|
||||||
@@ -33,7 +35,48 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
target: 'esnext', // 必须设置为esnext以支持顶层await
|
target: 'esnext', // 必须设置为esnext以支持顶层await
|
||||||
minify: false, // 开发阶段建议关闭混淆
|
minify: false, // 开发阶段建议关闭混淆
|
||||||
cssCodeSplit: false,
|
cssCodeSplit: true, // 改为true以便能分离样式文件
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'vuetify-lib': ['vuetify'] // 将vuetify单独分离出来
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: '/* 覆盖vuetify样式 */',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postcss: {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
postcssPlugin: 'internal:charset-removal',
|
||||||
|
AtRule: {
|
||||||
|
charset: (atRule) => {
|
||||||
|
if (atRule.name === 'charset') {
|
||||||
|
atRule.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
postcssPlugin: 'vuetify-filter',
|
||||||
|
Root(root) {
|
||||||
|
// 过滤掉所有vuetify相关的CSS
|
||||||
|
root.walkRules(rule => {
|
||||||
|
if (rule.selector && (
|
||||||
|
rule.selector.includes('.v-') ||
|
||||||
|
rule.selector.includes('.mdi-'))) {
|
||||||
|
rule.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 5001, // 使用不同于主应用的端口
|
port: 5001, // 使用不同于主应用的端口
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "2.4.4-1",
|
"version": "2.4.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": "dist/service.js",
|
"bin": "dist/service.js",
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ function onClose() {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<VDialog v-if="ruleInfoDialog" v-model="ruleInfoDialog" scrollable max-width="40rem">
|
<VDialog v-if="ruleInfoDialog" v-model="ruleInfoDialog" scrollable max-width="40rem">
|
||||||
<VCard :title="t('customRule.title', { id: props.rule.id })" class="rounded-t">
|
<VCard :title="t('customRule.title', { id: props.rule.id })">
|
||||||
<VDialogCloseBtn v-model="ruleInfoDialog" />
|
<VDialogCloseBtn v-model="ruleInfoDialog" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ function onClose() {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<VDialog v-if="groupInfoDialog" v-model="groupInfoDialog" scrollable max-width="80rem">
|
<VDialog v-if="groupInfoDialog" v-model="groupInfoDialog" scrollable max-width="80rem">
|
||||||
<VCard :title="`${props.group.name} - ${t('filterRule.title')}`" class="rounded-t">
|
<VCard :title="`${props.group.name} - ${t('filterRule.title')}`">
|
||||||
<VDialogCloseBtn v-model="groupInfoDialog" />
|
<VDialogCloseBtn v-model="groupInfoDialog" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardItem class="pt-1">
|
<VCardItem class="pt-1">
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ onMounted(() => {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<VDialog v-if="mediaServerInfoDialog" v-model="mediaServerInfoDialog" scrollable max-width="40rem">
|
<VDialog v-if="mediaServerInfoDialog" v-model="mediaServerInfoDialog" scrollable max-width="40rem">
|
||||||
<VCard :title="`${props.mediaserver.name} - ${t('common.config')}`" class="rounded-t">
|
<VCard :title="`${props.mediaserver.name} - ${t('common.config')}`">
|
||||||
<VDialogCloseBtn v-model="mediaServerInfoDialog" />
|
<VDialogCloseBtn v-model="mediaServerInfoDialog" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function onClose() {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
<VDialog v-if="notificationInfoDialog" v-model="notificationInfoDialog" scrollable max-width="40rem">
|
<VDialog v-if="notificationInfoDialog" v-model="notificationInfoDialog" scrollable max-width="40rem">
|
||||||
<VCard :title="`${props.notification.name} - ${t('notification.config')}`" class="rounded-t">
|
<VCard :title="`${props.notification.name} - ${t('notification.config')}`">
|
||||||
<VDialogCloseBtn v-model="notificationInfoDialog" />
|
<VDialogCloseBtn v-model="notificationInfoDialog" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
|||||||
@@ -293,21 +293,20 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧操作按钮区 -->
|
<!-- 右侧操作按钮区 -->
|
||||||
<VSheet
|
<VSheet class="site-card-actions absolute inset-y-0 right-0 z-20 flex flex-col py-2 px-1">
|
||||||
class="site-card-actions absolute inset-y-0 right-0 z-20 flex flex-col py-2 px-1 transform translate-x-full transition-transform duration-200"
|
|
||||||
>
|
|
||||||
<!-- 测试按钮 -->
|
<!-- 测试按钮 -->
|
||||||
<VBtn
|
<VBtn
|
||||||
icon
|
icon
|
||||||
variant="text"
|
variant="text"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
class="mb-1 relative w-10 h-10 min-w-10 flex items-center justify-center rounded-full"
|
class="mb-1 relative flex items-center justify-center rounded-full mx-auto"
|
||||||
:disabled="testButtonDisable"
|
:disabled="testButtonDisable"
|
||||||
@click.stop="testSite"
|
@click.stop="testSite"
|
||||||
|
size="36"
|
||||||
>
|
>
|
||||||
<div class="relative flex items-center justify-center w-full h-full">
|
<div class="relative flex items-center justify-center w-full h-full">
|
||||||
<div
|
<div
|
||||||
class="w-[22px] h-[22px] rounded-full shadow-[inset_0_0_0_2px_rgba(var(--v-theme-on-surface),0.1)] pulse-dot"
|
class="w-[20px] h-[20px] rounded-full shadow-[inset_0_0_0_2px_rgba(var(--v-theme-on-surface),0.1)] pulse-dot"
|
||||||
:class="statColor"
|
:class="statColor"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -322,29 +321,29 @@ onMounted(() => {
|
|||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<!-- 用户数据按钮 -->
|
<!-- 用户数据按钮 -->
|
||||||
<VBtn icon variant="text" @click.stop="handleSiteUserData">
|
<VBtn icon variant="text" @click.stop="handleSiteUserData" size="36">
|
||||||
<VIcon icon="mdi-chart-bell-curve" size="small" />
|
<VIcon icon="mdi-chart-bell-curve" size="20" />
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<!-- 更新按钮 -->
|
<!-- 更新按钮 -->
|
||||||
<VBtn icon variant="text" @click.stop="handleSiteUpdate">
|
<VBtn icon variant="text" @click.stop="handleSiteUpdate" size="36">
|
||||||
<VIcon icon="mdi-refresh" size="small" />
|
<VIcon icon="mdi-refresh" size="20" />
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<!-- 更多选项按钮 -->
|
<!-- 更多选项按钮 -->
|
||||||
<VBtn icon variant="text" class="mt-auto">
|
<VBtn icon variant="text" class="mt-auto" size="36">
|
||||||
<VIcon icon="mdi-dots-vertical" size="small" />
|
<VIcon icon="mdi-dots-vertical" size="20" />
|
||||||
<VMenu :activator="'parent'" :close-on-content-click="true" :location="'left'">
|
<VMenu :activator="'parent'" :close-on-content-click="true" :location="'left'">
|
||||||
<VList>
|
<VList>
|
||||||
<VListItem @click="handleResourceBrowse" base-color="info">
|
<VListItem @click="handleResourceBrowse" base-color="info">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon icon="mdi-web" size="small" />
|
<VIcon icon="mdi-web" size="20" />
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>{{ t('site.browseResources') }}</VListItemTitle>
|
<VListItemTitle>{{ t('site.browseResources') }}</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
<VListItem @click="deleteSiteInfo">
|
<VListItem @click="deleteSiteInfo">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon icon="mdi-delete-outline" size="small" color="error" />
|
<VIcon icon="mdi-delete-outline" size="20" color="error" />
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle class="text-error">{{ t('site.deleteSite') }}</VListItemTitle>
|
<VListItemTitle class="text-error">{{ t('site.deleteSite') }}</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
@@ -386,12 +385,6 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.site-card:hover {
|
|
||||||
.site-card-actions {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-status-indicator {
|
.site-status-indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -430,15 +423,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
/* 上传下载条样式 */
|
/* 上传下载条样式 */
|
||||||
.upload-bar {
|
.upload-bar {
|
||||||
|
animation: pulse-width 2s infinite;
|
||||||
background: linear-gradient(90deg, #4d79ff, #07f);
|
background: linear-gradient(90deg, #4d79ff, #07f);
|
||||||
box-shadow: 0 0 4px rgba(0, 119, 255, 50%);
|
box-shadow: 0 0 4px rgba(0, 119, 255, 50%);
|
||||||
animation: pulse-width 2s infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-bar {
|
.download-bar {
|
||||||
|
animation: pulse-width 2s infinite;
|
||||||
background: linear-gradient(90deg, #42d392, #00b77e);
|
background: linear-gradient(90deg, #42d392, #00b77e);
|
||||||
box-shadow: 0 0 4px rgba(0, 183, 126, 50%);
|
box-shadow: 0 0 4px rgba(0, 183, 126, 50%);
|
||||||
animation: pulse-width 2s infinite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 测试状态点样式 */
|
/* 测试状态点样式 */
|
||||||
@@ -446,22 +439,22 @@ onMounted(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
block-size: 70%;
|
||||||
content: '';
|
content: '';
|
||||||
height: 70%;
|
inline-size: 70%;
|
||||||
width: 70%;
|
inset-block-start: 15%;
|
||||||
top: 15%;
|
inset-inline-start: 15%;
|
||||||
left: 15%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulse-dot::after {
|
.pulse-dot::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
block-size: 100%;
|
||||||
content: '';
|
content: '';
|
||||||
height: 100%;
|
inline-size: 100%;
|
||||||
width: 100%;
|
inset-block-start: 0;
|
||||||
top: 0;
|
inset-inline-start: 0;
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulse-dot.error::before {
|
.pulse-dot.error::before {
|
||||||
@@ -508,11 +501,11 @@ onMounted(() => {
|
|||||||
.spinner-circle {
|
.spinner-circle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: 1px solid rgba(var(--v-theme-primary), 0.2);
|
border: 1px solid rgba(var(--v-theme-primary), 0.2);
|
||||||
border-top-color: rgba(var(--v-theme-primary), 1);
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
animation: spin 0.8s linear infinite;
|
animation: spin 0.8s linear infinite;
|
||||||
|
block-size: 100%;
|
||||||
|
border-block-start-color: rgba(var(--v-theme-primary), 1);
|
||||||
|
inline-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 动画关键帧 */
|
/* 动画关键帧 */
|
||||||
@@ -522,6 +515,7 @@ onMounted(() => {
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
transform: scaleX(0.95);
|
transform: scaleX(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scaleX(1.05);
|
transform: scaleX(1.05);
|
||||||
@@ -532,9 +526,11 @@ onMounted(() => {
|
|||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-error), 0.6);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-error), 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
box-shadow: 0 0 0 10px rgba(var(--v-theme-error), 0);
|
box-shadow: 0 0 0 10px rgba(var(--v-theme-error), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-error), 0);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-error), 0);
|
||||||
}
|
}
|
||||||
@@ -544,9 +540,11 @@ onMounted(() => {
|
|||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-warning), 0.6);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-warning), 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
box-shadow: 0 0 0 10px rgba(var(--v-theme-warning), 0);
|
box-shadow: 0 0 0 10px rgba(var(--v-theme-warning), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-warning), 0);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-warning), 0);
|
||||||
}
|
}
|
||||||
@@ -556,9 +554,11 @@ onMounted(() => {
|
|||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-success), 0.6);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-success), 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
box-shadow: 0 0 0 10px rgba(var(--v-theme-success), 0);
|
box-shadow: 0 0 0 10px rgba(var(--v-theme-success), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-success), 0);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-success), 0);
|
||||||
}
|
}
|
||||||
@@ -568,9 +568,11 @@ onMounted(() => {
|
|||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-secondary), 0.6);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-secondary), 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
box-shadow: 0 0 0 10px rgba(var(--v-theme-secondary), 0);
|
box-shadow: 0 0 0 10px rgba(var(--v-theme-secondary), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 0 rgba(var(--v-theme-secondary), 0);
|
box-shadow: 0 0 0 0 rgba(var(--v-theme-secondary), 0);
|
||||||
}
|
}
|
||||||
@@ -580,6 +582,7 @@ onMounted(() => {
|
|||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
@@ -589,8 +592,22 @@ onMounted(() => {
|
|||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.site-card-actions {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-card:hover .site-card-actions {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ async function savaAlistConfig() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog width="50rem" scrollable max-height="85vh">
|
<VDialog width="50rem" scrollable max-height="85vh">
|
||||||
<VCard :title="t('dialog.alistConfig.title')" class="rounded-t">
|
<VCard :title="t('dialog.alistConfig.title')">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VRow>
|
<VRow>
|
||||||
@@ -119,7 +119,7 @@ async function savaAlistConfig() {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn variant="elevated" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||||
{{ t('dialog.alistConfig.reset') }}
|
{{ t('dialog.alistConfig.reset') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog width="40rem" scrollable max-height="85vh">
|
<VDialog width="40rem" scrollable max-height="85vh">
|
||||||
<VCard :title="t('dialog.aliyunAuth.loginTitle')" class="rounded-t">
|
<VCard :title="t('dialog.aliyunAuth.loginTitle')">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VCardText class="pt-2 flex flex-col items-center">
|
<VCardText class="pt-2 flex flex-col items-center">
|
||||||
<div class="my-6 rounded text-center p-3 border">
|
<div class="my-6 rounded text-center p-3 border">
|
||||||
@@ -125,7 +125,7 @@ onUnmounted(() => {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn variant="elevated" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||||
{{ t('dialog.aliyunAuth.reset') }}
|
{{ t('dialog.aliyunAuth.reset') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function handleImport() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog width="40rem" scrollable max-height="85vh">
|
<VDialog width="40rem" scrollable max-height="85vh">
|
||||||
<VCard :title="props.title" class="rounded-t">
|
<VCard :title="props.title">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VCardText class="pt-2">
|
<VCardText class="pt-2">
|
||||||
<VTextarea v-model="codeString" />
|
<VTextarea v-model="codeString" />
|
||||||
|
|||||||
@@ -150,11 +150,7 @@ onBeforeMount(async () => {
|
|||||||
<template>
|
<template>
|
||||||
<VDialog scrollable max-width="60rem" :fullscreen="!display.mdAndUp.value">
|
<VDialog scrollable max-width="60rem" :fullscreen="!display.mdAndUp.value">
|
||||||
<!-- Vuetify 渲染模式 -->
|
<!-- Vuetify 渲染模式 -->
|
||||||
<VCard
|
<VCard v-if="renderMode === 'vuetify'" :title="`${props.plugin?.plugin_name} - ${t('dialog.pluginConfig.title')}`">
|
||||||
v-if="renderMode === 'vuetify'"
|
|
||||||
:title="`${props.plugin?.plugin_name} - ${t('dialog.pluginConfig.title')}`"
|
|
||||||
class="rounded-t"
|
|
||||||
>
|
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
|
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
|
||||||
@@ -182,7 +178,8 @@ onBeforeMount(async () => {
|
|||||||
</VCardActions>
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
<!-- Vue 渲染模式 -->
|
<!-- Vue 渲染模式 -->
|
||||||
<div v-else-if="renderMode === 'vue'">
|
<VCard v-else-if="renderMode === 'vue'">
|
||||||
|
<VCardText class="pa-0">
|
||||||
<component
|
<component
|
||||||
:is="dynamicComponent"
|
:is="dynamicComponent"
|
||||||
:initial-config="pluginConfigForm"
|
:initial-config="pluginConfigForm"
|
||||||
@@ -191,7 +188,9 @@ onBeforeMount(async () => {
|
|||||||
@switch="emit('switch')"
|
@switch="emit('switch')"
|
||||||
@close="emit('close')"
|
@close="emit('close')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
|
||||||
<!-- 进度框 -->
|
<!-- 进度框 -->
|
||||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
|
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
|
||||||
</VDialog>
|
</VDialog>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
|
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
|
||||||
<!-- Vuetify 渲染模式 -->
|
<!-- Vuetify 渲染模式 -->
|
||||||
<VCard v-if="renderMode === 'vuetify'" :title="`${props.plugin?.plugin_name}`" class="rounded-t">
|
<VCard v-if="renderMode === 'vuetify'" :title="`${props.plugin?.plugin_name}`">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
|
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
|
||||||
<VCardText v-else class="min-h-40">
|
<VCardText v-else class="min-h-40">
|
||||||
@@ -141,8 +141,16 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</VCard>
|
</VCard>
|
||||||
<!-- Vue 渲染模式 -->
|
<!-- Vue 渲染模式 -->
|
||||||
<div v-else-if="renderMode === 'vue'">
|
<VCard v-else-if="renderMode === 'vue'">
|
||||||
<component :is="dynamicComponent" :api="api" @action="handleAction" @switch="emit('switch')" @close="emit('close')" />
|
<VCardText class="pa-0">
|
||||||
</div>
|
<component
|
||||||
|
:is="dynamicComponent"
|
||||||
|
:api="api"
|
||||||
|
@action="handleAction"
|
||||||
|
@switch="emit('switch')"
|
||||||
|
@close="emit('close')"
|
||||||
|
/>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog width="50rem" scrollable max-height="85vh">
|
<VDialog width="50rem" scrollable max-height="85vh">
|
||||||
<VCard class="rounded-t">
|
<VCard>
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle>
|
<VCardTitle>
|
||||||
<VIcon icon="mdi-store-cog" class="me-2" />
|
<VIcon icon="mdi-store-cog" class="me-2" />
|
||||||
|
|||||||
@@ -44,12 +44,7 @@ async function handleReset() {
|
|||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('/storage/reset/rclone')
|
const result: { [key: string]: any } = await api.get('/storage/reset/rclone')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// 重置成功
|
|
||||||
alertType.value = 'success'
|
|
||||||
handleDone()
|
handleDone()
|
||||||
} else {
|
|
||||||
alertType.value = 'error'
|
|
||||||
text.value = result.message
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -59,7 +54,7 @@ async function handleReset() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog width="50rem" scrollable max-height="85vh">
|
<VDialog width="50rem" scrollable max-height="85vh">
|
||||||
<VCard :title="t('dialog.rcloneConfig.title')" class="rounded-t">
|
<VCard :title="t('dialog.rcloneConfig.title')">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VRow>
|
<VRow>
|
||||||
@@ -80,7 +75,7 @@ async function handleReset() {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn variant="elevated" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||||
{{ t('dialog.rcloneConfig.reset') }}
|
{{ t('dialog.rcloneConfig.reset') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
|
<VDialog scrollable max-width="45rem" :fullscreen="!display.mdAndUp.value">
|
||||||
<VCard :title="dialogTitle" class="rounded-t">
|
<VCard :title="dialogTitle">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ onMounted(async () => {
|
|||||||
:title="`${props.oper === 'add' ? t('site.actions.add') : t('site.actions.edit')}${t('site.title')}${
|
:title="`${props.oper === 'add' ? t('site.actions.add') : t('site.actions.edit')}${t('site.title')}${
|
||||||
props.oper !== 'add' ? ` - ${siteForm.name}` : ''
|
props.oper !== 'add' ? ` - ${siteForm.name}` : ''
|
||||||
}`"
|
}`"
|
||||||
class="rounded-t"
|
|
||||||
>
|
>
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ onBeforeMount(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog scrollable eager max-width="80rem" :fullscreen="!display.mdAndUp.value">
|
<VDialog scrollable eager max-width="80rem" :fullscreen="!display.mdAndUp.value">
|
||||||
<VCard class="rounded-t">
|
<VCard>
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle
|
<VCardTitle
|
||||||
>{{ t('dialog.siteUserData.title') }} - {{ props.site?.name }}
|
>{{ t('dialog.siteUserData.title') }} - {{ props.site?.name }}
|
||||||
|
|||||||
@@ -292,7 +292,6 @@ onMounted(() => {
|
|||||||
: '',
|
: '',
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
class="rounded-t"
|
|
||||||
>
|
>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ onBeforeMount(() => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
|
<VDialog scrollable max-width="80rem" :fullscreen="!display.mdAndUp.value">
|
||||||
<VCard class="rounded-t">
|
<VCard>
|
||||||
<VCardItem class="my-2">
|
<VCardItem class="my-2">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ const $toast = useToast()
|
|||||||
:title="`${t('dialog.subscribeShare.shareSubscription')} - ${props.sub?.name} ${
|
:title="`${t('dialog.subscribeShare.shareSubscription')} - ${props.sub?.name} ${
|
||||||
props.sub?.season ? t('dialog.subscribeShare.season', { number: props.sub?.season }) : ''
|
props.sub?.season ? t('dialog.subscribeShare.season', { number: props.sub?.season }) : ''
|
||||||
}`"
|
}`"
|
||||||
class="rounded-t"
|
|
||||||
>
|
>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog width="40rem" scrollable max-height="85vh">
|
<VDialog width="40rem" scrollable max-height="85vh">
|
||||||
<VCard :title="t('dialog.u115Auth.loginTitle')" class="rounded-t">
|
<VCard :title="t('dialog.u115Auth.loginTitle')">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VCardText class="pt-2 flex flex-col items-center">
|
<VCardText class="pt-2 flex flex-col items-center">
|
||||||
<div class="my-6 rounded text-center p-3 border">
|
<div class="my-6 rounded text-center p-3 border">
|
||||||
@@ -124,7 +124,7 @@ onUnmounted(() => {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn variant="elevated" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
<VBtn variant="tonal" color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
||||||
{{ t('dialog.u115Auth.reset') }}
|
{{ t('dialog.u115Auth.reset') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
<VBtn variant="elevated" @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
||||||
|
|||||||
@@ -295,7 +295,6 @@ onMounted(() => {
|
|||||||
:title="`${props.oper === 'add' ? t('dialog.userAddEdit.add') : t('dialog.userAddEdit.edit')}${
|
:title="`${props.oper === 'add' ? t('dialog.userAddEdit.add') : t('dialog.userAddEdit.edit')}${
|
||||||
props.oper !== 'add' ? ` - ${userName}` : ''
|
props.oper !== 'add' ? ` - ${userName}` : ''
|
||||||
}`"
|
}`"
|
||||||
class="rounded-t"
|
|
||||||
>
|
>
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog width="40rem" max-height="85vh">
|
<VDialog width="40rem" max-height="85vh">
|
||||||
<VCard :title="t('dialog.userAuth.title')" class="rounded-t">
|
<VCard :title="t('dialog.userAuth.title')">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VRow>
|
<VRow>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async function editWorkflow() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog scrollable :close-on-back="false" eager max-width="30rem" :fullscreen="!display.mdAndUp.value">
|
<VDialog scrollable :close-on-back="false" eager max-width="30rem" :fullscreen="!display.mdAndUp.value">
|
||||||
<VCard :title="title" class="rounded-t">
|
<VCard :title="title">
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ const sortIcon = computed(() => {
|
|||||||
<IconBtn @click="changeSort">
|
<IconBtn @click="changeSort">
|
||||||
<VIcon :icon="sortIcon" />
|
<VIcon :icon="sortIcon" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<IconBtn @click="goUp">
|
<IconBtn v-if="pathSegments.length > 0" @click="goUp">
|
||||||
<VIcon icon="mdi-arrow-up-bold-outline" />
|
<VIcon icon="mdi-arrow-up-bold-outline" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<VDialog v-model="newFolderPopper" max-width="35rem">
|
<VDialog v-model="newFolderPopper" max-width="35rem">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, defineAsyncComponent } from 'vue'
|
import api from '@/api'
|
||||||
import { DashboardItem } from '@/api/types'
|
import { DashboardItem } from '@/api/types'
|
||||||
import AnalyticsMediaStatistic from '@/views/dashboard/AnalyticsMediaStatistic.vue'
|
import AnalyticsMediaStatistic from '@/views/dashboard/AnalyticsMediaStatistic.vue'
|
||||||
import AnalyticsScheduler from '@/views/dashboard/AnalyticsScheduler.vue'
|
import AnalyticsScheduler from '@/views/dashboard/AnalyticsScheduler.vue'
|
||||||
@@ -88,7 +88,7 @@ onUnmounted(() => {
|
|||||||
<template v-else-if="!isNullOrEmptyObject(props.config)">
|
<template v-else-if="!isNullOrEmptyObject(props.config)">
|
||||||
<!-- Vue 渲染模式 -->
|
<!-- Vue 渲染模式 -->
|
||||||
<div v-if="pluginRenderMode === 'vue'">
|
<div v-if="pluginRenderMode === 'vue'">
|
||||||
<component :is="dynamicPluginComponent" :config="props.config" :allow-refresh="props.allowRefresh" />
|
<component :is="dynamicPluginComponent" :config="props.config" :allow-refresh="props.allowRefresh" :api="api" />
|
||||||
<!-- Vue 模式下也可以显示拖拽句柄 -->
|
<!-- Vue 模式下也可以显示拖拽句柄 -->
|
||||||
<div class="absolute right-5 top-5">
|
<div class="absolute right-5 top-5">
|
||||||
<VIcon class="cursor-move">mdi-drag</VIcon>
|
<VIcon class="cursor-move">mdi-drag</VIcon>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ onBeforeUnmount(() => {
|
|||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<div class="notification-list-container">
|
<div class="notification-list-container">
|
||||||
<div v-if="notificationList.length > 0" class="notification-list">
|
<div v-if="notificationList.length > 0" class="h-full overflow-y-auto">
|
||||||
<VListItem v-for="(item, i) in notificationList" :key="i" lines="two" class="mb-1">
|
<VListItem v-for="(item, i) in notificationList" :key="i" lines="two" class="mb-1">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VAvatar rounded>
|
<VAvatar rounded>
|
||||||
@@ -120,12 +120,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.notification-list-container {
|
.notification-list-container {
|
||||||
max-height: 50vh;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
max-block-size: 50vh;
|
||||||
|
|
||||||
.notification-list {
|
|
||||||
max-height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,12 +6,55 @@ import {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from 'virtual:__federation__'
|
} from 'virtual:__federation__'
|
||||||
|
|
||||||
|
// 扩展全局接口,添加federation所需的共享作用域
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__rf_placeholder__shareScope?: Record<string, any>
|
||||||
|
vue?: any
|
||||||
|
vuetify?: any
|
||||||
|
pinia?: any
|
||||||
|
'vue-i18n'?: any
|
||||||
|
'vue-router'?: any
|
||||||
|
axios?: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 定义远程模块接口
|
// 定义远程模块接口
|
||||||
interface RemoteModule {
|
interface RemoteModule {
|
||||||
id: string
|
id: string
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化共享作用域
|
||||||
|
*/
|
||||||
|
function initShareScope() {
|
||||||
|
// 确保全局共享作用域存在
|
||||||
|
if (!window.__rf_placeholder__shareScope) {
|
||||||
|
window.__rf_placeholder__shareScope = {}
|
||||||
|
}
|
||||||
|
// 为共享模块设置默认作用域
|
||||||
|
const shared = ['vue', 'vuetify', 'pinia', 'vue-i18n', 'vue-router', 'axios']
|
||||||
|
shared.forEach(lib => {
|
||||||
|
if (window.__rf_placeholder__shareScope) {
|
||||||
|
window.__rf_placeholder__shareScope[lib] = { default: { get: () => (window as any)[lib] } }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log('已初始化共享作用域:', window.__rf_placeholder__shareScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个dummy远程模块以解决生产环境中的共享作用域问题
|
||||||
|
*/
|
||||||
|
function addDummyRemote() {
|
||||||
|
__federation_method_setRemote('dummy', {
|
||||||
|
url: () => Promise.resolve(''),
|
||||||
|
format: 'esm',
|
||||||
|
from: 'vite',
|
||||||
|
shareScope: 'default',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载远程组件
|
* 加载远程组件
|
||||||
* @param id 远程模块ID
|
* @param id 远程模块ID
|
||||||
@@ -40,15 +83,39 @@ async function fetchRemoteModules(): Promise<RemoteModule[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成唯一的版本标记用于防止缓存
|
||||||
|
*/
|
||||||
|
function generateVersionTag(): string {
|
||||||
|
return `v=${Date.now()}`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态注入Federation Remote模块
|
* 动态注入Federation Remote模块
|
||||||
* @param modules 远程模块列表
|
* @param modules 远程模块列表
|
||||||
*/
|
*/
|
||||||
function injectRemoteModule(module: RemoteModule): void {
|
function injectRemoteModule(module: RemoteModule): void {
|
||||||
|
// 从浏览器地址栏获取当前地址前缀
|
||||||
|
const baseUrl = new URL(window.location.href)
|
||||||
|
// 环境变量
|
||||||
|
let apiBase = import.meta.env.VITE_API_BASE_URL
|
||||||
|
if (apiBase.startsWith('/')) {
|
||||||
|
apiBase = apiBase.slice(1)
|
||||||
|
}
|
||||||
|
if (apiBase.endsWith('/')) {
|
||||||
|
apiBase = apiBase.slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加版本标记防止缓存
|
||||||
|
const versionTag = generateVersionTag()
|
||||||
|
const remoteUrl = `${baseUrl.origin}/${apiBase}${module.url}`
|
||||||
|
const urlWithVersion = remoteUrl.includes('?') ? `${remoteUrl}&${versionTag}` : `${remoteUrl}?${versionTag}`
|
||||||
|
|
||||||
__federation_method_setRemote(module.id, {
|
__federation_method_setRemote(module.id, {
|
||||||
url: () => Promise.resolve(`${import.meta.env.VITE_API_BASE_URL}/${module.url}`),
|
url: () => Promise.resolve(urlWithVersion),
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
from: 'vite',
|
from: 'vite',
|
||||||
|
shareScope: 'default',
|
||||||
})
|
})
|
||||||
console.log('已注入远程模块:', module)
|
console.log('已注入远程模块:', module)
|
||||||
}
|
}
|
||||||
@@ -58,6 +125,12 @@ function injectRemoteModule(module: RemoteModule): void {
|
|||||||
*/
|
*/
|
||||||
export async function loadRemoteComponents(): Promise<void> {
|
export async function loadRemoteComponents(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
// 初始化共享作用域
|
||||||
|
initShareScope()
|
||||||
|
|
||||||
|
// 添加dummy远程模块解决生产环境问题
|
||||||
|
addDummyRemote()
|
||||||
|
|
||||||
// 获取远程模块列表
|
// 获取远程模块列表
|
||||||
const modules = await fetchRemoteModules()
|
const modules = await fetchRemoteModules()
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ const props = defineProps({
|
|||||||
// 是否刷新过
|
// 是否刷新过
|
||||||
let isRefreshed = ref(false)
|
let isRefreshed = ref(false)
|
||||||
|
|
||||||
// 顺序存储键值
|
|
||||||
const localOrderKey = props.type === t('media.movie') ? 'MP_SUBSCRIBE_MOVIE_ORDER' : 'MP_SUBSCRIBE_TV_ORDER'
|
|
||||||
const orderRequestKey = props.type === t('media.movie') ? 'SubscribeMovieOrder' : 'SubscribeTvOrder'
|
|
||||||
|
|
||||||
// 刷新状态
|
// 刷新状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
@@ -53,6 +49,10 @@ const orderConfig = ref<{ id: number }[]>([])
|
|||||||
// 显示的订阅列表
|
// 显示的订阅列表
|
||||||
const displayList = ref<Subscribe[]>([])
|
const displayList = ref<Subscribe[]>([])
|
||||||
|
|
||||||
|
// 顺序存储键值(计算属性)
|
||||||
|
const localOrderKey = computed(() => (props.type === '电影' ? 'MP_SUBSCRIBE_MOVIE_ORDER' : 'MP_SUBSCRIBE_TV_ORDER'))
|
||||||
|
const orderRequestKey = computed(() => (props.type === '电影' ? 'SubscribeMovieOrder' : 'SubscribeTvOrder'))
|
||||||
|
|
||||||
// 监听dataList变化,同步更新displayList
|
// 监听dataList变化,同步更新displayList
|
||||||
watch([dataList, () => props.keyword], () => {
|
watch([dataList, () => props.keyword], () => {
|
||||||
if (superUser)
|
if (superUser)
|
||||||
@@ -74,26 +74,27 @@ watch([dataList, () => props.keyword], () => {
|
|||||||
// 加载顺序
|
// 加载顺序
|
||||||
async function loadSubscribeOrderConfig() {
|
async function loadSubscribeOrderConfig() {
|
||||||
// 顺序配置
|
// 顺序配置
|
||||||
const local_order = localStorage.getItem(localOrderKey)
|
const local_order = localStorage.getItem(localOrderKey.value)
|
||||||
if (local_order) {
|
if (local_order) {
|
||||||
orderConfig.value = JSON.parse(local_order)
|
orderConfig.value = JSON.parse(local_order)
|
||||||
} else {
|
} else {
|
||||||
const response = await api.get(`/user/config/${orderRequestKey}`)
|
const response = await api.get(`/user/config/${orderRequestKey}`)
|
||||||
if (response && response.data && response.data.value) {
|
if (response && response.data && response.data.value) {
|
||||||
orderConfig.value = response.data.value
|
orderConfig.value = response.data.value
|
||||||
localStorage.setItem(localOrderKey, JSON.stringify(orderConfig.value))
|
localStorage.setItem(localOrderKey.value, JSON.stringify(orderConfig.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按order的顺序排序
|
// 按order的顺序排序
|
||||||
function sortSubscribeOrder() {
|
async function sortSubscribeOrder() {
|
||||||
if (!orderConfig.value) {
|
if (!orderConfig.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (displayList.value.length === 0) {
|
if (displayList.value.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
await loadSubscribeOrderConfig()
|
||||||
displayList.value.sort((a, b) => {
|
displayList.value.sort((a, b) => {
|
||||||
const aIndex = orderConfig.value.findIndex((item: { id: number }) => item.id === a.id)
|
const aIndex = orderConfig.value.findIndex((item: { id: number }) => item.id === a.id)
|
||||||
const bIndex = orderConfig.value.findIndex((item: { id: number }) => item.id === b.id)
|
const bIndex = orderConfig.value.findIndex((item: { id: number }) => item.id === b.id)
|
||||||
@@ -107,7 +108,7 @@ async function saveSubscribeOrder() {
|
|||||||
const orderObj = displayList.value.map(item => ({ id: item.id }))
|
const orderObj = displayList.value.map(item => ({ id: item.id }))
|
||||||
orderConfig.value = orderObj
|
orderConfig.value = orderObj
|
||||||
const orderString = JSON.stringify(orderObj)
|
const orderString = JSON.stringify(orderObj)
|
||||||
localStorage.setItem(localOrderKey, orderString)
|
localStorage.setItem(localOrderKey.value, orderString)
|
||||||
|
|
||||||
// 保存到服务端
|
// 保存到服务端
|
||||||
try {
|
try {
|
||||||
@@ -136,7 +137,6 @@ function historyDone() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSubscribeOrderConfig()
|
|
||||||
await fetchData()
|
await fetchData()
|
||||||
if (props.subid) {
|
if (props.subid) {
|
||||||
// 找到这个订阅
|
// 找到这个订阅
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ const groupedDataList = ref<Map<string, Context[]>>()
|
|||||||
const filterMenuOpen = ref(false)
|
const filterMenuOpen = ref(false)
|
||||||
const currentFilter = ref('site')
|
const currentFilter = ref('site')
|
||||||
|
|
||||||
|
const currentFilterTitle = computed(() => filterTitles[currentFilter.value])
|
||||||
|
const currentFilterOptions = computed(() => {
|
||||||
|
return filterOptions[currentFilter.value]
|
||||||
|
})
|
||||||
|
|
||||||
// 添加全部筛选菜单相关
|
// 添加全部筛选菜单相关
|
||||||
const allFilterMenuOpen = ref(false)
|
const allFilterMenuOpen = ref(false)
|
||||||
|
|
||||||
@@ -522,7 +527,23 @@ function loadMore({ done }: { done: any }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 筛选图标按钮区域 -->
|
<!-- 筛选图标按钮区域 -->
|
||||||
<div class="filter-buttons-grid w-100">
|
<div class="filter-buttons-grid w-100 mt-2">
|
||||||
|
<!-- 全部筛选按钮 -->
|
||||||
|
<VBtn variant="text" color="primary" class="filter-btn-mobile" @click="toggleAllFilterMenu">
|
||||||
|
<VIcon icon="mdi-filter-variant" class="filter-icon me-1"></VIcon>
|
||||||
|
<span class="filter-label">
|
||||||
|
{{ t('torrent.allFilters') }}
|
||||||
|
</span>
|
||||||
|
<VBadge
|
||||||
|
v-if="getFilterCount > 0"
|
||||||
|
:content="getFilterCount"
|
||||||
|
color="primary"
|
||||||
|
location="top end"
|
||||||
|
offset-x="-10"
|
||||||
|
offset-y="-10"
|
||||||
|
></VBadge>
|
||||||
|
</VBtn>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
v-for="(title, key) in filterTitles"
|
v-for="(title, key) in filterTitles"
|
||||||
v-show="filterOptions[key].length > 0"
|
v-show="filterOptions[key].length > 0"
|
||||||
@@ -550,7 +571,7 @@ function loadMore({ done }: { done: any }) {
|
|||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<!-- 全部筛选弹窗 -->
|
<!-- 全部筛选弹窗 -->
|
||||||
<VDialog v-model="allFilterMenuOpen" max-width="50rem" location="center" scrollable>
|
<VDialog v-model="allFilterMenuOpen" max-width="50rem" max-height="90%" location="center" scrollable>
|
||||||
<VCard>
|
<VCard>
|
||||||
<VDialogCloseBtn @click="allFilterMenuOpen = false" />
|
<VDialogCloseBtn @click="allFilterMenuOpen = false" />
|
||||||
<VCardTitle class="py-3 d-flex align-center">
|
<VCardTitle class="py-3 d-flex align-center">
|
||||||
@@ -619,6 +640,51 @@ function loadMore({ done }: { done: any }) {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
|
|
||||||
|
<!-- 筛选弹窗 -->
|
||||||
|
<VDialog v-model="filterMenuOpen" max-width="25rem" max-height="80%" location="center">
|
||||||
|
<VCard>
|
||||||
|
<VCardTitle class="py-3 d-flex align-center">
|
||||||
|
<VIcon :icon="getFilterIcon(currentFilter)" class="me-2"></VIcon>
|
||||||
|
<span>{{ currentFilterTitle }}</span>
|
||||||
|
<VSpacer />
|
||||||
|
<VBtn
|
||||||
|
v-if="filterForm[currentFilter].length > 0"
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
@click="clearFilter(currentFilter)"
|
||||||
|
>
|
||||||
|
{{ t('torrent.clear') }}
|
||||||
|
</VBtn>
|
||||||
|
<VBtn variant="text" size="small" color="primary" @click="selectAll(currentFilter)">
|
||||||
|
{{ t('torrent.selectAll') }}
|
||||||
|
</VBtn>
|
||||||
|
</VCardTitle>
|
||||||
|
<VDivider />
|
||||||
|
<VCardText>
|
||||||
|
<VChipGroup v-model="filterForm[currentFilter]" column multiple class="filter-options">
|
||||||
|
<VChip
|
||||||
|
v-for="option in currentFilterOptions"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
filter
|
||||||
|
variant="elevated"
|
||||||
|
class="ma-1 filter-chip"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</VCardText>
|
||||||
|
<VCardActions>
|
||||||
|
<VSpacer />
|
||||||
|
<VBtn variant="elevated" color="primary" @click="filterMenuOpen = false">
|
||||||
|
{{ t('torrent.confirm') }}
|
||||||
|
</VBtn>
|
||||||
|
</VCardActions>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
|
|
||||||
<!-- 资源列表 -->
|
<!-- 资源列表 -->
|
||||||
<VInfiniteScroll mode="intersect" side="end" :items="displayDataList" class="overflow-visible" @load="loadMore">
|
<VInfiniteScroll mode="intersect" side="end" :items="displayDataList" class="overflow-visible" @load="loadMore">
|
||||||
<template #loading />
|
<template #loading />
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ onMounted(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<!-- 全部筛选弹窗 -->
|
<!-- 全部筛选弹窗 -->
|
||||||
<VDialog v-model="allFilterMenuOpen" max-width="50rem" location="center" scrollable>
|
<VDialog v-model="allFilterMenuOpen" max-width="50rem" max-height="90%" location="center" scrollable>
|
||||||
<VCard>
|
<VCard>
|
||||||
<VDialogCloseBtn @click="allFilterMenuOpen = false" />
|
<VDialogCloseBtn @click="allFilterMenuOpen = false" />
|
||||||
<VCardTitle class="py-3 d-flex align-center">
|
<VCardTitle class="py-3 d-flex align-center">
|
||||||
|
|||||||
+1
-1
@@ -167,7 +167,7 @@ export default defineConfig({
|
|||||||
minify: 'terser',
|
minify: 'terser',
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
compress: {
|
compress: {
|
||||||
drop_console: true,
|
drop_console: false,
|
||||||
drop_debugger: false,
|
drop_debugger: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user