mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 16:16:42 +08:00
fix:优化文件管理
This commit is contained in:
@@ -11,10 +11,6 @@ import { isNullOrEmptyObject } from '@/@core/utils'
|
|||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
storages: String,
|
storages: String,
|
||||||
path: String,
|
|
||||||
fileid: String,
|
|
||||||
pickcode: String,
|
|
||||||
fileidstack: Array as PropType<string[]>,
|
|
||||||
tree: Boolean,
|
tree: Boolean,
|
||||||
endpoints: Object as PropType<EndPoints>,
|
endpoints: Object as PropType<EndPoints>,
|
||||||
axios: {
|
axios: {
|
||||||
@@ -22,6 +18,11 @@ const props = defineProps({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
axiosconfig: Object,
|
axiosconfig: Object,
|
||||||
|
item: {
|
||||||
|
type: Object as PropType<FileItem>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
fileidstack: Array as PropType<string[]>,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 对外事件
|
// 对外事件
|
||||||
@@ -165,10 +166,9 @@ function u115AuthDone() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VCard class="mx-auto" :loading="loading > 0">
|
<VCard class="mx-auto" :loading="loading > 0">
|
||||||
<div v-if="activeStorage && (path || fileid)">
|
<div v-if="activeStorage && item">
|
||||||
<FileToolbar
|
<FileToolbar
|
||||||
:path="path"
|
:item="item"
|
||||||
:fileid="fileid"
|
|
||||||
:fileidstack="fileidstack"
|
:fileidstack="fileidstack"
|
||||||
:storages="storagesArray"
|
:storages="storagesArray"
|
||||||
:storage="activeStorage"
|
:storage="activeStorage"
|
||||||
@@ -180,9 +180,7 @@ function u115AuthDone() {
|
|||||||
@sortchanged="sortChanged"
|
@sortchanged="sortChanged"
|
||||||
/>
|
/>
|
||||||
<FileList
|
<FileList
|
||||||
:path="path"
|
:item="item"
|
||||||
:fileid="fileid"
|
|
||||||
:pickcode="pickcode"
|
|
||||||
:storage="activeStorage"
|
:storage="activeStorage"
|
||||||
:icons="fileIcons"
|
:icons="fileIcons"
|
||||||
:endpoints="endpoints"
|
:endpoints="endpoints"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Axios } from 'axios'
|
import type { Axios, AxiosRequestConfig } from 'axios'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import { useConfirm } from 'vuetify-use-dialog'
|
import { useConfirm } from 'vuetify-use-dialog'
|
||||||
import { useToast } from 'vue-toast-notification'
|
import { useToast } from 'vue-toast-notification'
|
||||||
@@ -24,21 +24,25 @@ const appMode = computed(() => {
|
|||||||
const inProps = defineProps({
|
const inProps = defineProps({
|
||||||
icons: Object,
|
icons: Object,
|
||||||
storage: String,
|
storage: String,
|
||||||
path: String,
|
|
||||||
fileid: String,
|
|
||||||
pickcode: String,
|
|
||||||
endpoints: Object as PropType<EndPoints>,
|
endpoints: Object as PropType<EndPoints>,
|
||||||
axios: {
|
axios: {
|
||||||
type: Object as PropType<Axios>,
|
type: Object as PropType<Axios>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
refreshpending: Boolean,
|
refreshpending: Boolean,
|
||||||
|
item: {
|
||||||
|
type: Object as PropType<FileItem>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
sort: String,
|
sort: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 对外事件
|
// 对外事件
|
||||||
const emit = defineEmits(['loading', 'pathchanged', 'refreshed', 'filedeleted', 'renamed'])
|
const emit = defineEmits(['loading', 'pathchanged', 'refreshed', 'filedeleted', 'renamed'])
|
||||||
|
|
||||||
|
// 确认框
|
||||||
|
const createConfirm = useConfirm()
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
|
|
||||||
@@ -57,9 +61,6 @@ const progressText = ref('请稍候 ...')
|
|||||||
// 识别进度
|
// 识别进度
|
||||||
const progressValue = ref(0)
|
const progressValue = ref(0)
|
||||||
|
|
||||||
// 确认框
|
|
||||||
const createConfirm = useConfirm()
|
|
||||||
|
|
||||||
// 内容列表
|
// 内容列表
|
||||||
const items = ref<FileItem[]>([])
|
const items = ref<FileItem[]>([])
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ const newName = ref('')
|
|||||||
// 处理目录内所有文件
|
// 处理目录内所有文件
|
||||||
const renameAll = ref(false)
|
const renameAll = ref(false)
|
||||||
|
|
||||||
// 当前名称
|
// 当前操作项
|
||||||
const currentItem = ref<FileItem>()
|
const currentItem = ref<FileItem>()
|
||||||
|
|
||||||
// 识别结果
|
// 识别结果
|
||||||
@@ -98,35 +99,34 @@ const dirs = computed(() => items.value.filter(item => item.type === 'dir' && it
|
|||||||
|
|
||||||
// 文件过滤
|
// 文件过滤
|
||||||
const files = computed(() => items.value.filter(item => item.type === 'file' && item.name.includes(filter.value)))
|
const files = computed(() => items.value.filter(item => item.type === 'file' && item.name.includes(filter.value)))
|
||||||
|
|
||||||
// 是否目录
|
// 是否目录
|
||||||
const isDir = computed(() => inProps.path?.endsWith('/'))
|
const isDir = computed(() => inProps.item.path?.endsWith('/'))
|
||||||
|
|
||||||
// 是否文件
|
// 是否文件
|
||||||
const isFile = computed(() => !isDir.value)
|
const isFile = computed(() => !isDir.value)
|
||||||
|
|
||||||
// 是否为图片文件
|
// 是否为图片文件
|
||||||
const isImage = computed(() => {
|
const isImage = computed(() => {
|
||||||
const ext = inProps.path?.split('.').pop()?.toLowerCase()
|
const ext = inProps.item.path?.split('.').pop()?.toLowerCase()
|
||||||
return ['png', 'jpg', 'jpeg', 'gif', 'bmp'].includes(ext ?? '')
|
return ['png', 'jpg', 'jpeg', 'gif', 'bmp'].includes(ext ?? '')
|
||||||
})
|
})
|
||||||
|
|
||||||
// 调API加载内容
|
// 调API加载文件夹内的内容
|
||||||
async function load() {
|
async function list_files() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
emit('loading', true)
|
emit('loading', true)
|
||||||
|
|
||||||
// 参数
|
// 参数
|
||||||
const url = inProps.endpoints?.list.url
|
const url = inProps.endpoints?.list.url
|
||||||
.replace(/{storage}/g, inProps.storage)
|
.replace(/{storage}/g, inProps.storage)
|
||||||
.replace(/{path}/g, encodeURIComponent(inProps.path || ''))
|
|
||||||
.replace(/{sort}/g, inProps.sort || 'name')
|
.replace(/{sort}/g, inProps.sort || 'name')
|
||||||
.replace(/{fileid}/g, inProps.fileid || '')
|
|
||||||
.replace(/{filetype}/g, isDir.value ? 'dir' : 'file')
|
const config: AxiosRequestConfig<FileItem> = {
|
||||||
.replace(/{pickcode}/g, inProps.pickcode || '')
|
|
||||||
const config = {
|
|
||||||
url,
|
url,
|
||||||
method: inProps.endpoints?.list.method || 'get',
|
method: inProps.endpoints?.list.method || 'get',
|
||||||
|
data: inProps.item,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
items.value = (await inProps.axios.request(config)) ?? []
|
items.value = (await inProps.axios.request(config)) ?? []
|
||||||
emit('loading', false)
|
emit('loading', false)
|
||||||
@@ -142,52 +142,45 @@ async function deleteItem(item: FileItem) {
|
|||||||
|
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
emit('loading', true)
|
emit('loading', true)
|
||||||
const url = inProps.endpoints?.delete.url
|
|
||||||
.replace(/{storage}/g, inProps.storage)
|
|
||||||
.replace(/{path}/g, encodeURIComponent(item.path))
|
|
||||||
.replace(/{fileid}/g, item.fileid || '')
|
|
||||||
|
|
||||||
const config = {
|
const url = inProps.endpoints?.delete.url.replace(/{storage}/g, inProps.storage)
|
||||||
|
const config: AxiosRequestConfig<FileItem> = {
|
||||||
url,
|
url,
|
||||||
method: inProps.endpoints?.delete.method || 'post',
|
method: inProps.endpoints?.delete.method || 'post',
|
||||||
|
data: item,
|
||||||
}
|
}
|
||||||
|
|
||||||
await inProps.axios.request(config)
|
await inProps.axios.request(config)
|
||||||
emit('filedeleted')
|
emit('filedeleted')
|
||||||
emit('loading', false)
|
emit('loading', false)
|
||||||
// 重新加载
|
// 重新加载
|
||||||
load()
|
list_files()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换路径
|
// 切换路径
|
||||||
function changePath(item: FileItem) {
|
function changePath(item: FileItem) {
|
||||||
item.path = inProps.path + item.name + (item.type === 'dir' ? '/' : '')
|
item.path = inProps.item.path + item.name + (item.type === 'dir' ? '/' : '')
|
||||||
emit('pathchanged', item)
|
emit('pathchanged', item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新窗口中下载文件
|
// 新窗口中下载文件
|
||||||
function download(item: FileItem) {
|
async function download(item: FileItem) {
|
||||||
const token = store.state.auth.token
|
const url = inProps.endpoints?.download.url.replace(/{storage}/g, inProps.storage)
|
||||||
const url_path = inProps.endpoints?.download.url
|
const filterEntries = Object.entries(item).filter(([key, value]) => !['children', 'thumbnail'].includes(key) && value)
|
||||||
.replace(/{storage}/g, inProps.storage)
|
const queryParams = filterEntries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join('&')
|
||||||
.replace(/{path}/g, encodeURIComponent(item.path))
|
window.open(
|
||||||
.replace(/{fileid}/g, item.fileid || '')
|
`${import.meta.env.VITE_API_BASE_URL}${url.slice(1)}?${queryParams}&token=${store.state.auth.token}`,
|
||||||
.replace(/{pickcode}/g, item.pickcode || '')
|
'_blank',
|
||||||
const url = `${import.meta.env.VITE_API_BASE_URL}${url_path.slice(1)}&token=${token}`
|
)
|
||||||
// 下载文件
|
|
||||||
window.open(url, '_blank')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示图片
|
// 获取图片地址
|
||||||
function getImgLink(item: FileItem) {
|
function getImgLink(item: FileItem) {
|
||||||
const token = store.state.auth.token
|
let url = inProps.endpoints?.image.url.replace(/{storage}/g, inProps.storage)
|
||||||
const url_path = inProps.endpoints?.image.url
|
const filterEntries = Object.entries(item).filter(([key, value]) => !['children', 'thumbnail'].includes(key) && value)
|
||||||
.replace(/{storage}/g, inProps.storage)
|
const queryParams = filterEntries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join('&')
|
||||||
.replace(/{path}/g, encodeURIComponent(item.path))
|
return `${import.meta.env.VITE_API_BASE_URL}${url.slice(1)}?${queryParams}&token=${store.state.auth.token}`
|
||||||
.replace(/{fileid}/g, item.fileid || '')
|
|
||||||
.replace(/{pickcode}/g, item.pickcode || '')
|
|
||||||
return `${import.meta.env.VITE_API_BASE_URL}${url_path.slice(1)}&token=${token}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示重命名弹窗
|
// 显示重命名弹窗
|
||||||
@@ -204,7 +197,7 @@ async function get_recommend_name() {
|
|||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('transfer/name', {
|
const result: { [key: string]: any } = await api.get('transfer/name', {
|
||||||
params: {
|
params: {
|
||||||
path: `${inProps.path}${currentItem.value?.name}`,
|
path: `${inProps.item.path}${currentItem.value?.name}`,
|
||||||
filetype: currentItem.value?.type ?? 'file',
|
filetype: currentItem.value?.type ?? 'file',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -223,22 +216,6 @@ async function get_recommend_name() {
|
|||||||
async function rename() {
|
async function rename() {
|
||||||
emit('loading', true)
|
emit('loading', true)
|
||||||
|
|
||||||
let url = inProps.endpoints?.rename.url
|
|
||||||
.replace(/{storage}/g, inProps.storage)
|
|
||||||
.replace(/{path}/g, encodeURIComponent(currentItem.value?.path || ''))
|
|
||||||
.replace(/{fileid}/g, currentItem.value?.fileid || '')
|
|
||||||
.replace(/{newname}/g, encodeURIComponent(newName.value))
|
|
||||||
.replace(/{filetype}/g, currentItem.value?.type || 'file')
|
|
||||||
|
|
||||||
if (renameAll.value) {
|
|
||||||
url += '&recursive=true'
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
url,
|
|
||||||
method: inProps.endpoints?.mkdir.method || 'post',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
renamePopper.value = false
|
renamePopper.value = false
|
||||||
|
|
||||||
@@ -255,6 +232,18 @@ async function rename() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 调API
|
// 调API
|
||||||
|
let url = inProps.endpoints?.rename.url
|
||||||
|
.replace(/{storage}/g, inProps.storage)
|
||||||
|
.replace(/{newname}/g, encodeURIComponent(newName.value))
|
||||||
|
if (renameAll.value) {
|
||||||
|
url += '&recursive=true'
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: AxiosRequestConfig<FileItem> = {
|
||||||
|
url,
|
||||||
|
method: inProps.endpoints?.rename.method || 'post',
|
||||||
|
data: currentItem.value,
|
||||||
|
}
|
||||||
const result: { [key: string]: any } = await inProps.axios?.request(config)
|
const result: { [key: string]: any } = await inProps.axios?.request(config)
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
$toast.error(result.message)
|
$toast.error(result.message)
|
||||||
@@ -284,9 +273,20 @@ function formatTime(timestape: number) {
|
|||||||
return new Date(timestape * 1000).toLocaleString()
|
return new Date(timestape * 1000).toLocaleString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听path变化或者storage变化
|
// 监听refreshPending变化
|
||||||
watch(
|
watch(
|
||||||
[() => inProps.path, () => inProps.fileid, () => inProps.storage],
|
() => inProps.refreshpending,
|
||||||
|
async () => {
|
||||||
|
if (inProps.refreshpending) {
|
||||||
|
await list_files()
|
||||||
|
emit('refreshed')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听item变化或者storage变化
|
||||||
|
watch(
|
||||||
|
[() => inProps.item, () => inProps.storage],
|
||||||
async () => {
|
async () => {
|
||||||
// 清空列表
|
// 清空列表
|
||||||
items.value = []
|
items.value = []
|
||||||
@@ -346,22 +346,11 @@ watch(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
await load()
|
await list_files()
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听refreshPending变化
|
|
||||||
watch(
|
|
||||||
() => inProps.refreshpending,
|
|
||||||
async () => {
|
|
||||||
if (inProps.refreshpending) {
|
|
||||||
await load()
|
|
||||||
emit('refreshed')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// 调用API识别
|
// 调用API识别
|
||||||
async function recognize(path: string) {
|
async function recognize(path: string) {
|
||||||
try {
|
try {
|
||||||
@@ -427,7 +416,7 @@ function stopLoadingProgress() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
load()
|
list_files()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -447,13 +436,13 @@ onMounted(() => {
|
|||||||
rounded="0"
|
rounded="0"
|
||||||
/>
|
/>
|
||||||
<VSpacer v-if="isFile" />
|
<VSpacer v-if="isFile" />
|
||||||
<IconBtn v-if="isFile" @click="recognize(inProps.path || '')">
|
<IconBtn v-if="isFile" @click="recognize(inProps.item.path || '')">
|
||||||
<VIcon color="primary"> mdi-text-recognition </VIcon>
|
<VIcon color="primary"> mdi-text-recognition </VIcon>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<IconBtn v-if="isFile && items.length > 0" @click="download(items[0])">
|
<IconBtn v-if="isFile && items.length > 0" @click="download(items[0])">
|
||||||
<VIcon color="primary"> mdi-download </VIcon>
|
<VIcon color="primary"> mdi-download </VIcon>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<IconBtn v-if="!isFile" @click="load">
|
<IconBtn v-if="!isFile" @click="list_files">
|
||||||
<VIcon color="primary"> mdi-refresh </VIcon>
|
<VIcon color="primary"> mdi-refresh </VIcon>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</VToolbar>
|
</VToolbar>
|
||||||
@@ -506,7 +495,7 @@ onMounted(() => {
|
|||||||
{{ formatBytes(item.size) }}
|
{{ formatBytes(item.size) }}
|
||||||
</VListItemSubtitle>
|
</VListItemSubtitle>
|
||||||
<template #append>
|
<template #append>
|
||||||
<IconBtn class="d-sm-none">
|
<IconBtn v-if="display.smAndDown.value">
|
||||||
<VIcon icon="mdi-dots-vertical" />
|
<VIcon icon="mdi-dots-vertical" />
|
||||||
<VMenu activator="parent" close-on-content-click>
|
<VMenu activator="parent" close-on-content-click>
|
||||||
<VList>
|
<VList>
|
||||||
@@ -526,38 +515,38 @@ onMounted(() => {
|
|||||||
</VList>
|
</VList>
|
||||||
</VMenu>
|
</VMenu>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<span v-if="hover.isHovering" class="flex">
|
<span v-if="hover.isHovering && display.mdAndUp" class="flex">
|
||||||
<VTooltip text="识别">
|
<VTooltip text="识别">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="recognize(item.path)">
|
<IconBtn v-bind="props" @click.stop="recognize(item.path)">
|
||||||
<VIcon icon="mdi-text-recognition" />
|
<VIcon icon="mdi-text-recognition" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
</VTooltip>
|
</VTooltip>
|
||||||
<VTooltip text="刮削" v-if="storage == 'local'">
|
<VTooltip text="刮削" v-if="storage == 'local'">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="scrape(item.path)">
|
<IconBtn v-bind="props" @click.stop="scrape(item.path)">
|
||||||
<VIcon icon="mdi-auto-fix" />
|
<VIcon icon="mdi-auto-fix" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
</VTooltip>
|
</VTooltip>
|
||||||
<VTooltip text="重命名">
|
<VTooltip text="重命名">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="showRenmae(item)">
|
<IconBtn v-bind="props" @click.stop="showRenmae(item)">
|
||||||
<VIcon icon="mdi-rename" />
|
<VIcon icon="mdi-rename" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
</VTooltip>
|
</VTooltip>
|
||||||
<VTooltip text="整理" v-if="storage == 'local'">
|
<VTooltip text="整理" v-if="storage == 'local'">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="showTransfer(item)">
|
<IconBtn v-bind="props" @click.stop="showTransfer(item)">
|
||||||
<VIcon icon="mdi-folder-arrow-right" />
|
<VIcon icon="mdi-folder-arrow-right" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
</VTooltip>
|
</VTooltip>
|
||||||
<VTooltip text="删除">
|
<VTooltip text="删除">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<IconBtn v-bind="props" class="d-none d-sm-block" @click.stop="deleteItem(item)">
|
<IconBtn v-bind="props" @click.stop="deleteItem(item)">
|
||||||
<VIcon icon="mdi-delete-outline" color="error" />
|
<VIcon icon="mdi-delete-outline" color="error" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
@@ -609,7 +598,7 @@ onMounted(() => {
|
|||||||
@done="
|
@done="
|
||||||
() => {
|
() => {
|
||||||
transferPopper = false
|
transferPopper = false
|
||||||
load()
|
list_files()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@close="transferPopper = false"
|
@close="transferPopper = false"
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Axios } from 'axios'
|
import type { Axios, AxiosRequestConfig } from 'axios'
|
||||||
import type { EndPoints } from '@/api/types'
|
import type { EndPoints, FileItem } from '@/api/types'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
|
// 显示器宽度
|
||||||
|
const display = useDisplay()
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const inProps = defineProps({
|
const inProps = defineProps({
|
||||||
storages: Array as PropType<any[]>,
|
storages: Array as PropType<any[]>,
|
||||||
storage: String,
|
storage: String,
|
||||||
path: String,
|
item: {
|
||||||
fileid: String,
|
type: Object as PropType<FileItem>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
fileidstack: {
|
fileidstack: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -42,8 +48,8 @@ function changeSort() {
|
|||||||
// 计算PATH面包屑
|
// 计算PATH面包屑
|
||||||
const pathSegments = computed(() => {
|
const pathSegments = computed(() => {
|
||||||
let path_str = ''
|
let path_str = ''
|
||||||
const isFolder = inProps.path?.endsWith('/')
|
const isFolder = inProps.item.path?.endsWith('/')
|
||||||
const segments = inProps.path?.split('/').filter(item => item)
|
const segments = inProps.item.path?.split('/').filter(item => item)
|
||||||
const fileids = inProps.fileidstack ?? []
|
const fileids = inProps.fileidstack ?? []
|
||||||
return (
|
return (
|
||||||
segments?.map((item, index) => {
|
segments?.map((item, index) => {
|
||||||
@@ -57,6 +63,7 @@ const pathSegments = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 当前存储
|
||||||
const storageObject = computed(() => {
|
const storageObject = computed(() => {
|
||||||
return inProps.storages?.find(item => item.code === inProps.storage)
|
return inProps.storages?.find(item => item.code === inProps.storage)
|
||||||
})
|
})
|
||||||
@@ -89,12 +96,12 @@ async function mkdir() {
|
|||||||
emit('loading', true)
|
emit('loading', true)
|
||||||
const url = inProps.endpoints?.mkdir.url
|
const url = inProps.endpoints?.mkdir.url
|
||||||
.replace(/{storage}/g, inProps.storage)
|
.replace(/{storage}/g, inProps.storage)
|
||||||
.replace(/{path}/g, encodeURIComponent(inProps.path + newFolderName.value))
|
.replace(/{name}/g, newFolderName.value)
|
||||||
.replace(/{fileid}/g, inProps.fileid || '')
|
|
||||||
|
|
||||||
const config = {
|
const config: AxiosRequestConfig<FileItem> = {
|
||||||
url,
|
url,
|
||||||
method: inProps.endpoints?.mkdir.method || 'post',
|
method: inProps.endpoints?.mkdir.method || 'post',
|
||||||
|
data: inProps.item,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调API
|
// 调API
|
||||||
@@ -138,15 +145,16 @@ const sortIcon = computed(() => {
|
|||||||
</VListItem>
|
</VListItem>
|
||||||
</VList>
|
</VList>
|
||||||
</VMenu>
|
</VMenu>
|
||||||
<VBtn variant="text" :input-value="path === '/'" class="px-1" @click="changePath('/', 'root')">
|
<VBtn variant="text" :input-value="item.path === '/'" class="px-1" @click="changePath('/', 'root')">
|
||||||
<VIcon :icon="storageObject?.icon" class="mr-2" />
|
<VIcon :icon="storageObject?.icon" class="mr-2" />
|
||||||
{{ storageObject?.name }}
|
{{ storageObject?.name }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<template v-for="(segment, index) in pathSegments" :key="index">
|
<template v-for="(segment, index) in pathSegments" :key="index">
|
||||||
<VBtn
|
<VBtn
|
||||||
|
v-if="display.mdAndUp.value"
|
||||||
variant="text"
|
variant="text"
|
||||||
:input-value="index === pathSegments.length - 1"
|
:input-value="index === pathSegments.length - 1"
|
||||||
class="px-1 d-none d-md-block"
|
class="px-1"
|
||||||
@click="changePath(segment.path, inProps.fileidstack[index + 1])"
|
@click="changePath(segment.path, inProps.fileidstack[index + 1])"
|
||||||
>
|
>
|
||||||
<VIcon icon=" mdi-chevron-right" />
|
<VIcon icon=" mdi-chevron-right" />
|
||||||
@@ -180,13 +188,16 @@ const sortIcon = computed(() => {
|
|||||||
</IconBtn>
|
</IconBtn>
|
||||||
</template>
|
</template>
|
||||||
<VCard title="新建文件夹">
|
<VCard title="新建文件夹">
|
||||||
|
<DialogCloseBtn @click="newFolderPopper = false" />
|
||||||
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VTextField v-model="newFolderName" label="名称" />
|
<VTextField v-model="newFolderName" label="名称" />
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<div class="flex-grow-1" />
|
<div class="flex-grow-1" />
|
||||||
<VBtn depressed @click="newFolderPopper = false"> 取消 </VBtn>
|
<VBtn :disabled="!newFolderName" variant="elevated" @click="mkdir" prepend-icon="mdi-check" class="px-5 me-3">
|
||||||
<VBtn :disabled="!newFolderName" depressed variant="tonal" @click="mkdir"> 新建 </VBtn>
|
新建
|
||||||
|
</VBtn>
|
||||||
</VCardActions>
|
</VCardActions>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import SlideViewTitle from '@/components/slide/SlideViewTitle.vue'
|
import SlideViewTitle from '@/components/slide/SlideViewTitle.vue'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
|
// 显示器宽度
|
||||||
|
const display = useDisplay()
|
||||||
|
|
||||||
// 元素
|
// 元素
|
||||||
const slideview_content = ref()
|
const slideview_content = ref()
|
||||||
@@ -91,7 +95,7 @@ onActivated(() => {
|
|||||||
<slot name="title">
|
<slot name="title">
|
||||||
<SlideViewTitle />
|
<SlideViewTitle />
|
||||||
</slot>
|
</slot>
|
||||||
<div v-if="disabled !== 3" class="me-1 d-none d-md-flex">
|
<div v-if="disabled !== 3 && display.mdAndUp.value" class="me-1 d-flex">
|
||||||
<VBtn
|
<VBtn
|
||||||
class="rounded-circle"
|
class="rounded-circle"
|
||||||
variant="text"
|
variant="text"
|
||||||
@@ -122,8 +126,8 @@ onActivated(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.slideview_content {
|
.slideview_content {
|
||||||
-ms-overflow-style: none !important;
|
|
||||||
overflow: scroll hidden !important;
|
overflow: scroll hidden !important;
|
||||||
|
-ms-overflow-style: none !important;
|
||||||
overscroll-behavior-x: contain !important;
|
overscroll-behavior-x: contain !important;
|
||||||
scrollbar-width: none !important;
|
scrollbar-width: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ function initOptions(data: Context) {
|
|||||||
optionValue(resolutionFilterOptions.value, meta_info?.resource_pix)
|
optionValue(resolutionFilterOptions.value, meta_info?.resource_pix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 对季过滤选项进行排序
|
// 对季过滤选项进行排序
|
||||||
const sortSeasonFilterOptions = computed(() => {
|
const sortSeasonFilterOptions = computed(() => {
|
||||||
return seasonFilterOptions.value.sort((a, b) => {
|
return seasonFilterOptions.value.sort((a, b) => {
|
||||||
@@ -182,7 +181,7 @@ onMounted(() => {
|
|||||||
</VVirtualScroll>
|
</VVirtualScroll>
|
||||||
</VList>
|
</VList>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol xl="2" md="3" class="d-none d-md-block">
|
<VCol xl="2" md="3" v-if="display.mdAndUp.value">
|
||||||
<VList
|
<VList
|
||||||
lines="one"
|
lines="one"
|
||||||
class="rounded shadow-lg"
|
class="rounded shadow-lg"
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ import store from '@/store'
|
|||||||
|
|
||||||
const endpoints = {
|
const endpoints = {
|
||||||
list: {
|
list: {
|
||||||
url: '/{storage}/list?path={path}&sort={sort}&fileid={fileid}&filetype={filetype}&pickcode={pickcode}',
|
url: '/{storage}/list?sort={sort}',
|
||||||
method: 'get',
|
method: 'post',
|
||||||
},
|
},
|
||||||
mkdir: {
|
mkdir: {
|
||||||
url: '/{storage}/mkdir?path={path}&fileid={fileid}',
|
url: '/{storage}/mkdir?name={name}',
|
||||||
method: 'get',
|
method: 'post',
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
url: '/{storage}/delete?path={path}&fileid={fileid}',
|
url: '/{storage}/delete',
|
||||||
method: 'get',
|
method: 'post',
|
||||||
},
|
},
|
||||||
download: {
|
download: {
|
||||||
url: '/{storage}/download?path={path}&fileid={fileid}&pickcode={pickcode}',
|
url: '/{storage}/download',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
url: '/{storage}/image?path={path}&fileid={fileid}&pickcode={pickcode}',
|
url: '/{storage}/image',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
rename: {
|
rename: {
|
||||||
url: '/{storage}/rename?path={path}&new_name={newname}&fileid={fileid}&filetype={filetype}',
|
url: '/{storage}/rename?new_name={newname}',
|
||||||
method: 'get',
|
method: 'post',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,14 +36,13 @@ const user_level = store.state.auth.level
|
|||||||
// 用户存储
|
// 用户存储
|
||||||
const userStorage = user_level > 1 ? 'local,aliyun,u115' : 'local'
|
const userStorage = user_level > 1 ? 'local,aliyun,u115' : 'local'
|
||||||
|
|
||||||
// 当前目录
|
// 当前文件项
|
||||||
const path = ref<string>('')
|
const operItem = ref<FileItem>({
|
||||||
|
type: 'dir',
|
||||||
// 当前fileid
|
name: '/',
|
||||||
const fileid = ref<string>('root')
|
path: '/',
|
||||||
|
fileid: 'root',
|
||||||
// 当前pickcode
|
})
|
||||||
const pickcode = ref<string>('')
|
|
||||||
|
|
||||||
// fileid的堆栈
|
// fileid的堆栈
|
||||||
const fileidstack = ref<string[]>(['root'])
|
const fileidstack = ref<string[]>(['root'])
|
||||||
@@ -91,7 +90,13 @@ async function loadDownloadDirectories() {
|
|||||||
const result: { [key: string]: any } = await api.get('system/setting/DownloadDirectories')
|
const result: { [key: string]: any } = await api.get('system/setting/DownloadDirectories')
|
||||||
if (result.success && result.data?.value) {
|
if (result.success && result.data?.value) {
|
||||||
downloadDirectories.value = result.data.value
|
downloadDirectories.value = result.data.value
|
||||||
path.value = findCommonPath(downloadDirectories.value.map(item => item.path) as string[])
|
const path = findCommonPath(downloadDirectories.value.map(item => item.path) as string[])
|
||||||
|
const name = path.split('/').filter(Boolean).pop() ?? ''
|
||||||
|
operItem.value = {
|
||||||
|
type: 'dir',
|
||||||
|
name: name,
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -100,10 +105,8 @@ async function loadDownloadDirectories() {
|
|||||||
|
|
||||||
// 目录变化
|
// 目录变化
|
||||||
function pathChanged(item: FileItem) {
|
function pathChanged(item: FileItem) {
|
||||||
path.value = item.path
|
operItem.value = item
|
||||||
pickcode.value = item.pickcode || ''
|
|
||||||
if (item.fileid) {
|
if (item.fileid) {
|
||||||
fileid.value = item.fileid
|
|
||||||
if (fileidstack.value.includes(item.fileid)) {
|
if (fileidstack.value.includes(item.fileid)) {
|
||||||
fileidstack.value = fileidstack.value.slice(0, fileidstack.value.indexOf(item.fileid) + 1)
|
fileidstack.value = fileidstack.value.slice(0, fileidstack.value.indexOf(item.fileid) + 1)
|
||||||
} else {
|
} else {
|
||||||
@@ -112,6 +115,7 @@ function pathChanged(item: FileItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载初始目录
|
||||||
onBeforeMount(loadDownloadDirectories)
|
onBeforeMount(loadDownloadDirectories)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -120,12 +124,10 @@ onBeforeMount(loadDownloadDirectories)
|
|||||||
<FileBrowser
|
<FileBrowser
|
||||||
:storages="userStorage"
|
:storages="userStorage"
|
||||||
:tree="false"
|
:tree="false"
|
||||||
:path="path"
|
|
||||||
:fileid="fileid"
|
|
||||||
:pickcode="pickcode"
|
|
||||||
:fileidstack="fileidstack"
|
:fileidstack="fileidstack"
|
||||||
:endpoints="endpoints"
|
:endpoints="endpoints"
|
||||||
:axios="api"
|
:axios="api"
|
||||||
|
:item="operItem"
|
||||||
@pathchanged="pathChanged"
|
@pathchanged="pathChanged"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import { requiredValidator } from '@/@validators'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { User } from '@/api/types'
|
import type { User } from '@/api/types'
|
||||||
import avatar1 from '@images/avatars/avatar-1.png'
|
import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
|
// 显示器宽度
|
||||||
|
const display = useDisplay()
|
||||||
|
|
||||||
const isNewPasswordVisible = ref(false)
|
const isNewPasswordVisible = ref(false)
|
||||||
const isConfirmPasswordVisible = ref(false)
|
const isConfirmPasswordVisible = ref(false)
|
||||||
@@ -250,7 +254,7 @@ onMounted(() => {
|
|||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<VBtn color="primary" @click="refInputEl?.click()">
|
<VBtn color="primary" @click="refInputEl?.click()">
|
||||||
<VIcon icon="mdi-cloud-upload-outline" />
|
<VIcon icon="mdi-cloud-upload-outline" />
|
||||||
<span class="d-none d-sm-block ms-2">上传头像</span>
|
<span v-if="display.mdAndUp.value" class="ms-2">上传头像</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -264,7 +268,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<VBtn type="reset" color="error" variant="tonal" @click="resetAvatar">
|
<VBtn type="reset" color="error" variant="tonal" @click="resetAvatar">
|
||||||
<VIcon icon="mdi-refresh" />
|
<VIcon icon="mdi-refresh" />
|
||||||
<span class="d-none d-sm-block ms-2">重置</span>
|
<span v-if="display.mdAndUp.value" class="ms-2">重置</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
@@ -273,7 +277,9 @@ onMounted(() => {
|
|||||||
@click.stop="accountInfo.is_otp ? disableOtp() : getOtpUri()"
|
@click.stop="accountInfo.is_otp ? disableOtp() : getOtpUri()"
|
||||||
>
|
>
|
||||||
<VIcon icon="mdi-account-key" />
|
<VIcon icon="mdi-account-key" />
|
||||||
<span class="d-none d-sm-block ms-2">{{ accountInfo.is_otp ? '关闭验证' : '双重验证' }}</span>
|
<span v-if="display.mdAndUp.value" class="ms-2">{{
|
||||||
|
accountInfo.is_otp ? '关闭验证' : '双重验证'
|
||||||
|
}}</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user