From 5b66c33ef1709bd39e23b8c771b47e6b5e6d3ba7 Mon Sep 17 00:00:00 2001 From: cshaptx4869 <994774638@qq.com> Date: Mon, 10 Jun 2024 12:18:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(PageContent):=20:sparkles:=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=BB=98=E8=AE=A4=E5=B7=A5=E5=85=B7=E6=A0=8F=E7=9A=84?= =?UTF-8?q?=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PageContent/index.vue | 306 ++++++++++++++++++++++----- src/hooks/usePage.ts | 2 +- 2 files changed, 257 insertions(+), 51 deletions(-) diff --git a/src/components/PageContent/index.vue b/src/components/PageContent/index.vue index a79b9648..8e0c7e78 100644 --- a/src/components/PageContent/index.vue +++ b/src/components/PageContent/index.vue @@ -87,13 +87,23 @@ + + + + + + + + + + + +
+ 将文件拖到此处,或点击上传 +
+ +
+
+
+
+ + +
@@ -399,11 +477,15 @@ import { ref, reactive } from "vue"; import { useDateFormat, useThrottleFn } from "@vueuse/core"; import { hasAuth } from "@/plugins/permission"; import SvgIcon from "@/components/SvgIcon/index.vue"; -import type { - TableProps, - PaginationProps, - FormInstance, - FormRules, +import { + type TableProps, + type PaginationProps, + type FormInstance, + type FormRules, + type UploadInstance, + type UploadUserFile, + type UploadRawFile, + genFileId, } from "element-plus"; // 对象类型 @@ -442,18 +524,22 @@ export interface IContentConfig { list: IObject[]; [key: string]: any; }; - // 删除的网络请求函数(需返回promise) - deleteAction?: (ids: string) => Promise; - // 后端导出的网络请求函数(需返回promise) - exportAction?: (queryParams: T) => Promise; - // 前端全量导出的网络请求函数(需返回promise) - exportsAction?: (queryParams: T) => Promise; // 修改属性的网络请求函数(需返回promise) modifyAction?: (data: { [key: string]: any; field: string; value: boolean | string | number; }) => Promise; + // 删除的网络请求函数(需返回promise) + deleteAction?: (ids: string) => Promise; + // 后端导出的网络请求函数(需返回promise) + exportAction?: (queryParams: T) => Promise; + // 前端全量导出的网络请求函数(需返回promise) + exportsAction?: (queryParams: T) => Promise; + // 前端导入模板 + importsTemplate?: string | (() => Promise); + // 前端导入的网络请求函数(需返回promise) + importsAction?: (data: IObject[]) => Promise; // 主键名(默认为id) pk?: string; // 表格工具栏(默认支持add,delete,export,也可自定义) @@ -472,6 +558,7 @@ export interface IContentConfig { defaultToolbar?: Array< | "refresh" | "filter" + | "imports" | "exports" | "search" | { @@ -561,8 +648,6 @@ const toolbar = props.contentConfig.toolbar ?? ["add", "delete"]; const defaultToolbar = props.contentConfig.defaultToolbar ?? [ "refresh", "filter", - "exports", - "search", ]; // 表格列 const cols = ref( @@ -716,36 +801,147 @@ function handleExports() { if (props.contentConfig.exportsAction) { props.contentConfig.exportsAction(lastFormData).then((res) => { worksheet.addRows(res); - downloadXlsx(workbook, filename); + workbook.xlsx + .writeBuffer() + .then((buffer) => { + saveXlsx(buffer, filename); + }) + .catch((error) => console.log(error)); }); } else { ElMessage.error("未配置exportsAction"); } - } else if (exportsFormData.origin === ExportsOriginEnum.SELECTED) { - worksheet.addRows(selectionData.value); - downloadXlsx(workbook, filename); } else { - worksheet.addRows(pageData.value); - downloadXlsx(workbook, filename); + worksheet.addRows( + exportsFormData.origin === ExportsOriginEnum.SELECTED + ? selectionData.value + : pageData.value + ); + workbook.xlsx + .writeBuffer() + .then((buffer) => { + saveXlsx(buffer, filename); + }) + .catch((error) => console.log(error)); } } -function downloadXlsx(workbook: ExcelJS.Workbook, filename: string) { - workbook.xlsx - .writeBuffer() - .then((buffer) => { - const blob = new Blob([buffer], { - type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - }); - const downloadUrl = window.URL.createObjectURL(blob); - const downloadLink = document.createElement("a"); - downloadLink.href = downloadUrl; - downloadLink.download = filename; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - window.URL.revokeObjectURL(downloadUrl); - }) - .catch((error) => console.log("Error writing excel export", error)); +// 导入表单 +const uploadRef = ref(); +const importsModalVisible = ref(false); +const importsFormRef = ref(); +const importsFormData = reactive<{ + files: UploadUserFile[]; +}>({ + files: [], +}); +const importsFormRules: FormRules = { + files: [{ required: true, message: "请选择文件" }], +}; +// 打开导入弹窗 +function handleOpenImportsModal() { + importsModalVisible.value = true; +} +// 覆盖前一个文件 +function handleFileExceed(files: File[]) { + uploadRef.value!.clearFiles(); + const file = files[0] as UploadRawFile; + file.uid = genFileId(); + uploadRef.value!.handleStart(file); +} +// 下载导入模板 +function handleDownloadTemplate() { + const importsTemplate = props.contentConfig.importsTemplate; + if (typeof importsTemplate === "string") { + window.open(importsTemplate); + } else if (typeof importsTemplate === "function") { + importsTemplate().then((response) => { + const fileData = response.data; + const fileName = decodeURI( + response.headers["content-disposition"].split(";")[1].split("=")[1] + ); + saveXlsx(fileData, fileName); + }); + } else { + ElMessage.error("未配置importsTemplate"); + } +} +// 导入确认 +const handleImportsSubmit = useThrottleFn(() => { + importsFormRef.value?.validate((valid: boolean) => { + valid && handleImports(); + }); +}, 3000); +// 关闭导入弹窗 +function handleCloseImportsModal() { + importsModalVisible.value = false; + importsFormRef.value?.resetFields(); + nextTick(() => { + importsFormRef.value?.clearValidate(); + }); +} +// 导入 +function handleImports() { + const importsAction = props.contentConfig.importsAction; + if (importsAction === undefined) { + ElMessage.error("未配置importsAction"); + return; + } + // 获取选择的文件 + const file = importsFormData.files[0].raw as File; + // 创建Workbook实例 + const workbook = new ExcelJS.Workbook(); + // 使用FileReader对象来读取文件内容 + const fileReader = new FileReader(); + // 二进制字符串的形式加载文件 + fileReader.readAsArrayBuffer(file); + fileReader.onload = (ev) => { + if (ev.target !== null && ev.target.result !== null) { + const result = ev.target.result as ArrayBuffer; + // 从 buffer中加载数据解析 + workbook.xlsx + .load(result) + .then((workbook) => { + // 解析后的数据 + const data = []; + // 获取第一个worksheet内容 + const worksheet = workbook.getWorksheet(1); + if (worksheet) { + // 获取第一行的标题 + const fields: any[] = []; + worksheet.getRow(1).eachCell((cell) => { + fields.push(cell.value); + }); + // 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行) + for ( + let rowNumber = 2; + rowNumber <= worksheet.rowCount; + rowNumber++ + ) { + const rowData: IObject = {}; + const row = worksheet.getRow(rowNumber); + // 遍历当前行的每个单元格 + row.eachCell((cell, colNumber) => { + // 获取标题对应的键,并将当前单元格的值存储到相应的属性名中 + rowData[fields[colNumber - 1]] = cell.value; + }); + // 将当前行的数据对象添加到数组中 + data.push(rowData); + } + } + if (data.length === 0) { + ElMessage.error("未解析到数据"); + return; + } + importsAction(data).then(() => { + ElMessage.success("导入数据成功"); + handleCloseImportsModal(); + }); + }) + .catch((error) => console.log(error)); + } else { + ElMessage.error("读取文件失败"); + } + }; } // 操作栏 function handleToolbar(name: string) { @@ -756,6 +952,9 @@ function handleToolbar(name: string) { case "exports": handleOpenExportsModal(); break; + case "imports": + handleOpenImportsModal(); + break; case "search": emit("searchClick"); break; @@ -878,23 +1077,30 @@ function exportPageData(formData: IObject = {}) { const fileName = decodeURI( response.headers["content-disposition"].split(";")[1].split("=")[1] ); - const fileType = - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"; - - const blob = new Blob([fileData], { type: fileType }); - const downloadUrl = window.URL.createObjectURL(blob); - const downloadLink = document.createElement("a"); - downloadLink.href = downloadUrl; - downloadLink.download = fileName; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - window.URL.revokeObjectURL(downloadUrl); + saveXlsx(fileData, fileName); }); } else { ElMessage.error("未配置exportAction"); } } +// 浏览器保存文件 +function saveXlsx(fileData: BlobPart, fileName: string) { + const fileType = + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8"; + + const blob = new Blob([fileData], { type: fileType }); + const downloadUrl = window.URL.createObjectURL(blob); + + const downloadLink = document.createElement("a"); + downloadLink.href = downloadUrl; + downloadLink.download = fileName; + + document.body.appendChild(downloadLink); + downloadLink.click(); + + document.body.removeChild(downloadLink); + window.URL.revokeObjectURL(downloadUrl); +} // 暴露的属性和方法 defineExpose({ fetchPageData, exportPageData, getFilterParams }); diff --git a/src/hooks/usePage.ts b/src/hooks/usePage.ts index a92252ec..3cc73ceb 100644 --- a/src/hooks/usePage.ts +++ b/src/hooks/usePage.ts @@ -48,7 +48,7 @@ function usePage() { searchRef.value?.toggleVisible(); } // 涮选数据 - function handleFilterChange(filterParams) { + function handleFilterChange(filterParams: IObject) { const queryParams = searchRef.value?.getQueryParams(); contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true); }