Files
workspace/code/g3hd/frontend/src/components/g3-file/index.vue
T
2026-08-08 17:29:51 +08:00

1285 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>