mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 07:28:37 +08:00
refactor:重构路径输入组件
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TransferDirectoryConf } from '@/api/types'
|
import type { TransferDirectoryConf } from '@/api/types'
|
||||||
import PathInput from '@/components/input/PathInput.vue'
|
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import { storageOptions } from '@/api/constants'
|
import { storageOptions } from '@/api/constants'
|
||||||
@@ -20,12 +19,6 @@ const props = defineProps({
|
|||||||
height: String,
|
height: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 下载路径
|
|
||||||
const downloadPath = ref<string>('')
|
|
||||||
|
|
||||||
// 媒体库路径
|
|
||||||
const libraryPath = ref<string>('')
|
|
||||||
|
|
||||||
// 卡版是否折叠状态
|
// 卡版是否折叠状态
|
||||||
const isCollapsed = ref(true)
|
const isCollapsed = ref(true)
|
||||||
|
|
||||||
@@ -210,16 +203,12 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="8">
|
<VCol cols="8">
|
||||||
<PathInput v-model="props.directory.download_path" :storage="props.directory.storage">
|
<VPathField
|
||||||
<template #activator="{ menuprops }">
|
v-model="props.directory.download_path"
|
||||||
<VTextField
|
:storage="props.directory.storage"
|
||||||
:model-value="props.directory.download_path"
|
variant="underlined"
|
||||||
v-bind="menuprops"
|
label="下载目录/源目录"
|
||||||
variant="underlined"
|
/>
|
||||||
label="下载目录/源目录"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</PathInput>
|
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
<VCol cols="6" v-if="!props.directory.media_type || props.directory.media_type === ''">
|
||||||
<VSwitch v-model="props.directory.download_type_folder" label="按类型分类"></VSwitch>
|
<VSwitch v-model="props.directory.download_type_folder" label="按类型分类"></VSwitch>
|
||||||
@@ -257,16 +246,12 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="8">
|
<VCol cols="8">
|
||||||
<PathInput v-model="props.directory.library_path" :storage="props.directory.library_storage">
|
<VPathField
|
||||||
<template #activator="{ menuprops }">
|
v-model="props.directory.library_path"
|
||||||
<VTextField
|
:storage="props.directory.library_storage"
|
||||||
:modelValue="props.directory.library_path"
|
variant="underlined"
|
||||||
v-bind="menuprops"
|
label="媒体库目录"
|
||||||
variant="underlined"
|
/>
|
||||||
label="媒体库目录"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</PathInput>
|
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="4">
|
<VCol cols="4">
|
||||||
<VSelect
|
<VSelect
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import PathInput from '@/components/input/PathInput.vue'
|
||||||
|
|
||||||
|
const attrs = useAttrs()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '* * * * *',
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
type: String,
|
||||||
|
default: 'local',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const innerValue = ref(props.modelValue)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
value => {
|
||||||
|
innerValue.value = value
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const propsWithoutModelValue = computed(() => {
|
||||||
|
const { modelValue, ...rest } = props
|
||||||
|
return { ...rest, ...attrs }
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateModelValue(value: string) {
|
||||||
|
innerValue.value = value
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PathInput v-model="innerValue" :storage="props.storage" @update:modelValue="updateModelValue">
|
||||||
|
<template #activator="{ menuprops }">
|
||||||
|
<VTextField
|
||||||
|
:modelValue="innerValue"
|
||||||
|
@update:modelValue="updateModelValue"
|
||||||
|
v-bind="{ ...menuprops, ...propsWithoutModelValue }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</PathInput>
|
||||||
|
</template>
|
||||||
@@ -3,6 +3,10 @@ import api from '@/api'
|
|||||||
import { FileItem } from '@/api/types'
|
import { FileItem } from '@/api/types'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '/',
|
||||||
|
},
|
||||||
root: {
|
root: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '/',
|
default: '/',
|
||||||
@@ -16,7 +20,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const activedDirs = ref<Record<string, any>[]>([])
|
const menuVisible = ref(false)
|
||||||
|
|
||||||
const treeItems = ref<FileItem[]>([
|
const treeItems = ref<FileItem[]>([
|
||||||
{
|
{
|
||||||
@@ -29,16 +33,70 @@ const treeItems = ref<FileItem[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const activedDirs = ref<FileItem[]>([])
|
||||||
|
|
||||||
|
const openedDirs = ref<FileItem[]>([])
|
||||||
|
|
||||||
|
// 调用API查询子目录
|
||||||
async function fetchDirs(item: any) {
|
async function fetchDirs(item: any) {
|
||||||
return api
|
return api
|
||||||
.post('/storage/list', item)
|
.post('/storage/list', item)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
data = data.filter((i: any) => i.type === 'dir')
|
data = data.filter((i: any) => i.type === 'dir')
|
||||||
item.children.push(...data)
|
item.children?.push(...data)
|
||||||
})
|
})
|
||||||
.catch(err => console.warn(err))
|
.catch(err => console.warn(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 递归查询路径
|
||||||
|
function findPath(item: FileItem, path: string): FileItem | null {
|
||||||
|
if (item.path === path) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
for (const child of item.children) {
|
||||||
|
const res: FileItem | null = findPath(child, path)
|
||||||
|
if (res) {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据路径展开所有子目录
|
||||||
|
async function expandDirs(path: string) {
|
||||||
|
// 分割路径
|
||||||
|
const paths = path.split('/').filter(i => i)
|
||||||
|
// 展开根目录
|
||||||
|
const root_item = treeItems.value[0]
|
||||||
|
await fetchDirs(root_item)
|
||||||
|
openedDirs.value.push(root_item)
|
||||||
|
// 逐级展开
|
||||||
|
let currentPath = '/'
|
||||||
|
for (const p of paths) {
|
||||||
|
currentPath += `${p}/`
|
||||||
|
// 查询当前目录
|
||||||
|
const item = findPath(root_item, currentPath)
|
||||||
|
if (!item) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 加载子目录
|
||||||
|
if (item.children?.length === 0) {
|
||||||
|
await fetchDirs(item)
|
||||||
|
}
|
||||||
|
// 打开当前目录
|
||||||
|
if (!openedDirs.value.includes(item) && path != currentPath) {
|
||||||
|
openedDirs.value.push(item)
|
||||||
|
}
|
||||||
|
// 选中当前目录
|
||||||
|
if (path == currentPath) {
|
||||||
|
activedDirs.value = [item]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前选中项
|
||||||
const selectedPath = computed(() => {
|
const selectedPath = computed(() => {
|
||||||
if (activedDirs.value.length > 0) {
|
if (activedDirs.value.length > 0) {
|
||||||
return activedDirs.value[0].path
|
return activedDirs.value[0].path
|
||||||
@@ -51,6 +109,27 @@ watch(activedDirs, newVal => {
|
|||||||
emit('update:modelValue', selectedPath.value)
|
emit('update:modelValue', selectedPath.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => menuVisible.value,
|
||||||
|
async visible => {
|
||||||
|
if (visible) {
|
||||||
|
treeItems.value = [
|
||||||
|
{
|
||||||
|
name: '/',
|
||||||
|
path: props.root,
|
||||||
|
children: [],
|
||||||
|
type: 'dir',
|
||||||
|
basename: props.root,
|
||||||
|
storage: props.storage,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
openedDirs.value = []
|
||||||
|
activedDirs.value = []
|
||||||
|
await expandDirs(props.modelValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.storage,
|
() => props.storage,
|
||||||
async newVal => {
|
async newVal => {
|
||||||
@@ -65,18 +144,20 @@ watch(
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
activedDirs.value = []
|
activedDirs.value = []
|
||||||
|
openedDirs.value = []
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VMenu :close-on-content-click="false" content-class="cursor-default">
|
<VMenu v-model="menuVisible" :close-on-content-click="false" content-class="cursor-default">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<slot name="activator" :menuprops="props" />
|
<slot name="activator" :menuprops="props" />
|
||||||
</template>
|
</template>
|
||||||
<VTreeview
|
<VTreeview
|
||||||
v-model:activated="activedDirs"
|
v-model:activated="activedDirs"
|
||||||
|
v-model:opened="openedDirs"
|
||||||
:items="treeItems"
|
:items="treeItems"
|
||||||
:load-children="fetchDirs"
|
:load-children="fetchDirs"
|
||||||
item-key="path"
|
item-key="path"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import MediaInfoCard from './components/cards/MediaInfoCard.vue'
|
|||||||
import TorrentCard from './components/cards/TorrentCard.vue'
|
import TorrentCard from './components/cards/TorrentCard.vue'
|
||||||
import MediaIdSelector from './components/misc/MediaIdSelector.vue'
|
import MediaIdSelector from './components/misc/MediaIdSelector.vue'
|
||||||
import CronField from './components/field/CronField.vue'
|
import CronField from './components/field/CronField.vue'
|
||||||
|
import PathField from './components/field/PathField.vue'
|
||||||
|
|
||||||
// 7. 样式文件
|
// 7. 样式文件
|
||||||
import '@core/scss/template/libs/vuetify/index.scss'
|
import '@core/scss/template/libs/vuetify/index.scss'
|
||||||
@@ -82,6 +83,7 @@ initializeApp().then(() => {
|
|||||||
.component('VTorrentCard', TorrentCard)
|
.component('VTorrentCard', TorrentCard)
|
||||||
.component('VMediaIdSelector', MediaIdSelector)
|
.component('VMediaIdSelector', MediaIdSelector)
|
||||||
.component('VCronField', CronField)
|
.component('VCronField', CronField)
|
||||||
|
.component('VPathField', PathField)
|
||||||
|
|
||||||
// 注册插件
|
// 注册插件
|
||||||
app
|
app
|
||||||
|
|||||||
Reference in New Issue
Block a user