|
|
@@ -29,13 +29,13 @@
|
|
|
<el-button icon="el-icon-view" size="mini" style="margin-left: 10px;" type="primary" @click="handleViewSnapshot">
|
|
|
{{ $t('查看快照') }}
|
|
|
</el-button>
|
|
|
- <el-button icon="el-icon-picture-outline" size="mini" style="margin-left: 10px;" type="warning" @click="exportTableAsImage" :disabled="!devicelevelList || devicelevelList.length === 0 || deviceTrainingLoad">
|
|
|
- {{ $t('导出图片') }}
|
|
|
+ <el-button icon="el-icon-download" size="mini" style="margin-left: 10px;" type="success" @click="exportToExcel" :disabled="!devicelevelList || devicelevelList.length === 0 || deviceTrainingLoad">
|
|
|
+ {{ $t('导出Excel') }}
|
|
|
</el-button>
|
|
|
<el-button icon="el-icon-arrow-down" size="mini" style="margin-left: 10px;" type="primary" @click="increaseHeight">
|
|
|
增加高度
|
|
|
</el-button>
|
|
|
-
|
|
|
+
|
|
|
<el-row style="text-align: right">
|
|
|
<svg-icon class="rectangleGreen" icon-class="rectangleGreen"></svg-icon>
|
|
|
<span>{{ $t('需参加培训,已经完成培训人员') }}</span>
|
|
|
@@ -160,7 +160,6 @@ import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
|
|
import {addCommonfile, allFileList, delCommonfile, updateCommonfile} from "@/api/common/commonfile";
|
|
|
import VueDraggableResizable from 'vue-draggable-resizable';
|
|
|
import { createSnapshot, querySnapshot } from '@/api/training/snapshot';
|
|
|
-import html2canvas from 'html2canvas';
|
|
|
|
|
|
export default {
|
|
|
name: "records",
|
|
|
@@ -334,7 +333,7 @@ export default {
|
|
|
},
|
|
|
//人员表查询参数
|
|
|
staffmgrQueryParams: {
|
|
|
- units: "10,18,20,30",
|
|
|
+ units: this.$constants.DEFAULT_UNITS,
|
|
|
leftYear: this.getNowTime(),
|
|
|
},
|
|
|
queryCompanyParams: {
|
|
|
@@ -348,7 +347,7 @@ export default {
|
|
|
trainingType: null
|
|
|
},
|
|
|
deviceParams: {
|
|
|
- units: "10,18,20,30",
|
|
|
+ units: this.$constants.DEFAULT_UNITS,
|
|
|
staffId: null,
|
|
|
regularId: null,
|
|
|
year: null,
|
|
|
@@ -482,35 +481,35 @@ export default {
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
// vue-draggable-resizable 相关方法
|
|
|
onVdrActivated() {
|
|
|
console.log('容器激活');
|
|
|
},
|
|
|
-
|
|
|
+
|
|
|
onVdrDeactivated() {
|
|
|
console.log('容器失活');
|
|
|
},
|
|
|
-
|
|
|
+
|
|
|
onVdrDragStop(x, y) {
|
|
|
console.log('拖拽停止:', { x, y });
|
|
|
this.vdr.x = x;
|
|
|
this.vdr.y = y;
|
|
|
},
|
|
|
-
|
|
|
+
|
|
|
// 增加容器高度
|
|
|
increaseHeight() {
|
|
|
console.log('增加高度前:', this.vdr.height);
|
|
|
this.vdr.height += 200; // 每次增加200px
|
|
|
console.log('增加高度后:', this.vdr.height);
|
|
|
-
|
|
|
+
|
|
|
// 同时更新表格高度
|
|
|
this.clientHeight = (this.vdr.height - 80) * 0.8;
|
|
|
console.log('更新表格高度:', this.clientHeight);
|
|
|
},
|
|
|
-
|
|
|
+
|
|
|
colspanMethod({row, column, rowIndex, columnIndex}) {
|
|
|
if (columnIndex === 0) {
|
|
|
const _row = this.setTable(this.companylevelList).merge[rowIndex];
|
|
|
@@ -1269,163 +1268,83 @@ export default {
|
|
|
this.$refs.snapshotForm.resetFields();
|
|
|
});
|
|
|
},
|
|
|
- /** 导出图片 */
|
|
|
- exportTableAsImage() {
|
|
|
+ /** 导出Excel */
|
|
|
+ exportToExcel() {
|
|
|
if (!this.devicelevelList || this.devicelevelList.length === 0) {
|
|
|
this.msgError('没有可导出的数据');
|
|
|
return;
|
|
|
}
|
|
|
- // 构建离屏纯HTML表格,避免Element UI结构影响
|
|
|
- const { container, width } = this.buildPlainExportTable();
|
|
|
- const height = container.scrollHeight;
|
|
|
- const scale = Math.min((window.devicePixelRatio || 2), 2);
|
|
|
-
|
|
|
- this.msgSuccess('正在导出,请稍候...');
|
|
|
- html2canvas(container, {
|
|
|
- backgroundColor: '#FFFFFF',
|
|
|
- scale,
|
|
|
- width,
|
|
|
- height,
|
|
|
- windowWidth: width,
|
|
|
- windowHeight: height,
|
|
|
- scrollX: 0,
|
|
|
- scrollY: 0,
|
|
|
- useCORS: true,
|
|
|
- allowTaint: true
|
|
|
- }).then(canvas => {
|
|
|
- const dataURL = canvas.toDataURL('image/png');
|
|
|
- const link = document.createElement('a');
|
|
|
- const fileName = `培训矩阵_${this.deviceParams.year || ''}.png`;
|
|
|
- link.href = dataURL;
|
|
|
- link.download = fileName;
|
|
|
- document.body.appendChild(link);
|
|
|
- link.click();
|
|
|
- document.body.removeChild(link);
|
|
|
- }).catch(err => {
|
|
|
- console.error(err);
|
|
|
- this.msgError('导出失败,请重试');
|
|
|
- }).finally(() => {
|
|
|
- // 移除离屏容器
|
|
|
- if (container && container.parentNode) {
|
|
|
- container.parentNode.removeChild(container);
|
|
|
- }
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- // 构建离屏纯HTML表格,返回 { container, width }
|
|
|
- buildPlainExportTable() {
|
|
|
- const padding = 24;
|
|
|
- const codeColWidth = 100;
|
|
|
- const nameColWidth = 250;
|
|
|
- const staffColWidth = 150;
|
|
|
-
|
|
|
- // 计算总宽
|
|
|
- const staffCount = Array.isArray(this.deviceStaffmgrs) ? this.deviceStaffmgrs.length : 0;
|
|
|
- const totalWidth = padding * 2 + codeColWidth + nameColWidth + staffCount * staffColWidth;
|
|
|
-
|
|
|
- // 创建离屏容器
|
|
|
- const container = document.createElement('div');
|
|
|
- container.style.position = 'fixed';
|
|
|
- container.style.left = '-10000px';
|
|
|
- container.style.top = '0';
|
|
|
- container.style.background = '#FFFFFF';
|
|
|
- container.style.zIndex = '-1';
|
|
|
- container.style.padding = padding + 'px';
|
|
|
- container.style.width = totalWidth + 'px';
|
|
|
- container.style.boxSizing = 'border-box';
|
|
|
- container.style.color = '#000';
|
|
|
-
|
|
|
- // 标题
|
|
|
- const title = document.createElement('div');
|
|
|
- title.style.fontSize = '16px';
|
|
|
- title.style.fontWeight = '600';
|
|
|
- title.style.marginBottom = '12px';
|
|
|
- title.innerText = `${this.deviceParams.year || ''} 装置级 培训人员 名单`;
|
|
|
- container.appendChild(title);
|
|
|
-
|
|
|
- // 说明
|
|
|
- const tip = document.createElement('div');
|
|
|
- tip.style.fontSize = '12px';
|
|
|
- tip.style.color = '#666';
|
|
|
- tip.style.marginBottom = '8px';
|
|
|
- tip.innerText = '(导出自系统视图,含完整数据)';
|
|
|
- container.appendChild(tip);
|
|
|
|
|
|
- // 生成纯表格
|
|
|
- const table = document.createElement('table');
|
|
|
- table.style.borderCollapse = 'collapse';
|
|
|
- table.style.width = totalWidth - padding * 2 + 'px';
|
|
|
- table.style.tableLayout = 'fixed';
|
|
|
- table.style.fontSize = '12px';
|
|
|
+ try {
|
|
|
+ // 构建表格HTML
|
|
|
+ let html = '<table border="1" cellspacing="0" cellpadding="5">';
|
|
|
|
|
|
- const borderStyle = '1px solid #ccc';
|
|
|
-
|
|
|
- // thead
|
|
|
- const thead = document.createElement('thead');
|
|
|
- const headTr = document.createElement('tr');
|
|
|
- const thStyle = (w) => `border:${borderStyle};padding:6px;background:#f7f7f7;word-break:break-all;width:${w}px;`;
|
|
|
- const tdStyle = (w) => `border:${borderStyle};padding:6px;word-break:break-all;width:${w}px;`;
|
|
|
-
|
|
|
- const thCode = document.createElement('th');
|
|
|
- thCode.style.cssText = thStyle(codeColWidth);
|
|
|
- thCode.innerText = '课程代码';
|
|
|
- headTr.appendChild(thCode);
|
|
|
-
|
|
|
- const thName = document.createElement('th');
|
|
|
- thName.style.cssText = thStyle(nameColWidth);
|
|
|
- thName.innerText = '培训课程名称';
|
|
|
- headTr.appendChild(thName);
|
|
|
-
|
|
|
- // 人员列
|
|
|
- if (staffCount > 0) {
|
|
|
- for (let i = 0; i < staffCount; i++) {
|
|
|
- const th = document.createElement('th');
|
|
|
- th.style.cssText = thStyle(staffColWidth);
|
|
|
- const staff = this.deviceStaffmgrs[i] || {};
|
|
|
- th.innerText = `${staff.name || ''}${staff.actualpost ? '(' + staff.actualpost + ')' : ''}`;
|
|
|
- headTr.appendChild(th);
|
|
|
+ // 构建表头
|
|
|
+ html += '<thead><tr>';
|
|
|
+ html += '<th style="background:#f0f0f0;">课程代码</th>';
|
|
|
+ html += '<th style="background:#f0f0f0;">培训课程名称</th>';
|
|
|
+ if (Array.isArray(this.deviceStaffmgrs)) {
|
|
|
+ this.deviceStaffmgrs.forEach(staff => {
|
|
|
+ const headerText = `${staff.name || ''}${staff.actualpost ? '(' + staff.actualpost + ')' : ''}`;
|
|
|
+ html += `<th style="background:#f0f0f0;">${this.escapeHtml(headerText)}</th>`;
|
|
|
+ });
|
|
|
}
|
|
|
- }
|
|
|
- thead.appendChild(headTr);
|
|
|
- table.appendChild(thead);
|
|
|
+ html += '</tr></thead>';
|
|
|
|
|
|
- // tbody
|
|
|
- const tbody = document.createElement('tbody');
|
|
|
- const rows = Array.isArray(this.devicelevelList) ? this.devicelevelList : [];
|
|
|
- for (let r = 0; r < rows.length; r++) {
|
|
|
- const tr = document.createElement('tr');
|
|
|
- const row = rows[r] || [];
|
|
|
+ // 构建数据行
|
|
|
+ html += '<tbody>';
|
|
|
+ if (Array.isArray(this.devicelevelList)) {
|
|
|
+ this.devicelevelList.forEach(row => {
|
|
|
+ html += '<tr>';
|
|
|
+ if (Array.isArray(row)) {
|
|
|
+ row.forEach(cell => {
|
|
|
+ const cellValue = cell != null ? String(cell) : '';
|
|
|
+ html += `<td>${this.escapeHtml(cellValue)}</td>`;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ html += '</tr>';
|
|
|
+ });
|
|
|
+ }
|
|
|
+ html += '</tbody></table>';
|
|
|
|
|
|
- const tdCode = document.createElement('td');
|
|
|
- tdCode.style.cssText = tdStyle(codeColWidth);
|
|
|
- tdCode.innerText = row[0] != null ? String(row[0]) : '';
|
|
|
- tr.appendChild(tdCode);
|
|
|
+ // 创建Blob并下载
|
|
|
+ const blob = new Blob(['\ufeff' + html], {
|
|
|
+ type: 'application/vnd.ms-excel;charset=utf-8;'
|
|
|
+ });
|
|
|
|
|
|
- const tdName = document.createElement('td');
|
|
|
- tdName.style.cssText = tdStyle(nameColWidth);
|
|
|
- tdName.innerText = row[1] != null ? String(row[1]) : '';
|
|
|
- tr.appendChild(tdName);
|
|
|
+ const fileName = `装置级培训人员名单_${this.deviceParams.year || new Date().getFullYear()}.xls`;
|
|
|
|
|
|
- for (let c = 0; c < staffCount; c++) {
|
|
|
- const td = document.createElement('td');
|
|
|
- const cell = row[c + 2];
|
|
|
- td.style.cssText = tdStyle(staffColWidth) + ';text-align:center;';
|
|
|
- td.innerText = cell != null ? String(cell) : '';
|
|
|
- tr.appendChild(td);
|
|
|
+ // 创建下载链接
|
|
|
+ if (window.navigator.msSaveOrOpenBlob) {
|
|
|
+ // IE浏览器
|
|
|
+ window.navigator.msSaveOrOpenBlob(blob, fileName);
|
|
|
+ } else {
|
|
|
+ // 其他浏览器
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = URL.createObjectURL(blob);
|
|
|
+ link.download = fileName;
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ URL.revokeObjectURL(link.href);
|
|
|
}
|
|
|
- tbody.appendChild(tr);
|
|
|
- }
|
|
|
- table.appendChild(tbody);
|
|
|
-
|
|
|
- container.appendChild(table);
|
|
|
- document.body.appendChild(container);
|
|
|
|
|
|
- return { container, width: totalWidth };
|
|
|
+ this.msgSuccess('导出成功');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('导出Excel失败:', error);
|
|
|
+ this.msgError('导出失败,请重试');
|
|
|
+ }
|
|
|
},
|
|
|
-
|
|
|
- // 根据单元格值返回导出时的内联样式(背景/字体颜色),与页面配色保持一致语义
|
|
|
- getExportCellStyle(value) {
|
|
|
- return '';
|
|
|
+ /** HTML转义 */
|
|
|
+ escapeHtml(text) {
|
|
|
+ const map = {
|
|
|
+ '&': '&',
|
|
|
+ '<': '<',
|
|
|
+ '>': '>',
|
|
|
+ '"': '"',
|
|
|
+ "'": '''
|
|
|
+ };
|
|
|
+ return String(text).replace(/[&<>"']/g, m => map[m]);
|
|
|
}
|
|
|
}
|
|
|
};
|