|
|
@@ -0,0 +1,323 @@
|
|
|
+<template>
|
|
|
+ <el-button plain size="small" @click="internalVisible = true">
|
|
|
+ 导出数据
|
|
|
+ </el-button>
|
|
|
+ <el-dialog
|
|
|
+ center
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ destroy-on-close
|
|
|
+ v-model="internalVisible"
|
|
|
+ :title="dialogTitle"
|
|
|
+ width="480px"
|
|
|
+ :before-close="handleClose"
|
|
|
+ >
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" @submit.prevent>
|
|
|
+ <el-row justify="center">
|
|
|
+ <el-form-item prop="startPage">
|
|
|
+ <el-input
|
|
|
+ v-model="form.startPage"
|
|
|
+ placeholder="请输入开始页码"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleExport"
|
|
|
+ style="max-width: 120px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <span style="margin: 5px 10px 0">至</span>
|
|
|
+
|
|
|
+ <el-form-item prop="endPage">
|
|
|
+ <el-input
|
|
|
+ v-model="form.endPage"
|
|
|
+ placeholder="请输入结束页码"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleExport"
|
|
|
+ style="max-width: 120px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="handleClose">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleExport" :loading="loading">
|
|
|
+ 导出
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, computed } from "vue";
|
|
|
+import type { FormInstance, FormRules } from "element-plus";
|
|
|
+import { ElMessage } from "element-plus";
|
|
|
+import dayjs from "dayjs";
|
|
|
+
|
|
|
+import { http } from "@/utils/http/index";
|
|
|
+
|
|
|
+// 定义接口
|
|
|
+interface ExportForm {
|
|
|
+ startPage: any;
|
|
|
+ endPage: any;
|
|
|
+}
|
|
|
+
|
|
|
+// Props 定义
|
|
|
+const props = withDefaults(
|
|
|
+ defineProps<{
|
|
|
+ title?: string;
|
|
|
+ url: string;
|
|
|
+ fileName: string;
|
|
|
+ queryParams?: any;
|
|
|
+ likeTable?: boolean;
|
|
|
+ }>(),
|
|
|
+ {
|
|
|
+ title: "导出数据",
|
|
|
+ likeTable: false
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const internalVisible = ref(false);
|
|
|
+const loading = ref(false);
|
|
|
+const formRef = ref<FormInstance>();
|
|
|
+
|
|
|
+// 计算属性
|
|
|
+const dialogTitle = computed(() => props.title);
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const form = reactive<ExportForm>({
|
|
|
+ startPage: "1",
|
|
|
+ endPage: "10"
|
|
|
+});
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description 从1开始正整数校验
|
|
|
+ * @param val
|
|
|
+ * @returns {boolean}
|
|
|
+ */
|
|
|
+const isNumbAvailable = (val: any): boolean => {
|
|
|
+ let nubReg = /^[1-9]\d*$/;
|
|
|
+ return nubReg.test(val);
|
|
|
+};
|
|
|
+
|
|
|
+// 自定义验证函数
|
|
|
+const validatePageNumber = (rule: any, value: string, callback: any) => {
|
|
|
+ if (!value) {
|
|
|
+ callback(new Error("请输入页码"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isNumbAvailable(value)) {
|
|
|
+ callback(new Error("请输入有效的正整数"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ callback();
|
|
|
+};
|
|
|
+
|
|
|
+const validateEndPage = (rule: any, value: string, callback: any) => {
|
|
|
+ if (!value) {
|
|
|
+ callback(new Error("请输入结束页码"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isNumbAvailable(value)) {
|
|
|
+ callback(new Error("请输入有效的正整数"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const startPageNum = parseInt(form.startPage);
|
|
|
+ const endPageNum = parseInt(value);
|
|
|
+
|
|
|
+ if (endPageNum < startPageNum) {
|
|
|
+ callback(new Error("结束页不能小于开始页"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ callback();
|
|
|
+};
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const rules = reactive<FormRules<ExportForm>>({
|
|
|
+ startPage: [{ validator: validatePageNumber, trigger: "blur" }],
|
|
|
+ endPage: [{ validator: validateEndPage, trigger: "blur" }]
|
|
|
+});
|
|
|
+
|
|
|
+/**
|
|
|
+ * 下载文件
|
|
|
+ * @param blob 文件blob对象
|
|
|
+ * @param fileName 文件名
|
|
|
+ */
|
|
|
+const downloadFile = (blob: Blob, fileName: string) => {
|
|
|
+ try {
|
|
|
+ const url = window.URL.createObjectURL(blob);
|
|
|
+ const link = document.createElement("a");
|
|
|
+ link.href = url;
|
|
|
+ link.download = fileName;
|
|
|
+ link.style.display = "none";
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ window.URL.revokeObjectURL(url);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("文件下载失败:", error);
|
|
|
+ ElMessage.error("文件下载失败");
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 生成文件名
|
|
|
+ * @param baseName 基础文件名
|
|
|
+ * @param extension 文件扩展名
|
|
|
+ * @returns 完整文件名
|
|
|
+ */
|
|
|
+const generateFileName = (
|
|
|
+ baseName: string,
|
|
|
+ extension: string = "xlsx"
|
|
|
+): string => {
|
|
|
+ const timestamp = dayjs().format("YYYYMMDD_HHmmss");
|
|
|
+ return `${baseName}_${timestamp}.${extension}`;
|
|
|
+};
|
|
|
+
|
|
|
+// 关闭对话框
|
|
|
+const handleClose = () => {
|
|
|
+ internalVisible.value = false;
|
|
|
+ resetForm();
|
|
|
+};
|
|
|
+
|
|
|
+// 重置表单
|
|
|
+const resetForm = () => {
|
|
|
+ if (formRef.value) {
|
|
|
+ formRef.value.resetFields();
|
|
|
+ }
|
|
|
+ form.startPage = "1";
|
|
|
+ form.endPage = "10";
|
|
|
+ loading.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+const calculateNum = (param: any) => {
|
|
|
+ if (!param.pageSize) {
|
|
|
+ param.pageSize = 10;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (param.startPage < 0) {
|
|
|
+ ElMessage.warning("行数必须大于0");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (Number(param.startPage) > Number(param.endPage)) {
|
|
|
+ ElMessage.warning("结束行不能小于开始行");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((param.endPage - param.startPage) * param.pageSize > 500000) {
|
|
|
+ ElMessage.warning("导出行数不能超过5万");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+};
|
|
|
+
|
|
|
+// 处理导出
|
|
|
+const handleExport = async () => {
|
|
|
+ if (!formRef.value) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 验证表单
|
|
|
+ await formRef.value.validate();
|
|
|
+
|
|
|
+ loading.value = true;
|
|
|
+
|
|
|
+ let params = { ...props.queryParams };
|
|
|
+
|
|
|
+ if (!props.likeTable) {
|
|
|
+ params.endPage = form.endPage;
|
|
|
+ params.startPage = form.startPage;
|
|
|
+ if (!calculateNum(params)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let rows =
|
|
|
+ (form.endPage - form.startPage + 1) * props.queryParams.pageSize;
|
|
|
+ params.pageNum = form.startPage;
|
|
|
+ params.pageSize = Math.abs(rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (params.reportDate) {
|
|
|
+ // 将param.queryTime转为字符串,隔开
|
|
|
+ params.reportDate = params.reportDate.join();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (params.multipleDate) {
|
|
|
+ params.multipleDate = params.multipleDate.join();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用导出接口
|
|
|
+ const blob = await http.postExport<Blob, any>(props.url, { data: params });
|
|
|
+
|
|
|
+ // 生成文件名
|
|
|
+ const fullFileName = generateFileName(props.fileName);
|
|
|
+
|
|
|
+ // 下载文件
|
|
|
+ downloadFile(blob, fullFileName);
|
|
|
+
|
|
|
+ // 显示成功消息
|
|
|
+ ElMessage.success(`导出成功!文件名:${fullFileName}`);
|
|
|
+
|
|
|
+ // 关闭对话框
|
|
|
+ handleClose();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("导出失败:", error);
|
|
|
+
|
|
|
+ // 根据错误类型显示不同的错误信息
|
|
|
+ let errorMessage = "导出失败,请稍后重试";
|
|
|
+
|
|
|
+ if (error?.response) {
|
|
|
+ // HTTP错误
|
|
|
+ const status = error.response.status;
|
|
|
+ switch (status) {
|
|
|
+ case 400:
|
|
|
+ errorMessage = "请求参数错误";
|
|
|
+ break;
|
|
|
+ case 401:
|
|
|
+ errorMessage = "未授权,请重新登录";
|
|
|
+ break;
|
|
|
+ case 403:
|
|
|
+ errorMessage = "没有导出权限";
|
|
|
+ break;
|
|
|
+ case 404:
|
|
|
+ errorMessage = "导出接口不存在";
|
|
|
+ break;
|
|
|
+ case 500:
|
|
|
+ errorMessage = "服务器内部错误";
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ errorMessage = `导出失败 (${status})`;
|
|
|
+ }
|
|
|
+ } else if (error?.code === "NETWORK_ERROR") {
|
|
|
+ errorMessage = "网络连接失败,请检查网络";
|
|
|
+ } else if (error?.message) {
|
|
|
+ errorMessage = error.message;
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessage.error(errorMessage);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-form-item__label) {
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-input__inner) {
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+</style>
|