mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-11 09:59:59 +08:00
✨ Feature(custom): migrate all pages to tailwind
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
- 优化相册页面卡片样式,边界更清晰,提升选择框视觉效果。
|
- 优化相册页面卡片样式,边界更清晰,提升选择框视觉效果。
|
||||||
- 优化多个页面在窄屏下的显示,避免内容溢出。
|
- 优化多个页面在窄屏下的显示,避免内容溢出。
|
||||||
- 文件浏览侧边栏名称超出宽度时支持滚动显示完整名称。
|
- 文件浏览侧边栏名称超出宽度时支持滚动显示完整名称。
|
||||||
|
- 优化了视频播放页面的显示效果。
|
||||||
- 相册页面支持显示已选数量、匹配的 URL 列表和记忆过滤器状态。
|
- 相册页面支持显示已选数量、匹配的 URL 列表和记忆过滤器状态。
|
||||||
- 支持浏览完整插件列表、查看详情及一键安装。
|
- 支持浏览完整插件列表、查看详情及一键安装。
|
||||||
- 新增新手引导页面,首次运行自动弹出。
|
- 新增新手引导页面,首次运行自动弹出。
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
|
|
||||||
- 修复了管理页面中排序下拉框显示异常的问题。
|
- 修复了管理页面中排序下拉框显示异常的问题。
|
||||||
- 修复了管理页面图床列表未正确高亮当前选中项的问题。
|
- 修复了管理页面图床列表未正确高亮当前选中项的问题。
|
||||||
|
- 修复了管理页面markdown预览内容没有正确渲染的问题。
|
||||||
- 修复了暗色模式下任务页面的显示异常。
|
- 修复了暗色模式下任务页面的显示异常。
|
||||||
- 修复了图床设置页面“设置为默认图床”按钮状态更新不及时的问题。
|
- 修复了图床设置页面“设置为默认图床”按钮状态更新不及时的问题。
|
||||||
- 修复了预处理设置页面中,图床水印独立设置按钮状态不同步的问题。
|
- 修复了预处理设置页面中,图床水印独立设置按钮状态不同步的问题。
|
||||||
|
|||||||
@@ -29,3 +29,4 @@
|
|||||||
@utility no-drag-region {
|
@utility no-drag-region {
|
||||||
-webkit-app-region: none;
|
-webkit-app-region: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,6 @@ function validateForm(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearFieldError(fieldName: string) {
|
function clearFieldError(fieldName: string) {
|
||||||
console.log('Clearing error for field:', fieldName)
|
|
||||||
if (validationErrors[fieldName]) {
|
if (validationErrors[fieldName]) {
|
||||||
delete validationErrors[fieldName]
|
delete validationErrors[fieldName]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,14 @@
|
|||||||
<span v-if="required" class="ml-1 text-danger">*</span>
|
<span v-if="required" class="ml-1 text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<slot name="title-extra"></slot>
|
<slot name="title-extra"></slot>
|
||||||
<div v-if="tips" class="relative">
|
<div v-if="tips" class="group relative inline-block">
|
||||||
<div
|
<div
|
||||||
class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent"
|
class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent"
|
||||||
@click="toggleTooltip()"
|
|
||||||
>
|
>
|
||||||
<Info :size="16" />
|
<Info :size="16" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="visibleTooltips"
|
class="invisible absolute top-[125%] left-1/2 z-1000 w-max max-w-[200px] translate-x-[-50%] rounded-md border border-border bg-bg-tertiary p-2 text-center text-xs text-main opacity-0 shadow-md transition-opacity duration-300 group-hover:visible group-hover:opacity-100"
|
||||||
class="absolute top-full left-0 z-1000 max-w-[300px] min-w-[200px] rounded-md border border-border bg-bg-secondary p-3 text-xs leading-[1.4] text-main shadow-lg max-md:max-w-[250px] max-md:min-w-[150px]"
|
|
||||||
v-html="transformMarkdownToHTML(tips)"
|
v-html="transformMarkdownToHTML(tips)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +60,6 @@ const [modelValue, modifiers] = defineModel<any>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const type = ref('text')
|
const type = ref('text')
|
||||||
const visibleTooltips = ref(false)
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isPassword = false,
|
isPassword = false,
|
||||||
@@ -80,10 +77,6 @@ const {
|
|||||||
tips?: string
|
tips?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
function toggleTooltip() {
|
|
||||||
visibleTooltips.value = !visibleTooltips.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformMarkdownToHTML(markdown: string) {
|
function transformMarkdownToHTML(markdown: string) {
|
||||||
try {
|
try {
|
||||||
return marked.parse(markdown)
|
return marked.parse(markdown)
|
||||||
|
|||||||
@@ -29,16 +29,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<slot name="title-extra"></slot>
|
<slot name="title-extra"></slot>
|
||||||
<div v-if="tips" class="relative">
|
<div v-if="tips" class="group relative inline-block">
|
||||||
<div
|
<div
|
||||||
class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent"
|
class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent"
|
||||||
@click="toggleTooltip()"
|
|
||||||
>
|
>
|
||||||
<Info :size="16" />
|
<Info :size="16" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="visibleTooltips"
|
class="invisible absolute top-[125%] left-1/2 z-1000 w-max max-w-[200px] translate-x-[-50%] rounded-md border border-border bg-bg-tertiary p-2 text-center text-xs text-main opacity-0 shadow-md transition-opacity duration-300 group-hover:visible group-hover:opacity-100"
|
||||||
class="absolute top-full left-0 z-1000 max-w-[300px] min-w-[200px] rounded-md border border-border bg-bg-secondary p-3 text-xs leading-[1.4] text-main shadow-lg max-md:max-w-[250px] max-md:min-w-[150px]"
|
|
||||||
v-html="transformMarkdownToHTML(tips)"
|
v-html="transformMarkdownToHTML(tips)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,12 +46,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Info } from 'lucide-vue-next'
|
import { Info } from 'lucide-vue-next'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
const emit = defineEmits(['change'])
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
const visibleTooltips = ref(false)
|
|
||||||
|
|
||||||
const modelValue = defineModel<boolean>()
|
const modelValue = defineModel<boolean>()
|
||||||
const {
|
const {
|
||||||
title = '',
|
title = '',
|
||||||
@@ -75,10 +71,6 @@ const {
|
|||||||
tighter?: boolean
|
tighter?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
function toggleTooltip() {
|
|
||||||
visibleTooltips.value = !visibleTooltips.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformMarkdownToHTML(markdown: string) {
|
function transformMarkdownToHTML(markdown: string) {
|
||||||
try {
|
try {
|
||||||
return marked.parse(markdown)
|
return marked.parse(markdown)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@apply m-0 h-full p-0 w-full bg-transparent;
|
@apply m-0 h-full w-full bg-transparent p-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
@@ -189,5 +189,5 @@
|
|||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
<div
|
<div
|
||||||
ref="bucketContainerRef"
|
ref="bucketContainerRef"
|
||||||
class="relative flex h-full w-full items-center justify-center"
|
class="relative flex h-full w-full items-center justify-center"
|
||||||
:class="{ 'content-fullscreen': isContentFullscreen }"
|
|
||||||
@scroll="handleBucketContainerScroll"
|
@scroll="handleBucketContainerScroll"
|
||||||
>
|
>
|
||||||
<div class="relative z-1 flex h-full w-full flex-col items-center justify-start gap-2 rounded-xl border-none p-0">
|
<div class="relative z-1 flex h-full w-full flex-col items-center justify-start gap-1 rounded-xl border-none p-0">
|
||||||
<!-- Header Card -->
|
<!-- Header Card -->
|
||||||
<div
|
<div
|
||||||
v-if="!isContentFullscreen"
|
v-if="!isContentFullscreen"
|
||||||
class="flex w-full flex-wrap items-center justify-between gap-4 overflow-visible rounded-xl border border-border-secondary p-0 shadow-sm"
|
class="flex w-full flex-wrap items-center justify-between gap-4 overflow-visible rounded-xl p-0"
|
||||||
>
|
>
|
||||||
<div class="flex flex-1 flex-wrap items-center gap-4 p-1">
|
<div class="flex flex-1 flex-wrap items-center gap-4 p-1">
|
||||||
<!-- Custom Domain Input/Select -->
|
<!-- Custom Domain Input/Select -->
|
||||||
@@ -130,7 +129,7 @@
|
|||||||
<!-- Breadcrumb Card -->
|
<!-- Breadcrumb Card -->
|
||||||
<div
|
<div
|
||||||
v-if="!isContentFullscreen"
|
v-if="!isContentFullscreen"
|
||||||
class="flex w-full items-center justify-between gap-4 overflow-hidden rounded-xl border border-border-secondary p-0 shadow-sm"
|
class="flex w-full items-center justify-between gap-4 overflow-hidden rounded-sm border border-border-secondary p-0"
|
||||||
>
|
>
|
||||||
<div class="flex flex-1 items-center gap-0 overflow-x-auto px-4 py-1">
|
<div class="flex flex-1 items-center gap-0 overflow-x-auto px-4 py-1">
|
||||||
<HomeIcon class="h-[16px] w-[16px] shrink-0 text-accent" />
|
<HomeIcon class="h-[16px] w-[16px] shrink-0 text-accent" />
|
||||||
@@ -138,7 +137,7 @@
|
|||||||
<template v-for="(item, index) in configMap.prefix.replace(/\/$/g, '').split('/')" :key="index">
|
<template v-for="(item, index) in configMap.prefix.replace(/\/$/g, '').split('/')" :key="index">
|
||||||
<ChevronRightIcon v-if="index !== 0" class="h-[16px] w-[15px] shrink-0 text-accent" />
|
<ChevronRightIcon v-if="index !== 0" class="h-[16px] w-[15px] shrink-0 text-accent" />
|
||||||
<button
|
<button
|
||||||
class="flex shrink-0 cursor-pointer items-center gap-1 rounded-md border-none bg-bg-secondary p-1 text-sm font-semibold text-secondary hover:bg-accent/10 hover:text-main"
|
class="flex shrink-0 cursor-pointer items-center gap-1 rounded-md border-none bg-bg-secondary p-1 text-sm font-semibold text-secondary last:bg-accent/10 hover:bg-accent/10 hover:text-main"
|
||||||
@click="handleBreadcrumbClick(Number(index))"
|
@click="handleBreadcrumbClick(Number(index))"
|
||||||
>
|
>
|
||||||
{{ item === '' ? t('pages.manage.bucket.rootFolder') : item }}
|
{{ item === '' ? t('pages.manage.bucket.rootFolder') : item }}
|
||||||
@@ -158,7 +157,7 @@
|
|||||||
<!-- Control Panel Card -->
|
<!-- Control Panel Card -->
|
||||||
<div
|
<div
|
||||||
v-if="!isContentFullscreen"
|
v-if="!isContentFullscreen"
|
||||||
class="flex w-full flex-wrap items-center justify-between gap-2 overflow-visible rounded-xl border border-border-secondary p-0 shadow-sm"
|
class="flex w-full flex-wrap items-center justify-between gap-2 overflow-visible rounded-sm border border-border-secondary p-0"
|
||||||
>
|
>
|
||||||
<FileInfo :current-page-files-info="currentPageFilesInfo" :calculate-all-file-size="calculateAllFileSize" />
|
<FileInfo :current-page-files-info="currentPageFilesInfo" :calculate-all-file-size="calculateAllFileSize" />
|
||||||
|
|
||||||
@@ -187,14 +186,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Sort Dropdown -->
|
<!-- Sort Dropdown -->
|
||||||
<div class="dropdown">
|
<div class="relative">
|
||||||
<button class="dropdown-button" @click="sortDropdownOpen = !sortDropdownOpen">
|
<button
|
||||||
|
class="flex cursor-pointer items-center gap-2 rounded-md border border-border bg-bg-secondary px-3 py-2 text-sm font-medium text-main hover:border-accent hover:bg-accent/10"
|
||||||
|
@click="sortDropdownOpen = !sortDropdownOpen"
|
||||||
|
>
|
||||||
<ArrowUpDownIcon class="action-icon" />
|
<ArrowUpDownIcon class="action-icon" />
|
||||||
{{ t(`pages.manage.bucket.sort.${currentSortType}`) }}
|
{{ t(`pages.manage.bucket.sort.${currentSortType}`) }}
|
||||||
<ChevronDownIcon class="action-icon" />
|
<ChevronDownIcon class="action-icon" />
|
||||||
</button>
|
</button>
|
||||||
<div v-if="sortDropdownOpen" class="dropdown-content">
|
<div
|
||||||
<div v-for="item in sortTypeList" :key="item" class="dropdown-item" @click="sortFile(item as any)">
|
v-if="sortDropdownOpen"
|
||||||
|
class="absolute top-full left-0 z-1000 mt-1 min-w-[150px] rounded-md border border-border bg-bg-tertiary shadow-md"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="item in sortTypeList"
|
||||||
|
:key="item"
|
||||||
|
class="cursor-pointer bg-bg-tertiary px-3 py-2 text-sm text-main transition-all duration-fast ease-apple hover:bg-accent/30 hover:text-white"
|
||||||
|
@click="sortFile(item as any)"
|
||||||
|
>
|
||||||
{{ t(`pages.manage.bucket.sort.${item}`) }}
|
{{ t(`pages.manage.bucket.sort.${item}`) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,7 +245,7 @@
|
|||||||
class="flex w-full flex-wrap items-center justify-between gap-2 overflow-visible rounded-xl border border-border-secondary p-0 shadow-sm"
|
class="flex w-full flex-wrap items-center justify-between gap-2 overflow-visible rounded-xl border border-border-secondary p-0 shadow-sm"
|
||||||
>
|
>
|
||||||
<div class="flex max-w-[400px] min-w-[200px] items-center overflow-x-auto px-4 py-1">
|
<div class="flex max-w-[400px] min-w-[200px] items-center overflow-x-auto px-4 py-1">
|
||||||
<div class="fullscreen-breadcrumb rounded-md shadow-sm">
|
<div class="flex flex-wrap items-center gap-1 rounded-md shadow-sm">
|
||||||
<HomeIcon class="h-[16px] w-[16px] shrink-0 text-accent" />
|
<HomeIcon class="h-[16px] w-[16px] shrink-0 text-accent" />
|
||||||
<template v-if="configMap.prefix !== '/'">
|
<template v-if="configMap.prefix !== '/'">
|
||||||
<template v-for="(item, index) in configMap.prefix.replace(/\/$/g, '').split('/')" :key="index">
|
<template v-for="(item, index) in configMap.prefix.replace(/\/$/g, '').split('/')" :key="index">
|
||||||
@@ -258,7 +268,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FileInfo :current-page-files-info="currentPageFilesInfo" :calculate-all-file-size="calculateAllFileSize" />
|
<FileInfo :current-page-files-info="currentPageFilesInfo" :calculate-all-file-size="calculateAllFileSize" />
|
||||||
<div class="fullscreen-header-right">
|
<div class="flex min-w-[200px] flex-1 items-center justify-end gap-3">
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<input
|
<input
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
@@ -282,7 +292,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="no-scrollbar flex min-h-[500px] w-full flex-1 flex-col flex-wrap items-center justify-center gap-2 overflow-auto rounded-2xl border border-border-secondary p-1 shadow-md"
|
class="no-scrollbar flex min-h-[500px] w-full flex-1 flex-col flex-wrap items-center justify-center gap-2 overflow-auto rounded-md border border-border-secondary p-1 shadow-md"
|
||||||
>
|
>
|
||||||
<div v-if="filterList.length === 0" class="h-full w-full">
|
<div v-if="filterList.length === 0" class="h-full w-full">
|
||||||
<EmptyPage />
|
<EmptyPage />
|
||||||
@@ -366,13 +376,13 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex shrink-0 flex-col justify-between gap-0.5">
|
<div class="flex min-w-0 shrink-0 flex-col justify-between gap-0.5">
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-main"
|
class="w-full truncate text-center text-sm font-medium text-main"
|
||||||
:title="item.fileName"
|
:title="item.fileName"
|
||||||
@click.stop="copyToClipboard(item.fileName ?? '')"
|
@click.stop="copyToClipboard(item.fileName ?? '')"
|
||||||
>
|
>
|
||||||
<div class="text-center">{{ item.fileName ?? '' }}</div>
|
{{ item.fileName ?? '' }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!item.isDir"
|
v-if="!item.isDir"
|
||||||
@@ -512,7 +522,7 @@
|
|||||||
@click="copyToClipboard(JSON.stringify(currentShowedFileInfo, null, 2))"
|
@click="copyToClipboard(JSON.stringify(currentShowedFileInfo, null, 2))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-content">
|
<div class="flex-1 overflow-y-auto bg-bg p-6">
|
||||||
<div
|
<div
|
||||||
v-for="(value, key) in currentShowedFileInfo"
|
v-for="(value, key) in currentShowedFileInfo"
|
||||||
:key="key"
|
:key="key"
|
||||||
@@ -548,9 +558,12 @@
|
|||||||
<div class="mb-6 last:mb-0">
|
<div class="mb-6 last:mb-0">
|
||||||
<label class="mb-2 flex items-center gap-2 text-sm font-medium text-main">
|
<label class="mb-2 flex items-center gap-2 text-sm font-medium text-main">
|
||||||
{{ t('pages.manage.bucket.matchedPattern', { num: matchedFilesNumber.length }) }}
|
{{ t('pages.manage.bucket.matchedPattern', { num: matchedFilesNumber.length }) }}
|
||||||
<div class="tooltip">
|
<div class="group relative inline-block">
|
||||||
<InfoIcon class="action-icon" />
|
<InfoIcon class="action-icon" />
|
||||||
<span class="tooltip-text">{{ t('pages.manage.bucket.regexPatternTips') }}</span>
|
<span
|
||||||
|
class="invisible absolute top-[125%] left-1/2 z-1000 w-max max-w-[200px] translate-x-[-50%] rounded-md border border-border bg-bg-tertiary p-2 text-center text-xs text-main opacity-0 shadow-md transition-opacity duration-300 group-hover:visible group-hover:opacity-100"
|
||||||
|
>{{ t('pages.manage.bucket.regexPatternTips') }}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -663,55 +676,55 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Upload Drawer -->
|
<!-- Upload Drawer -->
|
||||||
<div
|
<CustomModal
|
||||||
v-if="isShowUploadPanel"
|
v-if="isShowUploadPanel"
|
||||||
class="drawer-overlay"
|
v-model:visible="isShowUploadPanel"
|
||||||
:class="{ open: isShowUploadPanel }"
|
:title="t('pages.manage.bucket.uploadFile')"
|
||||||
@click="isShowUploadPanel = false"
|
width="900px"
|
||||||
|
height="90vh"
|
||||||
>
|
>
|
||||||
<div class="drawer-container" @click.stop>
|
<div class="flex h-full w-full flex-col gap-2">
|
||||||
<div class="drawer-header">
|
<div class="flex justify-end">
|
||||||
<h3 class="drawer-title">
|
<CustomSwitch
|
||||||
{{ t('pages.manage.bucket.uploadFile') }}
|
v-model="isUploadKeepDirStructure"
|
||||||
</h3>
|
:title="
|
||||||
<div class="switch-container">
|
|
||||||
<label class="switch">
|
|
||||||
<input v-model="isUploadKeepDirStructure" type="checkbox" @change="handleUploadKeepDirChange" />
|
|
||||||
<span class="switch-slider" />
|
|
||||||
</label>
|
|
||||||
<span class="switch-label">
|
|
||||||
{{
|
|
||||||
isUploadKeepDirStructure
|
isUploadKeepDirStructure
|
||||||
? t('pages.manage.bucket.keepDirStructure')
|
? t('pages.manage.bucket.keepDirStructure')
|
||||||
: t('pages.manage.bucket.noKeepDirStructure')
|
: t('pages.manage.bucket.noKeepDirStructure')
|
||||||
}}
|
"
|
||||||
</span>
|
small
|
||||||
</div>
|
no-border
|
||||||
<button class="modal-close" @click="isShowUploadPanel = false">
|
@change="handleUploadKeepDirChange"
|
||||||
<XIcon class="action-icon" />
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-content">
|
<div class="no-scrollbar w-full flex-1 overflow-hidden rounded-md border border-border p-4 shadow-md">
|
||||||
|
<div class="flex h-full w-full flex-col">
|
||||||
<div
|
<div
|
||||||
v-if="!tableData.length"
|
v-if="!tableData.length"
|
||||||
class="upload-area"
|
ref="uploadDialog"
|
||||||
|
class="h-[200px] w-full cursor-pointer rounded-lg border-2 border-dashed border-border bg-surface p-4 text-center transition-all duration-fast ease-apple hover:border-accent hover:bg-accent/10 [.dragover]:border-accent [.dragover]:bg-accent/10"
|
||||||
:class="{ dragover: isDragover }"
|
:class="{ dragover: isDragover }"
|
||||||
@drop.prevent="onDrop"
|
@drop.prevent="onDrop"
|
||||||
@dragover.prevent="isDragover = true"
|
@dragover.prevent="isDragover = true"
|
||||||
@dragleave.prevent="isDragover = false"
|
@dragleave.prevent="isDragover = false"
|
||||||
@click="openFileSelectDialog"
|
@click="openFileSelectDialog"
|
||||||
>
|
>
|
||||||
<div class="upload-area-text">
|
<div class="flex h-full flex-col items-center justify-center gap-2">
|
||||||
|
<div class="mb-2 text-lg font-semibold text-secondary">
|
||||||
{{ t('pages.manage.bucket.dragUpload') }}
|
{{ t('pages.manage.bucket.dragUpload') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="upload-area-subtext">
|
<div class="text-sm font-medium text-secondary">
|
||||||
{{ t('pages.manage.bucket.clickUpload') }}
|
{{ t('pages.manage.bucket.clickUpload') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Upload File List -->
|
<!-- Upload File List -->
|
||||||
<div v-if="tableData.length">
|
<div
|
||||||
|
v-if="tableData.length"
|
||||||
|
class="flex h-[200px] w-full cursor-pointer rounded-lg border-2 border-dashed border-border bg-surface p-4 text-center transition-all duration-fast ease-apple"
|
||||||
|
>
|
||||||
<VirtualScroller
|
<VirtualScroller
|
||||||
:items="
|
:items="
|
||||||
tableData.sort((a, b) =>
|
tableData.sort((a, b) =>
|
||||||
@@ -719,23 +732,32 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
:item-height="90"
|
:item-height="90"
|
||||||
:style="{ height: '100%' }"
|
class="min-h-0 w-full flex-1 p-3"
|
||||||
view-mode="list"
|
view-mode="list"
|
||||||
>
|
>
|
||||||
<template #default="{ item }">
|
<template #default="{ item }">
|
||||||
<div class="file-list-item">
|
<div
|
||||||
<div class="file-list-icon">
|
class="m-0 flex w-full cursor-pointer items-center gap-2 rounded-md border border-border-secondary bg-bg-secondary px-4 py-3 hover:bg-accent/10"
|
||||||
<FolderIcon v-if="item.isFolder" class="file-icon" />
|
>
|
||||||
<FileIcon v-else class="file-icon" />
|
<div class="flex h-[25px] w-[25px] shrink-0 items-center justify-center">
|
||||||
|
<FolderIcon v-if="item.isFolder" class="h-[48px] w-[48px] text-tertiary" />
|
||||||
|
<FileIcon v-else class="h-[48px] w-[48px] text-tertiary" />
|
||||||
</div>
|
</div>
|
||||||
<div class="file-list-info">
|
<div class="min-w-0 flex-1 flex-col">
|
||||||
<div class="file-list-name">
|
<div class="flex flex-row justify-between gap-3">
|
||||||
{{ formatFileName(item.name) }}
|
<div
|
||||||
|
class="mb-1 cursor-pointer overflow-hidden text-sm font-semibold text-ellipsis whitespace-nowrap text-secondary"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.fullPath" class="file-list-path">
|
<div
|
||||||
|
v-if="item.fullPath"
|
||||||
|
class="overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-secondary"
|
||||||
|
>
|
||||||
{{ item.fullPath }}
|
{{ item.fullPath }}
|
||||||
</div>
|
</div>
|
||||||
<div class="file-list-meta">
|
</div>
|
||||||
|
<div class="flex text-xs text-secondary">
|
||||||
<span>{{ formatFileSize(item.fileSize) }}</span>
|
<span>{{ formatFileSize(item.fileSize) }}</span>
|
||||||
<span v-if="item.isFolder"> {{ item.filesList.length }} files </span>
|
<span v-if="item.isFolder"> {{ item.filesList.length }} files </span>
|
||||||
</div>
|
</div>
|
||||||
@@ -746,162 +768,129 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Upload Actions -->
|
<!-- Upload Actions -->
|
||||||
<div v-if="tableData.length" style="display: flex; justify-content: center; gap: 1rem; margin-top: 1rem">
|
<div v-if="tableData.length" class="mt-4 flex justify-center gap-4">
|
||||||
<button class="action-button primary" :disabled="isLoadingUploadPanelFiles" @click="uploadFiles">
|
<CustomButton
|
||||||
<UploadIcon class="action-icon" />
|
type="primary"
|
||||||
{{ isLoadingUploadPanelFiles ? t('pages.manage.bucket.readingDir') : t('pages.manage.bucket.upload') }}
|
:disabled="isLoadingUploadPanelFiles"
|
||||||
</button>
|
:text="
|
||||||
<button class="action-button secondary" :disabled="isLoadingUploadPanelFiles" @click="clearTableData">
|
isLoadingUploadPanelFiles ? t('pages.manage.bucket.readingDir') : t('pages.manage.bucket.upload')
|
||||||
<Trash2Icon class="action-icon" />
|
"
|
||||||
{{ t('pages.manage.bucket.clear') }}
|
:icon="UploadIcon"
|
||||||
</button>
|
@click="uploadFiles"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:icon="Trash2Icon"
|
||||||
|
:text="t('pages.manage.bucket.clear')"
|
||||||
|
:disabled="isLoadingUploadPanelFiles"
|
||||||
|
@click="clearTableData"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Upload Tasks Tabs -->
|
<!-- Upload Tasks Tabs -->
|
||||||
<div class="tabs-container">
|
<div class="flex flex-1 flex-col gap-2 overflow-hidden border-t border-border-secondary">
|
||||||
<div class="tabs-header">
|
<div class="flex shrink-0 border-b border-b-border">
|
||||||
<button
|
<button
|
||||||
class="tab-button"
|
class="relative flex-1 rounded-md border-b-2 border-b-transparent bg-none px-6 py-3 text-sm font-semibold text-secondary shadow-sm transition-all duration-fast ease-apple hover:border-b-accent hover:text-main [.active]:border-b-accent [.active]:bg-accent [.active]:text-white"
|
||||||
:class="{ active: activeUpLoadTab === 'uploading' }"
|
:class="{ active: activeUpLoadTab === 'uploading' }"
|
||||||
@click="activeUpLoadTab = 'uploading'"
|
@click="activeUpLoadTab = 'uploading'"
|
||||||
>
|
>
|
||||||
{{ t('pages.manage.bucket.uploading') }}
|
{{ t('pages.manage.bucket.uploading') }}
|
||||||
<span v-if="uploadingTaskList.length" class="tab-badge">
|
<span
|
||||||
|
v-if="uploadingTaskList.length"
|
||||||
|
class="absolute top-1 right-1 min-w-[16px] rounded-full bg-accent px-1.5 py-0.5 text-center text-xs text-white"
|
||||||
|
>
|
||||||
{{ uploadingTaskList.length }}
|
{{ uploadingTaskList.length }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab-button"
|
class="relative flex-1 rounded-md border-b-2 border-b-transparent bg-none px-6 py-3 text-sm font-semibold text-secondary shadow-sm transition-all duration-fast ease-apple hover:border-b-accent hover:text-main [.active]:border-b-accent [.active]:bg-accent [.active]:text-white"
|
||||||
:class="{ active: activeUpLoadTab === 'finished' }"
|
:class="{ active: activeUpLoadTab === 'finished' }"
|
||||||
@click="activeUpLoadTab = 'finished'"
|
@click="activeUpLoadTab = 'finished'"
|
||||||
>
|
>
|
||||||
{{ t('pages.manage.bucket.success') }}
|
{{ t('pages.manage.bucket.success') }}
|
||||||
<span v-if="uploadedTaskList.filter(item => item.status === 'uploaded').length" class="tab-badge">
|
<span
|
||||||
|
v-if="uploadedTaskList.filter(item => item.status === 'uploaded').length"
|
||||||
|
class="absolute top-1 right-1 min-w-[16px] rounded-full bg-accent px-1.5 py-0.5 text-center text-xs text-white"
|
||||||
|
>
|
||||||
{{ uploadedTaskList.filter(item => item.status === 'uploaded').length }}
|
{{ uploadedTaskList.filter(item => item.status === 'uploaded').length }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab-button"
|
class="relative flex-1 rounded-md border-b-2 border-b-transparent bg-none px-6 py-3 text-sm font-semibold text-secondary shadow-sm transition-all duration-fast ease-apple hover:border-b-accent hover:text-main [.active]:border-b-accent [.active]:bg-accent [.active]:text-white"
|
||||||
:class="{ active: activeUpLoadTab === 'failed' }"
|
:class="{ active: activeUpLoadTab === 'failed' }"
|
||||||
@click="activeUpLoadTab = 'failed'"
|
@click="activeUpLoadTab = 'failed'"
|
||||||
>
|
>
|
||||||
{{ t('pages.manage.bucket.failed') }}
|
{{ t('pages.manage.bucket.failed') }}
|
||||||
<span v-if="uploadedTaskList.filter(item => item.status !== 'uploaded').length" class="tab-badge">
|
<span
|
||||||
|
v-if="uploadedTaskList.filter(item => item.status !== 'uploaded').length"
|
||||||
|
class="absolute top-1 right-1 min-w-[16px] rounded-full bg-accent px-1.5 py-0.5 text-center text-xs text-white"
|
||||||
|
>
|
||||||
{{ uploadedTaskList.filter(item => item.status !== 'uploaded').length }}
|
{{ uploadedTaskList.filter(item => item.status !== 'uploaded').length }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="flex flex-row justify-center gap-3 rounded-md border border-border shadow-sm">
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.bucket.copyUploadTask')"
|
||||||
|
:icon="CopyIcon"
|
||||||
|
@click="handleCopyUploadingTaskInfo"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.bucket.clearFinishedTasks')"
|
||||||
|
:icon="Trash2Icon"
|
||||||
|
@click="handleDeleteUploadedTask"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.bucket.clearAll')"
|
||||||
|
:icon="Trash2Icon"
|
||||||
|
@click="handleDeleteAllUploadedTask"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex-1 overflow-auto rounded-md border border-border-secondary p-2">
|
||||||
<!-- Uploading Tab -->
|
<!-- Uploading Tab -->
|
||||||
<div v-if="activeUpLoadTab === 'uploading'" class="tab-panel">
|
|
||||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem">
|
|
||||||
<button class="action-button secondary" @click="handleCopyUploadingTaskInfo">
|
|
||||||
<CopyIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.copyUploadTask') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteUploadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearFinishedTasks') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteAllUploadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearAll') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<VirtualScroller
|
<VirtualScroller
|
||||||
:items="uploadingTaskList"
|
:items="
|
||||||
|
activeUpLoadTab === 'uploading'
|
||||||
|
? uploadingTaskList
|
||||||
|
: activeUpLoadTab === 'finished'
|
||||||
|
? uploadedTaskList.filter(item => item.status === 'uploaded')
|
||||||
|
: uploadedTaskList.filter(item => item.status !== 'uploaded')
|
||||||
|
"
|
||||||
:item-height="70"
|
:item-height="70"
|
||||||
:style="{ height: '100%' }"
|
class="min-h-0 w-full flex-1 p-3"
|
||||||
view-mode="list"
|
view-mode="list"
|
||||||
>
|
>
|
||||||
<template #default="{ item }">
|
<template #default="{ item }">
|
||||||
<div class="file-list-item">
|
<div
|
||||||
<div class="file-list-info">
|
class="m-0 flex w-full cursor-pointer items-center gap-3 rounded-md border border-border bg-bg-secondary px-4 py-3 hover:border-accent hover:shadow-md"
|
||||||
<div class="file-list-name">
|
|
||||||
{{ formatFileName(item.sourceFileName) }}
|
|
||||||
</div>
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" :style="{ width: `${item.progress || 50}%` }" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VirtualScroller>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Finished Tab -->
|
|
||||||
<div v-if="activeUpLoadTab === 'finished'" class="tab-panel">
|
|
||||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem">
|
|
||||||
<button class="action-button secondary" @click="handleCopyUploadingTaskInfo">
|
|
||||||
<CopyIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.copyUploadTask') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteUploadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearFinishedTasks') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteAllUploadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearAll') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<VirtualScroller
|
|
||||||
:items="uploadedTaskList.filter(item => item.status === 'uploaded')"
|
|
||||||
:item-height="70"
|
|
||||||
:style="{ height: '100%' }"
|
|
||||||
view-mode="list"
|
|
||||||
>
|
>
|
||||||
<template #default="{ item }">
|
<div class="flex flex-1 flex-col gap-1">
|
||||||
<div class="file-list-item">
|
<div class="overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-secondary">
|
||||||
<div class="file-list-info">
|
{{ item.sourceFileName }}
|
||||||
<div class="file-list-name">
|
|
||||||
{{ formatFileName(item.sourceFileName) }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="file-list-meta">
|
<div
|
||||||
|
v-if="activeUpLoadTab === 'uploading'"
|
||||||
|
class="h-[8px] w-full overflow-hidden rounded-[4px] bg-surface-elevated"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-full rounded-[4px] bg-accent transition-all duration-300 ease-apple"
|
||||||
|
:style="{ width: `${item.progress}%` }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex gap-4 text-xs text-secondary">
|
||||||
<span>{{ item.finishTime }}</span>
|
<span>{{ item.finishTime }}</span>
|
||||||
<span class="badge success">
|
<span class="text-xs font-semibold text-success">
|
||||||
{{ t('pages.manage.bucket.success') }}
|
{{
|
||||||
</span>
|
activeUpLoadTab === 'finished'
|
||||||
</div>
|
? t('pages.manage.bucket.success')
|
||||||
</div>
|
: t('pages.manage.bucket.failed')
|
||||||
</div>
|
}}
|
||||||
</template>
|
|
||||||
</VirtualScroller>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Failed Tab -->
|
|
||||||
<div v-if="activeUpLoadTab === 'failed'" class="tab-panel">
|
|
||||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem">
|
|
||||||
<button class="action-button secondary" @click="handleCopyUploadingTaskInfo">
|
|
||||||
<CopyIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.copyUploadTask') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteUploadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearFinishedTasks') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteAllUploadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearAll') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<VirtualScroller
|
|
||||||
:items="uploadedTaskList.filter(item => item.status !== 'uploaded')"
|
|
||||||
:item-height="70"
|
|
||||||
:style="{ height: '100%' }"
|
|
||||||
view-mode="list"
|
|
||||||
>
|
|
||||||
<template #default="{ item }">
|
|
||||||
<div class="file-list-item">
|
|
||||||
<div class="file-list-info">
|
|
||||||
<div class="file-list-name">
|
|
||||||
{{ formatFileName(item.sourceFileName) }}
|
|
||||||
</div>
|
|
||||||
<div class="file-list-meta">
|
|
||||||
<span>{{ item.finishTime }}</span>
|
|
||||||
<span class="badge error">
|
|
||||||
{{ t('pages.manage.bucket.failed') }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -913,184 +902,128 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CustomModal>
|
||||||
|
|
||||||
<!-- Download Drawer -->
|
<!-- Download Drawer -->
|
||||||
<div
|
<CustomModal
|
||||||
v-if="isShowDownloadPanel"
|
v-if="isShowDownloadPanel"
|
||||||
class="drawer-overlay"
|
v-model:visible="isShowDownloadPanel"
|
||||||
:class="{ open: isShowDownloadPanel }"
|
:title="t('pages.manage.bucket.downloadPage')"
|
||||||
@click="isShowDownloadPanel = false"
|
width="900px"
|
||||||
|
height="90vh"
|
||||||
>
|
>
|
||||||
<div class="drawer-container" @click.stop>
|
<div class="no-scrollbar w-full flex-1 overflow-hidden rounded-md border border-border p-4 shadow-md">
|
||||||
<div class="drawer-header">
|
<div class="flex h-full w-full flex-col">
|
||||||
<h3 class="drawer-title">
|
|
||||||
{{ t('pages.manage.bucket.downloadPage') }}
|
|
||||||
</h3>
|
|
||||||
<button class="modal-close" @click="isShowDownloadPanel = false">
|
|
||||||
<XIcon class="action-icon" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-content">
|
|
||||||
<!-- Download Tasks Tabs -->
|
<!-- Download Tasks Tabs -->
|
||||||
<div class="tabs-container">
|
<div class="flex flex-1 flex-col gap-2 overflow-hidden border-t border-border-secondary">
|
||||||
<div class="tabs-header">
|
<div class="flex shrink-0 border-b border-b-border">
|
||||||
<button
|
<button
|
||||||
class="tab-button"
|
class="relative flex-1 rounded-md border-b-2 border-b-transparent bg-none px-6 py-3 text-sm font-semibold text-secondary shadow-sm transition-all duration-fast ease-apple hover:border-b-accent hover:text-main [.active]:border-b-accent [.active]:bg-accent [.active]:text-white"
|
||||||
:class="{ active: activeDownLoadTab === 'downloading' }"
|
:class="{ active: activeDownLoadTab === 'downloading' }"
|
||||||
@click="activeDownLoadTab = 'downloading'"
|
@click="activeDownLoadTab = 'downloading'"
|
||||||
>
|
>
|
||||||
{{ t('pages.manage.bucket.downloading') }}
|
{{ t('pages.manage.bucket.downloading') }}
|
||||||
<span v-if="downloadingTaskList.length" class="tab-badge">
|
<span
|
||||||
|
v-if="downloadingTaskList.length"
|
||||||
|
class="absolute top-1 right-1 min-w-[16px] rounded-full bg-accent px-1.5 py-0.5 text-center text-xs text-white"
|
||||||
|
>
|
||||||
{{ downloadingTaskList.length }}
|
{{ downloadingTaskList.length }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab-button"
|
class="relative flex-1 rounded-md border-b-2 border-b-transparent bg-none px-6 py-3 text-sm font-semibold text-secondary shadow-sm transition-all duration-fast ease-apple hover:border-b-accent hover:text-main [.active]:border-b-accent [.active]:bg-accent [.active]:text-white"
|
||||||
:class="{ active: activeDownLoadTab === 'finished' }"
|
:class="{ active: activeDownLoadTab === 'finished' }"
|
||||||
@click="activeDownLoadTab = 'finished'"
|
@click="activeDownLoadTab = 'finished'"
|
||||||
>
|
>
|
||||||
{{ t('pages.manage.bucket.success') }}
|
{{ t('pages.manage.bucket.success') }}
|
||||||
<span v-if="downloadedTaskList.filter(item => item.status === 'downloaded').length" class="tab-badge">
|
<span
|
||||||
|
v-if="downloadedTaskList.filter(item => item.status === 'downloaded').length"
|
||||||
|
class="absolute top-1 right-1 min-w-[16px] rounded-full bg-accent px-1.5 py-0.5 text-center text-xs text-white"
|
||||||
|
>
|
||||||
{{ downloadedTaskList.filter(item => item.status === 'downloaded').length }}
|
{{ downloadedTaskList.filter(item => item.status === 'downloaded').length }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab-button"
|
class="relative flex-1 rounded-md border-b-2 border-b-transparent bg-none px-6 py-3 text-sm font-semibold text-secondary shadow-sm transition-all duration-fast ease-apple hover:border-b-accent hover:text-main [.active]:border-b-accent [.active]:bg-accent [.active]:text-white"
|
||||||
:class="{ active: activeDownLoadTab === 'failed' }"
|
:class="{ active: activeDownLoadTab === 'failed' }"
|
||||||
@click="activeDownLoadTab = 'failed'"
|
@click="activeDownLoadTab = 'failed'"
|
||||||
>
|
>
|
||||||
{{ t('pages.manage.bucket.failed') }}
|
{{ t('pages.manage.bucket.failed') }}
|
||||||
<span v-if="downloadedTaskList.filter(item => item.status !== 'downloaded').length" class="tab-badge">
|
<span
|
||||||
|
v-if="downloadedTaskList.filter(item => item.status !== 'downloaded').length"
|
||||||
|
class="absolute top-1 right-1 min-w-[16px] rounded-full bg-accent px-1.5 py-0.5 text-center text-xs text-white"
|
||||||
|
>
|
||||||
{{ downloadedTaskList.filter(item => item.status !== 'downloaded').length }}
|
{{ downloadedTaskList.filter(item => item.status !== 'downloaded').length }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Download Tabs Content -->
|
<div class="flex flex-row justify-center gap-3 rounded-md border border-border shadow-sm">
|
||||||
<div class="tab-content">
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.bucket.copyDownloadTask')"
|
||||||
|
:icon="CopyIcon"
|
||||||
|
@click="handleCopyDownloadingTaskInfo"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.bucket.clearFinishedTasks')"
|
||||||
|
:icon="Trash2Icon"
|
||||||
|
@click="handleDeleteDownloadedTask"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.bucket.clearAll')"
|
||||||
|
:icon="Trash2Icon"
|
||||||
|
@click="handleDeleteAllDownloadedTask"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.bucket.openDownloadFolder')"
|
||||||
|
:icon="FolderIcon"
|
||||||
|
@click="handleOpenDownloadedFolder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex-1 overflow-auto rounded-md border border-border-secondary p-2">
|
||||||
<!-- Downloading Tab -->
|
<!-- Downloading Tab -->
|
||||||
<div v-if="activeDownLoadTab === 'downloading'" class="tab-panel">
|
|
||||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem">
|
|
||||||
<button class="action-button secondary" @click="handleCopyDownloadingTaskInfo">
|
|
||||||
<CopyIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.copyDownloadTask') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteDownloadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearFinishedTasks') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteAllDownloadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearAll') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleOpenDownloadedFolder">
|
|
||||||
<FolderIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.openDownloadFolder') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<VirtualScroller
|
<VirtualScroller
|
||||||
:items="downloadingTaskList"
|
:items="
|
||||||
|
activeDownLoadTab === 'downloading'
|
||||||
|
? downloadingTaskList
|
||||||
|
: activeDownLoadTab === 'finished'
|
||||||
|
? downloadedTaskList.filter(item => item.status === 'downloaded')
|
||||||
|
: downloadedTaskList.filter(item => item.status !== 'downloaded')
|
||||||
|
"
|
||||||
:item-height="70"
|
:item-height="70"
|
||||||
:style="{ height: '100%' }"
|
class="min-h-0 w-full flex-1 p-3"
|
||||||
view-mode="list"
|
view-mode="list"
|
||||||
>
|
>
|
||||||
<template #default="{ item }">
|
<template #default="{ item }">
|
||||||
<div class="file-list-item">
|
<div
|
||||||
<div class="file-list-info">
|
class="m-0 flex w-full cursor-pointer items-center gap-3 rounded-md border border-border bg-bg-secondary px-4 py-3 hover:border-accent hover:shadow-md"
|
||||||
<div class="file-list-name">
|
|
||||||
{{ formatFileName(item.sourceFileName) }}
|
|
||||||
</div>
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" :style="{ width: `${item.progress}%` }" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VirtualScroller>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Finished Tab -->
|
|
||||||
<div v-if="activeDownLoadTab === 'finished'" class="tab-panel">
|
|
||||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem">
|
|
||||||
<button class="action-button secondary" @click="handleCopyDownloadingTaskInfo">
|
|
||||||
<CopyIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.copyDownloadTask') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteDownloadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearFinishedTasks') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteAllDownloadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearAll') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleOpenDownloadedFolder">
|
|
||||||
<FolderIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.openDownloadFolder') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<VirtualScroller
|
|
||||||
:items="downloadedTaskList.filter(item => item.status === 'downloaded')"
|
|
||||||
:item-height="70"
|
|
||||||
:style="{ height: '100%' }"
|
|
||||||
view-mode="list"
|
|
||||||
>
|
>
|
||||||
<template #default="{ item }">
|
<div class="flex flex-1 flex-col gap-1">
|
||||||
<div class="file-list-item">
|
<div class="overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-secondary">
|
||||||
<div class="file-list-info">
|
{{ item.sourceFileName }}
|
||||||
<div class="file-list-name">
|
|
||||||
{{ formatFileName(item.sourceFileName) }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="file-list-meta">
|
<div
|
||||||
|
v-if="activeDownLoadTab === 'downloading'"
|
||||||
|
class="relative h-[8px] w-full overflow-hidden rounded-[4px] bg-surface-elevated"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="h-full rounded-[4px] bg-accent transition-all duration-300 ease-apple"
|
||||||
|
:style="{ width: `${item.progress}%` }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex gap-4 text-xs text-secondary">
|
||||||
<span>{{ item.finishTime }}</span>
|
<span>{{ item.finishTime }}</span>
|
||||||
<span class="badge success">
|
<span class="text-xs font-semibold text-success">
|
||||||
{{ t('pages.manage.bucket.success') }}
|
{{
|
||||||
</span>
|
activeUpLoadTab === 'finished'
|
||||||
</div>
|
? t('pages.manage.bucket.success')
|
||||||
</div>
|
: t('pages.manage.bucket.failed')
|
||||||
</div>
|
}}
|
||||||
</template>
|
|
||||||
</VirtualScroller>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Failed Tab -->
|
|
||||||
<div v-if="activeDownLoadTab === 'failed'" class="tab-panel">
|
|
||||||
<div style="display: flex; gap: 0.5rem; margin-bottom: 1rem">
|
|
||||||
<button class="action-button secondary" @click="handleCopyDownloadingTaskInfo">
|
|
||||||
<CopyIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.copyDownloadTask') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteDownloadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearFinishedTasks') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleDeleteAllDownloadedTask">
|
|
||||||
<Trash2Icon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.clearAll') }}
|
|
||||||
</button>
|
|
||||||
<button class="action-button secondary" @click="handleOpenDownloadedFolder">
|
|
||||||
<FolderIcon class="action-icon" />
|
|
||||||
{{ t('pages.manage.bucket.openDownloadFolder') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<VirtualScroller
|
|
||||||
:items="downloadedTaskList.filter(item => item.status !== 'downloaded')"
|
|
||||||
:style="{ height: '100%' }"
|
|
||||||
:item-height="70"
|
|
||||||
view-mode="list"
|
|
||||||
>
|
|
||||||
<template #default="{ item }">
|
|
||||||
<div class="file-list-item">
|
|
||||||
<div class="file-list-info">
|
|
||||||
<div class="file-list-name">
|
|
||||||
{{ formatFileName(item.sourceFileName) }}
|
|
||||||
</div>
|
|
||||||
<div class="file-list-meta">
|
|
||||||
<span>{{ item.finishTime }}</span>
|
|
||||||
<span class="badge error">
|
|
||||||
{{ t('pages.manage.bucket.failed') }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1101,57 +1034,68 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CustomModal>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Markdown Preview Dialog -->
|
<!-- Markdown Preview Dialog -->
|
||||||
<div v-if="isShowMarkDownDialog" class="modal-overlay" @click="isShowMarkDownDialog = false">
|
<CustomModal
|
||||||
<div class="modal-container" style="width: 90vw; height: 90vh" @click.stop>
|
v-if="isShowMarkDownDialog"
|
||||||
<div class="modal-header">
|
v-model:visible="isShowMarkDownDialog"
|
||||||
<h3 class="modal-title">
|
width="80vw"
|
||||||
{{ t('pages.manage.bucket.preview') }}
|
height="80vh"
|
||||||
</h3>
|
:title="t('pages.manage.bucket.preview')"
|
||||||
<button class="modal-close" @click="isShowMarkDownDialog = false">
|
>
|
||||||
<XIcon class="action-icon" />
|
<div class="flex h-full w-full">
|
||||||
</button>
|
<div class="notes-body" style="user-select: text" v-html="markDownContent" />
|
||||||
</div>
|
|
||||||
<div class="modal-content" style="user-select: text" v-html="markDownContent" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</CustomModal>
|
||||||
|
|
||||||
<!-- Text File Preview Dialog -->
|
<!-- Text File Preview Dialog -->
|
||||||
<div v-if="isShowTextFileDialog" class="modal-overlay" @click="isShowTextFileDialog = false">
|
<CustomModal
|
||||||
<div class="modal-container" style="width: 90vw; height: 90vh" @click.stop>
|
v-if="isShowTextFileDialog"
|
||||||
<div class="modal-header">
|
v-model:visible="isShowTextFileDialog"
|
||||||
<h3 class="modal-title">
|
width="80vw"
|
||||||
{{ t('pages.manage.bucket.preview') }}
|
height="80vh"
|
||||||
</h3>
|
:title="t('pages.manage.bucket.preview')"
|
||||||
<button class="modal-close" @click="isShowTextFileDialog = false">
|
>
|
||||||
<XIcon class="action-icon" />
|
<div class="flex h-full w-full">
|
||||||
</button>
|
<pre class="overflow-auto font-['SF_Mono',Monaco,Menlo,'Ubuntu_Mono',monospace] text-base text-main">{{
|
||||||
</div>
|
textfileContent
|
||||||
<div class="modal-content">
|
}}</pre>
|
||||||
<pre style="font-family: monospace; white-space: pre-wrap; user-select: text">{{ textfileContent }}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</CustomModal>
|
||||||
|
|
||||||
<!-- Video Player Dialog -->
|
<!-- Video Player Dialog -->
|
||||||
<div v-if="isShowVideoFileDialog" class="modal-overlay" @click="isShowVideoFileDialog = false">
|
<CustomModal
|
||||||
<div class="modal-container" style="width: 90vw; height: 90vh" @click.stop>
|
v-if="isShowVideoFileDialog"
|
||||||
<div class="modal-header">
|
v-model:visible="isShowVideoFileDialog"
|
||||||
<h3 class="modal-title">
|
width="90vw"
|
||||||
{{ t('pages.manage.bucket.play') }}
|
height="90vh"
|
||||||
</h3>
|
:title="t('pages.manage.bucket.play')"
|
||||||
<button class="modal-close" @click="isShowVideoFileDialog = false">
|
>
|
||||||
<XIcon class="action-icon" />
|
<div class="flex h-full w-full items-center justify-center bg-black">
|
||||||
</button>
|
<video-player
|
||||||
</div>
|
class="video-player"
|
||||||
<div class="modal-content">
|
:src="videoFileUrl"
|
||||||
<video :src="videoFileUrl" controls loop autoplay style="width: 100%; height: auto; max-height: 70vh" />
|
:volume="0.6"
|
||||||
</div>
|
:options="{
|
||||||
</div>
|
autoplay: true,
|
||||||
|
muted: false,
|
||||||
|
responsive: true,
|
||||||
|
fill: true,
|
||||||
|
fluid: false,
|
||||||
|
controlBar: {
|
||||||
|
volumePanel: {
|
||||||
|
inline: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
controls
|
||||||
|
playsinline
|
||||||
|
loop
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</CustomModal>
|
||||||
|
|
||||||
<!-- Create Folder Dialog -->
|
<!-- Create Folder Dialog -->
|
||||||
<CustomModal
|
<CustomModal
|
||||||
@@ -1234,7 +1178,6 @@ import { useDownloadFileTransferStore, useFileTransferStore, useManageStore } fr
|
|||||||
import {
|
import {
|
||||||
customStrMatch,
|
customStrMatch,
|
||||||
customStrReplace,
|
customStrReplace,
|
||||||
formatFileName,
|
|
||||||
formatFileSize,
|
formatFileSize,
|
||||||
formatLink,
|
formatLink,
|
||||||
getFileIconPath,
|
getFileIconPath,
|
||||||
@@ -1245,6 +1188,7 @@ import { getConfig, saveConfig } from '@/manage/utils/dataSender'
|
|||||||
import { textFileExt } from '@/manage/utils/textfile'
|
import { textFileExt } from '@/manage/utils/textfile'
|
||||||
import { videoExt } from '@/manage/utils/videofile'
|
import { videoExt } from '@/manage/utils/videofile'
|
||||||
import { trimPath } from '@/utils/common'
|
import { trimPath } from '@/utils/common'
|
||||||
|
import { useDragEventListeners } from '@/utils/drag'
|
||||||
import { IRPCActionType } from '@/utils/enum'
|
import { IRPCActionType } from '@/utils/enum'
|
||||||
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/utils/static'
|
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/utils/static'
|
||||||
/*
|
/*
|
||||||
@@ -1265,6 +1209,8 @@ const props = defineProps<{
|
|||||||
|
|
||||||
type ISortTypeList = 'name' | 'size' | 'time' | 'ext' | 'check' | 'init'
|
type ISortTypeList = 'name' | 'size' | 'time' | 'ext' | 'check' | 'init'
|
||||||
|
|
||||||
|
const uploadDialog = useTemplateRef<HTMLDivElement>('uploadDialog')
|
||||||
|
useDragEventListeners(uploadDialog)
|
||||||
let fileTransferInterval: NodeJS.Timeout | undefined
|
let fileTransferInterval: NodeJS.Timeout | undefined
|
||||||
let downloadInterval: NodeJS.Timeout | undefined
|
let downloadInterval: NodeJS.Timeout | undefined
|
||||||
let scrollTimeout: ReturnType<typeof setTimeout> | undefined
|
let scrollTimeout: ReturnType<typeof setTimeout> | undefined
|
||||||
@@ -1536,8 +1482,8 @@ function getList() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUploadKeepDirChange() {
|
function handleUploadKeepDirChange(value: boolean) {
|
||||||
saveConfig('settings.isUploadKeepDirStructure', isUploadKeepDirStructure.value)
|
saveConfig('settings.isUploadKeepDirStructure', value)
|
||||||
manageStore.refreshConfig()
|
manageStore.refreshConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1896,7 +1842,7 @@ async function handleClickFile(item: any) {
|
|||||||
const fileUrl = item.url
|
const fileUrl = item.url
|
||||||
const res = await fetch(fileUrl, options)
|
const res = await fetch(fileUrl, options)
|
||||||
const content = await res.text()
|
const content = await res.text()
|
||||||
markDownContent.value = await marked.parse(content)
|
markDownContent.value = await marked(content, { breaks: true, gfm: true })
|
||||||
isShowMarkDownDialog.value = true
|
isShowMarkDownDialog.value = true
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
message.error(t('pages.manage.bucket.loadingFailed'))
|
message.error(t('pages.manage.bucket.loadingFailed'))
|
||||||
|
|||||||
@@ -40,16 +40,16 @@
|
|||||||
>
|
>
|
||||||
<div class="flex h-full w-full">
|
<div class="flex h-full w-full">
|
||||||
<div
|
<div
|
||||||
class="flex min-h-0 max-w-[400px] min-w-[40px] flex-col border-r-2 border-r-border transition-all duration-100 ease-out"
|
class="flex min-h-0 w-[40px] max-w-[400px] min-w-[40px] flex-col border-r-2 border-r-border transition-all duration-100 ease-out"
|
||||||
:style="{ width: sidebarWidth + 'px' }"
|
:style="{ width: sidebarWidth + 'px' }"
|
||||||
>
|
>
|
||||||
<div class="shrink-0 border-b-2 border-b-border-secondary p-2">
|
<div v-if="menuTitleMap[currentPicBedName]" class="shrink-0 border-b-2 border-b-border-secondary p-2">
|
||||||
<h3 class="m-0 text-center text-sm font-semibold text-secondary">
|
<h3 class="m-0 text-center text-sm font-semibold text-secondary">
|
||||||
{{ menuTitleMap[currentPicBedName] }}
|
{{ menuTitleMap[currentPicBedName] }}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="min-h-0 flex-1 overflow-y-auto p-2">
|
<div class="min-h-0 flex-1 overflow-y-auto">
|
||||||
<div v-if="isLoadingBucketList" class="flex flex-col items-center justify-center gap-2 p-8">
|
<div v-if="isLoadingBucketList" class="flex flex-col items-center justify-center gap-2 p-8">
|
||||||
<div
|
<div
|
||||||
class="h-[25px] w-[25px] animate-spin rounded-full border-3 border-t-2 border-border border-t-accent"
|
class="h-[25px] w-[25px] animate-spin rounded-full border-3 border-t-2 border-border border-t-accent"
|
||||||
@@ -287,7 +287,7 @@ const configMap = ref<any>(null)
|
|||||||
const currentAlias = ref(route.query.alias as string)
|
const currentAlias = ref(route.query.alias as string)
|
||||||
const currentPicBedName = ref(route.query.picBedName as string)
|
const currentPicBedName = ref(route.query.picBedName as string)
|
||||||
|
|
||||||
const sidebarWidth = ref(160)
|
const sidebarWidth = ref(120)
|
||||||
const isResizing = ref(false)
|
const isResizing = ref(false)
|
||||||
|
|
||||||
let allPicBedConfigure = JSON.parse(route.query.allPicBedConfigure as string)
|
let allPicBedConfigure = JSON.parse(route.query.allPicBedConfigure as string)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -242,12 +242,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex shrink-0 flex-col justify-between">
|
<div class="flex min-w-0 shrink-0 flex-col justify-between">
|
||||||
<div
|
<div
|
||||||
class="mb-1.5 overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-main"
|
class="mb-1.5 w-full truncate text-center text-sm font-medium text-main"
|
||||||
:title="(item.fileName || '').toString().length > 30 ? item.fileName || '' : ''"
|
:title="(item.fileName || '').toString().length > 30 ? item.fileName || '' : ''"
|
||||||
>
|
>
|
||||||
<div class="text-center">{{ formatFileName(item.fileName || '') }}</div>
|
{{ formatFileName(item.fileName || '') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mr-2 flex items-center justify-between">
|
<div class="mr-2 flex items-center justify-between">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:class="[osGlobal === 'linux' ? 'rounded-none bg-size-[100vh_100vw]' : 'rounded-full bg-size-[90vh_90vw]']"
|
:class="[osGlobal === 'linux' ? 'rounded-none bg-size-[100vh_100vw]' : 'rounded-full bg-size-[90vh_90vw]']"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="upload-area"
|
ref="uploadArea"
|
||||||
class="h-full w-full transition-all duration-200 ease-in-out"
|
class="h-full w-full transition-all duration-200 ease-in-out"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-[rgba(0,0,0,0.3)]': dragover,
|
'bg-[rgba(0,0,0,0.3)]': dragover,
|
||||||
@@ -35,11 +35,12 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { IConfig } from 'piclist'
|
import type { IConfig } from 'piclist'
|
||||||
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
|
import { onBeforeMount, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'
|
||||||
|
|
||||||
import { osGlobal } from '@/hooks/useGlobal'
|
import { osGlobal } from '@/hooks/useGlobal'
|
||||||
import { isUrl } from '@/utils/common'
|
import { isUrl } from '@/utils/common'
|
||||||
import { getConfig } from '@/utils/dataSender'
|
import { getConfig } from '@/utils/dataSender'
|
||||||
|
import { useDragEventListeners } from '@/utils/drag'
|
||||||
import { IRPCActionType } from '@/utils/enum'
|
import { IRPCActionType } from '@/utils/enum'
|
||||||
|
|
||||||
const logoPath = ref('')
|
const logoPath = ref('')
|
||||||
@@ -51,6 +52,9 @@ const wX = ref(-1)
|
|||||||
const wY = ref(-1)
|
const wY = ref(-1)
|
||||||
const screenX = ref(-1)
|
const screenX = ref(-1)
|
||||||
const screenY = ref(-1)
|
const screenY = ref(-1)
|
||||||
|
const uploadArea = useTemplateRef<HTMLDivElement>('uploadArea')
|
||||||
|
|
||||||
|
useDragEventListeners(uploadArea)
|
||||||
|
|
||||||
let removeListeners: () => void = () => {}
|
let removeListeners: () => void = () => {}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
small
|
small
|
||||||
no-border
|
no-border
|
||||||
:title="t('pages.settings.system.isHideDock')"
|
:title="t('pages.settings.system.isHideDock')"
|
||||||
@change="handleHideDockChange(formOfSetting.isHideDock)"
|
@change="handleHideDockChange"
|
||||||
/>
|
/>
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
small
|
small
|
||||||
no-border
|
no-border
|
||||||
:title="t('pages.settings.system.miniWindowOnTop')"
|
:title="t('pages.settings.system.miniWindowOnTop')"
|
||||||
@click="handleMiniWindowOntop(formOfSetting.miniWindowOntop)"
|
@change="handleMiniWindowOntop"
|
||||||
/>
|
/>
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
no-border
|
no-border
|
||||||
:title="t('pages.settings.system.autoLaunch')"
|
:title="t('pages.settings.system.autoLaunch')"
|
||||||
:description="t('pages.settings.system.autoLaunchDesc')"
|
:description="t('pages.settings.system.autoLaunchDesc')"
|
||||||
@change="handleAutoStartChange(formOfSetting.autoStart)"
|
@change="handleAutoStartChange"
|
||||||
/>
|
/>
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
<CustomNavCard
|
<CustomNavCard
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="upload-area"
|
id="upload-area"
|
||||||
|
ref="uploadArea"
|
||||||
class="group/upload relative flex h-full w-full cursor-pointer items-center justify-center rounded-xl border-2 border-dashed border-border bg-bg-secondary px-1 py-12 duration-medium ease-standard focus-visible:focus-ring focus-visible:outline-offset-4 max-md:px-4 max-md:py-8 max-xs:px-2 max-xs:py-6 [:hover,.drag-active]:border-accent [:hover,.drag-active]:bg-[linear-gradient(135deg,var(--color-surface-elevated)_0%,color-mix(in_srgb,var(--color-accent),transparent_95%)_100%)] [:hover,.drag-active]:shadow-lg [:hover,.drag-active&]:-translate-y-[2px]"
|
class="group/upload relative flex h-full w-full cursor-pointer items-center justify-center rounded-xl border-2 border-dashed border-border bg-bg-secondary px-1 py-12 duration-medium ease-standard focus-visible:focus-ring focus-visible:outline-offset-4 max-md:px-4 max-md:py-8 max-xs:px-2 max-xs:py-6 [:hover,.drag-active]:border-accent [:hover,.drag-active]:bg-[linear-gradient(135deg,var(--color-surface-elevated)_0%,color-mix(in_srgb,var(--color-accent),transparent_95%)_100%)] [:hover,.drag-active]:shadow-lg [:hover,.drag-active&]:-translate-y-[2px]"
|
||||||
:class="{ 'drag-active': dragover }"
|
:class="{ 'drag-active': dragover }"
|
||||||
@drop.prevent="onDrop"
|
@drop.prevent="onDrop"
|
||||||
@@ -736,7 +737,8 @@ interface IUploadTaskQueueStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useDragEventListeners()
|
const uploadArea = useTemplateRef('uploadArea')
|
||||||
|
useDragEventListeners(uploadArea)
|
||||||
const $router = useRouter()
|
const $router = useRouter()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import { onBeforeUnmount, onMounted } from 'vue'
|
import { onBeforeUnmount, onMounted, type Ref } from 'vue'
|
||||||
|
|
||||||
function disableDrag(e: DragEvent) {
|
export function useDragEventListeners(dropZoneRef: Ref<HTMLElement | null>) {
|
||||||
const dropzone = document.getElementById('upload-area')
|
function disableDrag(e: DragEvent) {
|
||||||
if (dropzone === null || !dropzone.contains(e.target as Node)) {
|
const dropzone = dropZoneRef.value
|
||||||
|
if (!dropzone || !dropzone.contains(e.target as Node)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.dataTransfer!.effectAllowed = 'none'
|
if (e.dataTransfer) {
|
||||||
e.dataTransfer!.dropEffect = 'none'
|
e.dataTransfer.effectAllowed = 'none'
|
||||||
|
e.dataTransfer.dropEffect = 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function useDragEventListeners() {
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('dragenter', disableDrag, false)
|
window.addEventListener('dragenter', disableDrag, false)
|
||||||
window.addEventListener('dragover', disableDrag)
|
window.addEventListener('dragover', disableDrag, false)
|
||||||
window.addEventListener('drop', disableDrag)
|
window.addEventListener('drop', disableDrag, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('dragenter', disableDrag, false)
|
window.removeEventListener('dragenter', disableDrag, false)
|
||||||
window.removeEventListener('dragover', disableDrag)
|
window.removeEventListener('dragover', disableDrag, false)
|
||||||
window.removeEventListener('drop', disableDrag)
|
window.removeEventListener('drop', disableDrag, false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
| .amv | AMV视频文件 | .mpg | MPEG视频文件 |
|
| .amv | AMV视频文件 | .mpg | MPEG视频文件 |
|
||||||
| .avi | AVI视频文件 | .mts | AVCHD视频文件 |
|
| .avi | AVI视频文件 | .mts | AVCHD视频文件 |
|
||||||
| .flac | FLAC音频文件 | .ogg | Ogg Vorbis音频文件 |
|
| .flac | FLAC音频文件 | .ogg | Ogg Vorbis音频文件 |
|
||||||
| .flv | Flash视频文件 | .ogv | Ogg Theora视频文件 |
|
|
||||||
| .m2ts | M2TS视频文件 | .vob | DVD视频文件 |
|
| .m2ts | M2TS视频文件 | .vob | DVD视频文件 |
|
||||||
| .m4a | MPEG-4音频文件 | .wav | WAV音频文件 |
|
| .m4a | MPEG-4音频文件 | .wav | WAV音频文件 |
|
||||||
| .m4v | MPEG-4视频文件 | .webm | WebM视频文件 |
|
| .m4v | MPEG-4视频文件 | .webm | WebM视频文件 |
|
||||||
|
|||||||
Reference in New Issue
Block a user