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