增强配置向导功能

This commit is contained in:
jxxghp
2025-09-10 14:46:02 +08:00
parent 55b383780e
commit 3750d5cba0
10 changed files with 911 additions and 229 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>