系统管理基本搞定

This commit is contained in:
李志强 2026-04-01 15:51:34 +08:00
parent e927026495
commit f627414c36

View File

@ -4,7 +4,9 @@
<template #header>
<div class="card-header">
<span class="title">
<el-icon><Folder /></el-icon>
<el-icon>
<Folder />
</el-icon>
文件管理系统
</span>
<el-button :icon="Refresh" @click="refushData"> 刷新 </el-button>
@ -18,40 +20,29 @@
<template #header>
<div class="card-title">
<div style="display: flex; align-items: center; gap: 8px">
<el-icon><Grid /></el-icon>
<el-icon>
<Grid />
</el-icon>
<span>文件分组</span>
</div>
<el-button
type="primary"
size="small"
@click="handleCreateCategory"
>新建分组</el-button
>
<el-button type="primary" size="small" @click="handleCreateCategory">新建分组</el-button>
</div>
</template>
<el-input
v-model="groupSearchQuery"
placeholder="搜索分组..."
:prefix-icon="Search"
clearable
class="search-input"
/>
<el-input v-model="groupSearchQuery" placeholder="搜索分组..." :prefix-icon="Search" clearable
class="search-input" />
<el-scrollbar class="group-list" v-loading="loading">
<div
v-for="group in filteredGroups"
:key="group.id"
:class="[
'group-item',
{ active: selectedGroup?.id === group.id },
]"
@click="selectGroup(group)"
>
<div v-for="group in filteredGroups" :key="group.id" :class="[
'group-item',
{ active: selectedGroup?.id === group.id },
]" @click="selectGroup(group)">
<div class="group-info">
<div class="group-name">
<div style="display: flex; align-items: center; gap: 8px">
<el-icon class="group-icon"><FolderOpened /></el-icon>
<el-icon class="group-icon">
<FolderOpened />
</el-icon>
{{ group.name }}
</div>
</div>
@ -59,47 +50,35 @@
</div>
<div class="group-actions" v-if="group.id !== 0">
<div class="action-buttons" @click.stop>
<el-button
type="primary"
link
size="small"
:icon="Edit"
@click="handleRenameCategory(group)"
title="重命名"
/>
<el-button
type="danger"
link
size="small"
:icon="Delete"
@click="deleteFileCateData(group.id)"
title="删除"
/>
<el-button type="primary" link size="small" :icon="Edit" @click="handleRenameCategory(group)"
title="重命名" />
<el-button type="danger" link size="small" :icon="Delete" @click="deleteFileCateData(group.id)"
title="删除" />
</div>
</div>
</div>
<el-empty
v-if="filteredGroups.length === 0"
description="暂无分组数据"
/>
<el-empty v-if="filteredGroups.length === 0" description="暂无分组数据" />
</el-scrollbar>
</el-card>
</el-col>
<!-- 右侧文件内容 -->
<el-col :span="18">
<el-card
style="min-height: 600px"
shadow="hover"
class="file-content-card"
>
<el-card style="min-height: 600px" shadow="hover" class="file-content-card">
<template #header>
<div class="card-title">
<div style="display: flex; align-items: center; gap: 8px">
<el-icon><Document /></el-icon>
<el-icon>
<Document />
</el-icon>
<span>文件列表</span>
</div>
<el-button type="primary" :icon="Upload" @click="handleUpload">
上传文件
</el-button>
</div>
</template>
@ -110,56 +89,35 @@
<div v-else class="file-content">
<!-- 工具栏 -->
<div class="toolbar">
<el-input
v-model="fileSearchQuery"
placeholder="搜索文件..."
:prefix-icon="Search"
clearable
style="width: 300px"
@clear="handleFileSearch"
@keyup.enter="handleFileSearch"
/>
<el-input v-model="fileSearchQuery" placeholder="搜索文件..." :prefix-icon="Search" clearable
style="width: 300px" @clear="handleFileSearch" @keyup.enter="handleFileSearch" />
<div class="actions">
<el-button
type="primary"
:icon="Upload"
@click="handleUpload"
>
上传文件
</el-button>
<el-button
type="warning"
:icon="FolderOpened"
@click="handleBatchMove"
:disabled="selectedFileIds.length === 0"
>
<el-link type="warning" :underline="false" @click="handleBatchMove"
:disabled="selectedFileIds.length === 0">
<el-icon :size="16">
<FolderOpened />
</el-icon>
批量移动 ({{ selectedFileIds.length }})
</el-button>
<el-button
type="danger"
:icon="Delete"
@click="handleBatchDelete"
:disabled="selectedFileIds.length === 0"
>
</el-link>
<el-link type="danger" :underline="false" @click="handleBatchDelete"
:disabled="selectedFileIds.length === 0">
<el-icon :size="16">
<Delete />
</el-icon>
批量删除 ({{ selectedFileIds.length }})
</el-button>
<el-button
type="danger"
plain
@click="handleBatchDeletePermanently"
:disabled="selectedFileIds.length === 0"
>
<el-icon><DeleteFilled /></el-icon>
</el-link>
<el-link type="danger" :underline="false" @click="handleBatchDeletePermanently"
:disabled="selectedFileIds.length === 0">
<el-icon :size="16">
<DeleteFilled />
</el-icon>
批量永久删除 ({{ selectedFileIds.length }})
</el-button>
<el-button
type="info"
@click="clearSelection"
:disabled="selectedFileIds.length === 0"
>
</el-link>
<el-link type="info" :underline="false" @click="clearSelection"
:disabled="selectedFileIds.length === 0">
取消选择
</el-button>
</el-link>
</div>
</div>
@ -168,28 +126,16 @@
<div class="file-grid-header" v-if="files.length > 0">
<button class="allbtns" @click="toggleSelectAll">全选/反选</button>
</div>
<div
v-loading="loading"
class="file-grid"
v-if="files.length > 0"
>
<el-card
v-for="file in files"
:key="file.id"
shadow="hover"
<div v-loading="loading" class="file-grid" v-if="files.length > 0">
<el-card v-for="file in files" :key="file.id" shadow="hover"
:class="['file-card', { 'file-card-selected': selectedFileIds.includes(file.id) }]"
@click="toggleFileSelection(file)"
>
@click="toggleFileSelection(file)">
<div class="file-checkbox">
<el-checkbox :model-value="selectedFileIds.includes(file.id)" />
</div>
<div class="file-icon">
<div v-if="isImage(file)">
<img
:src="getFileUrl(file.url)"
alt="file"
class="preview-image"
/>
<img :src="getFileUrl(file.url)" alt="file" class="preview-image" />
</div>
<div v-else-if="isVideo(file)">
<video :src="getFileUrl(file.url)" alt="file" />
@ -214,67 +160,33 @@
}}</span>
</div>
<div class="file-actions">
<el-button
v-if="isImage(file)"
type="primary"
link
@click.stop="handleImagePreview(file)"
title="预览"
>
<el-button v-if="isImage(file)" type="primary" link @click.stop="handleImagePreview(file)"
title="预览">
<i class="fa-solid fa-eye"></i>
</el-button>
<el-button
type="primary"
link
@click.stop="handleMoveClick(file)"
title="移动"
>
<el-button type="primary" link @click.stop="handleMoveClick(file)" title="移动">
<i class="fa-solid fa-arrows-up-down-left-right"></i>
</el-button>
<el-button
type="primary"
link
@click.stop="handleDownload(file)"
title="下载"
>
<el-button type="primary" link @click.stop="handleDownload(file)" title="下载">
<i class="fa-solid fa-download"></i>
</el-button>
<el-button
type="danger"
link
@click.stop="handleDelete(file)"
title="删除"
>
<el-button type="danger" link @click.stop="handleDelete(file)" title="删除">
<i class="fa-regular fa-trash-can"></i>
</el-button>
<el-button
type="danger"
link
@click.stop="handlePermanentDelete(file)"
title="彻底删除"
>
<el-button type="danger" link @click.stop="handlePermanentDelete(file)" title="彻底删除">
<i class="fa-solid fa-trash"></i>
</el-button>
</div>
</el-card>
</div>
<el-empty
v-if="!loading && files.length === 0"
description="该分组暂无文件"
/>
<el-empty v-if="!loading && files.length === 0" description="该分组暂无文件" />
</div>
<!-- 分页组件 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[12, 24, 48, 96]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize"
:page-sizes="[12, 24, 48, 96]" :total="total" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange" @current-change="handlePageChange" />
</div>
</div>
</el-card>
@ -283,114 +195,46 @@
</el-card>
<!-- 重命名分组对话框 -->
<el-dialog
v-model="showRenameDialog"
title="重命名分组"
width="400px"
@close="handleRenameDialogClose"
>
<el-form
ref="renameFormRef"
:model="renameForm"
:rules="renameFormRules"
label-width="80px"
>
<el-dialog v-model="showRenameDialog" title="重命名分组" width="400px" @close="handleRenameDialogClose">
<el-form ref="renameFormRef" :model="renameForm" :rules="renameFormRules" label-width="80px">
<el-form-item label="分组名称" prop="name">
<el-input
v-model="renameForm.name"
placeholder="请输入分组名称"
maxlength="20"
show-word-limit
/>
<el-input v-model="renameForm.name" placeholder="请输入分组名称" maxlength="20" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showRenameDialog = false">取消</el-button>
<el-button
type="primary"
@click="handleRenameConfirm"
:loading="renaming"
>
<el-button type="primary" @click="handleRenameConfirm" :loading="renaming">
确定
</el-button>
</template>
</el-dialog>
<!-- 上传文件对话框 -->
<UploadFile
v-model="showUploadDialog"
:category-id="selectedGroup?.id"
@success="handleUploadSuccess"
/>
<UploadFile v-model="showUploadDialog" :category-id="selectedGroup?.id" @success="handleUploadSuccess" />
<!-- 图片预览对话框 -->
<el-dialog
v-model="showImagePreview"
title="图片预览"
width="90%"
:before-close="handlePreviewClose"
class="image-preview-dialog"
@opened="handlePreviewOpened"
>
<el-dialog v-model="showImagePreview" title="图片预览" width="90%" :before-close="handlePreviewClose"
class="image-preview-dialog" @opened="handlePreviewOpened">
<template #header>
<div class="preview-header">
<span>图片预览</span>
<div class="preview-toolbar">
<el-button
type="primary"
:icon="ZoomOut"
circle
size="small"
@click="zoomOut"
:disabled="imageScale <= 0.5"
/>
<el-button type="primary" :icon="ZoomOut" circle size="small" @click="zoomOut"
:disabled="imageScale <= 0.5" />
<span class="zoom-text">{{ Math.round(imageScale * 100) }}%</span>
<el-button
type="primary"
:icon="ZoomIn"
circle
size="small"
@click="zoomIn"
:disabled="imageScale >= 3"
/>
<el-button
type="primary"
:icon="RefreshLeft"
circle
size="small"
@click="resetZoom"
/>
<el-button
type="primary"
:icon="FullScreen"
circle
size="small"
@click="toggleFullscreen"
/>
<el-button type="primary" :icon="ZoomIn" circle size="small" @click="zoomIn" :disabled="imageScale >= 3" />
<el-button type="primary" :icon="RefreshLeft" circle size="small" @click="resetZoom" />
<el-button type="primary" :icon="FullScreen" circle size="small" @click="toggleFullscreen" />
</div>
</div>
</template>
<div
class="preview-container"
ref="previewContainerRef"
@wheel="handleWheel"
>
<div
class="preview-image-wrapper"
:style="{
transform: `scale(${imageScale}) translate(${imagePosition.x}px, ${imagePosition.y}px)`,
transformOrigin: 'center center',
cursor: imageScale > 1 ? 'move' : 'default',
}"
@mousedown="handleMouseDown"
>
<img
:src="previewImageUrl"
alt="预览图片"
class="preview-image-full"
draggable="false"
@load="handleImageLoad"
/>
<div class="preview-container" ref="previewContainerRef" @wheel="handleWheel">
<div class="preview-image-wrapper" :style="{
transform: `scale(${imageScale}) translate(${imagePosition.x}px, ${imagePosition.y}px)`,
transformOrigin: 'center center',
cursor: imageScale > 1 ? 'move' : 'default',
}" @mousedown="handleMouseDown">
<img :src="previewImageUrl" alt="预览图片" class="preview-image-full" draggable="false" @load="handleImageLoad" />
</div>
<div class="preview-info" v-if="previewFile">
<div class="preview-name">{{ previewFile.name }}</div>
@ -400,48 +244,25 @@
</el-dialog>
<!-- 移动文件对话框 -->
<MoveFile
v-model="showMoveDialog"
:file-id="selectedFile?.id"
:current-cate-id="selectedFile?.groupId"
:cate-list="groups"
@moved="handleMoveSuccess"
/>
<MoveFile v-model="showMoveDialog" :file-id="selectedFile?.id" :current-cate-id="selectedFile?.groupId"
:cate-list="groups" @moved="handleMoveSuccess" />
<!-- 重命名文件分组对话框 -->
<RenameCategory
ref="renameCategoryRef"
v-model="showRenameCategoryDialog"
:category-id="currentRenameCategoryId"
:category-name="currentRenameCategoryName"
@success="handleRenameCategorySuccess"
@close="handleRenameCategoryClose"
/>
<RenameCategory ref="renameCategoryRef" v-model="showRenameCategoryDialog" :category-id="currentRenameCategoryId"
:category-name="currentRenameCategoryName" @success="handleRenameCategorySuccess"
@close="handleRenameCategoryClose" />
<!-- 创建文件分组对话框 -->
<CreateCategory
ref="createCategoryRef"
v-model="showCreateCategoryDialog"
@success="handleCreateCategorySuccess"
@close="handleCreateCategoryClose"
/>
<CreateCategory ref="createCategoryRef" v-model="showCreateCategoryDialog" @success="handleCreateCategorySuccess"
@close="handleCreateCategoryClose" />
<!-- 批量移动文件对话框 -->
<el-dialog
v-model="showBatchMoveDialog"
title="批量移动文件"
width="400px"
>
<el-dialog v-model="showBatchMoveDialog" title="批量移动文件" width="400px">
<el-form label-width="80px">
<el-form-item label="目标分组">
<el-select v-model="batchMoveTargetCate" placeholder="请选择目标分组">
<el-option :value="0" label="未分类" />
<el-option
v-for="group in groups"
:key="group.id"
:value="group.id"
:label="group.name"
/>
<el-option v-for="group in groups" :key="group.id" :value="group.id" :label="group.name" />
</el-select>
</el-form-item>
</el-form>
@ -1100,7 +921,7 @@ const toggleFileSelection = (file: FileItem) => {
const toggleSelectAll = () => {
const allFileIds = files.value.map(file => file.id);
const isAllSelected = allFileIds.length === selectedFileIds.value.length && allFileIds.length > 0;
if (isAllSelected) {
selectedFileIds.value = [];
} else {
@ -1133,7 +954,7 @@ const confirmBatchMove = async () => {
ElMessage.warning('请选择目标分组');
return;
}
batchMoveLoading.value = true;
try {
const res = await batchMoveFiles(selectedFileIds.value, batchMoveTargetCate.value);
@ -1166,7 +987,7 @@ const handleBatchDelete = () => {
ElMessage.warning('请先选择要删除的文件');
return;
}
ElMessageBox.confirm(
`确定要删除选中的 ${selectedFileIds.value.length} 个文件吗?`,
'提示',
@ -1205,7 +1026,7 @@ const handleBatchDeletePermanently = () => {
ElMessage.warning('请先选择要永久删除的文件');
return;
}
ElMessageBox.confirm(
`确定要永久删除选中的 ${selectedFileIds.value.length} 个文件吗?此操作不可恢复,服务器上的文件也会被删除!`,
'危险操作',
@ -1375,11 +1196,11 @@ onMounted(() => {
display: flex;
justify-content: flex-start;
.allbtns{
color:#0081ff;
.allbtns {
color: #0081ff;
&:hover{
color:#0088ff;
&:hover {
color: #0088ff;
}
}
}