一、回收站
src/views/recycle/Recycle.vue
<template><!-- 上方两个按钮 --><div class="top"><el-button type="success" :disabled="selectFileIdList.length == 0" @click="revertBatch"><span class="iconfont icon-revert"></span> 还原</el-button><el-button type="danger" :disabled="selectFileIdList.length == 0" @click="delBatch"><span class="iconfont icon-del"></span> 批量删除</el-button></div><!-- 已删除的文件列表 --><div class="file-list"><!-- 引入Table组件 --><Table :columns="columns" :showPagination="true" :dataSource="tableData" :fetch="loadDataList":options="tableOptions" @rowSelected="rowSelected"><!-- 文件名 --><template #fileName="{ index, row }"><!-- showOp(row) 当鼠标放在当前行时,分享下载等图标出现 --><!-- cancelShowOp(row) 当鼠标离开当前行时,分享下载等图标消失 --><div class="file-item" @mouseenter="showOp(row)" @mouseleave="cancelShowOp(row)"><!-- 显示文件图标 --><template v-if="(row.fileType == 3 || row.fileType == 1) && row.status !== 0"><!-- 如果文件类型是图片或者视频,且已经成功转码,则执行 Icon中的cover --><Icon :cover="row.fileCover"></Icon></template><template v-else><!-- 如果文件夹类型是文件,则文件类型是该文件类型 --><Icon v-if="row.folderType == 0" :fileType="row.fileType"></Icon><!-- 如果文件夹类型是目录,则文件类型就是目录0 --><Icon v-if="row.folderType == 1" :fileType="0"></Icon></template><!-- 显示文件名称 --><span class="file-name" :title="row.fileName"><span>{{ row.fileName }}</span></span><!-- 当鼠标放在当前行时显示 --><span class="op"><template v-if="row.showOp && row.fileId"><span class="iconfont icon-revert" @click="revert(row)">还原</span><span class="iconfont icon-del" @click="delFile(row)">删除</span></template></span></div></template><!-- 文件大小显示 --><template #fileSize="{ index, row }"><span v-if="row.fileSize">{{ proxy.Utils.size2Str(row.fileSize) }}</span></template></Table></div>
</template><script setup>
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
const { proxy } = getCurrentInstance();const api = {loadDataList: "/recycle/loadRecycleList",delFile: "/recycle/delFile",recoverFile: "/recycle/recoverFile",
};//列表
const columns = [{label: "文件名",prop: "fileName",scopedSlots: "fileName",},{label: "删除时间",prop: "recoveryTime",width: 200,},{label: "大小",prop: "fileSize",scopedSlots: "fileSize",width: 200,},
];// 数据源
const tableData = ref({});
// 表格选项
const tableOptions = {extHeight: 20,selectType: "checkbox",
};// 获得数据;
const loadDataList = async () => {let params = {// 页码pageNo: tableData.value.pageNo,// 分页大小pageSize: tableData.value.pageSize,};if (params.category !== "all") {delete params.filePid;}let result = await proxy.Request({url: api.loadDataList,params,});if (!result) {return;}tableData.value = result.data;
};// 当鼠标放在当前行时,分享下载等图标出现
// 展示操作
const showOp = (row) => {// 关闭所有的显示tableData.value.list.forEach((element) => {element.showOp = false;});// 只开启当前显示row.showOp = true;
};// 取消展示
const cancelShowOp = (row) => {row.showOp = false;
};// 行选中
// 多选 批量选中
const selectFileIdList = ref([]);
const rowSelected = (rows) => {selectFileIdList.value = [];rows.forEach((item) => {selectFileIdList.value.push(item.fileId);});
};const revertIdList = ref([]);
// 还原/恢复(单个)
const revert = (row) => {revertIdList.value = [row.fileId];revertDone();
};// 批量还原/恢复
const revertBatch = () => {if (selectFileIdList.value.length == 0) {return;}revertIdList.value = selectFileIdList.value;revertDone();
};// 还原http请求
const revertDone = async () => {proxy.Confirm(`你确定要还原吗?`, async () => {let result = await proxy.Request({url: api.recoverFile,params: {fileIds: revertIdList.value.join(","),},});if (!result) {return;}proxy.Message.success("还原成功");loadDataList();});
};const emit = defineEmits(["reload"]);
const delIdList = ref([]);
// 删除(单个)
const delFile = (row) => {delIdList.value = [row.fileId];delDone();
};// 批量删除
const delBatch = () => {if (selectFileIdList.value.length == 0) {return;}delIdList.value = selectFileIdList.value;delDone();
};// 删除http请求
const delDone = async () => {proxy.Confirm(`你确定要删除吗?`, async () => {let result = await proxy.Request({url: api.delFile,params: {fileIds: delIdList.value.join(","),},});if (!result) {return;}proxy.Message.success("删除成功");loadDataList();emit("reload");});
};
</script><style lang="scss" scoped>
@import "@/assets/file.list.scss";.file-list {margin-top: 10px;.file-item {.op {width: 120px;}}
}
</style>
二、设置(管理员)
1.用户文件
src/views/admin/Filelist.vue
<template><!-- 设置->用户文件 --><div><!-- 头部搜索框+按钮 --><div class="top"><!-- 头部按钮处 --><div class="top-op"><div class="search-panel"><el-input clearable placeholder="请输入文件名搜索" v-model="fileNameFuzzy" @keyup.enter="search"><template #suffix><i class="iconfont icon-search" @click="search"></i></template></el-input></div><!-- 刷新 --><div class="iconfont icon-refresh" @click="loadDataList"></div><!-- 删除按钮 --><el-button :style="{ 'margin-left': '10px' }" @click="delFileBatch" type="danger":disabled="selectFileIdList.length == 0"><span class="iconfont icon-del"></span> 批量删除</el-button></div><!-- 导航 --><Navigation ref="navigationRef" @navChange="navChange"></Navigation></div><!-- 文件列表 --><div class="file-list" v-if="tableData.list && tableData.list.length > 0"><Table :columns="columns" :showPagination="true" :dataSource="tableData" :fetch="loadDataList":initFetch="false" :options="tableOptions" @rowSelected="rowSelected"><!-- 文件名 --><template #fileName="{ index, row }"><!-- showOp(row) 当鼠标放在当前行时,分享下载等图标出现 --><!-- cancelShowOp(row) 当鼠标离开当前行时,分享下载等图标消失 --><div class="file-item" @mouseenter="showOp(row)" @mouseleave="cancelShowOp(row)"><!-- 显示文件图标 --><template v-if="(row.fileType == 3 || row.fileType == 1) && row.status == 2"><!-- 如果文件类型是图片或者视频,且已经成功转码,则执行 Icon中的cover --><Icon :cover="row.fileCover" :width="32"></Icon></template><template v-else><!-- 如果文件夹类型是文件,则文件类型是该文件类型 --><Icon v-if="row.folderType == 0" :fileType="row.fileType"></Icon><!-- 如果文件夹类型是目录,则文件类型就是目录0 --><Icon v-if="row.folderType == 1" :fileType="0"></Icon></template><!-- 显示文件名称 --><!-- v-if="!row.showEdit" 如果该行文件没有编辑 --><span class="file-name" v-if="!row.showEdit" :title="row.fileName"><span @click="preview(row)">{{ row.fileName }}</span><span v-if="row.status == 0" class="transfer-status">转码中</span><span v-if="row.status == 1" class="transfer-status transfer-fail">转码失败</span></span><!-- 点击新建文件夹时显示行 --><div class="edit-panel" v-if="row.showEdit"><el-input v-model.trim="row.fileNameReal" ref="editNameRef" :maxLength="190"@keyup.enter="saveNameEdit(index)"><template #suffix>{{ row.fileSuffix }}</template></el-input><!-- 对号 确定 --><span :class="['iconfont icon-right1',row.fileNameReal ? '' : 'not-allow',]" @click="saveNameEdit(index)"></span><!-- 叉号 取消 --><span class="iconfont icon-error" @click="cancelNameEdit(index)"></span></div><!-- 当鼠标放在当前行时显示 --><span class="op"><template v-if="row.showOp && row.fileId && row.status == 2"><!-- 只有当是文件夹时才可下载 --><span class="iconfont icon-download" v-if="row.folderType == 0" @click="download(row)">下载</span><span class="iconfont icon-del" @click="delFile(row)">删除</span></template></span></div></template><!-- 文件大小 --><template #fileSize="{ index, row }"><span v-if="row.fileSize">{{ proxy.Utils.size2Str(row.fileSize) }}</span></template></Table></div></div>
</template><script setup>
import { ref, reactive, getCurrentInstance, nextTick, computed } from "vue";
const { proxy } = getCurrentInstance();const api = {loadDataList: "/admin/loadFileList",delFile: "/admin/delFile",createDownloadUrl: "/admin/createDownloadUrl",download: "/api/admin/download",
};// 列表头信息
const columns = [{label: "文件名",prop: "fileName",scopedSlots: "fileName",},{label: "发布人",prop: "nickName",width: 250,},{label: "修改时间",prop: "lastUpdateTime",width: 200,},{label: "文件大小",prop: "fileSize",scopedSlots: "fileSize",width: 200,},
];// 搜索功能
const search = () => {showLoading.value = true;loadDataList();
};// 数据源
const tableData = ref({});
// 表格选项
const tableOptions = {extHeight: 50,selectType: "checkbox",
};
// 文件名
const fileNameFuzzy = ref();const showLoading = ref(true);// 当前文件夹
const currentFolder = ref({ fileId: 0 });// 获得数据;
const loadDataList = async () => {let params = {// 页码pageNo: tableData.value.pageNo,// 分页大小pageSize: tableData.value.pageSize,// 文件名(模糊)fileNameFuzzy: fileNameFuzzy.value,// 文件父idfilePid: currentFolder.value.fileId,};let result = await proxy.Request({url: api.loadDataList,showLoading: showLoading,params,});if (!result) {return;}tableData.value = result.data;
};// 当鼠标放在当前行时,分享下载等图标出现
const showOp = (row) => {// 关闭所有的显示tableData.value.list.forEach((element) => {element.showOp = false;});// 只开启当前显示row.showOp = true;
};const cancelShowOp = (row) => {row.showOp = false;
};// 行选中
// 多选 批量选中
const selectFileIdList = ref([]);
const rowSelected = (rows) => {selectFileIdList.value = [];rows.forEach((item) => {selectFileIdList.value.push(item.userId + "_" + item.fileId);});
};// 删除单个文件
const delFile = (row) => {proxy.Confirm(`你确定要删除【$row.fileName】吗?删除的文件可在 10 天内通过回收站还原`,async () => {let result = await proxy.Request({url: api.delFile,params: {fileIdAndUserIds: row.userId + "_" + row.fileId,},});if (!result) {return;}proxy.Message.success("删除成功");// 重新获取数据loadDataList();});
};// 批量删除文件
const delFileBatch = () => {if (selectFileIdList.value.length == 0) {return;}proxy.Confirm(`你确定要删除这些文件吗?删除的文件可在 10 天内通过回收站还原`,async () => {let result = await proxy.Request({url: api.delFile,params: {fileIdAndUserIds: selectFileIdList.value.join(","),},});if (!result) {return;}proxy.Message.success("删除成功");// 重新获取数据loadDataList();});
};// 预览
const previewRef = ref();
const preview = (data) => {// 如果是目录(文件夹)if (data.folderType == 1) {navigationRef.value.openFolder(data);return;}if (data.status != 2) {proxy.Message.warning("文件未完成转码,无法预览");return;}previewRef.value.showPreview(data, 1);
};// 目录
const navChange = (data) => {const { curFolder } = data;currentFolder.value = curFolder;showLoading.value = true;loadDataList();
};// 下载文件
const download = async (row) => {let result = await proxy.Request({url: api.createDownloadUrl + "/" + row.userId + "/" + row.fileId,});if (!result) {return;}window.location.href = api.download + "/" + result.data;
};
</script><style lang="scss" scoped>
@import "@/assets/file.list.scss";.search-panel {margin-left: 0px !important;
}.file-list {margin-top: 10px;.file-item {.op {width: 120px;}}
}
</style>
2.用户管理
src/views/admin/UserList.vue
<template><!-- 设置->用户管理 --><div class="top-panel"><el-form :model="searchFormData" label-width="80px" @submit.prevent><el-row><!-- 用户昵称+搜索框 --><el-col :span="4"><el-form-item label="用户昵称"><el-input clearable placeholder="支持模糊搜索" v-model.trim="searchFormData.nickNameFuzzy"@keyup.enter="loadDataList"></el-input></el-form-item></el-col><!-- 状态选择:启用禁用 --><el-col :span="4"><!-- 下拉框 --><el-form-item label="状态"><el-select clearable placeholder="请选择状态" v-model="searchFormData.status"><el-option :value="1" label="启用"></el-option><el-option :value="0" label="禁用"></el-option></el-select></el-form-item></el-col><!-- 查询按钮 --><el-col :span="4" :style="{ 'padding-left': '10px' }"><el-button type="primary" @click="loadDataList"> 查询 </el-button></el-col></el-row></el-form></div><div class="file-list"><Table :columns="columns" :showPagination="true" :dataSource="tableData" :fetch="loadDataList":options="tableOptions"><!-- 头像+昵称 --><template #avatar="{ index, row }"><div class="avatar"><Avatar :userId="row.userId" :avatar="row.qqAvatar"></Avatar></div></template><!-- 空间使用 --><template #space="{ index, row }">{{ proxy.Utils.size2Str(row.useSpace) }}/{{proxy.Utils.size2Str(row.totalSpace)}}</template><!-- 状态显示:启用禁用 --><template #status="{ index, row }"><span v-if="row.status == 1" style="color: #529b2e">启用</span><span v-if="row.status == 0" style="color: #f56c6c">禁用</span></template><!-- 操作:分配空间+启用禁用选择 --><template #op="{ index, row }"><span class="a-link" @click="updateSpace(row)">分配空间</span><el-divider direction="vertical" /><span class="a-link" @click="updateUserStatus(row)">{{ row.status == 0 ? "启用" : "禁用" }}</span></template></Table></div><!-- 点击分配空间的弹框 --><Dialog :show="dialogConfig.show" :title="dialogConfig.title" :buttons="dialogConfig.buttons" width="500px":showCancel="false" @close="dialogConfig.show = false"><el-form :model="formData" :rules="rules" ref="formDataRef" label-width="80px" @submit.prevent><el-form-item label="昵称">{{ formData.nickName }}</el-form-item><el-form-item label="空间大小" prop="changeSpace"><el-input clearable placeholder="请输入空间大小" v-model="formData.changeSpace"><template #suffix>MB</template></el-input></el-form-item></el-form></Dialog>
</template><script setup>
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();const api = {loadDataList: "/admin/loadUserList",updateUserStatus: "/admin/updateUserStatus",updateUserSpace: "/admin/updateUserSpace",
};//列表
const columns = [{label: "头像",prop: "avatar",width: 80,scopedSlots: "avatar",},{label: "昵称",prop: "nickName",},{label: "邮箱",prop: "email",},{label: "空间使用",prop: "space",scopedSlots: "space",},{label: "加入时间",prop: "joinTime",},{label: "最后登录时间",prop: "lastLoginTime",},{label: "状态",prop: "status",scopedSlots: "status",width: 80,},{label: "操作",prop: "op",width: 150,scopedSlots: "op",},
];const searchFormData = ref({});
// 数据源
const tableData = ref({});
// 表格选项
const tableOptions = {extHeight: 20,
};// 获得数据;
const loadDataList = async () => {let params = {// 页码pageNo: tableData.value.pageNo,// 分页大小pageSize: tableData.value.pageSize,};Object.assign(params, searchFormData.value);let result = await proxy.Request({url: api.loadDataList,params,});if (!result) {return;}tableData.value = result.data;
};// 修改状态
const updateUserStatus = (row) => {proxy.Confirm(`你确定要【${row.status == 0 ? "启用" : "禁用"}】吗?`,async () => {let result = await proxy.Request({url: api.updateUserStatus,params: {userId: row.userId,status: row.status == 0 ? 1 : 0,},});if (!result) {return;}loadDataList();});
};// 分配空间:修改空间大小
const dialogConfig = ref({show: false,title: "修改空间大小",buttons: [{type: "primary",text: "确定",click: (e) => {submitForm();},},],
});
const formData = ref({});
const formDataRef = ref();
// 表单校验
const rules = {changeSpace: [{ required: true, message: "请输入空间大小" }],
};// 更新空间
const updateSpace = (data) => {dialogConfig.value.show = true;nextTick(() => {formDataRef.value.resetFields();formData.value = Object.assign({}, data);});
};// 提交表单,刷新列表
const submitForm = () => {formDataRef.value.validate(async (valid) => {if (!valid) {return;}let params = {};Object.assign(params, formData.value);let result = await proxy.Request({url: api.updateUserSpace,params: params,});if (!result) {return;}dialogConfig.value.show = false;proxy.Message.success("操作成功");loadDataList();});
};</script><style lang="scss" scoped>
.top-panel {margin-top: 10px;
}.avatar {width: 50px;height: 50px;border-radius: 25px;overflow: hidden;img {width: 100%;height: 100;}
}
</style>
3.系统设置
src/views/admin/SysSetting.vue
<template><!-- 设置->系统设置 --><div class="sys-setting-panel"><el-form :model="formData" :rules="rules" ref="formDataRef" label-width="150px" @submit.prevent><el-form-item label="注册邮件标题" prop="registerEmailTitle"><el-input clearable placeholder="邮箱验证码" v-model.trim="formData.registerEmailTitle"></el-input></el-form-item><el-form-item label="注册邮件内容" prop="registerEmailContent"><el-input clearable placeholder="请输入注册邮箱验证码邮箱内容%s占位符位验证码内容"v-model.trim="formData.registerEmailContent"></el-input></el-form-item><el-form-item label="初始空间大小" prop="userInitUseSpace"><el-input clearable placeholder="初始空间大小" v-model.trim="formData.userInitUseSpace"></el-input></el-form-item><el-form-item><el-button type="primary" @click="saveSettings">保存</el-button></el-form-item></el-form></div>
</template><script setup>
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
import { useRouter, useRoute } from "vue-router";
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();const api = {getSysSettings: "/admin/getSysSettings",saveSettings: "/admin/saveSysSettings",
};const formData = ref({});
const formDataRef = ref();
const rules = {registerEmailTitle: [{ required: true, message: "请输入注册邮件验证码邮件标题" },],registerEmailContent: [{ required: true, message: "请输入注册邮件验证码邮件内容" },],userInitUseSpace: [{ required: true, message: "请输入初始化空间大小" },{ validator: proxy.Verify.number, message: "空间大小只能是数字" },],
};const getSysSettings = async () => {let result = await proxy.Request({url: api.getSysSettings,});if (!result) {return;}formData.value = result.data;
};
getSysSettings();const saveSettings = () => {formDataRef.value.validate(async (valid) => {if (!valid) {return;}let params = {};Object.assign(params, formData.value);let result = await proxy.Request({url: api.saveSettings,params: params,});if (!result) {return;}proxy.Message.success("保存成功");});
};
</script><style lang="scss" scoped>
.sys-setting-panel {margin-top: 20px;width: 600px;
}
</style>
源码:GitHub - yeguouu/easypanuu: 实现网盘的登录注册,文件的批量上传、新建、在线预览、下载、链接分享、移动以及删除还原等文件管理功能