1285 lines
32 KiB
Vue
1285 lines
32 KiB
Vue
<script setup>
|
||
import {
|
||
ArrowDown,
|
||
Back,
|
||
DArrowRight,
|
||
Delete,
|
||
Download,
|
||
HelpFilled,
|
||
More,
|
||
Refresh,
|
||
RefreshLeft,
|
||
RefreshRight,
|
||
Right,
|
||
UploadFilled,
|
||
ZoomIn,
|
||
ZoomOut,
|
||
} from "@element-plus/icons-vue";
|
||
import dayjs from "dayjs";
|
||
import {
|
||
ElMessage,
|
||
ElMessageBox,
|
||
ElTree,
|
||
ElButton,
|
||
ElCheckbox,
|
||
ElIcon,
|
||
ElDropdown,
|
||
ElDropdownMenu,
|
||
ElDropdownItem,
|
||
ElImageViewer,
|
||
ElTooltip,
|
||
ElImage,
|
||
ElInput,
|
||
ElDialog,
|
||
} from "element-plus";
|
||
import { computed, nextTick, ref, watch } from "vue";
|
||
import { useUserStore } from "@/store/user";
|
||
import axios from "@/utils/axios";
|
||
import { compareAndClassifyData, diffData } from "@/utils/dataDiffUtils";
|
||
import {
|
||
compressImage,
|
||
deleteFileApi,
|
||
downloadZip,
|
||
formatFileSize,
|
||
getAliyunFileBlob,
|
||
getFileExtension,
|
||
uploadFile,
|
||
} from "@/utils/file";
|
||
import { loaddataApi } from "@/utils/doSql";
|
||
import G3Icon from "@/components/g3-icon/index.vue";
|
||
import { VxeTable, VxeColumn } from "vxe-table";
|
||
|
||
const props = defineProps({
|
||
father: {
|
||
type: String,
|
||
required: true,
|
||
},
|
||
moduleid: {
|
||
type: String,
|
||
required: true,
|
||
},
|
||
cateid: {
|
||
type: String,
|
||
required: false,
|
||
},
|
||
searchcondition: {
|
||
type: String,
|
||
required: false,
|
||
},
|
||
readonly: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
categories: {
|
||
type: Array,
|
||
default: () => [],
|
||
},
|
||
});
|
||
const treeRef = ref(null);
|
||
const tableRef = ref(null);
|
||
|
||
const displayMode = ref(0);
|
||
|
||
const userStore = useUserStore();
|
||
|
||
// 根据 moduleid 查询主表状态,自动判断是否可编辑
|
||
const editable = ref(true);
|
||
const isReadonly = computed(() => props.readonly || !editable.value);
|
||
|
||
async function loadEditableStatus() {
|
||
try {
|
||
const modules = await loaddataApi("s_module", `b_id = '${props.moduleid}'`);
|
||
if (!modules?.length) {
|
||
editable.value = true;
|
||
return;
|
||
}
|
||
const saveTable = modules[0].b_SaveTable;
|
||
if (!saveTable) {
|
||
editable.value = true;
|
||
return;
|
||
}
|
||
const records = await loaddataApi(saveTable, `b_id = '${props.father}'`);
|
||
if (!records?.length) {
|
||
editable.value = true;
|
||
return;
|
||
}
|
||
const state = records[0].b_state;
|
||
editable.value = ["草拟", "审核驳回"].includes(state) || !state;
|
||
} catch {
|
||
editable.value = true;
|
||
}
|
||
}
|
||
|
||
const containerRef = ref(null);
|
||
const fileInputRef = ref(null);
|
||
|
||
const cateid = ref(props.cateid || "");
|
||
const categories = ref([
|
||
{
|
||
label: "全部分类",
|
||
cateid: "",
|
||
children: [],
|
||
},
|
||
]);
|
||
|
||
// 全选
|
||
const selectAll = ref(false);
|
||
const indeterminate = ref(false);
|
||
|
||
// 文件列表
|
||
const oldFileList = ref([]);
|
||
const fileList = ref([]);
|
||
|
||
// 未上传文件列表
|
||
const pendingFileList = computed(() => {
|
||
return fileList.value.filter((item) => item._status === "pending");
|
||
});
|
||
|
||
// 预览
|
||
const showPreview = ref(false);
|
||
const preViewImageIndex = ref(0);
|
||
const previewImageList = computed(() => {
|
||
return fileList.value
|
||
.filter((item) => isImage(item.mx_fileext))
|
||
.map((item) => item.mx_mapfilename);
|
||
});
|
||
|
||
const checkedFiles = computed(() => {
|
||
return fileList.value.filter((file) => file._checked === true); // 过滤出选中的文件
|
||
});
|
||
|
||
// 加载
|
||
const loading = ref(false);
|
||
const loadingTip = ref("正在加载中 . . .");
|
||
|
||
// 修改文件信息的dialog
|
||
const showUpdateDialog = ref(false);
|
||
const updateFile = ref(null);
|
||
const updateLoading = ref(false);
|
||
|
||
function handleUpdateDialogClosed() {
|
||
updateFile.value = null;
|
||
showUpdateDialog.value = false;
|
||
}
|
||
|
||
function handleShowUpdateDialog(file) {
|
||
updateFile.value = JSON.parse(JSON.stringify(file));
|
||
showUpdateDialog.value = true;
|
||
}
|
||
|
||
async function handleConfirmUpdate() {
|
||
if (!updateFile.value) {
|
||
return;
|
||
}
|
||
|
||
if (updateFile.value.mx_filename === "") {
|
||
ElMessage.warning("文件名不能为空");
|
||
return;
|
||
}
|
||
|
||
// 如果文件正在上传(pending状态),直接更新
|
||
if (updateFile.value._status === "pending") {
|
||
updateFileName(updateFile.value);
|
||
showUpdateDialog.value = false;
|
||
return;
|
||
}
|
||
|
||
updateLoading.value = true;
|
||
|
||
const requestData = [
|
||
{
|
||
tableName: "bf_files",
|
||
keyField: "subid",
|
||
inserts: [],
|
||
updates: [
|
||
{
|
||
subid: updateFile.value.subid,
|
||
mx_filename: updateFile.value.mx_filename,
|
||
mx_bz: updateFile.value.mx_bz,
|
||
},
|
||
],
|
||
deletes: [],
|
||
},
|
||
];
|
||
|
||
try {
|
||
const response = await axios.post("/saveobjectV2", requestData, {
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
|
||
if (response.data.success) {
|
||
ElMessage.success("文件名更新成功");
|
||
updateFileName(updateFile.value);
|
||
|
||
showUpdateDialog.value = false;
|
||
} else {
|
||
ElMessage.error("文件更新失败");
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error("更新文件时发生错误");
|
||
} finally {
|
||
updateLoading.value = false;
|
||
}
|
||
}
|
||
|
||
// 更新文件列表中的文件名
|
||
function updateFileName(file) {
|
||
const updatedFile = fileList.value.find((item) => item.subid === file.subid);
|
||
if (updatedFile) {
|
||
updatedFile.mx_filename = file.mx_filename;
|
||
updatedFile.mx_updateuser_id = userStore.userInfo.userid;
|
||
updatedFile.mx_updatedatetime = "";
|
||
}
|
||
}
|
||
|
||
// 监听 fileList 中每个文件的选中状态,来决定全选框的状态
|
||
watch(
|
||
() => checkedFiles.value,
|
||
(newValues) => {
|
||
const selectedCount = newValues.filter((v) => v).length;
|
||
if (selectedCount === 0) {
|
||
selectAll.value = false;
|
||
indeterminate.value = false;
|
||
} else if (selectedCount === fileList.value.length) {
|
||
selectAll.value = true;
|
||
indeterminate.value = false;
|
||
} else {
|
||
selectAll.value = false;
|
||
indeterminate.value = true;
|
||
}
|
||
},
|
||
{ deep: true },
|
||
);
|
||
|
||
watch(
|
||
() => props.categories,
|
||
(newCategories) => {
|
||
if (newCategories && newCategories.length > 0) {
|
||
categories.value[0].children.push(...newCategories);
|
||
}
|
||
},
|
||
{ immediate: true },
|
||
);
|
||
|
||
function onOpened() {
|
||
loadFiles();
|
||
loadEditableStatus();
|
||
}
|
||
|
||
async function loadFiles() {
|
||
fileList.value = [];
|
||
oldFileList.value = [];
|
||
|
||
loading.value = true;
|
||
loadingTip.value = "正在加载中 . . .";
|
||
|
||
let searchcondition = `father = '${props.father}' AND mx_moduleid = '${props.moduleid}'`;
|
||
|
||
if (cateid.value) {
|
||
searchcondition += ` AND mx_cate_id = '${cateid.value}'`;
|
||
}
|
||
|
||
if (props.searchcondition) {
|
||
searchcondition += ` AND ${props.searchcondition}`;
|
||
}
|
||
|
||
try {
|
||
const res = await axios.get("/loadData", {
|
||
params: {
|
||
viewName: "bf_files",
|
||
searchcondition,
|
||
orderField: "mx_xh",
|
||
},
|
||
});
|
||
|
||
if (res.data.success) {
|
||
fileList.value = res.data.data;
|
||
oldFileList.value = JSON.parse(JSON.stringify(res.data.data));
|
||
loading.value = false;
|
||
} else {
|
||
ElMessage.error(`加载附件失败:${res.data.message}`);
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error(`加载附件失败:${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 切换全选状态
|
||
function toggleSelectAll() {
|
||
fileList.value.forEach((file) => {
|
||
file._checked = selectAll.value;
|
||
});
|
||
}
|
||
|
||
// 格式化
|
||
function formatTime(date) {
|
||
const validDate = dayjs(date);
|
||
if (!validDate.isValid()) {
|
||
return date;
|
||
}
|
||
return validDate.format("YYYY-MM-DD HH:mm");
|
||
}
|
||
// 手动上传
|
||
function handleClickUpload() {
|
||
if (isReadonly.value) {
|
||
return;
|
||
}
|
||
fileInputRef.value.click();
|
||
}
|
||
|
||
function handleFileInputChange(event) {
|
||
const files = event.target.files; // 获取选择的文件
|
||
if (files.length > 0) {
|
||
uploadLocalFiles(files);
|
||
event.target.value = "";
|
||
}
|
||
}
|
||
|
||
function handlePaste(e) {
|
||
if (isReadonly.value) {
|
||
return;
|
||
}
|
||
|
||
// 获取剪贴板中的文件
|
||
const files = e.clipboardData.files;
|
||
|
||
// 过滤掉文件夹,只处理文件
|
||
const validFiles = Array.from(files).filter((file) => file.type);
|
||
|
||
if (validFiles.length === 0) {
|
||
return;
|
||
}
|
||
|
||
// 只处理有效的文件
|
||
uploadLocalFiles(validFiles);
|
||
}
|
||
|
||
// 上传
|
||
function handleDragOver(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
|
||
// 处理拖拽放下
|
||
function handleDrop(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
if (isReadonly.value) {
|
||
return;
|
||
}
|
||
const files = e.dataTransfer.files;
|
||
|
||
// 过滤掉文件夹,只处理文件
|
||
const validFiles = Array.from(files).filter((file) => file.type);
|
||
|
||
if (validFiles.length === 0) {
|
||
return;
|
||
}
|
||
|
||
uploadLocalFiles(validFiles);
|
||
}
|
||
|
||
// 本地上传方法
|
||
async function uploadLocalFiles(files) {
|
||
if (files.length > 0) {
|
||
const currentLength = fileList.value.length;
|
||
|
||
Array.from(files).forEach((file, index) => {
|
||
const fileInfo = {
|
||
mx_filename: file.name,
|
||
mx_filesize: file.size,
|
||
mx_fileext: getFileExtension(file.name),
|
||
mx_inputuser_id: userStore.userInfo.userid,
|
||
mx_inputdatetime: "",
|
||
mx_mapfilename: "",
|
||
mx_xh: currentLength + index + 1,
|
||
_content: null,
|
||
_status: "pending",
|
||
};
|
||
|
||
const reader = new FileReader();
|
||
|
||
reader.onload = async (event) => {
|
||
const blob = new Blob([event.target.result], { type: file.type });
|
||
|
||
if (file.type.startsWith("image/")) {
|
||
try {
|
||
const tempUrl = URL.createObjectURL(blob);
|
||
|
||
if (file.size > 512000) {
|
||
const { url, size, blob: compressedBlob } = await compressImage(tempUrl);
|
||
|
||
fileInfo.mx_mapfilename = url;
|
||
fileInfo.mx_filesize = size;
|
||
fileInfo._content = compressedBlob;
|
||
} else {
|
||
fileInfo.mx_mapfilename = tempUrl;
|
||
fileInfo._content = blob;
|
||
}
|
||
} catch (err) {
|
||
console.error("图片压缩失败:", err);
|
||
fileInfo.mx_mapfilename = URL.createObjectURL(blob);
|
||
}
|
||
} else {
|
||
fileInfo._content = blob;
|
||
}
|
||
|
||
fileList.value.push(fileInfo);
|
||
};
|
||
|
||
reader.readAsArrayBuffer(file);
|
||
});
|
||
|
||
nextTick(() => {
|
||
setTimeout(() => {
|
||
if (displayMode.value === 0) {
|
||
if (containerRef.value) {
|
||
containerRef.value.scrollTop = containerRef.value.scrollHeight;
|
||
}
|
||
} else {
|
||
if (tableRef.value) {
|
||
tableRef.value.scrollToEndRow();
|
||
}
|
||
}
|
||
}, 50);
|
||
});
|
||
}
|
||
}
|
||
|
||
function isImage(fileExtension) {
|
||
const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"];
|
||
|
||
return imageTypes.includes(fileExtension.toLowerCase());
|
||
}
|
||
|
||
function isExcel(fileExtension) {
|
||
if (!fileExtension) return false;
|
||
|
||
// 去掉前后的空格并转小写
|
||
const ext = fileExtension.trim().toLowerCase();
|
||
|
||
// Excel 常见扩展名
|
||
const excelExtensions = [".xls", ".xlsx", ".docx", "office"];
|
||
|
||
return excelExtensions.includes(ext);
|
||
}
|
||
|
||
// 根据文件名获取对应的图标
|
||
function getFileIcon(fileExtension) {
|
||
// 图片类型
|
||
if (isImage(fileExtension)) {
|
||
return "image";
|
||
}
|
||
|
||
// 音频类型
|
||
const audioTypes = ["mp3", "wav", "aac", "flac", "ogg"];
|
||
if (audioTypes.includes(fileExtension)) {
|
||
return "streamline-ultimate-color:audio-file";
|
||
}
|
||
|
||
// 视频类型
|
||
const videoTypes = ["mp4", "avi", "mkv", "mov", "wmv"];
|
||
if (videoTypes.includes(fileExtension)) {
|
||
return "vscode-icons:file-type-video";
|
||
}
|
||
|
||
if (fileExtension === "txt") {
|
||
return "tabler:file-type-txt";
|
||
}
|
||
|
||
if (fileExtension === "pdf") {
|
||
return "vscode-icons:file-type-pdf2";
|
||
}
|
||
|
||
// 文档类型
|
||
const documentTypes = ["docx", "xlsx", "pptx", "office"];
|
||
if (documentTypes.includes(fileExtension)) {
|
||
return "vscode-icons:file-type-excel";
|
||
}
|
||
|
||
// 压缩包类型
|
||
const archiveTypes = ["zip", "rar", "tar", "gz"];
|
||
if (archiveTypes.includes(fileExtension)) {
|
||
return "icon-park:zip";
|
||
}
|
||
|
||
// 其他类型
|
||
return "fluent-color:document-16";
|
||
}
|
||
|
||
async function handleDownloadFile(file) {
|
||
if (file?._status === "pending") {
|
||
const a = document.createElement("a");
|
||
a.download = file.mx_filename || "downloaded_file";
|
||
|
||
// 直接创建一个临时 URL,假设 file._content 是 Blob
|
||
const downloadUrl = URL.createObjectURL(file._content);
|
||
a.href = downloadUrl;
|
||
|
||
// 触发下载
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
|
||
// 清理临时 URL
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(downloadUrl);
|
||
} else {
|
||
try {
|
||
getAliyunFileBlob(file.mx_mapfilename).then((blob) => {
|
||
const blobUrl = window.URL.createObjectURL(blob);
|
||
|
||
// 创建一个链接并触发下载
|
||
const a = document.createElement("a");
|
||
a.href = blobUrl;
|
||
a.download = file.mx_filename;
|
||
a.style.display = "none";
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
|
||
// 清理 URL 对象
|
||
window.URL.revokeObjectURL(blobUrl);
|
||
a.remove();
|
||
});
|
||
} catch (error) {
|
||
// 捕获并处理下载过程中可能发生的错误
|
||
ElMessage.error(`下载过程中发生错误:${error.message}`);
|
||
console.error("下载错误:", error);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 预览
|
||
function handleShowPreview(file) {
|
||
console.log(file);
|
||
if (isImage(file.mx_fileext)) {
|
||
const index = previewImageList.value.findIndex((item) => item === file.mx_mapfilename);
|
||
|
||
if (index !== -1) {
|
||
preViewImageIndex.value = index;
|
||
}
|
||
|
||
// 显示预览
|
||
showPreview.value = true;
|
||
} else {
|
||
const fileUrl = file.mx_mapfilename;
|
||
|
||
if (isExcel(file.mx_fileext)) {
|
||
// https://view.officeapps.live.com/op/view.aspx?src=
|
||
window.open(`https://view.officeapps.live.com/op/view.aspx?src=${fileUrl}`, "_blank");
|
||
} else {
|
||
window.open(fileUrl, "_blank");
|
||
}
|
||
}
|
||
}
|
||
|
||
function handleDownloadPreviewImage(index) {
|
||
const previewFileUrl = previewImageList.value[index];
|
||
|
||
const previewFile = fileList.value.find((item) => item.mx_mapfilename === previewFileUrl);
|
||
|
||
if (previewFile) {
|
||
handleDownloadFile(previewFile);
|
||
} else {
|
||
console.error("未找到符合条件的文件,下载失败");
|
||
}
|
||
}
|
||
|
||
// 批量删除 / 单个删除
|
||
function handleDeleteOne(file) {
|
||
deleteFiles([file]);
|
||
}
|
||
|
||
function handleBatchDelete() {
|
||
if (checkedFiles.value.length === 0) {
|
||
ElMessage.warning("请选择需要删除的文件");
|
||
return;
|
||
}
|
||
deleteFiles(checkedFiles.value);
|
||
}
|
||
|
||
async function deleteFiles(files) {
|
||
await loadEditableStatus();
|
||
if (isReadonly.value) {
|
||
ElMessage.warning("当前单据已审核,不允许删除附件");
|
||
loadFiles();
|
||
return;
|
||
}
|
||
|
||
// 收集文件的 mx_filename
|
||
const filenames = files.map((file) => file.mx_filename).join(", ");
|
||
|
||
ElMessageBox({
|
||
title: "删除提示",
|
||
message: `确认要删除 ${filenames} 文件吗?`,
|
||
showCancelButton: true,
|
||
confirmButtonText: "确认",
|
||
cancelButtonText: "取消",
|
||
type: "warning",
|
||
beforeClose: async (action, instance, done) => {
|
||
if (action === "confirm") {
|
||
instance.confirmButtonLoading = true;
|
||
instance.confirmButtonText = "正在删除中...";
|
||
|
||
try {
|
||
for (const item of files) {
|
||
if (item.subid) {
|
||
await deleteFileApi("bf_files", item.subid);
|
||
}
|
||
}
|
||
|
||
ElMessage.success("文件删除成功");
|
||
done();
|
||
} catch (error) {
|
||
console.error("删除文件失败:", error);
|
||
ElMessage.error(`文件删除失败,请重试`);
|
||
done();
|
||
} finally {
|
||
instance.confirmButtonLoading = false;
|
||
}
|
||
} else {
|
||
done();
|
||
}
|
||
},
|
||
}).then((action) => {
|
||
if (action === "confirm") {
|
||
loadFiles();
|
||
loadEditableStatus();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 保存
|
||
async function handleSave() {
|
||
await loadEditableStatus();
|
||
if (isReadonly.value) {
|
||
ElMessage.warning("当前单据已审核,不允许上传附件");
|
||
loadFiles();
|
||
return;
|
||
}
|
||
|
||
loading.value = true;
|
||
loadingTip.value = "正在保存中 . . .";
|
||
|
||
const data = compareAndClassifyData(fileList.value, oldFileList.value, "subid", [
|
||
"_content",
|
||
"_status",
|
||
"checked",
|
||
]);
|
||
|
||
// 处理新增的文件
|
||
for (const item of data.inserts) {
|
||
try {
|
||
await uploadFile(
|
||
item._content,
|
||
item.mx_filename,
|
||
item.mx_fileext,
|
||
item.mx_filesize,
|
||
item.mx_xh,
|
||
props.moduleid,
|
||
props.father,
|
||
);
|
||
console.log(`文件 ${item.mx_filename} 上传成功`);
|
||
item._status = "";
|
||
} catch (error) {
|
||
console.error(`文件 ${item.mx_filename} 上传失败:`, error);
|
||
ElMessage.error(`文件 ${item.mx_filename} 上传失败,请重试`);
|
||
}
|
||
}
|
||
|
||
ElMessage.success("上传成功");
|
||
|
||
loading.value = false;
|
||
|
||
loadFiles();
|
||
loadEditableStatus();
|
||
}
|
||
|
||
async function handleDownloadZip() {
|
||
if (checkedFiles.value.length < 2) {
|
||
ElMessage.warning("打包文件数量不得少于2");
|
||
return;
|
||
}
|
||
|
||
loading.value = true;
|
||
loadingTip.value = "正在打包下载中 . . .";
|
||
|
||
downloadZip(checkedFiles.value);
|
||
|
||
loading.value = false;
|
||
}
|
||
|
||
function handleChangeDisplayMode(mode) {
|
||
displayMode.value = mode;
|
||
}
|
||
|
||
function onClosed() {
|
||
fileList.value = [];
|
||
selectAll.value = false;
|
||
indeterminate.value = false;
|
||
showPreview.value = false;
|
||
|
||
preViewImageIndex.value = 0;
|
||
|
||
loading.value = false;
|
||
|
||
loadingTip.value = "正在加载中 . . .";
|
||
}
|
||
|
||
function handleTreeNodeClick(data) {
|
||
console.log(data);
|
||
cateid.value = data.cateid || "";
|
||
|
||
loadFiles();
|
||
|
||
selectAll.value = false;
|
||
indeterminate.value = false;
|
||
showPreview.value = false;
|
||
preViewImageIndex.value = 0;
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<el-dialog
|
||
title="附件管理"
|
||
width="900"
|
||
draggable
|
||
modal-class="g3-file-modal"
|
||
v-bind="$attrs"
|
||
@closed="onClosed"
|
||
@opened="onOpened"
|
||
>
|
||
<template #header>
|
||
<div class="g3-title">
|
||
附件管理({{ fileList.length }})
|
||
|
||
<template v-if="!isReadonly"> 未上传 ({{ pendingFileList.length }}) </template>
|
||
</div>
|
||
</template>
|
||
<input
|
||
ref="fileInputRef"
|
||
type="file"
|
||
multiple
|
||
style="display: none"
|
||
@change="handleFileInputChange"
|
||
/>
|
||
|
||
<div class="g3-container">
|
||
<div v-if="categories[0].children.length > 0" class="g3-category">
|
||
<el-tree
|
||
ref="treeRef"
|
||
node-key="cateid"
|
||
:data="categories"
|
||
:default-expanded-keys="['']"
|
||
@node-click="handleTreeNodeClick"
|
||
/>
|
||
</div>
|
||
<div class="g3-file-main">
|
||
<div class="g3-header">
|
||
<div>
|
||
<el-checkbox
|
||
v-model="selectAll"
|
||
:indeterminate="indeterminate"
|
||
:disabled="loading"
|
||
@change="toggleSelectAll"
|
||
>
|
||
全选
|
||
</el-checkbox>
|
||
|
||
<el-button
|
||
size="small"
|
||
type="primary"
|
||
:disabled="checkedFiles.length === 0 || loading"
|
||
style="margin-left: 8px"
|
||
@click="handleDownloadZip"
|
||
>
|
||
打包下载
|
||
</el-button>
|
||
|
||
<el-button
|
||
v-if="!isReadonly"
|
||
size="small"
|
||
type="danger"
|
||
:disabled="checkedFiles.length === 0 || loading"
|
||
@click="handleBatchDelete()"
|
||
>
|
||
批量删除
|
||
</el-button>
|
||
</div>
|
||
|
||
<div>
|
||
<el-dropdown>
|
||
<span class="g3-mode">
|
||
模式:{{ displayMode === 0 ? "卡片视图" : "列表视图" }}
|
||
<el-icon class="el-icon--right">
|
||
<ArrowDown />
|
||
</el-icon>
|
||
</span>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click="handleChangeDisplayMode(0)">
|
||
卡片视图
|
||
</el-dropdown-item>
|
||
<el-dropdown-item @click="handleChangeDisplayMode(1)">
|
||
列表视图
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
ref="containerRef"
|
||
v-loading="loading"
|
||
:element-loading-text="loadingTip"
|
||
class="g3-main"
|
||
tabindex="0"
|
||
@paste="handlePaste"
|
||
@dragover="handleDragOver"
|
||
@drop="handleDrop"
|
||
>
|
||
<div v-if="fileList.length === 0" class="g3-tip">
|
||
<el-icon size="64">
|
||
<UploadFilled />
|
||
</el-icon>
|
||
<div>支持 拖拽文件、粘贴文件、多文件上传</div>
|
||
</div>
|
||
|
||
<div v-else-if="displayMode === 0" class="g3-files">
|
||
<div v-for="(file, index) in fileList" :key="index" class="g3-file-item">
|
||
<div class="g3-file-checkbox">
|
||
<el-checkbox v-model="file._checked" size="large" />
|
||
</div>
|
||
<!-- pending:未上传,completed:已上传,failed:上传失败 -->
|
||
<div
|
||
v-if="file._status === 'pending' || file._status === 'failed'"
|
||
:class="`g3-file-status status-${file._status}`"
|
||
>
|
||
<span v-if="file._status === 'pending'">未上传</span>
|
||
<span v-if="file._status === 'failed'">上传失败</span>
|
||
</div>
|
||
<div class="g3-file-preview" @click.stop="handleShowPreview(file)">
|
||
<template v-if="getFileIcon(file.mx_fileext) === 'image'">
|
||
<el-image class="g3-file-preview-img" :src="file.mx_mapfilename" />
|
||
</template>
|
||
<template v-else>
|
||
<G3Icon :icon="getFileIcon(file.mx_fileext)" size="88" />
|
||
</template>
|
||
</div>
|
||
<div class="g3-file-info">
|
||
<el-tooltip :content="file.mx_filename">
|
||
<div class="g3-file-name">
|
||
{{ file.mx_filename }}
|
||
</div>
|
||
</el-tooltip>
|
||
<div class="g3-file-details">
|
||
<div>{{ formatFileSize(file.mx_filesize) }}</div>
|
||
<div>
|
||
{{ file.mx_updateuser_id || file.mx_inputuser_id }}
|
||
<template v-if="file.mx_inputdatetime || file.mx_updatedatetime">
|
||
| {{ formatTime(file.mx_updatedatetime || file.mx_inputdatetime) }}
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<div class="g3-file-actions">
|
||
<el-button
|
||
v-if="!isReadonly"
|
||
type="danger"
|
||
:icon="Delete"
|
||
size="small"
|
||
circle
|
||
@click.stop="handleDeleteOne(file)"
|
||
/>
|
||
<el-button
|
||
type="primary"
|
||
:icon="Download"
|
||
size="small"
|
||
circle
|
||
@click.stop="handleDownloadFile(file)"
|
||
/>
|
||
<el-dropdown v-if="!isReadonly" placement="top" trigger="click">
|
||
<el-button
|
||
type="info"
|
||
:icon="More"
|
||
size="small"
|
||
circle
|
||
style="margin-left: 12px"
|
||
/>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click="handleShowUpdateDialog(file)">
|
||
修改文件信息
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else class="g3-table">
|
||
<div>
|
||
<vxe-table
|
||
ref="tableRef"
|
||
border
|
||
stripe
|
||
header-align="center"
|
||
:data="fileList"
|
||
height="100%"
|
||
show-overflow
|
||
show-header-overflow
|
||
:cell-config="{
|
||
height: 26,
|
||
}"
|
||
:sort-config="{
|
||
trigger: 'cell',
|
||
}"
|
||
:filter-config="{
|
||
showIcon: false,
|
||
}"
|
||
size="small"
|
||
>
|
||
<vxe-column type="seq" width="50" title=" " align="center" />
|
||
<vxe-column width="50" align="center">
|
||
<template #default="{ row }">
|
||
<el-checkbox v-model="row._checked" size="large" />
|
||
</template>
|
||
</vxe-column>
|
||
<vxe-column field="mx_filename" title="文件名" sortable align="center">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link @click.stop="handleShowPreview(row)">
|
||
{{ row.mx_filename }}
|
||
</el-button>
|
||
</template>
|
||
</vxe-column>
|
||
<vxe-column field="mx_inputdatetime" title="上传日期" sortable align="center">
|
||
<template #default="{ row }">
|
||
{{ formatTime(row.mx_inputdatetime) }}
|
||
</template>
|
||
</vxe-column>
|
||
<vxe-column
|
||
field="mx_fileext"
|
||
title="文件类型"
|
||
sortable
|
||
width="100"
|
||
align="center"
|
||
/>
|
||
<vxe-column
|
||
field="mx_filesize"
|
||
title="文件大小"
|
||
sortable
|
||
width="100"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
{{ formatFileSize(row.mx_filesize) }}
|
||
</template>
|
||
</vxe-column>
|
||
<vxe-column width="180" title="操作">
|
||
<template #default="{ row }">
|
||
<div style="display: flex; align-items: center; justify-content: center">
|
||
<el-button
|
||
v-if="!isReadonly"
|
||
type="danger"
|
||
link
|
||
@click.stop="handleDeleteOne(row)"
|
||
>
|
||
删除
|
||
</el-button>
|
||
<el-button type="primary" link @click.stop="handleDownloadFile(row)">
|
||
下载
|
||
</el-button>
|
||
<el-dropdown v-if="!isReadonly" placement="top" trigger="click">
|
||
<el-button type="info" link style="margin-left: 12px"> 更多 </el-button>
|
||
<el-icon class="el-icon--right">
|
||
<ArrowDown />
|
||
</el-icon>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click="handleShowUpdateDialog(row)">
|
||
修改文件信息
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
</template>
|
||
</vxe-column>
|
||
</vxe-table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<template v-if="!isReadonly" #footer>
|
||
<el-button type="primary" :icon="HelpFilled" :disabled="loading" @click="handleClickUpload">
|
||
选择
|
||
</el-button>
|
||
<el-button type="primary" :icon="UploadFilled" :disabled="loading" @click="handleSave">
|
||
上传
|
||
</el-button>
|
||
</template>
|
||
<el-image-viewer
|
||
v-if="showPreview"
|
||
show-progress
|
||
:url-list="previewImageList"
|
||
:initial-index="preViewImageIndex"
|
||
hide-on-click-modal
|
||
preview-teleported
|
||
@close="showPreview = false"
|
||
>
|
||
<template #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }">
|
||
<ElIcon @click="prev">
|
||
<Back />
|
||
</ElIcon>
|
||
<ElIcon @click="next">
|
||
<Right />
|
||
</ElIcon>
|
||
<ElIcon @click="setActiveItem(srcList.length - 1)">
|
||
<DArrowRight />
|
||
</ElIcon>
|
||
<ElIcon @click="actions('zoomOut')">
|
||
<ZoomOut />
|
||
</ElIcon>
|
||
<ElIcon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
|
||
<ZoomIn />
|
||
</ElIcon>
|
||
<ElIcon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
|
||
<RefreshRight />
|
||
</ElIcon>
|
||
<ElIcon @click="actions('anticlockwise')">
|
||
<RefreshLeft />
|
||
</ElIcon>
|
||
<ElIcon @click="reset">
|
||
<Refresh />
|
||
</ElIcon>
|
||
<ElIcon @click="handleDownloadPreviewImage(activeIndex)">
|
||
<Download />
|
||
</ElIcon>
|
||
</template>
|
||
</el-image-viewer>
|
||
|
||
<el-dialog
|
||
v-model="showUpdateDialog"
|
||
title="更新文件"
|
||
width="500"
|
||
:closed="handleUpdateDialogClosed"
|
||
>
|
||
<div>
|
||
<div style="margin-bottom: 4px">文件名:</div>
|
||
<el-input v-model="updateFile.mx_filename" />
|
||
<div style="margin: 4px 0px">备注:</div>
|
||
<el-input v-model="updateFile.mx_bz" />
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="showUpdateDialog = false"> 取消 </el-button>
|
||
<el-button type="primary" :loading="updateLoading" @click="handleConfirmUpdate">
|
||
确认
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<style>
|
||
.g3-file-modal .el-loading-text {
|
||
font-size: 22px !important;
|
||
}
|
||
.g3-file-modal .el-image-viewer__mask {
|
||
opacity: 0.9;
|
||
}
|
||
</style>
|
||
|
||
<style lang="scss" scoped>
|
||
.g3-title {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.g3-container {
|
||
height: 500px;
|
||
display: flex;
|
||
width: 100%;
|
||
}
|
||
|
||
.g3-category {
|
||
width: 140px;
|
||
}
|
||
|
||
.g3-file-main {
|
||
flex: 1;
|
||
padding: 0px 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.g3-main {
|
||
overflow-y: auto;
|
||
height: 100%;
|
||
/* padding: 16px 0px;
|
||
border-top: 1px solid #ddd;
|
||
border-bottom: 1px solid #ddd; */
|
||
width: 100%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.g3-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
|
||
div {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.g3-mode {
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 8px 12px;
|
||
border: 1px solid #ddd;
|
||
font-size: 12px;
|
||
border-radius: 4px;
|
||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.05);
|
||
}
|
||
}
|
||
|
||
.g3-tip {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.g3-files {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
|
||
.g3-table {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
div {
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
.g3-file-item {
|
||
background-color: #f8f9fa;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
transition:
|
||
transform 0.3s,
|
||
box-shadow 0.3s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 260px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.g3-file-item:hover {
|
||
transform: translateY(-5px);
|
||
}
|
||
|
||
.g3-file-checkbox {
|
||
position: absolute;
|
||
left: 6px;
|
||
:deep(.el-checkbox__inner) {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
}
|
||
|
||
.g3-file-status {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 6px;
|
||
padding: 4px 6px;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
color: #fff;
|
||
font-size: 11px;
|
||
z-index: 99;
|
||
white-space: nowrap;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.g3-file-status.status-pending {
|
||
background-color: #ff9800;
|
||
}
|
||
|
||
.g3-file-status.status-failed {
|
||
background-color: #f44336;
|
||
}
|
||
|
||
.g3-file-status.status-success {
|
||
background-color: #4caf50;
|
||
}
|
||
|
||
.g3-file-preview {
|
||
height: 140px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #fff;
|
||
color: #3498db;
|
||
font-size: 2.2rem;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.g3-file-preview-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.g3-file-info {
|
||
flex: 1;
|
||
padding: 12px;
|
||
flex-grow: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.g3-file-name {
|
||
font-weight: 500;
|
||
color: #2c3e50;
|
||
margin-bottom: 6px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.g3-file-details {
|
||
color: #7f8c8d;
|
||
font-size: 0.85rem;
|
||
margin-bottom: 10px;
|
||
line-height: 1.4;
|
||
div {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
}
|
||
|
||
.g3-file-actions {
|
||
display: flex;
|
||
justify-content: end;
|
||
margin-top: auto;
|
||
}
|
||
</style>
|