|
@@ -1,6 +1,6 @@
|
|
|
<template>
|
|
|
<div :style="styleObj">
|
|
|
- <superslide v-if="hackReset" :options="options" class="txtScroll-top">
|
|
|
+ <superslide v-if="hackReset" :options="options" class="txtScroll-top" ref="superslide">
|
|
|
<!--表头-->
|
|
|
<div class="title">
|
|
|
<div v-for="(item, index) in header" :key="index"
|
|
@@ -27,6 +27,48 @@
|
|
|
</ul>
|
|
|
</div>
|
|
|
</superslide>
|
|
|
+
|
|
|
+ <!-- 添加导出按钮,仅在预览模式显示 -->
|
|
|
+ <div v-if="ispreview && optionsSetUp.showExportButton" class="export-button-container" :style="exportButtonContainerStyle">
|
|
|
+ <div class="export-button" :style="exportButtonStyle" @click="openExportDialog">
|
|
|
+ <span class="button-text">导出</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 导出对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ title="导出表格数据"
|
|
|
+ :visible.sync="exportDialogVisible"
|
|
|
+ :width="dialogWidth"
|
|
|
+ :fullscreen="isMobile"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ center
|
|
|
+ :modal-append-to-body="true"
|
|
|
+ append-to-body
|
|
|
+ :z-index="9999"
|
|
|
+ class="export-dialog">
|
|
|
+ <el-form :model="exportForm" label-width="100px" size="small">
|
|
|
+ <el-form-item label="文件名称">
|
|
|
+ <el-input v-model="exportForm.fileName" placeholder="请输入文件名称" clearable></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="导出格式">
|
|
|
+ <el-radio-group v-model="exportForm.format">
|
|
|
+ <el-radio label="excel">Excel格式(.xlsx)</el-radio>
|
|
|
+ <el-radio label="csv">CSV格式(.csv)</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <div class="export-info">
|
|
|
+ <p><i class="el-icon-info"></i> 表格名称:数据表</p>
|
|
|
+ <p><i class="el-icon-tickets"></i> 数据总量:{{list.length}}行 {{header.length}}列</p>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <span slot="footer" class="dialog-footer">
|
|
|
+ <el-button size="small" @click="exportDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button size="small" type="primary" @click="confirmExport" :loading="exporting">确定导出</el-button>
|
|
|
+ </span>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
<script>
|
|
@@ -60,6 +102,16 @@ export default {
|
|
|
optionsPosition: {},
|
|
|
optionsData: {},
|
|
|
flagInter: null,
|
|
|
+ // 新增导出对话框相关数据
|
|
|
+ exportDialogVisible: false,
|
|
|
+ exporting: false,
|
|
|
+ exportForm: {
|
|
|
+ fileName: '',
|
|
|
+ format: 'excel'
|
|
|
+ },
|
|
|
+ // 添加设备检测变量
|
|
|
+ isMobile: false,
|
|
|
+ screenWidth: window.innerWidth
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
@@ -107,6 +159,57 @@ export default {
|
|
|
"border-color": bodyStyle.borderColor,
|
|
|
"color": bodyStyle.bodyColor,
|
|
|
};
|
|
|
+ },
|
|
|
+ // 新增样式计算属性
|
|
|
+ exportButtonStyle() {
|
|
|
+ // 根据表头字体大小动态计算按钮大小
|
|
|
+ const headerFontSize = parseInt(this.optionsSetUp.fontSizeHeader || 16);
|
|
|
+
|
|
|
+ // 计算图标尺寸,基于表头字体大小
|
|
|
+ const iconSize = Math.max(14, Math.min(24, headerFontSize * 1.1));
|
|
|
+
|
|
|
+ // 计算按钮高度,略大于图标尺寸
|
|
|
+ const buttonHeight = Math.round(iconSize * 1.8);
|
|
|
+
|
|
|
+ return {
|
|
|
+ height: `${buttonHeight}px`,
|
|
|
+ padding: '0 12px',
|
|
|
+ fontSize: `${iconSize}px`,
|
|
|
+ backgroundColor: 'rgba(39, 174, 96, 0.9)',
|
|
|
+ borderRadius: '4px',
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ boxShadow: '0 2px 12px 0 rgba(0, 0, 0, 0.1)',
|
|
|
+ color: '#ffffff'
|
|
|
+ };
|
|
|
+ },
|
|
|
+ exportButtonContainerStyle() {
|
|
|
+ // 计算按钮容器的位置,使其恰好超出表头几个像素
|
|
|
+ const headerHeight = this.optionsSetUp.rowHeight || 50;
|
|
|
+ // 按钮高度
|
|
|
+ const buttonHeight = parseInt(this.exportButtonStyle.height);
|
|
|
+ // 设置按钮位置
|
|
|
+ return {
|
|
|
+ position: 'absolute',
|
|
|
+ top: `-${buttonHeight + 5}px`, // 按钮高度 + 5px的间距
|
|
|
+ right: '10px',
|
|
|
+ zIndex: '100'
|
|
|
+ };
|
|
|
+ },
|
|
|
+ // 添加对话框宽度计算属性
|
|
|
+ dialogWidth() {
|
|
|
+ if (this.isMobile) {
|
|
|
+ return '100%';
|
|
|
+ } else if (this.screenWidth < 768) {
|
|
|
+ return '90%';
|
|
|
+ } else if (this.screenWidth < 992) {
|
|
|
+ return '80%';
|
|
|
+ } else if (this.screenWidth < 1200) {
|
|
|
+ return '60%';
|
|
|
+ } else {
|
|
|
+ return '420px';
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
watch: {
|
|
@@ -126,6 +229,13 @@ export default {
|
|
|
this.optionsData = this.value.data;
|
|
|
this.initData();
|
|
|
targetWidgetLinkageLogic(this); // 联动-目标组件逻辑
|
|
|
+ // 添加窗口大小变化监听
|
|
|
+ this.checkDeviceType();
|
|
|
+ window.addEventListener('resize', this.handleResize);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ // 移除窗口大小变化监听
|
|
|
+ window.removeEventListener('resize', this.handleResize);
|
|
|
},
|
|
|
methods: {
|
|
|
initData() {
|
|
@@ -230,7 +340,163 @@ export default {
|
|
|
styleJson["width"] = this.optionsSetUp.dynamicAddTable[index].width;
|
|
|
}
|
|
|
return styleJson;
|
|
|
- }
|
|
|
+ },
|
|
|
+ // 添加的方法 - 设备类型检测
|
|
|
+ checkDeviceType() {
|
|
|
+ this.screenWidth = window.innerWidth;
|
|
|
+ this.isMobile = this.screenWidth <= 576;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理窗口大小变化
|
|
|
+ handleResize() {
|
|
|
+ this.checkDeviceType();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 打开导出对话框前检查设备类型
|
|
|
+ openExportDialog() {
|
|
|
+ if (!this.list || this.list.length === 0) {
|
|
|
+ this.$message.warning('没有数据可导出');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新检查设备类型
|
|
|
+ this.checkDeviceType();
|
|
|
+
|
|
|
+ // 设置默认文件名(表格名称+日期时间)
|
|
|
+ const now = new Date();
|
|
|
+ const dateStr = `${now.getFullYear()}${(now.getMonth()+1).toString().padStart(2,'0')}${now.getDate().toString().padStart(2,'0')}`;
|
|
|
+ const timeStr = `${now.getHours().toString().padStart(2,'0')}${now.getMinutes().toString().padStart(2,'0')}`;
|
|
|
+
|
|
|
+ // 准备对话框数据
|
|
|
+ this.exportForm.fileName = `表格数据_${dateStr}_${timeStr}`;
|
|
|
+ this.exportForm.format = 'excel'; // 默认选择Excel格式
|
|
|
+
|
|
|
+ // 显示导出对话框
|
|
|
+ this.exportDialogVisible = true;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 确认导出方法
|
|
|
+ confirmExport() {
|
|
|
+ // 检查文件名是否为空
|
|
|
+ if (!this.exportForm.fileName || this.exportForm.fileName.trim() === '') {
|
|
|
+ this.$message.warning('请输入文件名');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置导出中状态
|
|
|
+ this.exporting = true;
|
|
|
+
|
|
|
+ // 根据选择的格式执行不同的导出方法
|
|
|
+ try {
|
|
|
+ if (this.exportForm.format === 'excel') {
|
|
|
+ this.doExportExcel();
|
|
|
+ } else {
|
|
|
+ this.doExportCSV();
|
|
|
+ // 只有CSV导出直接在这里显示成功消息和关闭对话框
|
|
|
+ this.$message.success('导出成功');
|
|
|
+ this.exportDialogVisible = false;
|
|
|
+ this.exporting = false;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('导出失败:', error);
|
|
|
+ this.$message.error('导出失败,请重试');
|
|
|
+ this.exporting = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 执行Excel导出
|
|
|
+ doExportExcel() {
|
|
|
+ // 引入xlsx库,使用动态导入以减少初始加载时间
|
|
|
+ import('xlsx').then(XLSX => {
|
|
|
+ // 准备数据:表头和内容
|
|
|
+ const tableHeaders = this.header.map(header => header.name);
|
|
|
+ const tableKeys = this.header.map(header => header.key);
|
|
|
+
|
|
|
+ // 创建表格数据数组,从列表数据映射
|
|
|
+ const tableData = this.list.map(row => {
|
|
|
+ const rowData = {};
|
|
|
+ tableKeys.forEach((key, index) => {
|
|
|
+ rowData[tableHeaders[index]] = row[key];
|
|
|
+ });
|
|
|
+ return rowData;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建工作簿和工作表
|
|
|
+ const wb = XLSX.utils.book_new();
|
|
|
+ const ws = XLSX.utils.json_to_sheet(tableData, { header: tableHeaders });
|
|
|
+
|
|
|
+ // 计算列宽 - 动态计算合适的列宽
|
|
|
+ ws['!cols'] = tableHeaders.map((header, index) => {
|
|
|
+ // 尝试计算合适的列宽,考虑标题和内容
|
|
|
+ const headerLength = header ? header.toString().length : 0;
|
|
|
+
|
|
|
+ // 计算该列数据的最大长度
|
|
|
+ let maxLength = headerLength;
|
|
|
+ tableData.forEach(row => {
|
|
|
+ const cellValue = row[header];
|
|
|
+ const cellLength = cellValue ? cellValue.toString().length : 0;
|
|
|
+ maxLength = Math.max(maxLength, cellLength);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 为确保列宽适合内容,添加一些额外空间
|
|
|
+ return { wch: Math.min(50, maxLength + 2) }; // 限制最大宽度为50个字符
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加工作表到工作簿
|
|
|
+ XLSX.utils.book_append_sheet(wb, ws, '数据表');
|
|
|
+
|
|
|
+ // 导出文件,使用用户指定的文件名
|
|
|
+ XLSX.writeFile(wb, `${this.exportForm.fileName}.xlsx`);
|
|
|
+
|
|
|
+ // 导出成功
|
|
|
+ this.$message.success('导出成功');
|
|
|
+ this.exportDialogVisible = false;
|
|
|
+ this.exporting = false;
|
|
|
+ }).catch(err => {
|
|
|
+ console.error('导出Excel失败:', err);
|
|
|
+ this.$message.error('导出Excel失败,请重试');
|
|
|
+ this.exporting = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 执行CSV导出
|
|
|
+ doExportCSV() {
|
|
|
+ // 准备表头和数据
|
|
|
+ const tableHeaders = this.header.map(header => header.name);
|
|
|
+ const tableKeys = this.header.map(header => header.key);
|
|
|
+
|
|
|
+ // 创建CSV内容
|
|
|
+ let csvContent = '\uFEFF'; // 添加BOM标记以支持中文
|
|
|
+
|
|
|
+ // 添加表头
|
|
|
+ csvContent += tableHeaders.join(',') + '\r\n';
|
|
|
+
|
|
|
+ // 添加数据行
|
|
|
+ this.list.forEach(row => {
|
|
|
+ const rowValues = tableKeys.map(key => {
|
|
|
+ const value = row[key] !== undefined && row[key] !== null ? row[key] : '';
|
|
|
+ // 如果值包含逗号、引号或换行符,需要用引号包裹并转义内部引号
|
|
|
+ if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) {
|
|
|
+ return `"${value.replace(/"/g, '""')}"`;
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ });
|
|
|
+ csvContent += rowValues.join(',') + '\r\n';
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建Blob对象
|
|
|
+ const blob = new Blob([csvContent], {
|
|
|
+ type: 'text/csv;charset=utf-8'
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建下载链接,使用用户指定的文件名
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = URL.createObjectURL(blob);
|
|
|
+ link.download = `${this.exportForm.fileName}.csv`;
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ },
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
@@ -272,11 +538,134 @@ export default {
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
-/*.txtScroll-top .infoList li:nth-child(n) {
|
|
|
- background: rgb(0, 59, 81);
|
|
|
+/* 添加导出按钮样式 */
|
|
|
+.export-button-container {
|
|
|
+ position: absolute;
|
|
|
+ right: 0px;
|
|
|
+ z-index: 100;
|
|
|
+
|
|
|
+ .export-button {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ cursor: pointer;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: scale(1.05);
|
|
|
+ background-color: rgba(39, 174, 96, 1) !important;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
|
+ }
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .button-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 添加导出信息样式 */
|
|
|
+.export-info {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 10px 15px;
|
|
|
+ margin-top: 5px;
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin: 5px 0;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 5px;
|
|
|
+ color: #409EFF;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保对话框在大屏中显示正常 */
|
|
|
+::v-deep .el-dialog {
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
|
|
|
+ position: relative;
|
|
|
+ z-index: 9999 !important;
|
|
|
+
|
|
|
+ .el-dialog__header {
|
|
|
+ padding: 15px 20px;
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog__body {
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog__footer {
|
|
|
+ padding: 10px 20px 15px;
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-form-item {
|
|
|
+ margin-bottom: 15px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 为移动设备添加特定样式 */
|
|
|
+::v-deep .export-dialog {
|
|
|
+ @media screen and (max-width: 576px) {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ margin: 0 !important;
|
|
|
+
|
|
|
+ .el-dialog {
|
|
|
+ width: 100% !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ max-width: 100%;
|
|
|
+ border-radius: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ .el-dialog__body {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-form-item__label {
|
|
|
+ width: 80px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-radio-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .el-radio {
|
|
|
+ margin-left: 0;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @media screen and (max-width: 768px) {
|
|
|
+ .el-form-item__label {
|
|
|
+ width: 80px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-form {
|
|
|
+ .el-radio-group {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.txtScroll-top .infoList li:nth-child(2n) {
|
|
|
- background: rgb(10, 39, 50);
|
|
|
-}*/
|
|
|
+::v-deep .v-modal {
|
|
|
+ z-index: 9998 !important;
|
|
|
+}
|
|
|
</style>
|