add path mapping

This commit is contained in:
stkevintan
2025-12-08 09:15:51 +08:00
parent d541ea41ad
commit 3d622d2efe
5 changed files with 105 additions and 3 deletions

View File

@@ -1084,6 +1084,8 @@ export interface DownloaderConf {
config: { [key: string]: any }
// 是否启用
enabled: boolean
// 路径映射
path_mapping?: Array<[src: string, dst: string]>
}
// 通知配置

View File

@@ -7,7 +7,7 @@ import type { DownloaderInfo } from '@/api/types'
import { getLogoUrl } from '@/utils/imageUtils'
import { cloneDeep } from 'lodash-es'
import { useI18n } from 'vue-i18n'
import { downloaderDict } from '@/api/constants'
import { downloaderDict, storageAttributes } from '@/api/constants'
import { useDisplay } from 'vuetify'
import { useBackgroundOptimization } from '@/composables/useBackgroundOptimization'
@@ -52,6 +52,24 @@ const download_rate = ref(0)
// 下载器详情弹窗
const downloaderInfoDialog = ref(false)
// 表单
const downloaderForm = ref()
// 路径校验规则
const remotePathRules = [
(v: string) => !!v || t('downloader.pathMappingRequired'),
(v: string) => {
const prefixes = storageAttributes.map(s => (s.type === 'local' ? '/' : `${s.type}:/`))
const pattern = new RegExp(`^(${prefixes.join('|')})`)
return pattern.test(v) || t('downloader.pathMappingFormatError')
},
]
const localPathRules = [
(v: string) => !!v || t('downloader.pathMappingRequired'),
(v: string) => v.startsWith('/') || t('downloader.pathMappingLocalError'),
]
// 下载器详情
const downloaderInfo = ref<DownloaderConf>({
name: '',
@@ -96,7 +114,11 @@ function openDownloaderInfoDialog() {
}
// 保存详情数据
function saveDownloaderInfo() {
async function saveDownloaderInfo() {
// 表单校验
const { valid } = await downloaderForm.value?.validate()
if (!valid) return
// 为空不保存,跳出警告框
if (!downloaderInfo.value.name) {
$toast.error(t('downloader.nameRequired'))
@@ -134,6 +156,19 @@ const getIcon = computed(() => {
}
})
// 添加路径映射
function addPathMapping() {
if (!downloaderInfo.value.path_mapping) {
downloaderInfo.value.path_mapping = []
}
downloaderInfo.value.path_mapping.push(['', ''])
}
// 移除路径映射
function removePathMapping(index: number) {
downloaderInfo.value.path_mapping?.splice(index, 1)
}
// 按钮点击
function onClose() {
emit('close')
@@ -152,6 +187,22 @@ onUnmounted(() => {
stopRefresh()
})
</script>
<style scoped lang="scss">
.path-icon {
line-height: 40px;
height: 40px;
}
.path-input {
flex: 0 0 100%;
}
@media (min-width: 960px) {
.path-input {
flex: 1 1 auto;
max-width: 50%;
}
}
</style>
<template>
<div>
<VHover v-slot="hover">
@@ -212,7 +263,7 @@ onUnmounted(() => {
<VDialogCloseBtn v-model="downloaderInfoDialog" />
<VDivider />
<VCardText>
<VForm>
<VForm ref="downloaderForm">
<VRow>
<VCol cols="12" md="6">
<VSwitch v-model="downloaderInfo.enabled" :label="t('downloader.enabled')" />
@@ -373,6 +424,40 @@ onUnmounted(() => {
/>
</VCol>
</VRow>
<VRow>
<VCol cols="12">
<VLabel class="mb-2">{{ t('downloader.pathMapping') }}</VLabel>
<div
v-for="(mapping, index) in downloaderInfo.path_mapping"
:key="index"
class="d-flex gap-2 mb-2 align-start flex-wrap"
>
<VTextField
v-model="mapping[0]"
placeholder="/remote/path"
density="compact"
hide-details="auto"
:rules="remotePathRules"
:appendIcon="'mdi-arrow-right'"
class="path-input"
/>
<VTextField
v-model="mapping[1]"
placeholder="/download/path"
density="compact"
hide-details="auto"
:rules="localPathRules"
:appendIcon="'mdi-close'"
@click:append="removePathMapping(index)"
/>
</div>
<div>
<VBtn variant="tonal" size="small" prepend-icon="mdi-plus" @click="addPathMapping">
{{ t('common.add') }}
</VBtn>
</div>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardActions class="pt-3">

View File

@@ -2676,6 +2676,11 @@ export default {
hostRequired: 'Host cannot be empty',
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',
pathMappingFormatError: 'Invalid storage type prefix',
pathMappingLocalError: 'Must start with /',
},
filterRule: {
title: 'Filter Rule',

View File

@@ -2644,6 +2644,11 @@ export default {
hostRequired: '地址不能为空',
usernameRequired: '用户名不能为空',
passwordRequired: '密码不能为空',
pathMapping: '路径映射',
pathMappingHint: '将下载器中的路径映射为媒体服务器可访问的路径,格式:源路径=>目标路径,多个映射使用换行分隔',
pathMappingRequired: '路径不能为空',
pathMappingFormatError: '存储类型前缀错误',
pathMappingLocalError: '必须以 / 开头',
},
filterRule: {
title: '过滤规则',

View File

@@ -2630,6 +2630,11 @@ export default {
hostRequired: '地址不能為空',
usernameRequired: '用戶名不能為空',
passwordRequired: '密碼不能為空',
pathMapping: '路徑映射',
pathMappingHint: '下載器服務端路徑映射為客戶端路徑,用於整理媒體文件',
pathMappingRequired: '路徑不能為空',
pathMappingFormatError: '存儲類型前綴錯誤',
pathMappingLocalError: '必須以 / 開頭',
},
filterRule: {
title: '過濾規則',