feat: 合并

This commit is contained in:
砂糖
2025-09-02 15:03:34 +08:00
parent c50adfd648
commit 6e8793b290
29 changed files with 4110 additions and 24 deletions

View File

@@ -0,0 +1,23 @@
<script>
export default {
name: 'ColumnRender',
functional: true,
props: {
column: {
type: Object,
required: true
},
render: {
type: Function,
required: true
},
data: {
type: Object,
required: true
}
},
render(h, { props }) {
return props.render(h, props.data);
}
}
</script>

View File

@@ -0,0 +1,179 @@
<template>
<el-col :span="12" class="table-action-toolbar">
<el-button v-if="tools.includes('fullScreen')" size="mini" icon="el-icon-full-screen" circle
@click="handleFullScreen"></el-button>
<el-button v-if="tools.includes('refresh')" size="mini" icon="el-icon-refresh" circle
@click="handleRefresh"></el-button>
<el-button v-if="tools.includes('export')" size="mini" icon="el-icon-download" circle
@click="handleExport"></el-button>
<el-button v-if="tools.includes('print')" size="mini" icon="el-icon-printer" circle
@click="handlePrint"></el-button>
<!-- 设置按钮和弹出框 -->
<el-dropdown v-if="tools.includes('setting')" trigger="click" :hide-on-click="false" style="margin-left: 10px;">
<el-tooltip class="item" effect="dark" content="显隐列" placement="top">
<el-button size="mini" circle icon="el-icon-menu" />
</el-tooltip>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="column in columns" :key="column.prop">
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
</template>
<script>
import XLSX from 'xlsx';
import { saveAs } from 'file-saver';
export default {
name: 'TableActionToolbar',
props: {
// 控制显示哪些工具按钮
tools: {
type: Array,
required: true
},
// 表格整体ref用于获取DOM
tableRef: {
type: Object,
default: null
},
// 表格数据,用于导出
tableData: {
type: Array,
default: () => []
},
// 表格列定义,用于导出和设置
columns: {
type: Array,
default: () => []
},
// 导出文件名
exportFileName: {
type: String,
default: '表格数据'
}
},
computed: {
_columns: {
get() {
console.log(this.columns);
return this.columns.filter(col => col.show);
},
set(value) {
this.$emit('columnChange', value);
}
}
},
methods: {
// 全屏处理
handleFullScreen() {
const tableEl = this.tableRef?.$el;
if (!tableEl) {
this.$emit('fullScreen');
return;
}
if (!document.fullscreenElement) {
tableEl.requestFullscreen().catch(err => {
this.$message.error(`全屏请求失败: ${err.message}`);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
},
// 刷新处理
handleRefresh() {
this.$emit('refresh');
},
// 导出Excel处理
handleExport() {
if (this.tableData.length === 0) {
this.$message.warning('没有数据可导出');
return;
}
// 准备导出数据 - 只包含列定义中指定的字段
const exportData = this.tableData.map(row => {
const formattedRow = {};
this.columns.forEach(col => {
if (col.prop && !col.hidden) {
// 使用列的label作为表头prop对应的数据作为值
formattedRow[col.label || col.prop] = row[col.prop];
}
});
return formattedRow;
});
// 创建工作簿和工作表
const worksheet = XLSX.utils.json_to_sheet(exportData);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
// 生成Excel文件并下载
const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
this.saveExcelFile(excelBuffer, this.exportFileName);
},
// 保存Excel文件
saveExcelFile(buffer, fileName) {
const blob = new Blob([buffer], { type: 'application/octet-stream' });
saveAs(blob, `${fileName}_${new Date().toLocaleDateString()}.xlsx`);
},
// 打印处理
handlePrint() {
this.$emit('print');
},
// 列显示状态变化处理
handleColumnChange(checked) {
// 找出所有列的显示状态变化
const columnChanges = this.visibleColumns.map(col => ({
prop: col.prop,
hidden: !checked.includes(col.prop)
}));
// 触发事件通知父组件更新列显示状态
this.$emit('columnChange', columnChanges);
// 刷新表格布局
if (this.tableRef && this.tableRef.doLayout) {
this.tableRef.doLayout();
}
}
}
};
</script>
<style scoped>
.table-action-toolbar {
text-align: left;
display: flex;
gap: 4px;
}
/* 按钮间距 */
::v-deep .el-button {
margin-left: 4px;
}
.column-setting {
padding: 5px 0;
}
::v-deep .el-checkbox {
display: block;
margin-bottom: 8px;
}
::v-deep .el-checkbox:last-child {
margin-bottom: 0;
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<div class="my-table-container">
<!-- 扩展层加载动画 -->
<div v-if="loading" class="table-loading">
<el-loading-spinner></el-loading-spinner>
<p class="loading-text">{{ loadingText || "加载中..." }}</p>
</div>
<el-row>
<!-- 自定义操作按钮左对齐 -->
<el-col :span="12">
<slot name='actionRow'>
<!-- 分页 -->
<span style="color: #ccc;"></span>
</slot>
</el-col>
<!-- 通用操作工具栏 -->
<TableActionToolbar
:tools="actionTool"
:table-ref="getTableInstance()"
:table-data="tableData"
:columns="columns"
:export-file-name="exportFileName"
@fullScreen="handleFullScreen"
@refresh="handleRefresh"
@export="handleExport"
@print="handlePrint"
@columnChange="updateColumnsVisibility"
/>
</el-row>
<!-- 原生 Table 核心 -->
<el-table
:ref="tableRef"
v-bind="$attrs"
v-on="$listeners"
:class="['my-table', customClass]"
:data="tableData"
>
<!-- 透传列定义插槽 -->
<template
v-for="(column, index) in $attrs.columns || []"
v-slot:[`header-${column.prop}`]="scope"
>
<slot :name="`header-${column.prop}`" v-bind="scope"></slot>
</template>
<template
v-for="(column, index) in $attrs.columns || []"
v-slot:[column.prop]="scope"
>
<slot :name="column.prop" v-bind="scope"></slot>
</template>
<!-- 透传原生内置插槽 -->
<template v-slot:empty="scope">
<slot name="empty" v-bind="scope"></slot>
</template>
<template v-slot:append="scope">
<slot name="append" v-bind="scope"></slot>
</template>
<!-- 透传自定义列插槽 -->
<slot v-bind:tableRef="tableRef"></slot>
<el-table-column
v-if="selectionColumn"
type="selection"
width="55"
align="center"
></el-table-column>
<el-table-column
v-if="indexColumn"
type="index"
width="55"
align="center"
></el-table-column>
<el-table-column
v-for="(column, index) in _columns"
v-bind="column"
:width="column.width"
:prop="column.prop"
:align="column.align"
:label="column.label"
:min-width="column.minWidth"
:fixed="column.fixed"
:show-overflow-tooltip="column.showOverflowTooltip"
:sortable="column.sortable"
>
<template v-slot="scope">
<ColumnRender v-if="column.render" :column="column" :data="scope.row" :render="column.render"/>
<Eclipse v-else-if="column.eclipse" :text="scope.row[column.prop]"></Eclipse>
<span v-else>{{ scope.row[column.prop] }}</span>
</template>
</el-table-column>
</el-table>
<!-- 分页插槽 -->
<slot name="pagination"></slot>
</div>
</template>
<script>
import ColumnRender from './ColumnRender.vue';
import Eclipse from './renderer/eclipse.vue';
import TableActionToolbar from './TableActionToolbar.vue';
export default {
name: "KLPTable",
components: {
ColumnRender,
Eclipse,
TableActionToolbar
},
props: {
// 基础扩展属性
loading: {
type: Boolean,
default: false
},
loadingText: {
type: String,
default: "加载中..."
},
customClass: {
type: String,
default: ""
},
// 表格数据
tableData: {
type: Array,
default: () => []
},
// 列定义
customColumns: {
type: Array,
default: () => []
},
// 选择列
selectionColumn: {
type: Boolean,
default: false
},
// 索引列
indexColumn: {
type: Boolean,
default: false
},
// 操作工具
actionTool: {
type: Array,
default: () => ['fullScreen', 'export', 'setting']
},
// 导出文件名
exportFileName: {
type: String,
default: '表格数据'
}
},
data() {
return {
tableRef: "myTableRef",
columns: []
};
},
computed: {
_columns() {
return this.columns.filter(col => col.show);
},
},
watch: {
customColumns: {
handler(newVal) {
console.log(newVal, this.customColumns, '自定义列');
this.columns = newVal.map(col => ({...col, show: true}));
},
deep: true,
immediate: true
}
},
methods: {
// 获取表格实例
getTableInstance() {
return this.$refs[this.tableRef];
},
// 透传原生方法
clearSelection() {
const table = this.getTableInstance();
if (table && table.clearSelection) {
table.clearSelection();
}
},
toggleRowSelection(row, selected) {
const table = this.getTableInstance();
if (table && table.toggleRowSelection) {
table.toggleRowSelection(row, selected);
}
},
// 操作处理方法(可被覆盖或扩展)
handleFullScreen() {
this.$emit('fullScreen');
},
handleRefresh() {
this.$emit('refresh');
},
handleExport() {
this.$emit('export');
},
handlePrint() {
this.$emit('print');
},
// handleSetting() {
// this.$emit('setting');
// },
updateColumnsVisibility(changes) {
// 更新列的hidden属性
changes.forEach(change => {
const column = this.columns.find(col => col.prop === change.prop);
if (column) {
column.show = !column.show;
}
});
}
},
mounted() {
console.log("KLPTable 初始化完成,原生实例:", this.getTableInstance());
}
};
</script>
<style scoped>
.my-table-container {
width: 100%;
position: relative;
}
.table-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
}
.loading-text {
margin-top: 12px;
color: #666;
font-size: 14px;
}
.my-table {
width: 100%;
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<div class="eclipse-container">
<el-tooltip :content="text" placement="top" :disabled="text.length < 10">
<span class="eclipse">{{ text }}</span>
</el-tooltip>
</div>
</template>
<script>
export default {
name: 'Eclipse',
props: {
text: {
type: String,
required: true
},
}
}
</script>
<style>
.eclipse-container {
position: relative; /* 为tooltip提供定位参考 */
display: inline-block; /* 确保容器只占内容宽度 */
}
.eclipse {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block; /* 确保元素有明确的尺寸 */
max-width: 100%; /* 限制最大宽度,确保溢出效果生效 */
}
</style>