mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-13 08:59:58 +08:00
增强配置向导功能
This commit is contained in:
@@ -45,6 +45,16 @@ export interface WizardData {
|
||||
quality: string
|
||||
subtitle: string
|
||||
resolution: string
|
||||
personalizationOptions?: {
|
||||
excludeDolbyVision: boolean
|
||||
excludeBluray: boolean
|
||||
}
|
||||
ruleSequences?: Array<{
|
||||
name: string
|
||||
rule_string: string
|
||||
media_type: string
|
||||
category: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +91,9 @@ export interface ValidationErrorState {
|
||||
const currentStep = ref(1)
|
||||
const totalSteps = 6
|
||||
|
||||
// 加载状态
|
||||
const isLoading = ref(false)
|
||||
|
||||
// 选中的预设规则
|
||||
const selectedPreset = ref('')
|
||||
|
||||
@@ -298,6 +311,15 @@ export function useSetupWizard() {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新偏好设置
|
||||
function updatePreferences(
|
||||
personalizationOptions: { excludeDolbyVision: boolean; excludeBluray: boolean },
|
||||
ruleSequences: Array<{ name: string; rule_string: string; media_type: string; category: string }>,
|
||||
) {
|
||||
wizardData.value.preferences.personalizationOptions = personalizationOptions
|
||||
wizardData.value.preferences.ruleSequences = ruleSequences
|
||||
}
|
||||
|
||||
// 清除验证错误状态
|
||||
function clearValidationErrors() {
|
||||
validationErrors.value.downloader = {
|
||||
@@ -412,7 +434,6 @@ export function useSetupWizard() {
|
||||
|
||||
// 根据通知类型验证必输项
|
||||
const config = wizardData.value.notification.config || {}
|
||||
alert(wizardData.value.notification.type)
|
||||
switch (wizardData.value.notification.type) {
|
||||
case 'wechat':
|
||||
if (!config.WECHAT_CORPID?.trim()) {
|
||||
@@ -591,11 +612,9 @@ export function useSetupWizard() {
|
||||
// 根据结果显示不同的消息
|
||||
if (testResult.success) {
|
||||
connectivityTest.value.testMessage = t('setupWizard.connectivityTestSuccess')
|
||||
$toast.success(t('setupWizard.connectivityTestSuccess'))
|
||||
} else {
|
||||
// 显示API返回的具体错误原因
|
||||
connectivityTest.value.testMessage = testResult.message || t('setupWizard.connectivityTestFailed')
|
||||
$toast.error(testResult.message || t('setupWizard.connectivityTestFailed'))
|
||||
}
|
||||
|
||||
// 成功时2秒后隐藏结果,失败时保持显示直到用户操作
|
||||
@@ -611,7 +630,6 @@ export function useSetupWizard() {
|
||||
connectivityTest.value.testResult = 'error'
|
||||
connectivityTest.value.showResult = true
|
||||
connectivityTest.value.testMessage = (error as Error).message || t('setupWizard.connectivityTestFailed')
|
||||
$toast.error((error as Error).message || t('setupWizard.connectivityTestFailed'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -757,29 +775,30 @@ export function useSetupWizard() {
|
||||
|
||||
// 下一步
|
||||
async function nextStep() {
|
||||
if (currentStep.value < totalSteps) {
|
||||
// 验证当前步骤的必输项
|
||||
const validation = validateCurrentStep()
|
||||
if (!validation.isValid) {
|
||||
// 显示验证错误
|
||||
validation.errors.forEach(error => {
|
||||
$toast.error(error)
|
||||
})
|
||||
// 验证当前步骤的必输项
|
||||
const validation = validateCurrentStep()
|
||||
if (!validation.isValid) {
|
||||
// 显示验证错误
|
||||
validation.errors.forEach(error => {
|
||||
$toast.error(error)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 保存当前步骤的设置
|
||||
await saveCurrentStepSettings()
|
||||
|
||||
// 检查是否需要进行测试
|
||||
const needsTest = shouldPerformTest(currentStep.value)
|
||||
if (needsTest) {
|
||||
const testResult = await testConnectivity(currentStep.value)
|
||||
if (!testResult) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前步骤的设置
|
||||
await saveCurrentStepSettings()
|
||||
|
||||
// 检查是否需要进行测试
|
||||
const needsTest = shouldPerformTest(currentStep.value)
|
||||
if (needsTest) {
|
||||
const testResult = await testConnectivity(currentStep.value)
|
||||
if (!testResult) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是最后一步,则前进到下一步
|
||||
if (currentStep.value < totalSteps) {
|
||||
currentStep.value++
|
||||
connectivityTest.value.showResult = false
|
||||
}
|
||||
@@ -825,7 +844,7 @@ export function useSetupWizard() {
|
||||
// 完成向导
|
||||
async function completeWizard() {
|
||||
try {
|
||||
// 先处理下一步
|
||||
// 先处理下一步(保存当前步骤设置)
|
||||
await nextStep()
|
||||
// 保存设置向导完成状态
|
||||
await saveSetupWizardState()
|
||||
@@ -855,7 +874,6 @@ export function useSetupWizard() {
|
||||
}
|
||||
|
||||
await api.put(`user/${currentUser.id}`, userData)
|
||||
$toast.success(t('setupWizard.passwordUpdateSuccess'))
|
||||
} else {
|
||||
// 如果用户不存在,创建新用户(通常不会发生)
|
||||
const userData = {
|
||||
@@ -866,11 +884,9 @@ export function useSetupWizard() {
|
||||
}
|
||||
|
||||
await api.post('user/', userData)
|
||||
$toast.success(t('setupWizard.userCreateSuccess'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Update user password failed:', error)
|
||||
$toast.error(t('setupWizard.passwordUpdateFailed'))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -888,9 +904,7 @@ export function useSetupWizard() {
|
||||
|
||||
// 保存基础设置
|
||||
const response: { [key: string]: any } = await api.post('system/env', basicSettings)
|
||||
if (response.success) {
|
||||
$toast.success(t('setupWizard.basicSettingsSaved'))
|
||||
} else {
|
||||
if (!response.success) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -943,10 +957,7 @@ export function useSetupWizard() {
|
||||
library_category_folder: true,
|
||||
}
|
||||
|
||||
const response: { [key: string]: any } = await api.post('system/setting/Directories', [directory])
|
||||
if (response.success) {
|
||||
$toast.success(t('setupWizard.storageSettingsSaved'))
|
||||
}
|
||||
await api.post('system/setting/Directories', [directory])
|
||||
} catch (error) {
|
||||
console.error('Save storage settings failed:', error)
|
||||
$toast.error(t('setupWizard.saveStorageSettingsFailed'))
|
||||
@@ -968,10 +979,7 @@ export function useSetupWizard() {
|
||||
config: config,
|
||||
}
|
||||
|
||||
const response: { [key: string]: any } = await api.post('system/setting/Downloaders', [downloader])
|
||||
if (response.success) {
|
||||
$toast.success(t('setupWizard.downloaderSettingsSaved'))
|
||||
}
|
||||
await api.post('system/setting/Downloaders', [downloader])
|
||||
} catch (error) {
|
||||
console.error('Save downloader settings failed:', error)
|
||||
$toast.error(t('setupWizard.saveDownloaderSettingsFailed'))
|
||||
@@ -998,10 +1006,7 @@ export function useSetupWizard() {
|
||||
sync_libraries: sync_libraries,
|
||||
}
|
||||
|
||||
const response: { [key: string]: any } = await api.post('system/setting/MediaServers', [mediaServer])
|
||||
if (response.success) {
|
||||
$toast.success(t('setupWizard.mediaServerSettingsSaved'))
|
||||
}
|
||||
await api.post('system/setting/MediaServers', [mediaServer])
|
||||
} catch (error) {
|
||||
console.error('Save media server settings failed:', error)
|
||||
$toast.error(t('setupWizard.saveMediaServerSettingsFailed'))
|
||||
@@ -1028,10 +1033,7 @@ export function useSetupWizard() {
|
||||
switchs: switchs,
|
||||
}
|
||||
|
||||
const response: { [key: string]: any } = await api.post('system/setting/Notifications', [notification])
|
||||
if (response.success) {
|
||||
$toast.success(t('setupWizard.notificationSettingsSaved'))
|
||||
}
|
||||
await api.post('system/setting/Notifications', [notification])
|
||||
} catch (error) {
|
||||
console.error('Save notification settings failed:', error)
|
||||
$toast.error(t('setupWizard.saveNotificationSettingsFailed'))
|
||||
@@ -1045,17 +1047,27 @@ export function useSetupWizard() {
|
||||
// 保存资源偏好设置
|
||||
async function savePreferenceSettings() {
|
||||
try {
|
||||
// 这里可以根据偏好设置创建相应的过滤规则
|
||||
// 暂时保存到系统设置中
|
||||
const preferenceSettings = {
|
||||
QUALITY_PREFERENCE: wizardData.value.preferences.quality,
|
||||
SUBTITLE_PREFERENCE: wizardData.value.preferences.subtitle,
|
||||
RESOLUTION_PREFERENCE: wizardData.value.preferences.resolution,
|
||||
}
|
||||
// 如果有自定义规则序列,保存到用户过滤规则组
|
||||
if (wizardData.value.preferences.ruleSequences && wizardData.value.preferences.ruleSequences.length > 0) {
|
||||
try {
|
||||
// 保存当前选中的规则组到 UserFilterRuleGroups
|
||||
const filterResponse: { [key: string]: any } = await api.post(
|
||||
'system/setting/UserFilterRuleGroups',
|
||||
wizardData.value.preferences.ruleSequences,
|
||||
)
|
||||
if (filterResponse.success) {
|
||||
// 保存规则组名称到其他设置
|
||||
const ruleGroupNames = wizardData.value.preferences.ruleSequences.map(rule => [rule.name])
|
||||
|
||||
const response: { [key: string]: any } = await api.post('system/env', preferenceSettings)
|
||||
if (response.success) {
|
||||
$toast.success(t('setupWizard.preferenceSettingsSaved'))
|
||||
// 保存到 SubscribeFilterRuleGroups
|
||||
await api.post('system/setting/SubscribeFilterRuleGroups', ruleGroupNames)
|
||||
|
||||
// 保存到 BestVersionFilterRuleGroups
|
||||
await api.post('system/setting/BestVersionFilterRuleGroups', ruleGroupNames)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Save rule sequences failed:', error)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Save preference settings failed:', error)
|
||||
@@ -1172,34 +1184,18 @@ export function useSetupWizard() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载资源偏好设置
|
||||
async function loadPreferenceSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
if (result.data.QUALITY_PREFERENCE) {
|
||||
wizardData.value.preferences.quality = result.data.QUALITY_PREFERENCE
|
||||
}
|
||||
if (result.data.SUBTITLE_PREFERENCE) {
|
||||
wizardData.value.preferences.subtitle = result.data.SUBTITLE_PREFERENCE
|
||||
}
|
||||
if (result.data.RESOLUTION_PREFERENCE) {
|
||||
wizardData.value.preferences.resolution = result.data.RESOLUTION_PREFERENCE
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Load preference settings failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
async function initialize() {
|
||||
await loadSystemSettings()
|
||||
await loadStorageSettings()
|
||||
await loadDownloaderSettings()
|
||||
await loadMediaServerSettings()
|
||||
await loadNotificationSettings()
|
||||
await loadPreferenceSettings()
|
||||
isLoading.value = true
|
||||
try {
|
||||
await loadSystemSettings()
|
||||
await loadStorageSettings()
|
||||
await loadDownloaderSettings()
|
||||
await loadMediaServerSettings()
|
||||
await loadNotificationSettings()
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1212,6 +1208,7 @@ export function useSetupWizard() {
|
||||
selectedPreset,
|
||||
connectivityTest,
|
||||
validationErrors,
|
||||
isLoading,
|
||||
|
||||
// 方法
|
||||
createRandomString,
|
||||
@@ -1220,6 +1217,7 @@ export function useSetupWizard() {
|
||||
selectMediaServer,
|
||||
selectNotification,
|
||||
selectPreset,
|
||||
updatePreferences,
|
||||
validateCurrentStep,
|
||||
validateDownloaderFields,
|
||||
validateMediaServerFields,
|
||||
|
||||
@@ -2914,6 +2914,7 @@ export default {
|
||||
completed: 'Setup Wizard completed!',
|
||||
failed: 'Setup Wizard failed, please try again',
|
||||
complete: 'Complete Configuration',
|
||||
loading: 'Loading configuration data...',
|
||||
testing: 'Testing',
|
||||
connectivityTestSuccess: 'Connectivity test passed',
|
||||
connectivityTestFailed: 'Connectivity test failed',
|
||||
@@ -3025,6 +3026,32 @@ export default {
|
||||
resolutionHint: 'Select preferred video resolution',
|
||||
presetRules: 'Preset Rules',
|
||||
detailedConfig: 'Detailed Configuration',
|
||||
quickPresets: 'Quick Presets',
|
||||
quickPresetsDesc: 'Select preset configuration, system will automatically apply corresponding rules',
|
||||
personalizationOptions: 'Personalization Options',
|
||||
personalizationOptionsDesc: 'Adjust rules according to your needs',
|
||||
excludeDolbyVision: 'Exclude Dolby Vision',
|
||||
excludeDolbyVisionHint: 'Exclude Dolby Vision resources from rules when selected',
|
||||
excludeBluray: 'Exclude Blu-ray',
|
||||
excludeBlurayHint: 'Exclude Blu-ray resources from rules when selected',
|
||||
presets: {
|
||||
'4k-enthusiast': {
|
||||
name: '4K Enthusiast',
|
||||
description: 'Pursue the highest quality, prioritize 4K',
|
||||
},
|
||||
'balanced': {
|
||||
name: 'Balanced Mode',
|
||||
description: 'Balance between quality and storage space',
|
||||
},
|
||||
'space-saver': {
|
||||
name: 'Space Saver',
|
||||
description: 'Prioritize smaller files to save storage space',
|
||||
},
|
||||
'free-priority': {
|
||||
name: 'Free Priority',
|
||||
description: 'Prioritize free resources, no other requirements',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2881,6 +2881,7 @@ export default {
|
||||
completed: '配置向导完成!',
|
||||
failed: '配置向导失败,请重试',
|
||||
complete: '完成配置',
|
||||
loading: '正在加载配置数据...',
|
||||
testing: '正在测试',
|
||||
connectivityTestSuccess: '连通性测试通过',
|
||||
connectivityTestFailed: '连通性测试失败',
|
||||
@@ -3011,6 +3012,32 @@ export default {
|
||||
resolutionHint: '选择偏好的视频分辨率',
|
||||
presetRules: '预设规则',
|
||||
detailedConfig: '详细配置',
|
||||
quickPresets: '快速预设',
|
||||
quickPresetsDesc: '选择预设配置,系统将自动应用对应的规则',
|
||||
personalizationOptions: '个性化选项',
|
||||
personalizationOptionsDesc: '根据您的需求调整规则',
|
||||
excludeDolbyVision: '排除杜比视界',
|
||||
excludeDolbyVisionHint: '选中后规则中将排除杜比视界资源',
|
||||
excludeBluray: '排除蓝光原盘',
|
||||
excludeBlurayHint: '选中后规则中将排除蓝光原盘资源',
|
||||
presets: {
|
||||
'4k-enthusiast': {
|
||||
name: '4K发烧友',
|
||||
description: '追求最高画质,优先4K',
|
||||
},
|
||||
'balanced': {
|
||||
name: '平衡模式',
|
||||
description: '画质与存储空间的平衡选择',
|
||||
},
|
||||
'space-saver': {
|
||||
name: '节省空间',
|
||||
description: '优先较小文件,节省存储空间',
|
||||
},
|
||||
'free-priority': {
|
||||
name: '免费优先',
|
||||
description: '优先免费资源,其它的没有要求',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2869,6 +2869,7 @@ export default {
|
||||
completed: '設定精靈完成!',
|
||||
failed: '設定精靈失敗,請重試',
|
||||
complete: '完成設定',
|
||||
loading: '正在載入配置資料...',
|
||||
testing: '正在測試',
|
||||
connectivityTestSuccess: '連通性測試通過',
|
||||
connectivityTestFailed: '連通性測試失敗',
|
||||
@@ -2979,6 +2980,32 @@ export default {
|
||||
resolutionHint: '選擇偏好的影片解析度',
|
||||
presetRules: '預設規則',
|
||||
detailedConfig: '詳細設定',
|
||||
quickPresets: '快速預設',
|
||||
quickPresetsDesc: '選擇預設配置,系統將自動應用對應的規則',
|
||||
personalizationOptions: '個性化選項',
|
||||
personalizationOptionsDesc: '根據您的需求調整規則',
|
||||
excludeDolbyVision: '排除杜比視界',
|
||||
excludeDolbyVisionHint: '選中後規則中將排除杜比視界資源',
|
||||
excludeBluray: '排除藍光原盤',
|
||||
excludeBlurayHint: '選中後規則中將排除藍光原盤資源',
|
||||
presets: {
|
||||
'4k-enthusiast': {
|
||||
name: '4K發燒友',
|
||||
description: '追求最高畫質,優先4K',
|
||||
},
|
||||
'balanced': {
|
||||
name: '平衡模式',
|
||||
description: '畫質與儲存空間的平衡選擇',
|
||||
},
|
||||
'space-saver': {
|
||||
name: '節省空間',
|
||||
description: '優先較小檔案,節省儲存空間',
|
||||
},
|
||||
'free-priority': {
|
||||
name: '免費優先',
|
||||
description: '優先免費資源,其它的沒有要求',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,8 +18,17 @@ const router = useRouter()
|
||||
// 显示器宽度
|
||||
const display = useDisplay()
|
||||
|
||||
const { currentStep, totalSteps, stepTitles, connectivityTest, nextStep, prevStep, completeWizard, initialize } =
|
||||
useSetupWizard()
|
||||
const {
|
||||
currentStep,
|
||||
totalSteps,
|
||||
stepTitles,
|
||||
connectivityTest,
|
||||
nextStep,
|
||||
prevStep,
|
||||
completeWizard,
|
||||
initialize,
|
||||
isLoading,
|
||||
} = useSetupWizard()
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
@@ -60,8 +69,14 @@ onMounted(async () => {
|
||||
<!-- 向导内容 -->
|
||||
<VCard max-width="800px" class="mx-auto my-7">
|
||||
<VCardText>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isLoading" class="d-flex flex-column align-center justify-center py-16">
|
||||
<VProgressCircular indeterminate color="primary" size="64" class="mb-4" />
|
||||
<p class="text-body-1 text-medium-emphasis">{{ t('setupWizard.loading') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 使用 VStepper 组件 -->
|
||||
<VStepper v-model="currentStep" class="elevation-0" flat alt-labels :mobile="display.smAndDown.value">
|
||||
<VStepper v-else v-model="currentStep" class="elevation-0" flat alt-labels :mobile="display.smAndDown.value">
|
||||
<!-- 步骤标题 -->
|
||||
<VStepperHeader class="elevation-0">
|
||||
<template v-for="(step, index) in stepTitles" :key="index">
|
||||
|
||||
@@ -250,10 +250,12 @@ const { wizardData, selectDownloader, validationErrors } = useSetupWizard()
|
||||
|
||||
/* 选中状态的样式 */
|
||||
.v-card--variant-tonal.v-theme--light {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.12);
|
||||
}
|
||||
|
||||
.v-card--variant-tonal.v-theme--dark {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -485,10 +485,12 @@ watch(
|
||||
|
||||
/* 选中状态的样式 */
|
||||
.v-card--variant-tonal.v-theme--light {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.12);
|
||||
}
|
||||
|
||||
.v-card--variant-tonal.v-theme--dark {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -541,10 +541,12 @@ const notificationTypes = [
|
||||
|
||||
/* 选中状态的样式 */
|
||||
.v-card--variant-tonal.v-theme--light {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.12);
|
||||
}
|
||||
|
||||
.v-card--variant-tonal.v-theme--dark {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,30 +1,143 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useSetupWizard } from '@/composables/useSetupWizard'
|
||||
import api from '@/api'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wizardData, selectedPreset, selectPreset } = useSetupWizard()
|
||||
const { updatePreferences } = useSetupWizard()
|
||||
|
||||
// 质量偏好选项
|
||||
const qualityItems = [
|
||||
{ title: '4K 优先', value: '4K' },
|
||||
{ title: '1080P 优先', value: '1080P' },
|
||||
{ title: '720P 优先', value: '720P' },
|
||||
]
|
||||
// 个性化选项
|
||||
const personalizationOptions = ref({
|
||||
excludeDolbyVision: true, // 排除杜比视界
|
||||
excludeBluray: true, // 排除蓝光原盘
|
||||
})
|
||||
|
||||
// 字幕偏好选项
|
||||
const subtitleItems = [
|
||||
{ title: '中文字幕优先', value: 'chinese' },
|
||||
{ title: '英文字幕优先', value: 'english' },
|
||||
{ title: '双语字幕优先', value: 'bilingual' },
|
||||
]
|
||||
// 预设配置 - 使用多语言
|
||||
const presetConfigs = computed(() => ({
|
||||
'4k-enthusiast': {
|
||||
name: t('setupWizard.preferences.presets.4k-enthusiast.name'),
|
||||
description: t('setupWizard.preferences.presets.4k-enthusiast.description'),
|
||||
icon: 'mdi-4k',
|
||||
color: 'primary',
|
||||
ruleString:
|
||||
' SPECSUB & 4K & 60FPS & UHD & !BLU & !DOLBY > CNSUB & 4K & 60FPS & UHD & !BLU & !DOLBY > 4K & 60FPS & UHD & !BLU & !DOLBY > SPECSUB & 4K & UHD & !BLU & !DOLBY > CNSUB & 4K & UHD & !BLU & !DOLBY > 4K & UHD & !BLU & !DOLBY > SPECSUB & 4K & !BLU & !DOLBY > CNSUB & 4K & !BLU & !DOLBY > 4K & !BLU & !DOLBY ',
|
||||
},
|
||||
'balanced': {
|
||||
name: t('setupWizard.preferences.presets.balanced.name'),
|
||||
description: t('setupWizard.preferences.presets.balanced.description'),
|
||||
icon: 'mdi-scale-unbalanced',
|
||||
color: 'success',
|
||||
ruleString:
|
||||
' SPECSUB & 4K & !BLU & !DOLBY & !UHD & !60FPS > CNSUB & 4K & !BLU & !DOLBY & !REMUX & !60FPS > SPECSUB & 1080P & !BLU & !DOLBY & !60FPS & !UHD > CNSUB & 1080P & !BLU & !DOLBY & !UHD & !60FPS > 4K & BLU & !DOLBY & !UHD & !60FPS > 1080P & !BLU & !DOLBY & !UHD & !60FPS ',
|
||||
},
|
||||
'space-saver': {
|
||||
name: t('setupWizard.preferences.presets.space-saver.name'),
|
||||
description: t('setupWizard.preferences.presets.space-saver.description'),
|
||||
icon: 'mdi-harddisk',
|
||||
color: 'warning',
|
||||
ruleString:
|
||||
' SPECSUB & 1080P & !BLU & !UHD & !60FPS & !DOLBY > CNSUB & 1080P & !BLU & !UHD & !60FPS & !DOLBY > 1080P & !BLU & !UHD & !60FPS & !DOLBY > !BLU & !UHD & !60FPS & !DOLBY ',
|
||||
},
|
||||
'free-priority': {
|
||||
name: t('setupWizard.preferences.presets.free-priority.name'),
|
||||
description: t('setupWizard.preferences.presets.free-priority.description'),
|
||||
icon: 'mdi-gift',
|
||||
color: 'info',
|
||||
ruleString:
|
||||
' SPECSUB & FREE & !BLU & !DOLBY > CNSUB & FREE & !BLU & !DOLBY > FREE & !BLU & !DOLBY > !BLU & !DOLBY ',
|
||||
},
|
||||
}))
|
||||
|
||||
// 分辨率选项
|
||||
const resolutionItems = [
|
||||
{ title: '2160P (4K)', value: '2160p' },
|
||||
{ title: '1080P', value: '1080p' },
|
||||
{ title: '720P', value: '720p' },
|
||||
]
|
||||
// 当前选中的预设
|
||||
const selectedPreset = ref('')
|
||||
|
||||
// 加载用户当前的规则组设置
|
||||
async function loadUserFilterRuleGroups() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/UserFilterRuleGroups')
|
||||
if (result.success && result.data?.value && result.data.value.length > 0) {
|
||||
const userRuleGroups = result.data.value
|
||||
|
||||
// 查找匹配的预设
|
||||
for (const [presetKey, preset] of Object.entries(presetConfigs.value)) {
|
||||
const matchingRule = userRuleGroups.find((rule: any) => rule.name === preset.name)
|
||||
if (matchingRule) {
|
||||
selectedPreset.value = presetKey
|
||||
|
||||
// 分析规则字符串,判断个性化选项
|
||||
const ruleString = matchingRule.rule_string || ''
|
||||
personalizationOptions.value.excludeDolbyVision = ruleString.includes('!DOLBY')
|
||||
personalizationOptions.value.excludeBluray = ruleString.includes('!BLU')
|
||||
|
||||
// 更新向导数据
|
||||
updateWizardData()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Load user filter rule groups failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 选择预设
|
||||
function selectPreset(presetKey: string) {
|
||||
if (selectedPreset.value === presetKey) {
|
||||
// 如果再次点击同一个预设,则取消选择
|
||||
selectedPreset.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
selectedPreset.value = presetKey
|
||||
updateWizardData()
|
||||
}
|
||||
|
||||
// 生成规则序列的逻辑
|
||||
const generateRuleSequences = computed(() => {
|
||||
if (!selectedPreset.value) {
|
||||
return []
|
||||
}
|
||||
|
||||
const preset = presetConfigs.value[selectedPreset.value as keyof typeof presetConfigs.value]
|
||||
if (!preset) {
|
||||
return []
|
||||
}
|
||||
|
||||
let ruleString = preset.ruleString
|
||||
|
||||
// 根据个性化选项调整规则
|
||||
if (!personalizationOptions.value.excludeDolbyVision) {
|
||||
// 移除所有 !DOLBY 条件
|
||||
ruleString = ruleString.replace(/ & !DOLBY/g, '').replace(/!DOLBY & /g, '')
|
||||
}
|
||||
|
||||
if (!personalizationOptions.value.excludeBluray) {
|
||||
// 移除所有 !BLU 条件
|
||||
ruleString = ruleString.replace(/ & !BLU/g, '').replace(/!BLU & /g, '')
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
name: preset.name,
|
||||
rule_string: ruleString,
|
||||
media_type: '',
|
||||
category: '',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
// 监听偏好变化,更新到wizardData
|
||||
function updateWizardData() {
|
||||
if (updatePreferences) {
|
||||
updatePreferences(personalizationOptions.value, generateRuleSequences.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载用户设置
|
||||
onMounted(() => {
|
||||
loadUserFilterRuleGroups()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -34,105 +147,70 @@ const resolutionItems = [
|
||||
<h3 class="text-h4 mb-2">{{ t('setupWizard.preferences.title') }}</h3>
|
||||
<p class="text-body-1 text-medium-emphasis">{{ t('setupWizard.preferences.description') }}</p>
|
||||
</div>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VAlert type="info" variant="tonal" class="mb-4">
|
||||
<VAlertTitle>{{ t('setupWizard.preferences.info') }}</VAlertTitle>
|
||||
{{ t('setupWizard.preferences.infoDesc') }}
|
||||
</VAlert>
|
||||
</VCol>
|
||||
|
||||
<!-- 预设规则选择 -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardTitle class="text-h6">{{ t('setupWizard.preferences.presetRules') }}</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12" md="4">
|
||||
<VCard
|
||||
:color="selectedPreset === '4k' ? 'primary' : 'default'"
|
||||
:variant="selectedPreset === '4k' ? 'tonal' : 'outlined'"
|
||||
class="cursor-pointer"
|
||||
@click="selectPreset('4k')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VIcon icon="mdi-4k" size="48" class="mb-2" />
|
||||
<div class="text-h6">4K 优先</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VCard
|
||||
:color="selectedPreset === 'balanced' ? 'primary' : 'default'"
|
||||
:variant="selectedPreset === 'balanced' ? 'tonal' : 'outlined'"
|
||||
class="cursor-pointer"
|
||||
@click="selectPreset('balanced')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VIcon icon="mdi-balance-scale" size="48" class="mb-2" />
|
||||
<div class="text-h6">平衡模式</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VCard
|
||||
:color="selectedPreset === 'chinese' ? 'primary' : 'default'"
|
||||
:variant="selectedPreset === 'chinese' ? 'tonal' : 'outlined'"
|
||||
class="cursor-pointer"
|
||||
@click="selectPreset('chinese')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VIcon icon="mdi-translate" size="48" class="mb-2" />
|
||||
<div class="text-h6">中文字幕</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<!-- 快速预设 -->
|
||||
<VCard class="mb-6">
|
||||
<VCardTitle class="text-h6 d-flex align-center">
|
||||
<VIcon icon="mdi-flash" class="me-2" />
|
||||
{{ t('setupWizard.preferences.quickPresets') }}
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<p class="text-body-2 text-medium-emphasis mb-4">{{ t('setupWizard.preferences.quickPresetsDesc') }}</p>
|
||||
<VRow>
|
||||
<VCol v-for="(preset, key) in presetConfigs" :key="key" cols="12" sm="6" md="3">
|
||||
<VCard
|
||||
:color="selectedPreset === key ? preset.color : 'default'"
|
||||
:variant="selectedPreset === key ? 'tonal' : 'outlined'"
|
||||
class="cursor-pointer preset-card"
|
||||
@click="selectPreset(key)"
|
||||
>
|
||||
<VCardText class="text-center pa-4">
|
||||
<VIcon :icon="preset.icon" size="40" class="mb-3" />
|
||||
<div class="text-h6 mb-2">{{ preset.name }}</div>
|
||||
<div class="text-body-2 text-medium-emphasis">{{ preset.description }}</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 详细配置 -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardTitle class="text-h6">{{ t('setupWizard.preferences.detailedConfig') }}</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12" md="4">
|
||||
<VSelect
|
||||
v-model="wizardData.preferences.quality"
|
||||
:label="t('setupWizard.preferences.quality')"
|
||||
:hint="t('setupWizard.preferences.qualityHint')"
|
||||
persistent-hint
|
||||
:items="qualityItems"
|
||||
prepend-inner-icon="mdi-star"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VSelect
|
||||
v-model="wizardData.preferences.subtitle"
|
||||
:label="t('setupWizard.preferences.subtitle')"
|
||||
:hint="t('setupWizard.preferences.subtitleHint')"
|
||||
persistent-hint
|
||||
:items="subtitleItems"
|
||||
prepend-inner-icon="mdi-subtitles"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VSelect
|
||||
v-model="wizardData.preferences.resolution"
|
||||
:label="t('setupWizard.preferences.resolution')"
|
||||
:hint="t('setupWizard.preferences.resolutionHint')"
|
||||
persistent-hint
|
||||
:items="resolutionItems"
|
||||
prepend-inner-icon="mdi-monitor"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<!-- 个性化选项 -->
|
||||
<VCard class="mb-6">
|
||||
<VCardTitle class="text-h6 d-flex align-center">
|
||||
<VIcon icon="mdi-cog" class="me-2" />
|
||||
{{ t('setupWizard.preferences.personalizationOptions') }}
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<p class="text-body-2 text-medium-emphasis mb-4">
|
||||
{{ t('setupWizard.preferences.personalizationOptionsDesc') }}
|
||||
</p>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="personalizationOptions.excludeDolbyVision"
|
||||
:label="t('setupWizard.preferences.excludeDolbyVision')"
|
||||
color="primary"
|
||||
hide-details
|
||||
@change="updateWizardData"
|
||||
/>
|
||||
<p class="text-caption text-medium-emphasis mt-1">
|
||||
{{ t('setupWizard.preferences.excludeDolbyVisionHint') }}
|
||||
</p>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="personalizationOptions.excludeBluray"
|
||||
:label="t('setupWizard.preferences.excludeBluray')"
|
||||
color="primary"
|
||||
hide-details
|
||||
@change="updateWizardData"
|
||||
/>
|
||||
<p class="text-caption text-medium-emphasis mt-1">{{ t('setupWizard.preferences.excludeBlurayHint') }}</p>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -140,24 +218,62 @@ const resolutionItems = [
|
||||
<style scoped>
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cursor-pointer:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 15%);
|
||||
.preset-card:hover {
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 15%);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.preset-card:active {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.cursor-pointer:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 选中状态的样式 */
|
||||
/* 预设卡片选中状态的样式 */
|
||||
.v-card--variant-tonal.v-theme--light {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.12);
|
||||
}
|
||||
|
||||
.v-card--variant-tonal.v-theme--dark {
|
||||
border: 2px solid rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 规则代码样式 */
|
||||
.v-code {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(var(--v-theme-surface-variant), 0.3);
|
||||
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 展开面板样式 */
|
||||
.v-expansion-panel-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v-expansion-panel-text {
|
||||
padding-block-start: 16px;
|
||||
}
|
||||
|
||||
/* 开关组件样式优化 */
|
||||
.v-switch {
|
||||
margin-block-end: 8px;
|
||||
}
|
||||
|
||||
/* 芯片组样式 */
|
||||
.v-chip-group {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.v-chip {
|
||||
margin-block: 4px;
|
||||
margin-inline: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user