// 动态表格生成器
class DynamicTableGenerator {
/**
* 构造函数
* @param {Object} config - 表格配置对象
*/
constructor(config) {
this.config = {
searchFields: [], // 设置默认值,避免undefined错误
...config
};
}
/**
* 生成表格组件的JavaScript代码
* @returns {string} - 完整的表格组件JS代码
*/
generateTableComponent() {
const {
componentId,
tableName,
columns = [], // 增加默认值
hasSearch = false, // 增加默认值
searchFields,
accountId = '', // 增加默认值
year = new Date().getFullYear() // 增加默认值
} = this.config;
// 1. 生成查询字段的data部分(统一缩进,避免生成代码杂乱)
let queryData = '';
if (hasSearch && searchFields.length > 0) {
queryData = `query: {
${searchFields.map(field => ` ${field.field}: ''`).join(',\n')}
},`;
}
// 2. 生成列配置(修复JSON转义,确保对象格式正确)
const columnsConfig = JSON.stringify(columns, null, 6) // 缩进6格,匹配组件内data结构
.replace(/"([^"]+)":/g, '$1:') // 去掉key的双引号,转为Vue可识别的对象字面量
.replace(/\\"/g, '"'); // 处理转义引号(如label含双引号时)
// 3. 生成搜索框模板(统一缩进,避免污染外层template)
let searchTemplate = '';
if (hasSearch && searchFields.length > 0) {
const colSpan = Math.max(2, Math.floor(24 / searchFields.length)); // 最小占2列,避免布局错乱
// 用String.raw确保内部引号不被转义,同时统一缩进为4格(匹配template内部结构)
searchTemplate = String.raw` <!-- 查询条件区域 -->
<div class="search-container" style="margin-bottom: 20px; padding: 10px; background-color: #f5f7fa; border-radius: 4px;">
<el-row :gutter="20">
${searchFields.map((field) => ` <el-col :span="${colSpan}">
<el-input
v-model="query.${field.field}"
placeholder="${field.placeholder || `请输入${field.label || ''}`}"
clearable
></el-input>
</el-col>`).join('\n')}
</el-row>
<div style="margin-top: 10px;">
<el-button @click="handleSearch" type="primary">查询</el-button>
<el-button @click="resetSearch" style="margin-left: 10px;">重置</el-button>
</div>
</div>`;
}
// 4. 生成表格列模板(修复属性绑定,统一缩进)
const tableColumnsTemplate = columns.map(column => String.raw`
<el-table-column
:prop="${JSON.stringify(column.prop)}"
:label="${JSON.stringify(column.label || '')}"
:width="${column.width ? JSON.stringify(column.width) : '""'}"
:sortable="true"
:align="${JSON.stringify(column.align || 'left')}"
:header-align="${JSON.stringify(column.align || 'left')}"
:visible="${column.visible ?? true}"
></el-table-column>`).join('');
// 5. 构建完整组件代码(核心:彻底处理template的嵌套转义)
const componentCode = String.raw`// 动态生成的表格组件 - ${componentId}
import createExportMixin from './mixins/export-mixin.js';
import axios from 'axios'; // 确保导入axios
// 创建mixin实例
const exportMixin1 = createExportMixin();
Vue.component('${componentId}', {
mixins: [exportMixin1],
data() {
return {
tableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
// 查询条件
${queryData}
// 排序相关配置
sortField: '',
sortOrder: '',
// 列配置信息
columns: ${columnsConfig},
// 列设置对话框显示状态
columnSettingDialogVisible: false,
// 账套信息
accountId: '${accountId}',
year: '${year}',
tableName: '${tableName || '数据表格'}'
};
},
mounted() {
this.fetchTableData();
},
methods: {
/**
* 查询数据方法
*/
handleSearch() {
console.log('查询按钮被点击,查询条件:', this.query);
this.currentPage = 1;
this.fetchTableData();
},
/**
* 重置查询条件方法
*/
resetSearch() {
// 重置查询条件
${hasSearch && searchFields.length > 0 ?
`this.query = {
${searchFields.map(field => ` ${field.field}: ''`).join(',\n')}
};` : ''}
this.currentPage = 1;
this.fetchTableData();
},
/**
* 处理表格排序变化
*/
handleSortChange({ prop, order }) {
this.sortField = prop;
this.sortOrder = order;
this.fetchTableData();
},
/**
* 获取表格数据方法
*/
fetchTableData() {
console.log('获取表格数据:', this.tableName);
const params = {
page: this.currentPage,
page_size: this.pageSize,
table_name: this.tableName,
account_id: this.accountId,
year: this.year
};
// 添加查询条件
${hasSearch && searchFields.length > 0 ?
searchFields.map(field => `
if (this.query.${field.field}?.trim()) {
params['${field.field}'] = this.query.${field.field}.trim();
}`).join('') : ''}
// 添加排序参数
if (this.sortField && this.sortOrder) {
params.order_field = this.sortField;
params.order_direction = this.sortOrder === 'ascending' ? 'asc' : 'desc';
}
// 发送请求
axios.get('/api/generic/query/', { params })
.then(response => {
this.tableData = response.data?.data || [];
this.total = response.data?.count || 0;
})
.catch(error => {
console.error('获取表格数据失败:', error);
this.tableData = [];
this.total = 0;
if (this.$message) {
this.$message.error('获取表格数据失败,请重试');
}
});
},
/**
* 处理每页条数变化
*/
handleSizeChange(size) {
this.pageSize = size;
this.fetchTableData();
},
/**
* 处理当前页码变化
*/
handleCurrentChange(current) {
this.currentPage = current;
this.fetchTableData();
},
/**
* 实现导出mixin所需的接口方法
*/
getExportData() {
const exportData = this.tableData.map(item => {
const row = {};
${columns.map(column => `
row['${column.label || ''}'] = item['${column.prop}'] ?? '';`).join('')}
return row;
});
// 设置列宽
const columnWidths = [
${columns.map(column => `
{wch: ${column.width ? Math.floor(parseInt(column.width) / 7) : 15}}`).join(',')}
];
return {
data: exportData,
columnWidths: columnWidths,
sheetName: '${tableName || '表格'}数据'
};
},
/**
* 打印表格方法
*/
printTable() {
const printWindow = window.open('', '_blank');
if (!printWindow) {
if (this.$message) {
this.$message.warning('请允许弹窗以完成打印操作');
}
return;
}
const tableHtml = this.$el?.querySelector('.el-table')?.outerHTML || '<div>无表格数据</div>';
// 内部模板字符串用转义,避免与外层冲突
const printContent = `
<!DOCTYPE html>
<html>
<head>
<title>打印表格</title>
<link rel="stylesheet" href="../../lib/element-ui.css">
<style>
@media print {
body { padding: 20px; }
.el-table { width: 100% !important; border-collapse: collapse; }
.el-table th, .el-table td { padding: 8px !important; border: 1px solid #ddd !important; }
.el-table__header-wrapper th, .el-table__body-wrapper td { border: 1px solid #ddd !important; }
}
</style>
</head>
<body>
${tableHtml}
<script>
window.print();
setTimeout(() => window.close(), 100);
</script>
</body>
</html>
`;
printWindow.document.open();
printWindow.document.write(printContent);
printWindow.document.close();
},
/**
* 预览表格方法
*/
previewTable() {
const previewWindow = window.open('', '_blank');
if (!previewWindow) {
if (this.$message) {
this.$message.warning('请允许弹窗以完成预览操作');
}
return;
},
const tableHtml = this.$el?.querySelector('.el-table')?.outerHTML || '<div>无表格数据</div>';
// 内部模板字符串用转义
const previewContent = `
<!DOCTYPE html>
<html>
<head>
<title>预览表格</title>
<link rel="stylesheet" href="../../lib/element-ui.css">
<style>
body { padding: 20px; font-family: Arial, sans-serif; }
.el-table { width: 100% !important; }
.el-table th, .el-table td { padding: 8px !important; }
.preview-controls {
position: fixed; top: 10px; right: 10px; background: white; padding: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1);
z-index: 9999;
}
</style>
</head>
<body>
<div class="preview-controls">
<button onclick="window.print()" style="padding: 5px 10px; margin-right: 10px;">打印</button>
<button onclick="window.close()" style="padding: 5px 10px;">关闭</button>
</div>
${tableHtml}
</body>
</html>
`;
previewWindow.document.open();
previewWindow.document.write(previewContent);
previewWindow.document.close();
},
/**
* 打开列设置对话框
*/
openColumnSettingDialog() {
this.columnSettingDialogVisible = true;
},
/**
* 关闭列设置对话框
*/
closeColumnSettingDialog() {
this.columnSettingDialogVisible = false;
},
/**
* 切换列显示状态
*/
toggleColumnVisibility(prop, visible) {
const column = this.columns.find(col => col.prop === prop);
if (column) {
column.visible = visible;
}
},
/**
* 重置列显示状态
*/
resetColumnSettings() {
this.columns.forEach(column => {
column.visible = true;
});
},
/**
* 更新列对齐方式
*/
updateColumnAlign(prop, align) {
const column = this.columns.find(col => col.prop === prop);
if (column) {
column.align = align;
}
},
/**
* 获取汇总数据
*/
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
// 只对数字类型字段进行汇总
const values = data.map(item => Number(item[column.property]));
const validValues = values.filter(val => !isNaN(val));
if (validValues.length > 0) {
sums[index] = validValues.reduce((prev, curr) => {
const value = Number(curr);
return !isNaN(value) ? prev + value : prev;
}, 0).toFixed(2);
} else {
sums[index] = '';
}
});
return sums;
}
},
template: `
<div class="${componentId}">
${searchTemplate}
<div class="table-container" style="overflow-x: auto; overflow-y: auto; margin-bottom: 20px; min-width: 100%; max-height: 600px;">
<el-table
style="min-width: 100%; white-space: nowrap;"
border
stripe
:data="tableData"
show-summary
:summary-method="getSummaries"
@sort-change="handleSortChange"
:sort-orders="['ascending', 'descending', null]"
>
${tableColumnsTemplate}
</el-table>
</div>
<!-- 分页组件 -->
<div class="pagination-container" style="margin-top: 20px; text-align: right;">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 20, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
<!-- 操作按钮区域 -->
<div style="margin-top: 10px;">
<el-button @click="openExportDialog" type="primary" icon="el-icon-download" style="margin-left: 10px;">导出</el-button>
<el-button @click="previewTable" type="primary" icon="el-icon-view" style="margin-left: 10px;">打印预览</el-button>
<el-button @click="printTable" type="primary" icon="el-icon-printer" style="margin-left: 10px;">打印</el-button>
<el-button @click="openColumnSettingDialog" type="primary" icon="el-icon-s-grid" style="margin-left: 10px;">列设置</el-button>
</div>
<!-- 列设置对话框 -->
<el-dialog
title="列设置"
:visible.sync="columnSettingDialogVisible"
width="400px"
@close="closeColumnSettingDialog"
>
<div style="max-height: 300px; overflow-y: auto;">
<div v-for="column in columns" :key="column.prop" style="margin-bottom: 10px;">
<el-checkbox
v-model="column.visible"
@change="(val) => toggleColumnVisibility(column.prop, val)"
>
{{ column.label || column.prop || '未知列' }}
</el-checkbox>
<div v-if="column.visible" style="margin-top: 5px; margin-left: 25px;">
<el-select
v-model="column.align"
placeholder="对齐方式"
size="small"
style="width: 120px;"
@change="(val) => updateColumnAlign(column.prop, val)"
>
<el-option label="左对齐" value="left"></el-option>
<el-option label="居中" value="center"></el-option>
<el-option label="右对齐" value="right"></el-option>
</el-select>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="resetColumnSettings">重置</el-button>
<el-button @click="closeColumnSettingDialog">确定</el-button>
</div>
</el-dialog>
</div>
`
});
`; // 外层componentCode的闭合
return componentCode;
}
}
// 导出类
export default DynamicTableGenerator;
最新发布