refine the logic of update storage path

This commit is contained in:
stkevintan
2025-12-08 16:21:41 +08:00
parent 8add4e6b46
commit 180195ab7d
4 changed files with 66 additions and 44 deletions

View File

@@ -59,44 +59,57 @@ const downloaderForm = ref()
const prefixOptions = computed(() => {
return storageAttributes.map(item => ({
title: t(`storage.${item.type}`),
value: `${item.type}:`,
value: item.type
}))
})
// 获取远程路径前缀
function getRemotePrefix(path: string) {
if (!path) return 'local:'
const match = prefixOptions.value
.find(p => path.startsWith(p.value))
return match ? match.value : 'local:'
function getStorageType(path: string) {
if (!path) return 'local'
// 查找匹配的存储类型
for (const storage of storageAttributes) {
// skip checking local storage as it is the default
if (storage.type === 'local') {
continue
}
if (path.startsWith(storage.type + ':')) {
return storage.type
}
}
return 'local'
}
// 获取远程路径后缀
function getRemotePath(path: string) {
if (!path) return ''
const prefix = getRemotePrefix(path)
return path.replace(new RegExp(`^${prefix}`), '')
function storage2Prefix(storage: string) {
return storage === 'local' ? '' : storage + ':'
}
// 更新远程路径前缀
function updateRemotePrefix(index: number, prefix: string) {
// 获取存储路径前
function parseStoragePath(path: string): [prefix: string, suffix: string] {
if (!path) return ['', '']
const storage = getStorageType(path)
const prefix = storage2Prefix(storage)
return [prefix, path.slice(prefix.length)]
}
// 更新存储路径前缀
function updateStoragePrefix(index: number, storage: string) {
if (!downloaderInfo.value.path_mapping) return
const currentPath = downloaderInfo.value.path_mapping[index][0] || ''
const currentSuffix = getRemotePath(currentPath)
prefix = prefix === 'local:' ? '' : prefix // 本地路径前缀为空
downloaderInfo.value.path_mapping[index][0] = prefix + currentSuffix
const currentMapping = downloaderInfo.value.path_mapping[index]
const currentPath = currentMapping[0] || ''
const [, currentSuffix] = parseStoragePath(currentPath)
const prefix = storage2Prefix(storage)
currentMapping[0] = prefix + currentSuffix
}
// 更新远程路径后缀
function updateRemotePath(index: number, suffix: string) {
// 更新存储路径后缀
function updateStorageSuffix(index: number, suffix: string) {
if (!downloaderInfo.value.path_mapping) return
const currentPath = downloaderInfo.value.path_mapping[index][0] || ''
let currentPrefix = getRemotePrefix(currentPath)
currentPrefix = currentPrefix === 'local:' ? '' : currentPrefix // 本地路径前缀为空
downloaderInfo.value.path_mapping[index][0] = currentPrefix + suffix
const currentMapping = downloaderInfo.value.path_mapping[index]
const currentPath = currentMapping[0] || ''
let [currentPrefix] = parseStoragePath(currentPath)
currentMapping[0] = currentPrefix + suffix
}
const downloadPathRules = [
const pathValidationRules = [
(v: string) => !!v || t('downloader.pathMappingRequired'),
(v: string) => v.startsWith('/') || t('downloader.pathMappingError'),
]
@@ -240,7 +253,10 @@ onUnmounted(() => {
}
.rp-input {
flex: 1 1 auto;
:deep(.v-field) {
:deep(.v-input__prepend) {
margin-inline-end: 0;
}
& > :deep(.v-input__control .v-field) {
padding-inline-start: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
@@ -478,33 +494,36 @@ onUnmounted(() => {
>
<VCol cols="12" md="6" class="pl-0 pr-0">
<div class="d-flex flex-nowrap align-start">
<VSelect
class="rp-select"
:model-value="getRemotePrefix(mapping[0])"
:items="prefixOptions"
density="compact"
hide-details
@update:model-value="v => updateRemotePrefix(index, v)"
/>
<VTextField
class="rp-input"
:model-value="getRemotePath(mapping[0])"
placeholder="/remote/path"
:model-value="parseStoragePath(mapping[0])[1]"
:placeholder="t('downloader.storagePath')"
density="compact"
hide-details="auto"
:rules="downloadPathRules"
@update:model-value="v => updateRemotePath(index, v)"
:rules="pathValidationRules"
@update:model-value="v => updateStorageSuffix(index, v)"
append-icon="mdi-arrow-right"
/>
>
<template v-slot:prepend>
<VSelect
class="rp-select"
:model-value="getStorageType(mapping[0])"
:items="prefixOptions"
density="compact"
hide-details
@update:model-value="v => updateStoragePrefix(index, v)"
/>
</template>
</VTextField>
</div>
</VCol>
<VCol cols="12" md="6" class="pl-0 pr-0">
<VTextField
v-model="mapping[1]"
placeholder="/download/path"
:placeholder="t('downloader.downloadPath')"
density="compact"
hide-details="auto"
:rules="downloadPathRules"
:rules="pathValidationRules"
append-icon="mdi-close"
@click:append="removePathMapping(index)"
/>

View File

@@ -2677,9 +2677,10 @@ export default {
usernameRequired: 'Username cannot be empty',
passwordRequired: 'Password cannot be empty',
pathMapping: 'Path Mapping',
pathMappingHint: 'Map download paths between downloader and client',
pathMappingRequired: 'Path cannot be empty',
pathMappingError: 'Must start with /',
storagePath: 'Storage Path',
downloadPath: 'Download Path',
},
filterRule: {
title: 'Filter Rule',

View File

@@ -2645,9 +2645,10 @@ export default {
usernameRequired: '用户名不能为空',
passwordRequired: '密码不能为空',
pathMapping: '路径映射',
pathMappingHint: '将下载器中的路径映射为媒体服务器可访问的路径,格式:源路径=>目标路径,多个映射使用换行分隔',
pathMappingRequired: '路径不能为空',
pathMappingError: '必须以 / 开头',
storagePath: '存储路径',
downloadPath: '下载路径',
},
filterRule: {
title: '过滤规则',

View File

@@ -2631,9 +2631,10 @@ export default {
usernameRequired: '用戶名不能為空',
passwordRequired: '密碼不能為空',
pathMapping: '路徑映射',
pathMappingHint: '下載器服務端路徑映射為客戶端路徑,用於整理媒體文件',
pathMappingRequired: '路徑不能為空',
pathMappingError: '必須以 / 開頭',
storagePath: '存儲路徑',
downloadPath: '下載路徑',
},
filterRule: {
title: '過濾規則',