mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-28 02:51:56 +08:00
增强配置向导功能
This commit is contained in:
@@ -54,59 +54,134 @@ export interface ConnectivityTestState {
|
||||
showResult: boolean
|
||||
}
|
||||
|
||||
export interface ValidationErrorState {
|
||||
downloader: {
|
||||
name: boolean
|
||||
host: boolean
|
||||
username: boolean
|
||||
password: boolean
|
||||
}
|
||||
mediaServer: {
|
||||
name: boolean
|
||||
host: boolean
|
||||
apikey: boolean
|
||||
token: boolean
|
||||
username: boolean
|
||||
password: boolean
|
||||
}
|
||||
notification: {
|
||||
name: boolean
|
||||
[key: string]: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// 全局状态,所有组件共享
|
||||
const currentStep = ref(1)
|
||||
const totalSteps = 6
|
||||
|
||||
// 选中的预设规则
|
||||
const selectedPreset = ref('')
|
||||
|
||||
// 向导数据
|
||||
const wizardData = ref<WizardData>({
|
||||
basic: {
|
||||
appDomain: '',
|
||||
apiToken: '',
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
proxyHost: '',
|
||||
githubToken: '',
|
||||
},
|
||||
storage: {
|
||||
downloadPath: '',
|
||||
libraryPath: '',
|
||||
transferType: 'link',
|
||||
overwriteMode: 'never',
|
||||
},
|
||||
downloader: {
|
||||
type: '',
|
||||
name: '',
|
||||
config: {},
|
||||
},
|
||||
mediaServer: {
|
||||
type: '',
|
||||
name: '',
|
||||
config: {},
|
||||
sync_libraries: [],
|
||||
},
|
||||
notification: {
|
||||
type: '',
|
||||
name: '',
|
||||
enabled: false,
|
||||
config: {},
|
||||
switchs: [],
|
||||
},
|
||||
preferences: {
|
||||
quality: '4K',
|
||||
subtitle: 'chinese',
|
||||
resolution: '2160p',
|
||||
},
|
||||
})
|
||||
|
||||
// 连通性测试状态
|
||||
const connectivityTest = ref<ConnectivityTestState>({
|
||||
isTesting: false,
|
||||
testMessage: '',
|
||||
testProgress: 0,
|
||||
testResult: null,
|
||||
showResult: false,
|
||||
})
|
||||
|
||||
// 验证错误状态
|
||||
const validationErrors = ref<ValidationErrorState>({
|
||||
downloader: {
|
||||
name: false,
|
||||
host: false,
|
||||
username: false,
|
||||
password: false,
|
||||
},
|
||||
mediaServer: {
|
||||
name: false,
|
||||
host: false,
|
||||
apikey: false,
|
||||
token: false,
|
||||
username: false,
|
||||
password: false,
|
||||
},
|
||||
notification: {
|
||||
name: false,
|
||||
},
|
||||
})
|
||||
|
||||
export function useSetupWizard() {
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const $toast = useToast()
|
||||
|
||||
// 当前步骤
|
||||
const currentStep = ref(1)
|
||||
const totalSteps = 6
|
||||
|
||||
// 选中的预设规则
|
||||
const selectedPreset = ref('')
|
||||
|
||||
// 向导数据
|
||||
const wizardData = ref<WizardData>({
|
||||
basic: {
|
||||
appDomain: '',
|
||||
apiToken: '',
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
proxyHost: '',
|
||||
githubToken: '',
|
||||
},
|
||||
storage: {
|
||||
downloadPath: '',
|
||||
libraryPath: '',
|
||||
transferType: 'link',
|
||||
overwriteMode: 'never',
|
||||
},
|
||||
// 类型到模块ID的映射关系
|
||||
const typeToModuleMapping = {
|
||||
// 下载器映射
|
||||
downloader: {
|
||||
type: '',
|
||||
name: '',
|
||||
config: {},
|
||||
'qbittorrent': 'QbittorrentModule',
|
||||
'transmission': 'TransmissionModule',
|
||||
},
|
||||
// 媒体服务器映射
|
||||
mediaServer: {
|
||||
type: '',
|
||||
name: '',
|
||||
config: {},
|
||||
sync_libraries: [],
|
||||
'emby': 'EmbyModule',
|
||||
'jellyfin': 'JellyfinModule',
|
||||
'plex': 'PlexModule',
|
||||
},
|
||||
// 通知映射
|
||||
notification: {
|
||||
type: '',
|
||||
name: '',
|
||||
enabled: false,
|
||||
config: {},
|
||||
switchs: [],
|
||||
'telegram': 'TelegramModule',
|
||||
'wechat': 'WechatModule',
|
||||
'slack': 'SlackModule',
|
||||
'synologychat': 'SynologyChatModule',
|
||||
'vocechat': 'VoceChatModule',
|
||||
'webpush': 'WebPushModule',
|
||||
},
|
||||
preferences: {
|
||||
quality: '4K',
|
||||
subtitle: 'chinese',
|
||||
resolution: '2160p',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 步骤标题
|
||||
const stepTitles = computed(() => [
|
||||
@@ -128,15 +203,6 @@ export function useSetupWizard() {
|
||||
t('setupWizard.preferences.description'),
|
||||
])
|
||||
|
||||
// 连通性测试状态
|
||||
const connectivityTest = ref<ConnectivityTestState>({
|
||||
isTesting: false,
|
||||
testMessage: '',
|
||||
testProgress: 0,
|
||||
testResult: null,
|
||||
showResult: false,
|
||||
})
|
||||
|
||||
// 创建随机API Token
|
||||
function createRandomString() {
|
||||
const charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
||||
@@ -162,23 +228,50 @@ export function useSetupWizard() {
|
||||
|
||||
// 选择下载器
|
||||
function selectDownloader(type: string) {
|
||||
wizardData.value.downloader.type = type
|
||||
wizardData.value.downloader.name = `${type} 下载器`
|
||||
wizardData.value.downloader.config = {}
|
||||
if (wizardData.value.downloader.type === type) {
|
||||
// 重复点击已选中的类型,取消选择
|
||||
wizardData.value.downloader.type = ''
|
||||
wizardData.value.downloader.name = ''
|
||||
wizardData.value.downloader.config = {}
|
||||
} else {
|
||||
wizardData.value.downloader.type = type
|
||||
wizardData.value.downloader.name = `${type} 下载器`
|
||||
wizardData.value.downloader.config = {}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择媒体服务器
|
||||
function selectMediaServer(type: string) {
|
||||
wizardData.value.mediaServer.type = type
|
||||
wizardData.value.mediaServer.name = `${type} 服务器`
|
||||
wizardData.value.mediaServer.config = {}
|
||||
if (wizardData.value.mediaServer.type === type) {
|
||||
// 重复点击已选中的类型,取消选择
|
||||
wizardData.value.mediaServer.type = ''
|
||||
wizardData.value.mediaServer.name = ''
|
||||
wizardData.value.mediaServer.config = {}
|
||||
wizardData.value.mediaServer.sync_libraries = []
|
||||
} else {
|
||||
wizardData.value.mediaServer.type = type
|
||||
wizardData.value.mediaServer.name = `${type} 服务器`
|
||||
wizardData.value.mediaServer.config = {}
|
||||
wizardData.value.mediaServer.sync_libraries = []
|
||||
}
|
||||
}
|
||||
|
||||
// 选择通知
|
||||
function selectNotification(type: string) {
|
||||
wizardData.value.notification.type = type
|
||||
wizardData.value.notification.name = `${type} 通知`
|
||||
wizardData.value.notification.config = {}
|
||||
if (wizardData.value.notification.type === type) {
|
||||
// 重复点击已选中的类型,取消选择
|
||||
wizardData.value.notification.type = ''
|
||||
wizardData.value.notification.name = ''
|
||||
wizardData.value.notification.enabled = false
|
||||
wizardData.value.notification.config = {}
|
||||
wizardData.value.notification.switchs = []
|
||||
} else {
|
||||
wizardData.value.notification.type = type
|
||||
wizardData.value.notification.name = `${type} 通知`
|
||||
wizardData.value.notification.enabled = true
|
||||
wizardData.value.notification.config = {}
|
||||
wizardData.value.notification.switchs = []
|
||||
}
|
||||
}
|
||||
|
||||
// 选择预设规则
|
||||
@@ -204,6 +297,264 @@ export function useSetupWizard() {
|
||||
}
|
||||
}
|
||||
|
||||
// 清除验证错误状态
|
||||
function clearValidationErrors() {
|
||||
validationErrors.value.downloader = {
|
||||
name: false,
|
||||
host: false,
|
||||
username: false,
|
||||
password: false,
|
||||
}
|
||||
validationErrors.value.mediaServer = {
|
||||
name: false,
|
||||
host: false,
|
||||
apikey: false,
|
||||
token: false,
|
||||
username: false,
|
||||
password: false,
|
||||
}
|
||||
validationErrors.value.notification = {
|
||||
name: false,
|
||||
}
|
||||
}
|
||||
|
||||
// 验证下载器字段
|
||||
function validateDownloaderFields(): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = []
|
||||
clearValidationErrors()
|
||||
|
||||
// 名称必输
|
||||
if (!wizardData.value.downloader.name?.trim()) {
|
||||
errors.push(t('downloader.nameRequired'))
|
||||
validationErrors.value.downloader.name = true
|
||||
}
|
||||
|
||||
// 主机地址必输
|
||||
if (!wizardData.value.downloader.config?.host?.trim()) {
|
||||
errors.push(t('downloader.hostRequired'))
|
||||
validationErrors.value.downloader.host = true
|
||||
}
|
||||
|
||||
// 根据下载器类型验证其他必输项
|
||||
if (wizardData.value.downloader.type === 'qbittorrent' || wizardData.value.downloader.type === 'transmission') {
|
||||
if (!wizardData.value.downloader.config?.username?.trim()) {
|
||||
errors.push(t('downloader.usernameRequired'))
|
||||
validationErrors.value.downloader.username = true
|
||||
}
|
||||
if (!wizardData.value.downloader.config?.password?.trim()) {
|
||||
errors.push(t('downloader.passwordRequired'))
|
||||
validationErrors.value.downloader.password = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
// 验证媒体服务器字段
|
||||
function validateMediaServerFields(): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = []
|
||||
clearValidationErrors()
|
||||
|
||||
// 名称必输
|
||||
if (!wizardData.value.mediaServer.name?.trim()) {
|
||||
errors.push(t('mediaserver.nameRequired'))
|
||||
validationErrors.value.mediaServer.name = true
|
||||
}
|
||||
|
||||
// 主机地址必输
|
||||
if (!wizardData.value.mediaServer.config?.host?.trim()) {
|
||||
errors.push(t('mediaserver.hostRequired'))
|
||||
validationErrors.value.mediaServer.host = true
|
||||
}
|
||||
|
||||
// 根据媒体服务器类型验证API密钥或Token
|
||||
if (wizardData.value.mediaServer.type === 'emby' || wizardData.value.mediaServer.type === 'jellyfin') {
|
||||
if (!wizardData.value.mediaServer.config?.apikey?.trim()) {
|
||||
errors.push(t('mediaserver.apiKeyRequired'))
|
||||
validationErrors.value.mediaServer.apikey = true
|
||||
}
|
||||
} else if (wizardData.value.mediaServer.type === 'plex') {
|
||||
if (!wizardData.value.mediaServer.config?.token?.trim()) {
|
||||
errors.push(t('mediaserver.tokenRequired'))
|
||||
validationErrors.value.mediaServer.token = true
|
||||
}
|
||||
} else if (wizardData.value.mediaServer.type === 'trimemedia') {
|
||||
if (!wizardData.value.mediaServer.config?.username?.trim()) {
|
||||
errors.push(t('mediaserver.usernameRequired'))
|
||||
validationErrors.value.mediaServer.username = true
|
||||
}
|
||||
if (!wizardData.value.mediaServer.config?.password?.trim()) {
|
||||
errors.push(t('mediaserver.passwordRequired'))
|
||||
validationErrors.value.mediaServer.password = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
// 验证通知字段
|
||||
function validateNotificationFields(): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = []
|
||||
clearValidationErrors()
|
||||
|
||||
// 名称必输
|
||||
if (!wizardData.value.notification.name?.trim()) {
|
||||
errors.push(t('notification.nameRequired'))
|
||||
validationErrors.value.notification.name = true
|
||||
}
|
||||
|
||||
// 根据通知类型验证必输项
|
||||
const config = wizardData.value.notification.config || {}
|
||||
switch (wizardData.value.notification.type) {
|
||||
case 'wechat':
|
||||
if (!config.WECHAT_CORPID?.trim()) {
|
||||
errors.push(t('notification.wechat.corpIdRequired'))
|
||||
validationErrors.value.notification.WECHAT_CORPID = true
|
||||
}
|
||||
if (!config.WECHAT_APP_ID?.trim()) {
|
||||
errors.push(t('notification.wechat.appIdRequired'))
|
||||
validationErrors.value.notification.WECHAT_APP_ID = true
|
||||
}
|
||||
if (!config.WECHAT_APP_SECRET?.trim()) {
|
||||
errors.push(t('notification.wechat.appSecretRequired'))
|
||||
validationErrors.value.notification.WECHAT_APP_SECRET = true
|
||||
}
|
||||
break
|
||||
case 'telegram':
|
||||
if (!config.TELEGRAM_TOKEN?.trim()) {
|
||||
errors.push(t('notification.telegram.tokenRequired'))
|
||||
validationErrors.value.notification.TELEGRAM_TOKEN = true
|
||||
}
|
||||
if (!config.TELEGRAM_CHAT_ID?.trim()) {
|
||||
errors.push(t('notification.telegram.chatIdRequired'))
|
||||
validationErrors.value.notification.TELEGRAM_CHAT_ID = true
|
||||
}
|
||||
break
|
||||
case 'slack':
|
||||
if (!config.SLACK_OAUTH_TOKEN?.trim()) {
|
||||
errors.push(t('notification.slack.oauthTokenRequired'))
|
||||
validationErrors.value.notification.SLACK_OAUTH_TOKEN = true
|
||||
}
|
||||
if (!config.SLACK_CHANNEL?.trim()) {
|
||||
errors.push(t('notification.slack.channelRequired'))
|
||||
validationErrors.value.notification.SLACK_CHANNEL = true
|
||||
}
|
||||
break
|
||||
case 'synologychat':
|
||||
if (!config.SYNOLOGYCHAT_WEBHOOK?.trim()) {
|
||||
errors.push(t('notification.synologychat.webhookRequired'))
|
||||
validationErrors.value.notification.SYNOLOGYCHAT_WEBHOOK = true
|
||||
}
|
||||
break
|
||||
case 'vocechat':
|
||||
if (!config.VOCECHAT_HOST?.trim()) {
|
||||
errors.push(t('notification.vocechat.hostRequired'))
|
||||
validationErrors.value.notification.VOCECHAT_HOST = true
|
||||
}
|
||||
if (!config.VOCECHAT_API_KEY?.trim()) {
|
||||
errors.push(t('notification.vocechat.apiKeyRequired'))
|
||||
validationErrors.value.notification.VOCECHAT_API_KEY = true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
// 验证当前步骤的必输项
|
||||
function validateCurrentStep(): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = []
|
||||
|
||||
switch (currentStep.value) {
|
||||
case 1: // 基础设置
|
||||
if (!wizardData.value.basic.username) {
|
||||
errors.push(t('dialog.userAddEdit.usernameRequired'))
|
||||
}
|
||||
// 密码是可选的,但如果输入了密码则需要验证
|
||||
if (wizardData.value.basic.password) {
|
||||
if (wizardData.value.basic.password.length < 6) {
|
||||
errors.push(t('dialog.userAddEdit.passwordMinLength'))
|
||||
}
|
||||
if (!wizardData.value.basic.confirmPassword) {
|
||||
errors.push(t('dialog.userAddEdit.confirmPasswordRequired'))
|
||||
} else if (wizardData.value.basic.password !== wizardData.value.basic.confirmPassword) {
|
||||
errors.push(t('dialog.userAddEdit.passwordMismatch'))
|
||||
}
|
||||
}
|
||||
if (!wizardData.value.basic.apiToken) {
|
||||
errors.push(t('setupWizard.basic.apiTokenRequired'))
|
||||
}
|
||||
break
|
||||
|
||||
case 2: // 存储设置
|
||||
if (!wizardData.value.storage.downloadPath) {
|
||||
errors.push(t('setupWizard.storage.downloadPathRequired'))
|
||||
}
|
||||
if (!wizardData.value.storage.libraryPath) {
|
||||
errors.push(t('setupWizard.storage.libraryPathRequired'))
|
||||
}
|
||||
break
|
||||
|
||||
case 3: // 下载器设置
|
||||
if (wizardData.value.downloader.type) {
|
||||
// 如果选择了下载器,则验证必输项
|
||||
const validation = validateDownloaderFields()
|
||||
errors.push(...validation.errors)
|
||||
}
|
||||
break
|
||||
|
||||
case 4: // 媒体服务器设置
|
||||
if (wizardData.value.mediaServer.type) {
|
||||
// 如果选择了媒体服务器,则验证必输项
|
||||
const validation = validateMediaServerFields()
|
||||
errors.push(...validation.errors)
|
||||
}
|
||||
break
|
||||
|
||||
case 5: // 通知设置
|
||||
if (wizardData.value.notification.type) {
|
||||
// 如果选择了通知,则验证必输项
|
||||
const validation = validateNotificationFields()
|
||||
errors.push(...validation.errors)
|
||||
}
|
||||
break
|
||||
|
||||
case 6: // 偏好设置
|
||||
// 偏好设置有默认值,不需要验证
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要进行测试
|
||||
function shouldPerformTest(step: number): boolean {
|
||||
switch (step) {
|
||||
case 2: // 存储目录测试 - 总是需要测试
|
||||
return true
|
||||
case 3: // 下载器测试 - 只有选择了下载器才测试
|
||||
return !!wizardData.value.downloader.type
|
||||
case 4: // 媒体服务器测试 - 只有选择了媒体服务器才测试
|
||||
return !!wizardData.value.mediaServer.type
|
||||
case 5: // 消息通知测试 - 只有选择了通知才测试
|
||||
return !!wizardData.value.notification.type
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 连通性测试函数
|
||||
async function testConnectivity(step: number) {
|
||||
connectivityTest.value.isTesting = true
|
||||
@@ -238,17 +589,17 @@ 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秒后隐藏结果,失败时保持显示直到用户操作
|
||||
if (testResult.success) {
|
||||
setTimeout(() => {
|
||||
connectivityTest.value.showResult = false
|
||||
connectivityTest.value.testResult = null
|
||||
}, 2000)
|
||||
connectivityTest.value.showResult = false
|
||||
connectivityTest.value.testResult = null
|
||||
}
|
||||
|
||||
return testResult.success
|
||||
@@ -258,6 +609,7 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -274,14 +626,14 @@ export function useSetupWizard() {
|
||||
connectivityTest.value.testProgress = 60
|
||||
connectivityTest.value.testMessage = t('setupWizard.checkingStorage')
|
||||
|
||||
// 调用存储测试API
|
||||
const result = await api.get('system/storagetest')
|
||||
// 调用存储测试API - 使用FileManagerModule
|
||||
const result: { [key: string]: any } = await api.get('system/moduletest/FileManagerModule')
|
||||
connectivityTest.value.testProgress = 100
|
||||
|
||||
if (result.data?.success) {
|
||||
if (result.success) {
|
||||
return { success: true, message: null }
|
||||
} else {
|
||||
return { success: false, message: result.data?.message || t('setupWizard.storageTestFailed') }
|
||||
return { success: false, message: result.message || t('setupWizard.storageTestFailed') }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Storage test failed:', error)
|
||||
@@ -301,19 +653,24 @@ export function useSetupWizard() {
|
||||
connectivityTest.value.testProgress = 60
|
||||
connectivityTest.value.testMessage = t('setupWizard.checkingDownloader')
|
||||
|
||||
// 调用下载器测试API
|
||||
const moduleid = wizardData.value.downloader.type
|
||||
if (!moduleid) {
|
||||
// 获取正确的模块ID
|
||||
const downloaderType = wizardData.value.downloader.type
|
||||
if (!downloaderType) {
|
||||
return { success: false, message: t('setupWizard.downloaderNotSelected') }
|
||||
}
|
||||
|
||||
const moduleid = typeToModuleMapping.downloader[downloaderType as keyof typeof typeToModuleMapping.downloader]
|
||||
if (!moduleid) {
|
||||
return { success: false, message: t('setupWizard.unsupportedDownloaderType', { type: downloaderType }) }
|
||||
}
|
||||
|
||||
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
|
||||
connectivityTest.value.testProgress = 100
|
||||
|
||||
if (result.data?.success) {
|
||||
if (result.success) {
|
||||
return { success: true, message: null }
|
||||
} else {
|
||||
return { success: false, message: result.data?.message || t('setupWizard.downloaderTestFailed') }
|
||||
return { success: false, message: result.message || t('setupWizard.downloaderTestFailed') }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Downloader test failed:', error)
|
||||
@@ -333,19 +690,24 @@ export function useSetupWizard() {
|
||||
connectivityTest.value.testProgress = 60
|
||||
connectivityTest.value.testMessage = t('setupWizard.checkingMediaServer')
|
||||
|
||||
// 调用媒体服务器测试API
|
||||
const moduleid = wizardData.value.mediaServer.type
|
||||
if (!moduleid) {
|
||||
// 获取正确的模块ID
|
||||
const mediaServerType = wizardData.value.mediaServer.type
|
||||
if (!mediaServerType) {
|
||||
return { success: false, message: t('setupWizard.mediaServerNotSelected') }
|
||||
}
|
||||
|
||||
const moduleid = typeToModuleMapping.mediaServer[mediaServerType as keyof typeof typeToModuleMapping.mediaServer]
|
||||
if (!moduleid) {
|
||||
return { success: false, message: t('setupWizard.unsupportedMediaServerType', { type: mediaServerType }) }
|
||||
}
|
||||
|
||||
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
|
||||
connectivityTest.value.testProgress = 100
|
||||
|
||||
if (result.data?.success) {
|
||||
if (result.success) {
|
||||
return { success: true, message: null }
|
||||
} else {
|
||||
return { success: false, message: result.data?.message || t('setupWizard.mediaServerTestFailed') }
|
||||
return { success: false, message: result.message || t('setupWizard.mediaServerTestFailed') }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Media server test failed:', error)
|
||||
@@ -365,19 +727,25 @@ export function useSetupWizard() {
|
||||
connectivityTest.value.testProgress = 60
|
||||
connectivityTest.value.testMessage = t('setupWizard.checkingNotification')
|
||||
|
||||
// 调用通知测试API
|
||||
const moduleid = wizardData.value.notification.type
|
||||
if (!moduleid) {
|
||||
// 获取正确的模块ID
|
||||
const notificationType = wizardData.value.notification.type
|
||||
if (!notificationType) {
|
||||
return { success: false, message: t('setupWizard.notificationNotSelected') }
|
||||
}
|
||||
|
||||
const moduleid =
|
||||
typeToModuleMapping.notification[notificationType as keyof typeof typeToModuleMapping.notification]
|
||||
if (!moduleid) {
|
||||
return { success: false, message: t('setupWizard.unsupportedNotificationType', { type: notificationType }) }
|
||||
}
|
||||
|
||||
const result: { [key: string]: any } = await api.get(`system/moduletest/${moduleid}`)
|
||||
connectivityTest.value.testProgress = 100
|
||||
|
||||
if (result.data?.success) {
|
||||
if (result.success) {
|
||||
return { success: true, message: null }
|
||||
} else {
|
||||
return { success: false, message: result.data?.message || t('setupWizard.notificationTestFailed') }
|
||||
return { success: false, message: result.message || t('setupWizard.notificationTestFailed') }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Notification test failed:', error)
|
||||
@@ -388,11 +756,22 @@ export function useSetupWizard() {
|
||||
// 下一步
|
||||
async function nextStep() {
|
||||
if (currentStep.value < totalSteps) {
|
||||
// 验证当前步骤的必输项
|
||||
const validation = validateCurrentStep()
|
||||
if (!validation.isValid) {
|
||||
// 显示验证错误
|
||||
validation.errors.forEach(error => {
|
||||
$toast.error(error)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 保存当前步骤的设置
|
||||
await saveCurrentStepSettings()
|
||||
|
||||
// 对于需要测试的步骤,进行连通性测试
|
||||
if ([2, 3, 4, 5].includes(currentStep.value)) {
|
||||
// 检查是否需要进行测试
|
||||
const needsTest = shouldPerformTest(currentStep.value)
|
||||
if (needsTest) {
|
||||
const testResult = await testConnectivity(currentStep.value)
|
||||
if (!testResult) {
|
||||
return
|
||||
@@ -400,6 +779,7 @@ export function useSetupWizard() {
|
||||
}
|
||||
|
||||
currentStep.value++
|
||||
connectivityTest.value.showResult = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +788,7 @@ export function useSetupWizard() {
|
||||
if (currentStep.value > 1) {
|
||||
currentStep.value--
|
||||
}
|
||||
connectivityTest.value.showResult = false
|
||||
}
|
||||
|
||||
// 保存当前步骤的设置
|
||||
@@ -442,15 +823,16 @@ export function useSetupWizard() {
|
||||
// 完成向导
|
||||
async function completeWizard() {
|
||||
try {
|
||||
// 验证密码一致性
|
||||
if (wizardData.value.basic.password !== wizardData.value.basic.confirmPassword) {
|
||||
$toast.error(t('user.passwordMismatch'))
|
||||
return
|
||||
// 如果输入了密码,验证密码一致性
|
||||
if (wizardData.value.basic.password) {
|
||||
if (wizardData.value.basic.password !== wizardData.value.basic.confirmPassword) {
|
||||
$toast.error(t('dialog.userAddEdit.passwordMismatch'))
|
||||
return
|
||||
}
|
||||
// 更新用户密码
|
||||
await updateUserPassword()
|
||||
}
|
||||
|
||||
// 创建用户(如果还没有创建)
|
||||
await createUser()
|
||||
|
||||
// 保存最后一步的设置(资源偏好)
|
||||
await savePreferenceSettings()
|
||||
|
||||
@@ -462,16 +844,28 @@ export function useSetupWizard() {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
async function createUser() {
|
||||
// 更新用户密码
|
||||
async function updateUserPassword() {
|
||||
if (wizardData.value.basic.username && wizardData.value.basic.password) {
|
||||
try {
|
||||
// 检查用户是否已存在
|
||||
// 获取当前用户信息
|
||||
const response = await api.get('user/')
|
||||
const existingUsers = response.data || []
|
||||
const userExists = existingUsers.some((user: any) => user.name === wizardData.value.basic.username)
|
||||
const currentUser = existingUsers.find((user: any) => user.name === wizardData.value.basic.username)
|
||||
|
||||
if (!userExists) {
|
||||
if (currentUser) {
|
||||
// 更新现有用户的密码
|
||||
const userData = {
|
||||
name: wizardData.value.basic.username,
|
||||
password: wizardData.value.basic.password,
|
||||
is_active: currentUser.is_active,
|
||||
is_superuser: currentUser.is_superuser,
|
||||
}
|
||||
|
||||
await api.put(`user/${currentUser.id}`, userData)
|
||||
$toast.success(t('setupWizard.passwordUpdateSuccess'))
|
||||
} else {
|
||||
// 如果用户不存在,创建新用户(通常不会发生)
|
||||
const userData = {
|
||||
name: wizardData.value.basic.username,
|
||||
password: wizardData.value.basic.password,
|
||||
@@ -480,9 +874,12 @@ export function useSetupWizard() {
|
||||
}
|
||||
|
||||
await api.post('user/', userData)
|
||||
$toast.success(t('setupWizard.userCreateSuccess'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Create user failed or user already exists:', error)
|
||||
console.error('Update user password failed:', error)
|
||||
$toast.error(t('setupWizard.passwordUpdateFailed'))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -526,11 +923,18 @@ export function useSetupWizard() {
|
||||
download_path: wizardData.value.storage.downloadPath,
|
||||
library_path: wizardData.value.storage.libraryPath,
|
||||
priority: 0,
|
||||
monitor_type: 'compatibility',
|
||||
media_type: 'movie,tv',
|
||||
media_category: 'default',
|
||||
monitor_type: 'downloader',
|
||||
media_type: '',
|
||||
media_category: '',
|
||||
download_type_folder: true,
|
||||
download_category_folder: true,
|
||||
transfer_type: wizardData.value.storage.transferType,
|
||||
overwrite_mode: wizardData.value.storage.overwriteMode,
|
||||
renaming: true,
|
||||
scraping: true,
|
||||
notify: true,
|
||||
library_type_folder: true,
|
||||
library_category_folder: true,
|
||||
}
|
||||
|
||||
const response = await api.post('system/setting/Directories', [directory])
|
||||
@@ -563,6 +967,9 @@ export function useSetupWizard() {
|
||||
console.error('Save downloader settings failed:', error)
|
||||
$toast.error(t('setupWizard.saveDownloaderSettingsFailed'))
|
||||
}
|
||||
} else {
|
||||
// 没有选择下载器时,清空现有配置
|
||||
console.log('No downloader selected, skipping save')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,6 +992,9 @@ export function useSetupWizard() {
|
||||
console.error('Save media server settings failed:', error)
|
||||
$toast.error(t('setupWizard.saveMediaServerSettingsFailed'))
|
||||
}
|
||||
} else {
|
||||
// 没有选择媒体服务器时,清空现有配置
|
||||
console.log('No media server selected, skipping save')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,6 +1017,9 @@ export function useSetupWizard() {
|
||||
console.error('Save notification settings failed:', error)
|
||||
$toast.error(t('setupWizard.saveNotificationSettingsFailed'))
|
||||
}
|
||||
} else {
|
||||
// 没有选择通知时,清空现有配置
|
||||
console.log('No notification selected, skipping save')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +1062,9 @@ export function useSetupWizard() {
|
||||
if (result.data.GITHUB_TOKEN) {
|
||||
wizardData.value.basic.githubToken = result.data.GITHUB_TOKEN
|
||||
}
|
||||
if (result.data.SUPERUSER) {
|
||||
wizardData.value.basic.username = result.data.SUPERUSER
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Load system settings failed:', error)
|
||||
@@ -741,13 +1157,17 @@ export function useSetupWizard() {
|
||||
|
||||
// 初始化
|
||||
async function initialize() {
|
||||
createRandomString()
|
||||
await loadSystemSettings()
|
||||
await loadStorageSettings()
|
||||
await loadDownloaderSettings()
|
||||
await loadMediaServerSettings()
|
||||
await loadNotificationSettings()
|
||||
await loadPreferenceSettings()
|
||||
|
||||
// 如果没有API Token,则创建一个随机的
|
||||
if (!wizardData.value.basic.apiToken) {
|
||||
createRandomString()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -759,7 +1179,8 @@ export function useSetupWizard() {
|
||||
wizardData,
|
||||
selectedPreset,
|
||||
connectivityTest,
|
||||
|
||||
validationErrors,
|
||||
|
||||
// 方法
|
||||
createRandomString,
|
||||
copyValue,
|
||||
@@ -767,10 +1188,15 @@ export function useSetupWizard() {
|
||||
selectMediaServer,
|
||||
selectNotification,
|
||||
selectPreset,
|
||||
validateCurrentStep,
|
||||
validateDownloaderFields,
|
||||
validateMediaServerFields,
|
||||
validateNotificationFields,
|
||||
clearValidationErrors,
|
||||
testConnectivity,
|
||||
nextStep,
|
||||
prevStep,
|
||||
completeWizard,
|
||||
initialize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,10 +414,13 @@ export default {
|
||||
name: 'WeChat Work',
|
||||
corpId: 'Corp ID',
|
||||
corpIdHint: 'Corp ID in WeChat Work backend enterprise information',
|
||||
corpIdRequired: 'Corp ID cannot be empty',
|
||||
appId: 'App AgentId',
|
||||
appIdHint: 'AgentId of self-built app in WeChat Work',
|
||||
appIdRequired: 'App AgentId cannot be empty',
|
||||
appSecret: 'App Secret',
|
||||
appSecretHint: 'Secret of self-built app in WeChat Work',
|
||||
appSecretRequired: 'App Secret cannot be empty',
|
||||
proxy: 'Proxy Address',
|
||||
proxyHint:
|
||||
'Proxy address for WeChat message forwarding, required for self-built apps created after June 20, 2022',
|
||||
@@ -433,8 +436,10 @@ export default {
|
||||
name: 'Telegram',
|
||||
token: 'Bot Token',
|
||||
tokenHint: 'Telegram bot token, format: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11',
|
||||
tokenRequired: 'Bot Token cannot be empty',
|
||||
chatId: 'Chat ID',
|
||||
chatIdHint: 'Chat ID of user, group or channel that receives notifications',
|
||||
chatIdRequired: 'Chat ID cannot be empty',
|
||||
users: 'User Whitelist',
|
||||
usersHint: 'User IDs that can use Telegram bot, separated by commas. Leave empty to allow all users',
|
||||
admins: 'Admin Whitelist',
|
||||
@@ -449,15 +454,18 @@ export default {
|
||||
name: 'Slack',
|
||||
oauthToken: 'Slack Bot User OAuth Token',
|
||||
oauthTokenHint: 'Bot User OAuth Token in Slack app OAuth & Permissions page',
|
||||
oauthTokenRequired: 'OAuth Token cannot be empty',
|
||||
appToken: 'Slack App-Level Token',
|
||||
appTokenHint: 'App-Level Token in Slack app OAuth & Permissions page',
|
||||
channel: 'Channel Name',
|
||||
channelHint: 'Channel to send messages, default is "all"',
|
||||
channelRequired: 'Channel Name cannot be empty',
|
||||
},
|
||||
synologychat: {
|
||||
name: 'Synology Chat',
|
||||
webhook: 'Webhook URL',
|
||||
webhookHint: 'Synology Chat bot webhook URL',
|
||||
webhookRequired: 'Webhook URL cannot be empty',
|
||||
token: 'Token',
|
||||
tokenHint: 'Synology Chat bot token',
|
||||
},
|
||||
@@ -465,8 +473,10 @@ export default {
|
||||
name: 'VoceChat',
|
||||
host: 'Address',
|
||||
hostHint: 'VoceChat server address, format: http(s)://ip:port',
|
||||
hostRequired: 'Address cannot be empty',
|
||||
apiKey: 'Bot API Key',
|
||||
apiKeyHint: 'VoceChat bot API key',
|
||||
apiKeyRequired: 'API Key cannot be empty',
|
||||
channelId: 'Channel ID',
|
||||
channelIdHint: 'VoceChat channel ID, without #',
|
||||
},
|
||||
@@ -474,6 +484,7 @@ export default {
|
||||
name: 'WebPush',
|
||||
username: 'Login Username',
|
||||
usernameHint: 'Only push messages to the corresponding logged-in user',
|
||||
usernameRequired: 'Username cannot be empty',
|
||||
},
|
||||
},
|
||||
shortcut: {
|
||||
@@ -1778,8 +1789,12 @@ export default {
|
||||
add: 'Add User',
|
||||
edit: 'Edit User',
|
||||
username: 'Username',
|
||||
usernameRequired: 'Username cannot be empty',
|
||||
password: 'Password',
|
||||
passwordMinLength: 'Password must be at least 6 characters',
|
||||
confirmPassword: 'Confirm Password',
|
||||
confirmPasswordRequired: 'Please confirm password',
|
||||
passwordMismatch: 'Passwords do not match',
|
||||
email: 'Email',
|
||||
nickname: 'Nickname',
|
||||
status: 'Status',
|
||||
@@ -1800,9 +1815,7 @@ export default {
|
||||
webPush: 'WebPush',
|
||||
creatingUser: 'Creating user [{name}], please wait',
|
||||
updatingUser: 'Updating user [{name}], please wait',
|
||||
usernameRequired: 'Username cannot be empty',
|
||||
usernameExists: 'Username already exists',
|
||||
passwordMismatch: 'The two passwords do not match',
|
||||
userCreated: 'User [{name}] created successfully',
|
||||
userCreateFailed: 'Failed to create user: {message}',
|
||||
userUpdateSuccess: 'User [{name}] updated successfully',
|
||||
@@ -2633,6 +2646,9 @@ export default {
|
||||
nameRequired: 'Name cannot be empty',
|
||||
nameDuplicate: 'Name already exists',
|
||||
defaultChanged: 'Default downloader exists, has been replaced',
|
||||
hostRequired: 'Host cannot be empty',
|
||||
usernameRequired: 'Username cannot be empty',
|
||||
passwordRequired: 'Password cannot be empty',
|
||||
},
|
||||
filterRule: {
|
||||
title: 'Filter Rule',
|
||||
@@ -2680,6 +2696,11 @@ export default {
|
||||
password: 'Password',
|
||||
syncLibraries: 'Sync Libraries',
|
||||
syncLibrariesHint: 'Only selected libraries will be synchronized',
|
||||
hostRequired: 'Host cannot be empty',
|
||||
apiKeyRequired: 'API Key cannot be empty',
|
||||
tokenRequired: 'Token cannot be empty',
|
||||
usernameRequired: 'Username cannot be empty',
|
||||
passwordRequired: 'Password cannot be empty',
|
||||
nameExists: '【{name}】 already exists, please use a different name',
|
||||
},
|
||||
bangumi: {
|
||||
@@ -2904,6 +2925,12 @@ export default {
|
||||
testingNotification: 'Testing notification',
|
||||
checkingNotification: 'Checking notification connectivity',
|
||||
testFailedHint: 'Please check if the configuration is correct, you can retest after modification',
|
||||
unsupportedDownloaderType: 'Unsupported downloader type: {type}',
|
||||
unsupportedMediaServerType: 'Unsupported media server type: {type}',
|
||||
unsupportedNotificationType: 'Unsupported notification type: {type}',
|
||||
passwordUpdateSuccess: 'Password updated successfully',
|
||||
userCreateSuccess: 'User created successfully',
|
||||
passwordUpdateFailed: 'Failed to update password',
|
||||
basic: {
|
||||
title: 'Basic Settings',
|
||||
description: 'Set access domain, username/password and network configuration',
|
||||
@@ -2915,6 +2942,10 @@ export default {
|
||||
recognizeSourceHint: 'Set the default media info recognition data source',
|
||||
apiToken: 'API Token',
|
||||
apiTokenHint: 'System automatically generated API access token',
|
||||
currentUserHint: 'Current user, cannot be modified',
|
||||
passwordOptionalHint: 'Leave blank to keep current password',
|
||||
confirmPasswordHint: 'Confirm new password',
|
||||
apiTokenRequired: 'API Token is required',
|
||||
},
|
||||
storage: {
|
||||
title: 'Storage Configuration',
|
||||
@@ -2925,12 +2956,14 @@ export default {
|
||||
downloadPathHint: 'Set the storage path for downloaded files',
|
||||
libraryPath: 'Media Library Directory',
|
||||
libraryPathHint: 'Set the storage path for media files',
|
||||
downloadPathRequired: 'Download directory is required',
|
||||
libraryPathRequired: 'Media library directory is required',
|
||||
},
|
||||
downloader: {
|
||||
title: 'Downloader Configuration',
|
||||
description: 'Configure downloader (optional)',
|
||||
description: 'Configure downloader',
|
||||
info: 'Downloader Configuration',
|
||||
infoDesc: 'Configure downloader for automatic resource download (optional)',
|
||||
infoDesc: 'Configure downloader for resource download, can choose qBittorrent or Transmission',
|
||||
type: 'Downloader Type',
|
||||
typeHint: 'Select the type of downloader to use',
|
||||
name: 'Downloader Name',
|
||||
@@ -2944,9 +2977,9 @@ export default {
|
||||
},
|
||||
mediaServer: {
|
||||
title: 'Media Server',
|
||||
description: 'Configure media server (optional)',
|
||||
description: 'Configure media server',
|
||||
info: 'Media Server Configuration',
|
||||
infoDesc: 'Configure media server for media library management (optional)',
|
||||
infoDesc: 'Configure media server for media library management, can choose Emby, Jellyfin or Plex etc.',
|
||||
type: 'Media Server Type',
|
||||
typeHint: 'Select the type of media server to use',
|
||||
name: 'Server Name',
|
||||
@@ -2960,7 +2993,7 @@ export default {
|
||||
},
|
||||
notification: {
|
||||
title: 'Notification Settings',
|
||||
description: 'Configure notification channels (optional)',
|
||||
description: 'Configure notification channels',
|
||||
info: 'Notification Configuration',
|
||||
infoDesc: 'Configure notification channels for receiving system messages (optional)',
|
||||
type: 'Notification Type',
|
||||
|
||||
@@ -412,10 +412,13 @@ export default {
|
||||
name: '企业微信',
|
||||
corpId: '企业ID',
|
||||
corpIdHint: '企业微信后台企业信息中的企业ID',
|
||||
corpIdRequired: '企业ID不能为空',
|
||||
appId: '应用 AgentId',
|
||||
appIdHint: '企业微信自建应用的AgentId',
|
||||
appIdRequired: '应用AgentId不能为空',
|
||||
appSecret: '应用 Secret',
|
||||
appSecretHint: '企业微信自建应用的Secret',
|
||||
appSecretRequired: '应用Secret不能为空',
|
||||
proxy: '代理地址',
|
||||
proxyHint: '微信消息的转发代理地址,2022年6月20日后创建的自建应用才需要,不使用代理时需要保留默认值',
|
||||
token: 'Token',
|
||||
@@ -430,8 +433,10 @@ export default {
|
||||
name: 'Telegram',
|
||||
token: 'Bot Token',
|
||||
tokenHint: 'Telegram机器人token,格式:123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11',
|
||||
tokenRequired: 'Bot Token不能为空',
|
||||
chatId: 'Chat ID',
|
||||
chatIdHint: '接受消息通知的用户、群组或频道Chat ID',
|
||||
chatIdRequired: 'Chat ID不能为空',
|
||||
users: '用户白名单',
|
||||
usersHint: '可使用Telegram机器人的用户ID清单,多个用户用,分隔,不填写则所有用户都能使用',
|
||||
admins: '管理员白名单',
|
||||
@@ -446,15 +451,18 @@ export default {
|
||||
name: 'Slack',
|
||||
oauthToken: 'Slack Bot User OAuth Token',
|
||||
oauthTokenHint: 'Slack应用`OAuth & Permissions`页面中的`Bot User OAuth Token`',
|
||||
oauthTokenRequired: 'OAuth Token不能为空',
|
||||
appToken: 'Slack App-Level Token',
|
||||
appTokenHint: 'Slack应用`OAuth & Permissions`页面中的`App-Level Token`',
|
||||
channel: '频道名称',
|
||||
channelHint: '消息发送频道,默认`全体`',
|
||||
channelRequired: '频道名称不能为空',
|
||||
},
|
||||
synologychat: {
|
||||
name: 'Synology Chat',
|
||||
webhook: '机器人传入URL',
|
||||
webhookHint: 'Synology Chat机器人传入URL',
|
||||
webhookRequired: 'Webhook URL不能为空',
|
||||
token: '令牌',
|
||||
tokenHint: 'Synology Chat机器人令牌',
|
||||
},
|
||||
@@ -462,8 +470,10 @@ export default {
|
||||
name: 'VoceChat',
|
||||
host: '地址',
|
||||
hostHint: 'VoceChat服务端地址,格式:http(s)://ip:port',
|
||||
hostRequired: '地址不能为空',
|
||||
apiKey: '机器人密钥',
|
||||
apiKeyHint: 'VoceChat机器人密钥',
|
||||
apiKeyRequired: 'API密钥不能为空',
|
||||
channelId: '频道ID',
|
||||
channelIdHint: 'VoceChat的频道ID,不包含#号',
|
||||
},
|
||||
@@ -471,6 +481,7 @@ export default {
|
||||
name: 'WebPush',
|
||||
username: '登录用户名',
|
||||
usernameHint: '只有对应的用户登录后才会推送消息',
|
||||
usernameRequired: '用户名不能为空',
|
||||
},
|
||||
},
|
||||
shortcut: {
|
||||
@@ -1754,8 +1765,12 @@ export default {
|
||||
add: '添加用户',
|
||||
edit: '编辑用户',
|
||||
username: '用户名',
|
||||
usernameRequired: '用户名不能为空',
|
||||
password: '密码',
|
||||
passwordMinLength: '密码长度不能少于6位',
|
||||
confirmPassword: '确认密码',
|
||||
confirmPasswordRequired: '请确认密码',
|
||||
passwordMismatch: '两次输入的密码不一致',
|
||||
email: '邮箱',
|
||||
nickname: '昵称',
|
||||
status: '状态',
|
||||
@@ -1778,6 +1793,8 @@ export default {
|
||||
updatingUser: '正在更新【{name}】用户,请稍后',
|
||||
usernameRequired: '用户名不能为空',
|
||||
usernameExists: '用户名已存在',
|
||||
passwordMinLength: '密码长度不能少于6位字符',
|
||||
confirmPasswordRequired: '请确认密码',
|
||||
passwordMismatch: '两次输入的密码不一致',
|
||||
userCreated: '用户【{name}】创建成功',
|
||||
userCreateFailed: '创建用户失败:{message}',
|
||||
@@ -2601,6 +2618,9 @@ export default {
|
||||
nameRequired: '不能为空,且不能重名',
|
||||
nameDuplicate: '名称已存在',
|
||||
defaultChanged: '存在默认下载器,已替换',
|
||||
hostRequired: '地址不能为空',
|
||||
usernameRequired: '用户名不能为空',
|
||||
passwordRequired: '密码不能为空',
|
||||
},
|
||||
filterRule: {
|
||||
title: '过滤规则',
|
||||
@@ -2649,6 +2669,11 @@ export default {
|
||||
syncLibraries: '同步媒体库',
|
||||
syncLibrariesHint: '只有选中的媒体库才会被同步',
|
||||
nameExists: '【{name}】已存在,请替换为其他名称',
|
||||
hostRequired: '地址不能为空',
|
||||
apiKeyRequired: 'API密钥不能为空',
|
||||
tokenRequired: 'Token不能为空',
|
||||
usernameRequired: '用户名不能为空',
|
||||
passwordRequired: '密码不能为空',
|
||||
},
|
||||
bangumi: {
|
||||
category: '类别',
|
||||
@@ -2864,13 +2889,39 @@ export default {
|
||||
connectivityTestFailed: '连通性测试失败',
|
||||
testingStorage: '正在测试存储目录',
|
||||
checkingStorage: '检查存储目录连通性',
|
||||
storageTestFailed: '存储目录测试失败',
|
||||
testingDownloader: '正在测试下载器',
|
||||
checkingDownloader: '检查下载器连通性',
|
||||
downloaderTestFailed: '下载器测试失败',
|
||||
downloaderNotSelected: '未选择下载器',
|
||||
unsupportedDownloaderType: '不支持的下载器类型: {type}',
|
||||
testingMediaServer: '正在测试媒体服务器',
|
||||
checkingMediaServer: '检查媒体服务器连通性',
|
||||
mediaServerTestFailed: '媒体服务器测试失败',
|
||||
mediaServerNotSelected: '未选择媒体服务器',
|
||||
unsupportedMediaServerType: '不支持的媒体服务器类型: {type}',
|
||||
testingNotification: '正在测试消息通知',
|
||||
checkingNotification: '检查消息通知连通性',
|
||||
notificationTestFailed: '消息通知测试失败',
|
||||
notificationNotSelected: '未选择通知类型',
|
||||
unsupportedNotificationType: '不支持的通知类型: {type}',
|
||||
testFailedHint: '请检查配置是否正确,修改后可以重新测试',
|
||||
saveStepFailed: '保存步骤设置失败',
|
||||
basicSettingsSaved: '基础设置保存成功',
|
||||
saveBasicSettingsFailed: '保存基础设置失败',
|
||||
storageSettingsSaved: '存储设置保存成功',
|
||||
saveStorageSettingsFailed: '保存存储设置失败',
|
||||
downloaderSettingsSaved: '下载器设置保存成功',
|
||||
saveDownloaderSettingsFailed: '保存下载器设置失败',
|
||||
mediaServerSettingsSaved: '媒体服务器设置保存成功',
|
||||
saveMediaServerSettingsFailed: '保存媒体服务器设置失败',
|
||||
notificationSettingsSaved: '通知设置保存成功',
|
||||
saveNotificationSettingsFailed: '保存通知设置失败',
|
||||
preferenceSettingsSaved: '偏好设置保存成功',
|
||||
savePreferenceSettingsFailed: '保存偏好设置失败',
|
||||
passwordUpdateSuccess: '密码更新成功',
|
||||
passwordUpdateFailed: '密码更新失败',
|
||||
userCreateSuccess: '用户创建成功',
|
||||
basic: {
|
||||
title: '基础设置',
|
||||
description: '设置访问域名、用户名密码和网络配置',
|
||||
@@ -2882,6 +2933,10 @@ export default {
|
||||
recognizeSourceHint: '设置默认媒体信息识别数据源',
|
||||
apiToken: 'API 令牌',
|
||||
apiTokenHint: '系统自动生成的 API 访问令牌',
|
||||
currentUserHint: '当前用户,不可修改',
|
||||
passwordOptionalHint: '留空表示不修改密码',
|
||||
confirmPasswordHint: '确认新密码',
|
||||
apiTokenRequired: 'API Token不能为空',
|
||||
},
|
||||
storage: {
|
||||
title: '存储配置',
|
||||
@@ -2892,12 +2947,14 @@ export default {
|
||||
downloadPathHint: '设置下载文件的存储路径',
|
||||
libraryPath: '媒体库目录',
|
||||
libraryPathHint: '设置媒体文件的存储路径',
|
||||
downloadPathRequired: '下载目录不能为空',
|
||||
libraryPathRequired: '媒体库目录不能为空',
|
||||
},
|
||||
downloader: {
|
||||
title: '下载器配置',
|
||||
description: '配置下载器(可选)',
|
||||
description: '配置下载器',
|
||||
info: '下载器配置说明',
|
||||
infoDesc: '配置下载器用于自动下载资源(可选)',
|
||||
infoDesc: '配置下载器用于下载资源,可选择qBittorrent或Transmission',
|
||||
type: '下载器类型',
|
||||
typeHint: '选择要使用的下载器类型',
|
||||
name: '下载器名称',
|
||||
@@ -2911,9 +2968,9 @@ export default {
|
||||
},
|
||||
mediaServer: {
|
||||
title: '媒体服务器',
|
||||
description: '配置媒体服务器(可选)',
|
||||
description: '配置媒体服务器',
|
||||
info: '媒体服务器配置说明',
|
||||
infoDesc: '配置媒体服务器用于媒体库管理(可选)',
|
||||
infoDesc: '配置媒体服务器用于媒体库管理,可选择Emby、Jellyfin或Plex等',
|
||||
type: '媒体服务器类型',
|
||||
typeHint: '选择要使用的媒体服务器类型',
|
||||
name: '服务器名称',
|
||||
@@ -2927,7 +2984,7 @@ export default {
|
||||
},
|
||||
notification: {
|
||||
title: '通知设置',
|
||||
description: '配置通知渠道(可选)',
|
||||
description: '配置通知渠道',
|
||||
info: '通知配置说明',
|
||||
infoDesc: '配置通知渠道用于接收系统消息(可选)',
|
||||
type: '通知类型',
|
||||
|
||||
@@ -1753,8 +1753,12 @@ export default {
|
||||
add: '添加用戶',
|
||||
edit: '編輯用戶',
|
||||
username: '用戶名',
|
||||
usernameRequired: '用戶名不能為空',
|
||||
password: '密碼',
|
||||
passwordMinLength: '密碼長度不能少於6位',
|
||||
confirmPassword: '確認密碼',
|
||||
confirmPasswordRequired: '請確認密碼',
|
||||
passwordMismatch: '兩次輸入的密碼不一致',
|
||||
email: '郵箱',
|
||||
nickname: '暱稱',
|
||||
status: '狀態',
|
||||
@@ -1775,9 +1779,7 @@ export default {
|
||||
webPush: 'WebPush',
|
||||
creatingUser: '正在創建【{name}】用戶,請稍後',
|
||||
updatingUser: '正在更新【{name}】用戶,請稍後',
|
||||
usernameRequired: '用戶名不能為空',
|
||||
usernameExists: '用戶名已存在',
|
||||
passwordMismatch: '兩次輸入的密碼不一致',
|
||||
userCreated: '用戶【{name}】創建成功',
|
||||
userCreateFailed: '創建用戶失敗:{message}',
|
||||
userUpdateSuccess: '用戶【{name}】更新成功',
|
||||
@@ -2600,6 +2602,9 @@ export default {
|
||||
nameRequired: '名稱不能為空',
|
||||
nameDuplicate: '名稱已存在',
|
||||
defaultChanged: '存在預設下載器,已替換',
|
||||
hostRequired: '地址不能為空',
|
||||
usernameRequired: '用戶名不能為空',
|
||||
passwordRequired: '密碼不能為空',
|
||||
},
|
||||
filterRule: {
|
||||
title: '過濾規則',
|
||||
@@ -2635,13 +2640,18 @@ export default {
|
||||
host: '地址',
|
||||
hostPlaceholder: 'http(s)://ip:port',
|
||||
hostHint: '服務端地址,格式:http(s)://ip:port',
|
||||
hostRequired: '地址不能為空',
|
||||
playHost: '外網播放地址',
|
||||
playHostPlaceholder: 'http(s)://domain:port',
|
||||
playHostHint: '跳轉播放頁面使用的地址,格式:http(s)://domain:port',
|
||||
apiKey: 'API密鑰',
|
||||
apiKeyRequired: 'API密鑰不能為空',
|
||||
embyApiKeyHint: 'Emby設置->高級->API密鑰中生成的密鑰',
|
||||
jellyfinApiKeyHint: 'Jellyfin設置->高級->API密鑰中生成的密鑰',
|
||||
plexToken: 'X-Plex-Token',
|
||||
tokenRequired: 'Token不能為空',
|
||||
usernameRequired: '用戶名不能為空',
|
||||
passwordRequired: '密碼不能為空',
|
||||
plexTokenHint: '瀏覽器F12->網絡,從Plex請求URL中獲取的X-Plex-Token',
|
||||
username: '用戶名',
|
||||
password: '密碼',
|
||||
@@ -2870,6 +2880,12 @@ export default {
|
||||
testingNotification: '正在測試消息通知',
|
||||
checkingNotification: '檢查消息通知連通性',
|
||||
testFailedHint: '請檢查配置是否正確,修改後可以重新測試',
|
||||
unsupportedDownloaderType: '不支援的下載器類型: {type}',
|
||||
unsupportedMediaServerType: '不支援的媒體服務器類型: {type}',
|
||||
unsupportedNotificationType: '不支援的通知類型: {type}',
|
||||
passwordUpdateSuccess: '密碼更新成功',
|
||||
userCreateSuccess: '使用者建立成功',
|
||||
passwordUpdateFailed: '密碼更新失敗',
|
||||
basic: {
|
||||
title: '基礎設定',
|
||||
description: '設定存取網域、用戶名密碼和網路配置',
|
||||
@@ -2881,6 +2897,10 @@ export default {
|
||||
recognizeSourceHint: '設定預設媒體資訊識別資料來源',
|
||||
apiToken: 'API 權杖',
|
||||
apiTokenHint: '系統自動產生的 API 存取權杖',
|
||||
currentUserHint: '目前使用者,不可修改',
|
||||
passwordOptionalHint: '留空表示不修改密碼',
|
||||
confirmPasswordHint: '確認新密碼',
|
||||
apiTokenRequired: 'API Token 不能為空',
|
||||
},
|
||||
storage: {
|
||||
title: '儲存配置',
|
||||
@@ -2891,12 +2911,14 @@ export default {
|
||||
downloadPathHint: '設定下載檔案的儲存路徑',
|
||||
libraryPath: '媒體庫目錄',
|
||||
libraryPathHint: '設定媒體檔案的儲存路徑',
|
||||
downloadPathRequired: '下載目錄不能為空',
|
||||
libraryPathRequired: '媒體庫目錄不能為空',
|
||||
},
|
||||
downloader: {
|
||||
title: '下載器配置',
|
||||
description: '設定下載器(可選)',
|
||||
description: '設定下載器',
|
||||
info: '下載器設定說明',
|
||||
infoDesc: '設定下載器用於自動下載資源(可選)',
|
||||
infoDesc: '設定下載器用於下載資源,可選擇qBittorrent或Transmission',
|
||||
type: '下載器類型',
|
||||
typeHint: '選擇要使用的下載器類型',
|
||||
name: '下載器名稱',
|
||||
@@ -2910,9 +2932,9 @@ export default {
|
||||
},
|
||||
mediaServer: {
|
||||
title: '媒體伺服器',
|
||||
description: '設定媒體伺服器(可選)',
|
||||
description: '設定媒體伺服器',
|
||||
info: '媒體伺服器設定說明',
|
||||
infoDesc: '設定媒體伺服器用於媒體庫管理(可選)',
|
||||
infoDesc: '設定媒體伺服器用於媒體庫管理,可選擇Emby、Jellyfin或Plex等',
|
||||
type: '媒體伺服器類型',
|
||||
typeHint: '選擇要使用的媒體伺服器類型',
|
||||
name: '伺服器名稱',
|
||||
@@ -2926,7 +2948,7 @@ export default {
|
||||
},
|
||||
notification: {
|
||||
title: '通知設定',
|
||||
description: '設定通知管道(可選)',
|
||||
description: '設定通知管道',
|
||||
info: '通知設定說明',
|
||||
infoDesc: '設定通知管道用於接收系統訊息(可選)',
|
||||
type: '通知類型',
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { useSetupWizard } from '@/composables/useSetupWizard'
|
||||
import BasicSettingsStep from '@/views/setup/BasicSettingsStep.vue'
|
||||
import StorageSettingsStep from '@/views/setup/StorageSettingsStep.vue'
|
||||
@@ -14,19 +13,9 @@ import ConnectivityTest from '@/views/setup/ConnectivityTest.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const $toast = useToast()
|
||||
|
||||
const {
|
||||
currentStep,
|
||||
totalSteps,
|
||||
stepTitles,
|
||||
stepDescriptions,
|
||||
connectivityTest,
|
||||
nextStep,
|
||||
prevStep,
|
||||
completeWizard,
|
||||
initialize,
|
||||
} = useSetupWizard()
|
||||
const { currentStep, totalSteps, stepTitles, connectivityTest, nextStep, prevStep, completeWizard, initialize } =
|
||||
useSetupWizard()
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
@@ -184,4 +173,4 @@ onMounted(async () => {
|
||||
margin-inline: auto;
|
||||
max-inline-size: 800px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,11 +3,59 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useSetupWizard } from '@/composables/useSetupWizard'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wizardData, createRandomString, copyValue } = useSetupWizard()
|
||||
const { wizardData, createRandomString, copyValue, validateCurrentStep } = useSetupWizard()
|
||||
|
||||
// 密码可见性控制
|
||||
const isPasswordVisible = ref(false)
|
||||
const isConfirmPasswordVisible = ref(false)
|
||||
|
||||
// 验证状态
|
||||
const validation = computed(() => validateCurrentStep())
|
||||
const hasErrors = computed(() => !validation.value.isValid)
|
||||
|
||||
// 密码相关验证
|
||||
const passwordError = computed(() => {
|
||||
if (!wizardData.value.basic.password) return false
|
||||
return wizardData.value.basic.password.length < 6
|
||||
})
|
||||
|
||||
const confirmPasswordError = computed(() => {
|
||||
if (!wizardData.value.basic.password) return false
|
||||
if (!wizardData.value.basic.confirmPassword) return true
|
||||
return wizardData.value.basic.password !== wizardData.value.basic.confirmPassword
|
||||
})
|
||||
|
||||
const passwordErrorMessage = computed(() => {
|
||||
if (passwordError.value) return t('dialog.userAddEdit.passwordMinLength')
|
||||
return ''
|
||||
})
|
||||
|
||||
const confirmPasswordErrorMessage = computed(() => {
|
||||
if (!wizardData.value.basic.password) return ''
|
||||
if (!wizardData.value.basic.confirmPassword) return t('dialog.userAddEdit.confirmPasswordRequired')
|
||||
if (confirmPasswordError.value) return t('dialog.userAddEdit.passwordMismatch')
|
||||
return ''
|
||||
})
|
||||
|
||||
// API Token验证
|
||||
const apiTokenError = computed(() => {
|
||||
return !wizardData.value.basic.apiToken && hasErrors.value
|
||||
})
|
||||
|
||||
const apiTokenErrorMessage = computed(() => {
|
||||
if (apiTokenError.value) return t('setupWizard.basic.apiTokenRequired')
|
||||
return ''
|
||||
})
|
||||
|
||||
// 用户名验证(虽然是只读的,但为了完整性)
|
||||
const usernameError = computed(() => {
|
||||
return !wizardData.value.basic.username && hasErrors.value
|
||||
})
|
||||
|
||||
const usernameErrorMessage = computed(() => {
|
||||
if (usernameError.value) return t('dialog.userAddEdit.usernameRequired')
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -32,10 +80,12 @@ const isConfirmPasswordVisible = ref(false)
|
||||
<VTextField
|
||||
v-model="wizardData.basic.username"
|
||||
:label="t('user.username')"
|
||||
:hint="t('user.usernameHint')"
|
||||
:hint="t('setupWizard.basic.currentUserHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account"
|
||||
:rules="[(v: string) => !!v || t('user.usernameRequired')]"
|
||||
readonly
|
||||
:error="usernameError"
|
||||
:error-messages="usernameError ? [usernameErrorMessage] : []"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -43,12 +93,14 @@ const isConfirmPasswordVisible = ref(false)
|
||||
v-model="wizardData.basic.password"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
:label="t('user.password')"
|
||||
:hint="t('user.passwordHint')"
|
||||
:hint="t('setupWizard.basic.passwordOptionalHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-lock"
|
||||
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
:rules="[(v: string) => !!v || t('user.passwordRequired'), (v: string) => v.length >= 6 || t('user.passwordMinLength')]"
|
||||
:error="passwordError"
|
||||
:error-messages="passwordError ? [passwordErrorMessage] : []"
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -56,15 +108,15 @@ const isConfirmPasswordVisible = ref(false)
|
||||
v-model="wizardData.basic.confirmPassword"
|
||||
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||
:label="t('user.confirmPassword')"
|
||||
:hint="t('user.confirmPasswordHint')"
|
||||
:hint="t('setupWizard.basic.confirmPasswordHint')"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-lock-check"
|
||||
:append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||
:rules="[
|
||||
(v: string) => !!v || t('user.confirmPasswordRequired'),
|
||||
(v: string) => v === wizardData.basic.password || t('user.passwordMismatch')
|
||||
]"
|
||||
:disabled="!wizardData.basic.password"
|
||||
:error="confirmPasswordError"
|
||||
:error-messages="confirmPasswordError ? [confirmPasswordErrorMessage] : []"
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -98,10 +150,11 @@ const isConfirmPasswordVisible = ref(false)
|
||||
@click:append-inner="
|
||||
wizardData.basic.apiToken ? copyValue(wizardData.basic.apiToken) : createRandomString()
|
||||
"
|
||||
readonly
|
||||
:error="apiTokenError"
|
||||
:error-messages="apiTokenError ? [apiTokenErrorMessage] : []"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useSetupWizard } from '@/composables/useSetupWizard'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wizardData, selectDownloader } = useSetupWizard()
|
||||
const { wizardData, selectDownloader, validationErrors } = useSetupWizard()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -34,12 +34,7 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
@click="selectDownloader('qbittorrent')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/qbittorrent.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/qbittorrent.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">qBittorrent</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -52,12 +47,7 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
@click="selectDownloader('transmission')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/transmission.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/transmission.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">Transmission</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -78,9 +68,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
:label="t('downloader.name')"
|
||||
:placeholder="t('downloader.nameRequired')"
|
||||
:hint="t('downloader.name')"
|
||||
:error="validationErrors.downloader.name"
|
||||
:error-messages="validationErrors.downloader.name ? [t('downloader.nameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -89,9 +82,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
:label="t('downloader.host')"
|
||||
placeholder="http(s)://ip:port"
|
||||
:hint="t('downloader.host')"
|
||||
:error="validationErrors.downloader.host"
|
||||
:error-messages="validationErrors.downloader.host ? [t('downloader.hostRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -99,9 +95,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
v-model="wizardData.downloader.config.username"
|
||||
:label="t('downloader.username')"
|
||||
:hint="t('downloader.username')"
|
||||
:error="validationErrors.downloader.username"
|
||||
:error-messages="validationErrors.downloader.username ? [t('downloader.usernameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-account"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -110,9 +109,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
type="password"
|
||||
:label="t('downloader.password')"
|
||||
:hint="t('downloader.password')"
|
||||
:error="validationErrors.downloader.password"
|
||||
:error-messages="validationErrors.downloader.password ? [t('downloader.passwordRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-lock"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -159,9 +161,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
:label="t('downloader.name')"
|
||||
:placeholder="t('downloader.nameRequired')"
|
||||
:hint="t('downloader.name')"
|
||||
:error="validationErrors.downloader.name"
|
||||
:error-messages="validationErrors.downloader.name ? [t('downloader.nameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -170,9 +175,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
:label="t('downloader.host')"
|
||||
placeholder="http(s)://ip:port"
|
||||
:hint="t('downloader.host')"
|
||||
:error="validationErrors.downloader.host"
|
||||
:error-messages="validationErrors.downloader.host ? [t('downloader.hostRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -180,9 +188,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
v-model="wizardData.downloader.config.username"
|
||||
:label="t('downloader.username')"
|
||||
:hint="t('downloader.username')"
|
||||
:error="validationErrors.downloader.username"
|
||||
:error-messages="validationErrors.downloader.username ? [t('downloader.usernameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-account"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -191,9 +202,12 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
type="password"
|
||||
:label="t('downloader.password')"
|
||||
:hint="t('downloader.password')"
|
||||
:error="validationErrors.downloader.password"
|
||||
:error-messages="validationErrors.downloader.password ? [t('downloader.passwordRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-lock"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -251,4 +265,4 @@ const { wizardData, selectDownloader } = useSetupWizard()
|
||||
.v-card--variant-tonal.v-theme--dark {
|
||||
background-color: rgb(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useSetupWizard } from '@/composables/useSetupWizard'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
const { wizardData, selectMediaServer, validationErrors } = useSetupWizard()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -34,12 +34,7 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
@click="selectMediaServer('emby')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/emby.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/emby.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">Emby</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -52,12 +47,7 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
@click="selectMediaServer('jellyfin')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/jellyfin.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/jellyfin.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">Jellyfin</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -70,12 +60,7 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
@click="selectMediaServer('plex')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/plex.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/plex.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">Plex</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -88,12 +73,7 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
@click="selectMediaServer('trimemedia')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/trimemedia.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/trimemedia.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">飞牛影视</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -114,9 +94,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('common.name')"
|
||||
:placeholder="t('mediaserver.nameRequired')"
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
:error="validationErrors.mediaServer.name"
|
||||
:error-messages="validationErrors.mediaServer.name ? [t('mediaserver.nameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -125,9 +108,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('mediaserver.host')"
|
||||
:placeholder="t('mediaserver.hostPlaceholder')"
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
:error="validationErrors.mediaServer.host"
|
||||
:error-messages="validationErrors.mediaServer.host ? [t('mediaserver.hostRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -146,9 +132,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
v-model="wizardData.mediaServer.config.apikey"
|
||||
:label="t('mediaserver.apiKey')"
|
||||
:hint="t('mediaserver.embyApiKeyHint')"
|
||||
:error="validationErrors.mediaServer.apikey"
|
||||
:error-messages="validationErrors.mediaServer.apikey ? [t('mediaserver.apiKeyRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -174,9 +163,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('common.name')"
|
||||
:placeholder="t('mediaserver.nameRequired')"
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
:error="validationErrors.mediaServer.name"
|
||||
:error-messages="validationErrors.mediaServer.name ? [t('mediaserver.nameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -185,9 +177,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('mediaserver.host')"
|
||||
:placeholder="t('mediaserver.hostPlaceholder')"
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
:error="validationErrors.mediaServer.host"
|
||||
:error-messages="validationErrors.mediaServer.host ? [t('mediaserver.hostRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -206,9 +201,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
v-model="wizardData.mediaServer.config.apikey"
|
||||
:label="t('mediaserver.apiKey')"
|
||||
:hint="t('mediaserver.jellyfinApiKeyHint')"
|
||||
:error="validationErrors.mediaServer.apikey"
|
||||
:error-messages="validationErrors.mediaServer.apikey ? [t('mediaserver.apiKeyRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -234,9 +232,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('common.name')"
|
||||
:placeholder="t('mediaserver.nameRequired')"
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
:error="validationErrors.mediaServer.name"
|
||||
:error-messages="validationErrors.mediaServer.name ? [t('mediaserver.nameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -245,9 +246,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('mediaserver.host')"
|
||||
:placeholder="t('mediaserver.hostPlaceholder')"
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
:error="validationErrors.mediaServer.host"
|
||||
:error-messages="validationErrors.mediaServer.host ? [t('mediaserver.hostRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -265,8 +269,11 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
<VTextField
|
||||
v-model="wizardData.mediaServer.config.username"
|
||||
:label="t('mediaserver.username')"
|
||||
:error="validationErrors.mediaServer.username"
|
||||
:error-messages="validationErrors.mediaServer.username ? [t('mediaserver.usernameRequired')] : []"
|
||||
active
|
||||
prepend-inner-icon="mdi-account"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -274,8 +281,11 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
type="password"
|
||||
v-model="wizardData.mediaServer.config.password"
|
||||
:label="t('mediaserver.password')"
|
||||
:error="validationErrors.mediaServer.password"
|
||||
:error-messages="validationErrors.mediaServer.password ? [t('mediaserver.passwordRequired')] : []"
|
||||
active
|
||||
prepend-inner-icon="mdi-lock"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -301,9 +311,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('common.name')"
|
||||
:placeholder="t('mediaserver.nameRequired')"
|
||||
:hint="t('mediaserver.serverAlias')"
|
||||
:error="validationErrors.mediaServer.name"
|
||||
:error-messages="validationErrors.mediaServer.name ? [t('mediaserver.nameRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -312,9 +325,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
:label="t('mediaserver.host')"
|
||||
:placeholder="t('mediaserver.hostPlaceholder')"
|
||||
:hint="t('mediaserver.hostHint')"
|
||||
:error="validationErrors.mediaServer.host"
|
||||
:error-messages="validationErrors.mediaServer.host ? [t('mediaserver.hostRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-server"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -333,9 +349,12 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
v-model="wizardData.mediaServer.config.token"
|
||||
:label="t('mediaserver.plexToken')"
|
||||
:hint="t('mediaserver.plexTokenHint')"
|
||||
:error="validationErrors.mediaServer.token"
|
||||
:error-messages="validationErrors.mediaServer.token ? [t('mediaserver.tokenRequired')] : []"
|
||||
persistent-hint
|
||||
active
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
@@ -406,4 +425,4 @@ const { wizardData, selectMediaServer } = useSetupWizard()
|
||||
.v-card--variant-tonal.v-theme--dark {
|
||||
background-color: rgb(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useSetupWizard } from '@/composables/useSetupWizard'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wizardData, selectNotification } = useSetupWizard()
|
||||
const { wizardData, selectNotification, validationErrors } = useSetupWizard()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -34,12 +34,7 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
@click="selectNotification('wechat')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/wechat.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/wechat.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">微信</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -52,12 +47,7 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
@click="selectNotification('telegram')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/telegram.webp"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/telegram.webp" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">Telegram</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -70,12 +60,7 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
@click="selectNotification('slack')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/slack.webp"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/slack.webp" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">Slack</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -88,12 +73,7 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
@click="selectNotification('synologychat')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/synologychat.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/synologychat.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">Synology Chat</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -106,12 +86,7 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
@click="selectNotification('vocechat')"
|
||||
>
|
||||
<VCardText class="text-center">
|
||||
<VImg
|
||||
src="/src/assets/images/logos/vocechat.png"
|
||||
height="48"
|
||||
width="48"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<VImg src="/src/assets/images/logos/vocechat.png" height="48" width="48" class="mx-auto mb-2" />
|
||||
<div class="text-h6">VoceChat</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
@@ -139,13 +114,10 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch v-model="wizardData.notification.enabled" :label="t('notification.enabled')" />
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VAutocomplete
|
||||
v-model="wizardData.notification.switchs"
|
||||
:items="[]"
|
||||
:items="[] as string[]"
|
||||
:label="t('notification.type')"
|
||||
:hint="t('notification.typeHint')"
|
||||
multiple
|
||||
@@ -163,8 +135,11 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
:error="validationErrors.notification.name"
|
||||
:error-messages="validationErrors.notification.name ? [t('notification.nameRequired')] : []"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -172,8 +147,13 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.WECHAT_CORPID"
|
||||
:label="t('notification.wechat.corpId')"
|
||||
:hint="t('notification.wechat.corpIdHint')"
|
||||
:error="validationErrors.notification.WECHAT_CORPID"
|
||||
:error-messages="
|
||||
validationErrors.notification.WECHAT_CORPID ? [t('notification.wechat.corpIdRequired')] : []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-domain"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -181,8 +161,13 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.WECHAT_APP_ID"
|
||||
:label="t('notification.wechat.appId')"
|
||||
:hint="t('notification.wechat.appIdHint')"
|
||||
:error="validationErrors.notification.WECHAT_APP_ID"
|
||||
:error-messages="
|
||||
validationErrors.notification.WECHAT_APP_ID ? [t('notification.wechat.appIdRequired')] : []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-application"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -190,8 +175,15 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.WECHAT_APP_SECRET"
|
||||
:label="t('notification.wechat.appSecret')"
|
||||
:hint="t('notification.wechat.appSecretHint')"
|
||||
:error="validationErrors.notification.WECHAT_APP_SECRET"
|
||||
:error-messages="
|
||||
validationErrors.notification.WECHAT_APP_SECRET
|
||||
? [t('notification.wechat.appSecretRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -239,8 +231,11 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
:error="validationErrors.notification.name"
|
||||
:error-messages="validationErrors.notification.name ? [t('notification.nameRequired')] : []"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -248,8 +243,13 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.TELEGRAM_TOKEN"
|
||||
:label="t('notification.telegram.token')"
|
||||
:hint="t('notification.telegram.tokenHint')"
|
||||
:error="validationErrors.notification.TELEGRAM_TOKEN"
|
||||
:error-messages="
|
||||
validationErrors.notification.TELEGRAM_TOKEN ? [t('notification.telegram.tokenRequired')] : []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -257,8 +257,15 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.TELEGRAM_CHAT_ID"
|
||||
:label="t('notification.telegram.chatId')"
|
||||
:hint="t('notification.telegram.chatIdHint')"
|
||||
:error="validationErrors.notification.TELEGRAM_CHAT_ID"
|
||||
:error-messages="
|
||||
validationErrors.notification.TELEGRAM_CHAT_ID
|
||||
? [t('notification.telegram.chatIdRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-chat"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -299,8 +306,11 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
:error="validationErrors.notification.name"
|
||||
:error-messages="validationErrors.notification.name ? [t('notification.nameRequired')] : []"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -309,8 +319,15 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.slack.oauthToken')"
|
||||
:placeholder="t('notification.slack.oauthTokenPlaceholder')"
|
||||
:hint="t('notification.slack.oauthTokenHint')"
|
||||
:error="validationErrors.notification.SLACK_OAUTH_TOKEN"
|
||||
:error-messages="
|
||||
validationErrors.notification.SLACK_OAUTH_TOKEN
|
||||
? [t('notification.slack.oauthTokenRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -329,8 +346,13 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.slack.channel')"
|
||||
:placeholder="t('notification.slack.channelPlaceholder')"
|
||||
:hint="t('notification.slack.channelHint')"
|
||||
:error="validationErrors.notification.SLACK_CHANNEL"
|
||||
:error-messages="
|
||||
validationErrors.notification.SLACK_CHANNEL ? [t('notification.slack.channelRequired')] : []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-pound"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -341,8 +363,11 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
:error="validationErrors.notification.name"
|
||||
:error-messages="validationErrors.notification.name ? [t('notification.nameRequired')] : []"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -350,8 +375,15 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.SYNOLOGYCHAT_WEBHOOK"
|
||||
:label="t('notification.synologychat.webhook')"
|
||||
:hint="t('notification.synologychat.webhookHint')"
|
||||
:error="validationErrors.notification.SYNOLOGYCHAT_WEBHOOK"
|
||||
:error-messages="
|
||||
validationErrors.notification.SYNOLOGYCHAT_WEBHOOK
|
||||
? [t('notification.synologychat.webhookRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-webhook"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -371,8 +403,11 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
:error="validationErrors.notification.name"
|
||||
:error-messages="validationErrors.notification.name ? [t('notification.nameRequired')] : []"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -380,8 +415,13 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.VOCECHAT_HOST"
|
||||
:label="t('notification.vocechat.host')"
|
||||
:hint="t('notification.vocechat.hostHint')"
|
||||
:error="validationErrors.notification.VOCECHAT_HOST"
|
||||
:error-messages="
|
||||
validationErrors.notification.VOCECHAT_HOST ? [t('notification.vocechat.hostRequired')] : []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-server"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -389,8 +429,15 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.VOCECHAT_API_KEY"
|
||||
:label="t('notification.vocechat.apiKey')"
|
||||
:hint="t('notification.vocechat.apiKeyHint')"
|
||||
:error="validationErrors.notification.VOCECHAT_API_KEY"
|
||||
:error-messages="
|
||||
validationErrors.notification.VOCECHAT_API_KEY
|
||||
? [t('notification.vocechat.apiKeyRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-key"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -411,8 +458,11 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
:label="t('notification.name')"
|
||||
:placeholder="t('notification.name')"
|
||||
:hint="t('notification.nameHint')"
|
||||
:error="validationErrors.notification.name"
|
||||
:error-messages="validationErrors.notification.name ? [t('notification.nameRequired')] : []"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-label"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -420,8 +470,15 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
v-model="wizardData.notification.config.WEBPUSH_USERNAME"
|
||||
:label="t('notification.webpush.username')"
|
||||
:hint="t('notification.webpush.usernameHint')"
|
||||
:error="validationErrors.notification.WEBPUSH_USERNAME"
|
||||
:error-messages="
|
||||
validationErrors.notification.WEBPUSH_USERNAME
|
||||
? [t('notification.webpush.usernameRequired')]
|
||||
: []
|
||||
"
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-account"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -478,4 +535,4 @@ const { wizardData, selectNotification } = useSetupWizard()
|
||||
.v-card--variant-tonal.v-theme--dark {
|
||||
background-color: rgb(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,11 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useSetupWizard } from '@/composables/useSetupWizard'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { wizardData } = useSetupWizard()
|
||||
const { wizardData, validateCurrentStep } = useSetupWizard()
|
||||
|
||||
// 验证状态
|
||||
const validation = computed(() => validateCurrentStep())
|
||||
const hasErrors = computed(() => !validation.value.isValid)
|
||||
|
||||
// 整理方式选项
|
||||
const transferTypeItems = [
|
||||
@@ -44,6 +48,10 @@ const overwriteModeItems = [
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-download"
|
||||
placeholder="/downloads"
|
||||
:error="!wizardData.storage.downloadPath && hasErrors"
|
||||
:error-messages="
|
||||
!wizardData.storage.downloadPath && hasErrors ? [t('setupWizard.storage.downloadPathRequired')] : []
|
||||
"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
@@ -54,10 +62,14 @@ const overwriteModeItems = [
|
||||
persistent-hint
|
||||
prepend-inner-icon="mdi-folder-multiple"
|
||||
placeholder="/media"
|
||||
:error="!wizardData.storage.libraryPath && hasErrors"
|
||||
:error-messages="
|
||||
!wizardData.storage.libraryPath && hasErrors ? [t('setupWizard.storage.libraryPathRequired')] : []
|
||||
"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSelect
|
||||
<VAutocomplete
|
||||
v-model="wizardData.storage.transferType"
|
||||
:label="t('directory.transferType')"
|
||||
:hint="t('directory.transferTypeHint')"
|
||||
@@ -67,7 +79,7 @@ const overwriteModeItems = [
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSelect
|
||||
<VAutocomplete
|
||||
v-model="wizardData.storage.overwriteMode"
|
||||
:label="t('directory.overwriteMode')"
|
||||
:hint="t('directory.overwriteModeHint')"
|
||||
@@ -79,4 +91,4 @@ const overwriteModeItems = [
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user