更新配置向导

This commit is contained in:
jxxghp
2025-09-10 08:51:05 +08:00
parent 7c8e94d1df
commit 6aec0ddf88
7 changed files with 383 additions and 94 deletions

View File

@@ -2892,31 +2892,21 @@ export default {
completed: 'Setup Wizard completed!',
failed: 'Setup Wizard failed, please try again',
complete: 'Complete Configuration',
step1: {
title: 'Basic',
description: 'Set access domain, background wallpaper and recognition data source',
},
step2: {
title: 'Storage Directory',
description: 'Configure download directory and media library directory',
},
step3: {
title: 'Downloader',
description: 'Configure downloader (optional)',
},
step4: {
title: 'Media Server',
description: 'Configure media server (optional)',
},
step5: {
title: 'Notification',
description: 'Configure notification channels (optional)',
},
step6: {
title: 'Resource Preferences',
description: 'Set resource download preferences',
},
testing: 'Testing',
connectivityTestSuccess: 'Connectivity test passed',
connectivityTestFailed: 'Connectivity test failed',
testingStorage: 'Testing storage',
checkingStorage: 'Checking storage connectivity',
testingDownloader: 'Testing downloader',
checkingDownloader: 'Checking downloader connectivity',
testingMediaServer: 'Testing media server',
checkingMediaServer: 'Checking media server connectivity',
testingNotification: 'Testing notification',
checkingNotification: 'Checking notification connectivity',
testFailedHint: 'Please check if the configuration is correct, you can retest after modification',
basic: {
title: 'Basic Settings',
description: 'Set access domain, username/password and network configuration',
appDomain: 'App Domain',
appDomainHint: 'Used to add quick jump links when sending notifications',
wallpaper: 'Background Wallpaper',
@@ -2927,6 +2917,8 @@ export default {
apiTokenHint: 'System automatically generated API access token',
},
storage: {
title: 'Storage Configuration',
description: 'Configure download directory and media library directory',
info: 'Storage Configuration',
infoDesc: 'Configure local storage directories for download and media library management',
downloadPath: 'Download Directory',
@@ -2935,6 +2927,8 @@ export default {
libraryPathHint: 'Set the storage path for media files',
},
downloader: {
title: 'Downloader Configuration',
description: 'Configure downloader (optional)',
info: 'Downloader Configuration',
infoDesc: 'Configure downloader for automatic resource download (optional)',
type: 'Downloader Type',
@@ -2949,6 +2943,8 @@ export default {
downloadPath: 'Download Path',
},
mediaServer: {
title: 'Media Server',
description: 'Configure media server (optional)',
info: 'Media Server Configuration',
infoDesc: 'Configure media server for media library management (optional)',
type: 'Media Server Type',
@@ -2963,6 +2959,8 @@ export default {
token: 'Access Token',
},
notification: {
title: 'Notification Settings',
description: 'Configure notification channels (optional)',
info: 'Notification Configuration',
infoDesc: 'Configure notification channels for receiving system messages (optional)',
type: 'Notification Type',
@@ -2980,6 +2978,8 @@ export default {
receiverEmail: 'Receiver Email',
},
preferences: {
title: 'Resource Preferences',
description: 'Set resource download preferences',
info: 'Resource Preferences',
infoDesc:
'Set resource download preferences, the system will automatically select the best resources based on these preferences',

View File

@@ -2859,31 +2859,21 @@ export default {
completed: '配置向导完成!',
failed: '配置向导失败,请重试',
complete: '完成配置',
step1: {
title: '基础',
description: '设置访问域名、背景壁纸和识别数据源',
},
step2: {
title: '存储目录',
description: '配置下载目录和媒体库目录',
},
step3: {
title: '下载器',
description: '配置下载器(可选)',
},
step4: {
title: '媒体服务器',
description: '配置媒体服务器(可选)',
},
step5: {
title: '通知',
description: '配置通知渠道(可选)',
},
step6: {
title: '资源偏好',
description: '设置资源下载偏好',
},
testing: '正在测试',
connectivityTestSuccess: '连通性测试通过',
connectivityTestFailed: '连通性测试失败',
testingStorage: '正在测试存储目录',
checkingStorage: '检查存储目录连通性',
testingDownloader: '正在测试下载器',
checkingDownloader: '检查下载器连通性',
testingMediaServer: '正在测试媒体服务器',
checkingMediaServer: '检查媒体服务器连通性',
testingNotification: '正在测试消息通知',
checkingNotification: '检查消息通知连通性',
testFailedHint: '请检查配置是否正确,修改后可以重新测试',
basic: {
title: '基础设置',
description: '设置访问域名、用户名密码和网络配置',
appDomain: '访问域名',
appDomainHint: '用于发送通知时,添加快捷跳转地址',
wallpaper: '背景壁纸',
@@ -2894,6 +2884,8 @@ export default {
apiTokenHint: '系统自动生成的 API 访问令牌',
},
storage: {
title: '存储配置',
description: '配置下载目录和媒体库目录',
info: '存储配置说明',
infoDesc: '配置本地存储目录,用于下载和媒体库管理',
downloadPath: '下载目录',
@@ -2902,6 +2894,8 @@ export default {
libraryPathHint: '设置媒体文件的存储路径',
},
downloader: {
title: '下载器配置',
description: '配置下载器(可选)',
info: '下载器配置说明',
infoDesc: '配置下载器用于自动下载资源(可选)',
type: '下载器类型',
@@ -2916,6 +2910,8 @@ export default {
downloadPath: '下载路径',
},
mediaServer: {
title: '媒体服务器',
description: '配置媒体服务器(可选)',
info: '媒体服务器配置说明',
infoDesc: '配置媒体服务器用于媒体库管理(可选)',
type: '媒体服务器类型',
@@ -2930,6 +2926,8 @@ export default {
token: '访问令牌',
},
notification: {
title: '通知设置',
description: '配置通知渠道(可选)',
info: '通知配置说明',
infoDesc: '配置通知渠道用于接收系统消息(可选)',
type: '通知类型',
@@ -2947,6 +2945,8 @@ export default {
receiverEmail: '接收邮箱',
},
preferences: {
title: '资源偏好',
description: '设置资源下载偏好',
info: '资源偏好说明',
infoDesc: '设置资源下载的偏好,系统将根据这些偏好自动选择最佳资源',
quality: '质量偏好',

View File

@@ -2858,31 +2858,21 @@ export default {
completed: '設定精靈完成!',
failed: '設定精靈失敗,請重試',
complete: '完成設定',
step1: {
title: '基礎',
description: '設定存取網域、背景桌布和識別資料來源',
},
step2: {
title: '儲存目錄',
description: '設定下載目錄和媒體庫目錄',
},
step3: {
title: '下載器',
description: '設定下載器(可選)',
},
step4: {
title: '媒體伺服器',
description: '設定媒體伺服器(可選)',
},
step5: {
title: '通知',
description: '設定通知管道(可選)',
},
step6: {
title: '資源偏好',
description: '設定資源下載偏好',
},
testing: '正在測試',
connectivityTestSuccess: '連通性測試通過',
connectivityTestFailed: '連通性測試失敗',
testingStorage: '正在測試存儲目錄',
checkingStorage: '檢查存儲目錄連通性',
testingDownloader: '正在測試下載器',
checkingDownloader: '檢查下載器連通性',
testingMediaServer: '正在測試媒體服務器',
checkingMediaServer: '檢查媒體服務器連通性',
testingNotification: '正在測試消息通知',
checkingNotification: '檢查消息通知連通性',
testFailedHint: '請檢查配置是否正確,修改後可以重新測試',
basic: {
title: '基礎設定',
description: '設定存取網域、用戶名密碼和網路配置',
appDomain: '存取網域',
appDomainHint: '用於發送通知時,新增快速跳轉位址',
wallpaper: '背景桌布',
@@ -2893,6 +2883,8 @@ export default {
apiTokenHint: '系統自動產生的 API 存取權杖',
},
storage: {
title: '儲存配置',
description: '設定下載目錄和媒體庫目錄',
info: '儲存設定說明',
infoDesc: '設定本機儲存目錄,用於下載和媒體庫管理',
downloadPath: '下載目錄',
@@ -2901,6 +2893,8 @@ export default {
libraryPathHint: '設定媒體檔案的儲存路徑',
},
downloader: {
title: '下載器配置',
description: '設定下載器(可選)',
info: '下載器設定說明',
infoDesc: '設定下載器用於自動下載資源(可選)',
type: '下載器類型',
@@ -2915,6 +2909,8 @@ export default {
downloadPath: '下載路徑',
},
mediaServer: {
title: '媒體伺服器',
description: '設定媒體伺服器(可選)',
info: '媒體伺服器設定說明',
infoDesc: '設定媒體伺服器用於媒體庫管理(可選)',
type: '媒體伺服器類型',
@@ -2929,6 +2925,8 @@ export default {
token: '存取權杖',
},
notification: {
title: '通知設定',
description: '設定通知管道(可選)',
info: '通知設定說明',
infoDesc: '設定通知管道用於接收系統訊息(可選)',
type: '通知類型',
@@ -2946,6 +2944,8 @@ export default {
receiverEmail: '接收信箱',
},
preferences: {
title: '資源偏好',
description: '設定資源下載偏好',
info: '資源偏好說明',
infoDesc: '設定資源下載的偏好,系統將根據這些偏好自動選擇最佳資源',
quality: '品質偏好',

View File

@@ -117,12 +117,17 @@ async function subscribeForPushNotifications() {
// 登录后处理
async function afterLogin(superuser: boolean, userPayload: userState, filteredMenus: any[]) {
// 如果有原始路径,优先跳转到原始路径
if (authStore.originalPath && authStore.originalPath !== '/') {
router.push(authStore.originalPath)
// 如果需要显示设置向导,跳转到设置向导页面
if (userPayload.wizard) {
router.push('/setup-wizard')
} else {
// 跳转到第一个有权限的菜单
router.push(filteredMenus[0].to)
// 如果有原始路径,优先跳转到原始路径
if (authStore.originalPath && authStore.originalPath !== '/') {
router.push(authStore.originalPath)
} else {
// 跳转到第一个有权限的菜单
router.push(filteredMenus[0].to)
}
}
// 订阅推送通知
@@ -165,6 +170,7 @@ function login() {
avatar: response.avatar,
level: response.level,
permissions: response.permissions,
wizard: response.wizard,
}
// 在保存用户信息之前检查权限

View File

@@ -66,28 +66,37 @@ const wizardData = ref({
// 步骤标题
const stepTitles = [
t('setupWizard.step1.title'),
t('setupWizard.step2.title'),
t('setupWizard.step3.title'),
t('setupWizard.step4.title'),
t('setupWizard.step5.title'),
t('setupWizard.step6.title'),
t('setupWizard.basic.title'),
t('setupWizard.storage.title'),
t('setupWizard.downloader.title'),
t('setupWizard.mediaServer.title'),
t('setupWizard.notification.title'),
t('setupWizard.preferences.title'),
]
// 步骤描述
const stepDescriptions = [
t('setupWizard.step1.description'),
t('setupWizard.step2.description'),
t('setupWizard.step3.description'),
t('setupWizard.step4.description'),
t('setupWizard.step5.description'),
t('setupWizard.step6.description'),
t('setupWizard.basic.description'),
t('setupWizard.storage.description'),
t('setupWizard.downloader.description'),
t('setupWizard.mediaServer.description'),
t('setupWizard.notification.description'),
t('setupWizard.preferences.description'),
]
// 密码可见性控制
const isPasswordVisible = ref(false)
const isConfirmPasswordVisible = ref(false)
// 连通性测试状态
const connectivityTest = ref({
isTesting: false,
testMessage: '',
testProgress: 0,
testResult: null as 'success' | 'error' | null,
showResult: false,
})
// 整理方式选项
const transferTypeItems = [
{ title: '硬链接', value: 'link' },
@@ -192,12 +201,201 @@ function selectPreset(preset: string) {
}
}
// 连通性测试函数
async function testConnectivity(step: number) {
connectivityTest.value.isTesting = true
connectivityTest.value.testMessage = ''
connectivityTest.value.testProgress = 0
connectivityTest.value.testResult = null
connectivityTest.value.showResult = false
try {
let testResult: { success: boolean; message: string | null } = { success: false, message: null }
switch (step) {
case 2: // 存储目录测试
testResult = await testStorageConnectivity()
break
case 3: // 下载器测试
testResult = await testDownloaderConnectivity()
break
case 4: // 媒体服务器测试
testResult = await testMediaServerConnectivity()
break
case 5: // 消息通知测试
testResult = await testNotificationConnectivity()
break
}
// 设置测试结果
connectivityTest.value.isTesting = false
connectivityTest.value.testResult = testResult.success ? 'success' : 'error'
connectivityTest.value.showResult = true
// 根据结果显示不同的消息
if (testResult.success) {
connectivityTest.value.testMessage = t('setupWizard.connectivityTestSuccess')
} else {
// 显示API返回的具体错误原因
connectivityTest.value.testMessage = testResult.message || t('setupWizard.connectivityTestFailed')
}
// 成功时2秒后隐藏结果失败时保持显示直到用户操作
if (testResult.success) {
setTimeout(() => {
connectivityTest.value.showResult = false
connectivityTest.value.testResult = null
}, 2000)
}
return testResult.success
} catch (error) {
console.error('Connectivity test failed:', error)
connectivityTest.value.isTesting = false
connectivityTest.value.testResult = 'error'
connectivityTest.value.showResult = true
connectivityTest.value.testMessage = (error as Error).message || t('setupWizard.connectivityTestFailed')
return false
}
}
// 存储目录连通性测试
async function testStorageConnectivity() {
try {
connectivityTest.value.testProgress = 30
connectivityTest.value.testMessage = t('setupWizard.testingStorage')
// 等待设置生效
await new Promise(resolve => setTimeout(resolve, 2000))
connectivityTest.value.testProgress = 60
connectivityTest.value.testMessage = t('setupWizard.checkingStorage')
// 调用存储测试API
const result = await api.get('system/storagetest')
connectivityTest.value.testProgress = 100
if (result.data?.success) {
return { success: true, message: null }
} else {
return { success: false, message: result.data?.message || t('setupWizard.storageTestFailed') }
}
} catch (error) {
console.error('Storage test failed:', error)
return { success: false, message: (error as Error).message || t('setupWizard.storageTestFailed') }
}
}
// 下载器连通性测试
async function testDownloaderConnectivity() {
try {
connectivityTest.value.testProgress = 30
connectivityTest.value.testMessage = t('setupWizard.testingDownloader')
// 等待设置生效
await new Promise(resolve => setTimeout(resolve, 2000))
connectivityTest.value.testProgress = 60
connectivityTest.value.testMessage = t('setupWizard.checkingDownloader')
// 调用下载器测试API
const moduleid = wizardData.value.downloader.type
if (!moduleid) {
return { success: false, message: t('setupWizard.downloaderNotSelected') }
}
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
connectivityTest.value.testProgress = 100
if (result.data?.success) {
return { success: true, message: null }
} else {
return { success: false, message: result.data?.message || t('setupWizard.downloaderTestFailed') }
}
} catch (error) {
console.error('Downloader test failed:', error)
return { success: false, message: (error as Error).message || t('setupWizard.downloaderTestFailed') }
}
}
// 媒体服务器连通性测试
async function testMediaServerConnectivity() {
try {
connectivityTest.value.testProgress = 30
connectivityTest.value.testMessage = t('setupWizard.testingMediaServer')
// 等待设置生效
await new Promise(resolve => setTimeout(resolve, 2000))
connectivityTest.value.testProgress = 60
connectivityTest.value.testMessage = t('setupWizard.checkingMediaServer')
// 调用媒体服务器测试API
const moduleid = wizardData.value.mediaServer.type
if (!moduleid) {
return { success: false, message: t('setupWizard.mediaServerNotSelected') }
}
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
connectivityTest.value.testProgress = 100
if (result.data?.success) {
return { success: true, message: null }
} else {
return { success: false, message: result.data?.message || t('setupWizard.mediaServerTestFailed') }
}
} catch (error) {
console.error('Media server test failed:', error)
return { success: false, message: (error as Error).message || t('setupWizard.mediaServerTestFailed') }
}
}
// 消息通知连通性测试
async function testNotificationConnectivity() {
try {
connectivityTest.value.testProgress = 30
connectivityTest.value.testMessage = t('setupWizard.testingNotification')
// 等待设置生效
await new Promise(resolve => setTimeout(resolve, 2000))
connectivityTest.value.testProgress = 60
connectivityTest.value.testMessage = t('setupWizard.checkingNotification')
// 调用通知测试API
const moduleid = wizardData.value.notification.type
if (!moduleid) {
return { success: false, message: t('setupWizard.notificationNotSelected') }
}
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
connectivityTest.value.testProgress = 100
if (result.data?.success) {
return { success: true, message: null }
} else {
return { success: false, message: result.data?.message || t('setupWizard.notificationTestFailed') }
}
} catch (error) {
console.error('Notification test failed:', error)
return { success: false, message: (error as Error).message || t('setupWizard.notificationTestFailed') }
}
}
// 下一步
async function nextStep() {
if (currentStep.value < totalSteps) {
// 保存当前步骤的设置
await saveCurrentStepSettings()
// 对于需要测试的步骤,进行连通性测试
if ([2, 3, 4, 5].includes(currentStep.value)) {
const testResult = await testConnectivity(currentStep.value)
if (!testResult) {
return
}
}
currentStep.value++
}
}
@@ -1907,22 +2105,83 @@ onMounted(async () => {
</VStepperWindowItem>
</VStepperWindow>
<!-- 连通性测试进度条 -->
<VCard v-if="connectivityTest.isTesting || connectivityTest.showResult" variant="outlined" class="mx-4 mb-4">
<VCardText class="text-center py-4">
<!-- 测试中 -->
<div v-if="connectivityTest.isTesting">
<VIcon icon="mdi-cog-sync" class="rotating mb-2" color="primary" size="24" />
<div class="text-body-2 mb-2">{{ connectivityTest.testMessage }}</div>
<VProgressLinear
v-model="connectivityTest.testProgress"
color="primary"
height="6"
rounded
class="mb-2"
/>
<div class="text-caption text-medium-emphasis">{{ Math.round(connectivityTest.testProgress) }}%</div>
</div>
<!-- 测试结果 -->
<div v-else-if="connectivityTest.showResult">
<VIcon
:icon="connectivityTest.testResult === 'success' ? 'mdi-check-circle' : 'mdi-alert-circle'"
:color="connectivityTest.testResult === 'success' ? 'success' : 'error'"
size="24"
class="mb-2"
/>
<div
:class="connectivityTest.testResult === 'success' ? 'text-success' : 'text-error'"
class="text-body-2 mb-2 font-weight-medium"
>
{{ connectivityTest.testMessage }}
</div>
<div v-if="connectivityTest.testResult === 'error'" class="text-caption text-medium-emphasis">
{{ t('setupWizard.testFailedHint') }}
</div>
</div>
</VCardText>
</VCard>
<!-- 操作按钮 -->
<VCardActions class="justify-space-between">
<div class="d-flex gap-2">
<VBtn v-if="currentStep !== 1" prepend-icon="mdi-chevron-left" @click="prevStep">
<VBtn
v-if="currentStep !== 1"
prepend-icon="mdi-chevron-left"
@click="prevStep"
:disabled="connectivityTest.isTesting"
>
{{ t('common.previous') }}
</VBtn>
<VBtn v-else color="primary" prepend-icon="mdi-keyboard-return" @click="router.push('/')">
<VBtn
v-else
color="primary"
prepend-icon="mdi-keyboard-return"
@click="router.push('/')"
:disabled="connectivityTest.isTesting"
>
{{ t('common.skip') }}
</VBtn>
</div>
<div class="d-flex gap-2">
<VBtn v-if="currentStep < totalSteps" color="primary" append-icon="mdi-chevron-right" @click="nextStep">
{{ t('common.next') }}
<VBtn
v-if="currentStep < totalSteps"
color="primary"
append-icon="mdi-chevron-right"
@click="nextStep"
:disabled="connectivityTest.isTesting"
>
{{ connectivityTest.isTesting ? t('setupWizard.testing') : t('common.next') }}
</VBtn>
<VBtn v-else color="success" prepend-icon="mdi-check" @click="completeWizard">
<VBtn
v-else
color="success"
prepend-icon="mdi-check"
@click="completeWizard"
:disabled="connectivityTest.isTesting"
>
{{ t('setupWizard.complete') }}
</VBtn>
</div>
@@ -1986,4 +2245,19 @@ onMounted(async () => {
.v-card--variant-tonal.v-theme--dark {
background-color: rgb(var(--v-theme-primary), 0.2);
}
/* 旋转动画 */
.rotating {
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -20,6 +20,8 @@ export interface userState {
level: number
// 权限
permissions: { [key: string]: any }
// 是否需要显示设置向导
wizard: boolean
}
export interface globalSettingsState {

View File

@@ -10,6 +10,7 @@ export const useUserStore = defineStore('user', {
avatar: '',
level: 1,
permissions: DEFAULT_PERMISSIONS,
wizard: false,
}),
// 全局持久化
@@ -34,6 +35,9 @@ export const useUserStore = defineStore('user', {
setPermissions(permissions: object) {
this.permissions = { ...DEFAULT_PERMISSIONS, ...permissions }
},
setWizard(wizard: boolean) {
this.wizard = wizard
},
loginUser(payload: userState) {
this.setSuperUser(payload.superUser)
this.setUserID(payload.userID)
@@ -41,6 +45,7 @@ export const useUserStore = defineStore('user', {
this.setAvatar(payload.avatar)
this.setLevel(payload.level)
this.setPermissions(payload.permissions)
this.setWizard(payload.wizard)
},
reset() {
this.setSuperUser(false)
@@ -49,6 +54,7 @@ export const useUserStore = defineStore('user', {
this.setAvatar('')
this.setLevel(1)
this.setPermissions(DEFAULT_PERMISSIONS)
this.setWizard(false)
},
},
@@ -59,5 +65,6 @@ export const useUserStore = defineStore('user', {
getAvatar: state => state.avatar,
getLevel: state => state.level,
getPermissions: state => state.permissions,
getWizard: state => state.wizard,
},
})