mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
implement path mapping UI
This commit is contained in:
@@ -55,19 +55,50 @@ const downloaderInfoDialog = ref(false)
|
|||||||
// 表单
|
// 表单
|
||||||
const downloaderForm = ref()
|
const downloaderForm = ref()
|
||||||
|
|
||||||
// 路径校验规则
|
// 路径前缀选项
|
||||||
const remotePathRules = [
|
const prefixOptions = computed(() => {
|
||||||
(v: string) => !!v || t('downloader.pathMappingRequired'),
|
return storageAttributes.map(item => ({
|
||||||
(v: string) => {
|
title: t(`storage.${item.type}`),
|
||||||
const prefixes = storageAttributes.map(s => (s.type === 'local' ? '/' : `${s.type}:/`))
|
value: `${item.type}:`,
|
||||||
const pattern = new RegExp(`^(${prefixes.join('|')})`)
|
}))
|
||||||
return pattern.test(v) || t('downloader.pathMappingFormatError')
|
})
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const localPathRules = [
|
// 获取远程路径前缀
|
||||||
|
function getRemotePrefix(path: string) {
|
||||||
|
if (!path) return 'local:'
|
||||||
|
const match = prefixOptions.value
|
||||||
|
.find(p => path.startsWith(p.value))
|
||||||
|
return match ? match.value : 'local:'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取远程路径后缀
|
||||||
|
function getRemotePath(path: string) {
|
||||||
|
if (!path) return ''
|
||||||
|
const prefix = getRemotePrefix(path)
|
||||||
|
return path.replace(new RegExp(`^${prefix}`), '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新远程路径前缀
|
||||||
|
function updateRemotePrefix(index: number, prefix: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新远程路径后缀
|
||||||
|
function updateRemotePath(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 downloadPathRules = [
|
||||||
(v: string) => !!v || t('downloader.pathMappingRequired'),
|
(v: string) => !!v || t('downloader.pathMappingRequired'),
|
||||||
(v: string) => v.startsWith('/') || t('downloader.pathMappingLocalError'),
|
(v: string) => v.startsWith('/') || t('downloader.pathMappingError'),
|
||||||
]
|
]
|
||||||
|
|
||||||
// 下载器详情
|
// 下载器详情
|
||||||
@@ -189,19 +220,32 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.path-icon {
|
.pm-row {
|
||||||
line-height: 40px;
|
padding-inline-start: 12px;
|
||||||
height: 40px;
|
padding-inline-end: 12px;
|
||||||
}
|
:deep(.v-input__append) {
|
||||||
.path-input {
|
margin-inline-start: 8px;
|
||||||
flex: 0 0 100%;
|
margin-inline-end: 8px;
|
||||||
}
|
|
||||||
@media (min-width: 960px) {
|
|
||||||
.path-input {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.rp-select {
|
||||||
|
flex: 0 0 95px;
|
||||||
|
margin-right: -1px;
|
||||||
|
:deep(.v-field) {
|
||||||
|
padding-inline-end: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
--v-field-padding-end: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.rp-input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
:deep(.v-field) {
|
||||||
|
padding-inline-start: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
@@ -427,35 +471,50 @@ onUnmounted(() => {
|
|||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VLabel class="mb-2">{{ t('downloader.pathMapping') }}</VLabel>
|
<VLabel class="mb-2">{{ t('downloader.pathMapping') }}</VLabel>
|
||||||
<div
|
<VRow
|
||||||
v-for="(mapping, index) in downloaderInfo.path_mapping"
|
v-for="(mapping, index) in downloaderInfo.path_mapping"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="d-flex gap-2 mb-2 align-start flex-wrap"
|
class="align-start flex-wrap pm-row"
|
||||||
>
|
>
|
||||||
<VTextField
|
<VCol cols="12" md="6" class="pl-0 pr-0">
|
||||||
v-model="mapping[0]"
|
<div class="d-flex flex-nowrap align-start">
|
||||||
placeholder="/remote/path"
|
<VSelect
|
||||||
density="compact"
|
class="rp-select"
|
||||||
hide-details="auto"
|
:model-value="getRemotePrefix(mapping[0])"
|
||||||
:rules="remotePathRules"
|
:items="prefixOptions"
|
||||||
:appendIcon="'mdi-arrow-right'"
|
density="compact"
|
||||||
class="path-input"
|
hide-details
|
||||||
/>
|
@update:model-value="v => updateRemotePrefix(index, v)"
|
||||||
<VTextField
|
/>
|
||||||
v-model="mapping[1]"
|
<VTextField
|
||||||
placeholder="/download/path"
|
class="rp-input"
|
||||||
density="compact"
|
:model-value="getRemotePath(mapping[0])"
|
||||||
hide-details="auto"
|
placeholder="/remote/path"
|
||||||
:rules="localPathRules"
|
density="compact"
|
||||||
:appendIcon="'mdi-close'"
|
hide-details="auto"
|
||||||
@click:append="removePathMapping(index)"
|
:rules="downloadPathRules"
|
||||||
/>
|
@update:model-value="v => updateRemotePath(index, v)"
|
||||||
</div>
|
append-icon="mdi-arrow-right"
|
||||||
<div>
|
/>
|
||||||
<VBtn variant="tonal" size="small" prepend-icon="mdi-plus" @click="addPathMapping">
|
</div>
|
||||||
{{ t('common.add') }}
|
</VCol>
|
||||||
</VBtn>
|
<VCol cols="12" md="6" class="pl-0 pr-0">
|
||||||
</div>
|
<VTextField
|
||||||
|
v-model="mapping[1]"
|
||||||
|
placeholder="/download/path"
|
||||||
|
density="compact"
|
||||||
|
hide-details="auto"
|
||||||
|
:rules="downloadPathRules"
|
||||||
|
append-icon="mdi-close"
|
||||||
|
@click:append="removePathMapping(index)"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VCol>
|
||||||
|
<VCol>
|
||||||
|
<VBtn variant="tonal" size="small" prepend-icon="mdi-plus" @click="addPathMapping">
|
||||||
|
{{ t('common.add') }}
|
||||||
|
</VBtn>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
</VForm>
|
</VForm>
|
||||||
|
|||||||
@@ -2679,8 +2679,7 @@ export default {
|
|||||||
pathMapping: 'Path Mapping',
|
pathMapping: 'Path Mapping',
|
||||||
pathMappingHint: 'Map download paths between downloader and client',
|
pathMappingHint: 'Map download paths between downloader and client',
|
||||||
pathMappingRequired: 'Path cannot be empty',
|
pathMappingRequired: 'Path cannot be empty',
|
||||||
pathMappingFormatError: 'Invalid storage type prefix',
|
pathMappingError: 'Must start with /',
|
||||||
pathMappingLocalError: 'Must start with /',
|
|
||||||
},
|
},
|
||||||
filterRule: {
|
filterRule: {
|
||||||
title: 'Filter Rule',
|
title: 'Filter Rule',
|
||||||
|
|||||||
@@ -2647,8 +2647,7 @@ export default {
|
|||||||
pathMapping: '路径映射',
|
pathMapping: '路径映射',
|
||||||
pathMappingHint: '将下载器中的路径映射为媒体服务器可访问的路径,格式:源路径=>目标路径,多个映射使用换行分隔',
|
pathMappingHint: '将下载器中的路径映射为媒体服务器可访问的路径,格式:源路径=>目标路径,多个映射使用换行分隔',
|
||||||
pathMappingRequired: '路径不能为空',
|
pathMappingRequired: '路径不能为空',
|
||||||
pathMappingFormatError: '存储类型前缀错误',
|
pathMappingError: '必须以 / 开头',
|
||||||
pathMappingLocalError: '必须以 / 开头',
|
|
||||||
},
|
},
|
||||||
filterRule: {
|
filterRule: {
|
||||||
title: '过滤规则',
|
title: '过滤规则',
|
||||||
|
|||||||
@@ -2633,8 +2633,7 @@ export default {
|
|||||||
pathMapping: '路徑映射',
|
pathMapping: '路徑映射',
|
||||||
pathMappingHint: '下載器服務端路徑映射為客戶端路徑,用於整理媒體文件',
|
pathMappingHint: '下載器服務端路徑映射為客戶端路徑,用於整理媒體文件',
|
||||||
pathMappingRequired: '路徑不能為空',
|
pathMappingRequired: '路徑不能為空',
|
||||||
pathMappingFormatError: '存儲類型前綴錯誤',
|
pathMappingError: '必須以 / 開頭',
|
||||||
pathMappingLocalError: '必須以 / 開頭',
|
|
||||||
},
|
},
|
||||||
filterRule: {
|
filterRule: {
|
||||||
title: '過濾規則',
|
title: '過濾規則',
|
||||||
|
|||||||
Reference in New Issue
Block a user