factory_web/src/components/scTable/index.vue

616 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* @Descripttion: 数据表格组件
* @version: 1.10
* @Author: sakuya
* @Date: 2021年11月29日21:51:15
* @LastEditors: sakuya
* @LastEditTime: 2022年6月4日17:35:26
-->
<template>
<div class="scTable" :style="{ height: _height }" ref="scTableMain" v-loading="loading">
<div class="scTable-table" :style="{ height: _table_height }">
<el-table v-bind="$attrs" :data="tableData" :row-key="rowKey || undefined" :key="toggleIndex" ref="scTable"
:height="height == 'auto' ? null : '100%'" :size="config.size" :border="config.border" :stripe="config.stripe"
:summary-method="remoteSummary ? remoteSummaryMethod : summaryMethod" :expand-row-keys="rowKey ? expandRowKeys : undefined" @sort-change="sortChange"
@filter-change="filterChange" @selection-change="selectionChange" @expand-change="handleExpandChange">
<slot></slot>
<template v-for="(item, index) in userColumn" :key="index">
<el-table-column v-if="!item.hide" :column-key="item.prop" :label="item.label" :prop="item.prop"
:width="item.width" :sortable="item.sortable" :fixed="item.fixed" :filters="item.filters" :filter-method="remoteFilter || !item.filters ? null : filterHandler
" :show-overflow-tooltip="item.showOverflowTooltip">
<template #default="scope">
<slot :name="item.prop" v-bind="scope">
{{ scope.row[item.prop] }}
</slot>
</template>
</el-table-column>
</template>
<!-- <el-table-column min-width="1"></el-table-column> -->
<template #empty>
<el-empty :description="emptyText" :image-size="100"></el-empty>
</template>
</el-table>
</div>
<div class="scTable-page" v-if="!hidePagination || !hideDo">
<div class="scTable-pagination">
<el-pagination v-if="!hidePagination" background size="small" :layout="paginationLayout" :total="total"
:page-size="scPageSize" :page-sizes="pageSizes" v-model:currentPage="currentPage"
@current-change="paginationChange" @update:page-size="pageSizeChange"></el-pagination>
</div>
<div class="scTable-do" v-if="!hideDo">
<span v-show="selectNum>0">已选择 <span style="font-weight: bold">{{selectNum}}</span> 项</span>
<el-button v-if="!hideRefresh" @click="refresh" icon="el-icon-refresh" circle style="margin-left: 15px">
</el-button>
<el-button v-if="!hideRefresh" @click="reload" icon="el-icon-refresh-right" circle style="margin-left: 15px">
</el-button>
<el-popover v-if="!hideExport" placement="top" trigger="click" :hide-after="0">
<template #reference>
<el-button icon="el-icon-download" circle style="margin-left: 15px"></el-button>
</template>
<div><el-button type="primary" size="small" @click="exportExcel(0)">1导出本页数据</el-button></div>
<div style="margin-top: 2px;" v-if="hExportCols"><el-button type="primary" size="small" @click="exportExcel(1)">2导出本页数据</el-button></div>
<div style="margin-top: 2px;" v-if="hExportCols"><el-button type="primary" size="small" @click="exportExcel(2)">3导出全部数据</el-button></div>
</el-popover>
<el-popover v-if="column || (slotColumns && slotColumns.length)" placement="top" title="列设置"
:width="column ? 500 : 260" trigger="click" :hide-after="0"
@show="customColumnShow = true" @after-leave="customColumnShow = false">
<template #reference>
<el-button icon="el-icon-set-up" circle style="margin-left: 15px"></el-button>
</template>
<columnSetting v-if="customColumnShow && column" ref="columnSetting" @userChange="columnSettingChange"
@save="columnSettingSave" @back="columnSettingBack" :column="userColumn"></columnSetting>
<template v-if="customColumnShow && slotColumns && slotColumns.length">
<div v-if="column" style="margin: 12px 0; border-top: 1px solid #eee;"></div>
<div v-for="col in slotUserColumn" :key="col.prop" style="padding: 4px 0;">
<el-checkbox :model-value="!col.hide" @change="(val) => toggleSlotCol(col, val)">{{col.label}}</el-checkbox>
</div>
<div style="margin-top: 8px; text-align: right; border-top: 1px solid #eee; padding-top: 8px;">
<el-button size="small" @click="resetSlotColumns">重置</el-button>
<el-button size="small" type="primary" @click="saveSlotColumns">保存</el-button>
</div>
</template>
</el-popover>
<el-popover v-if="!hideSetting" placement="top" title="表格设置" :width="400" trigger="click" :hide-after="0">
<template #reference>
<el-button icon="el-icon-setting" circle style="margin-left: 15px"></el-button>
</template>
<el-form label-width="80px" label-position="left">
<el-form-item label="表格尺寸">
<el-radio-group v-model="config.size" size="small" @change="configSizeChange">
<el-radio-button label="large">大</el-radio-button>
<el-radio-button label="default">正常</el-radio-button>
<el-radio-button label="small">小</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="样式">
<el-checkbox v-model="config.border" label="纵向边框"></el-checkbox>
<el-checkbox v-model="config.stripe" label="斑马纹"></el-checkbox>
</el-form-item>
</el-form>
</el-popover>
</div>
</div>
</div>
</template>
<script>
import config from "@/config/table";
import columnSetting from "./columnSetting";
import { genTree } from "@/utils/verificate";
import { domToExcel, dataToExcel } from "@/utils/exportExcel";
export default {
name: "scTable",
components: {
columnSetting,
},
props: {
expendShow:{ type: Boolean, default: false },
tableName: { type: String, default: "" },
apiObj: { type: Object, default: () => { } },
params: { type: Object, default: () => ({}) },
query: { type: Object, default: () => ({}) },
data: { type: Object, default: () => { } },
height: { type: [String, Number], default: "100%" },
size: { type: String, default: "default" },
border: { type: Boolean, default: false },
stripe: { type: Boolean, default: true },
pageSize: { type: Number, default: config.pageSize },
pageSizes: { type: Array, default: config.pageSizes },
pageStr: { type: String, default: config.request.page },
pageSizeStr: { type: String, default: config.request.pageSize },
orderStr: { type: String, default: config.request.order },
isTree: { type: Boolean, default: false },
parseData: { type: Function, default: config.parseData },
rowKey: { type: String, default: "" },
summaryMethod: { type: Function, default: null },
column: { type: Object, default: () => { } },
remoteSort: { type: Boolean, default: false },
remoteFilter: { type: Boolean, default: false },
remoteSummary: { type: Boolean, default: false },
hidePagination: { type: Boolean, default: false },
hideDo: { type: Boolean, default: false },
hideRefresh: { type: Boolean, default: false },
hideSetting: { type: Boolean, default: false },
hideExport: { type: Boolean, default: false },
hExportCols: { type: Array, default: null },
hExportName: { type: String, default: null },
paginationLayout: { type: String, default: config.paginationLayout },
},
watch: {
//监听从props里拿到值了
data() {
this.tableData = this.data;
this.total = this.tableData.length;
},
expendShow(){
this.toggleExpandAll();
},
apiObj() {
this.tableParams = this.params;
this.refresh();
},
// query 暂时不监听手动调用
// query: {
// handler() {
// this.refresh();
// },
// deep: true
// }
},
computed: {
_height() {
return Number(this.height) ? Number(this.height) + "px" : this.height;
},
_table_height() {
return this.hidePagination && this.hideDo ? "100%" : "calc(100% - 50px)";
},
},
data() {
return {
scPageSize: this.pageSize,
isActivat: true,
emptyText: "暂无数据",
toggleIndex: 0,
tableData: [],
expandRowKeys:[],
total: 0,
currentPage: 1,
prop: null,
order: null,
loading: false,
tableHeight: "100%",
tableParams: this.params,
userColumn: [],
customColumnShow: false,
summary: {},
config: {
size: this.size,
border: true,
stripe: this.stripe,
},
selectNum: 0
};
},
mounted() {
//判断是否开启自定义列
if (this.column) {
this.getCustomColumn();
} else {
this.userColumn = this.column;
}
//判断是否静态数据
if (this.apiObj) {
this.getData();
} else if (this.data) {
if (this.isTree) {
this.tableData = genTree(this.data);
} else {
this.tableData = this.data;
}
this.total = this.tableData.length;
}
},
activated() {
if (!this.isActivat) {
this.$refs.scTable.doLayout();
}
},
deactivated() {
this.isActivat = false;
},
methods: {
//滚动到顶部
scrollToTop() {
this.$refs.scTable.scrollTo(0, 0);
},
//获取列
async getCustomColumn() {
const userColumn = await config.columnSettingGet(
this.tableName,
this.column
);
this.userColumn = userColumn;
},
//获取数据
async getData() {
this.loading = true;
var reqData = {
[this.pageStr]: this.currentPage,
[this.pageSizeStr]: this.scPageSize,
// [config.request.prop]: this.prop,
[this.orderStr]: this.order,
};
if (this.hidePagination) {
reqData[this.pageStr] = 0;
// delete reqData[config.request.page]
// delete reqData[config.request.pageSize]
}
if (this.query[this.orderStr]){
reqData[this.orderStr] = this.query[this.orderStr]
}
if (this.params[this.orderStr]){
reqData[this.orderStr] = this.params[this.orderStr]
}
var c = Object.assign({}, this.query, this.tableParams, reqData)
try {
if (this.apiObj) {
var res = await this.apiObj.req(c);
var response = this.parseData(res);
if (response.total === 0) {
this.emptyText = "暂无数据";
}
let dataList = null;
if (this.hidePagination) {
dataList = response.data || [];
} else {
dataList = response.rows;
}
if (this.isTree) {
this.tableData = genTree(dataList);
} else {
this.tableData = dataList || [];
}
this.total = response.total || 0;
this.summary = response.summary || {};
this.loading = false;
// if(!this.hidePagination){
// this.$refs.scTable.setScrollTop(0);
// }
this.$emit("dataChange", res, this.tableData);
}
} catch (error) {
this.loading = false;
if (error && error.response && error.response.data) {
this.emptyText = error.response.data.err_msg;
} else if(error) {
this.emptyText = error.statusText || "数据格式错误";
}else {
this.emptyText = "数据格式错误";
}
return false;
}
},
//分页点击
paginationChange() {
this.getData();
},
//条数变化
pageSizeChange(size) {
this.scPageSize = size;
this.getData();
},
//刷新数据
refresh() {
// this.$refs.scTable.clearSelection();
this.getData();
},
//查询数据 追加query
upData(query, page = 1) {
this.currentPage = page;
// this.$refs.scTable.clearSelection();
Object.assign(this.tableParams, query);
this.getData();
},
//查询数据 追加query 去除列filter
queryData(query, page = 1) {
this.currentPage = page;
Object.assign(this.tableParams, query);
// this.$refs.scTable.clearSelection();
this.$refs.scTable.clearFilter();
this.getData();
},
//重载数据 tableParams变为固定的params
reload() {
this.currentPage = 1;
this.tableParams = Object.assign({}, this.params);
this.$refs.scTable.clearSelection();
this.$refs.scTable.clearSort();
this.$refs.scTable.clearFilter();
this.getData();
this.$emit("resetQuery");
},
//导出
exportExcel(type=0) {
if (type === 0) {
try {
const tableEl = this.$refs.scTable.$el;
const headerCells = Array.from(tableEl.querySelectorAll('thead tr th'));
const opIndex = headerCells.findIndex(th => th.textContent.trim() === '操作');
if (opIndex === -1) {
domToExcel(tableEl, this.hExportName || "表格数据");
} else {
const clone = tableEl.cloneNode(true);
clone.querySelectorAll('tr').forEach(row => {
const cells = row.querySelectorAll('th, td');
if (cells[opIndex]) cells[opIndex].remove();
});
domToExcel(clone, this.hExportName || "表格数据");
}
} catch (error) {
console.error('导出失败:', error);
this.$message.error("导出失败");
}
}
else if (type === 1) {
dataToExcel(this.hExportCols, this.tableData, this.hExportName?this.hExportName:'表格数据')
}else if (type === 2) {
const extractHeaderKeys = (columns) =>{
const keys = [];
columns.map(item => {
if(item.is_notquery){
let key = item.key.split('.')[0];
if(key.indexOf('mio')>-1){
keys.push('mio');
}
}else{
let key = item.key.split('.')[0];// 如果key中包含点号只取点号前面的部分
keys.push(key);
}
});
// 去重并转回字符串
return [...new Set(keys)].join(',');
}
var c = Object.assign({}, this.query, this.tableParams, {[this.orderStr]: this.order}, {page: 0},
{query: "{" + extractHeaderKeys(this.hExportCols) + "}"}
)
let ElLoading = this.$loading({
lock: true,
text: '数据请求中,请稍后...',
background: 'rgba(0, 0, 0, 0)',
})
this.apiObj.req(c).then(res=>{
ElLoading.close();
dataToExcel(this.hExportCols, res, this.hExportName?this.hExportName:'表格数据')
}).catch(err=>{
ElLoading.close();
console.log(err)
this.$message.error("导出失败");
})
}
},
//自定义变化事件
columnSettingChange(userColumn) {
this.userColumn = userColumn;
this.toggleIndex += 1;
},
//自定义列保存
async columnSettingSave(userColumn) {
this.$refs.columnSetting.isSave = true;
try {
await config.columnSettingSave(this.tableName, userColumn);
} catch (error) {
this.$message.error("保存失败");
this.$refs.columnSetting.isSave = false;
}
this.$message.success("保存成功");
this.$refs.columnSetting.isSave = false;
},
//自定义列重置
async columnSettingBack() {
this.$refs.columnSetting.isSave = true;
try {
const column = await config.columnSettingReset(
this.tableName,
this.column
);
this.userColumn = column;
this.$refs.columnSetting.usercolumn = JSON.parse(
JSON.stringify(this.userColumn || [])
);
} catch (error) {
this.$message.error("重置失败");
this.$refs.columnSetting.isSave = false;
}
this.$refs.columnSetting.isSave = false;
},
//排序事件
sortChange(obj) {
if (!this.remoteSort) {
return false;
}
if (obj.column && obj.prop) {
// this.prop = obj.prop
if (obj.order == "descending") {
this.order = "-" + obj.prop;
} else {
this.order = obj.prop;
}
// this.order = obj.order
} else {
// this.prop = null
this.order = null;
}
this.getData();
},
// select事件
selectionChange(rows){
this.selectNum = rows.length;
},
//本地过滤
filterHandler(value, row, column) {
const property = column.property;
return row[property] === value;
},
//过滤事件
filterChange(filters) {
if (!this.remoteFilter) {
return false;
}
Object.keys(filters).forEach((key) => {
filters[key] = filters[key].join(",");
});
this.upData(filters);
},
//远程合计行处理
remoteSummaryMethod(param) {
const { columns } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
const values = this.summary[column.property];
if (values) {
sums[index] = values;
} else {
sums[index] = "";
}
});
return sums;
},
configSizeChange() {
this.$refs.scTable.doLayout();
},
//插入行 unshiftRow
unshiftRow(row) {
this.tableData.unshift(row);
},
//插入行 pushRow
pushRow(row) {
this.tableData.push(row);
},
//根据key覆盖数据
updateKey(row, rowKey = this.rowKey) {
this.tableData
.filter((item) => item[rowKey] === row[rowKey])
.forEach((item) => {
Object.assign(item, row);
});
},
//根据index覆盖数据
updateIndex(row, index) {
Object.assign(this.tableData[index], row);
},
//根据index删除
removeIndex(index) {
this.tableData.splice(index, 1);
},
//根据index批量删除
removeIndexes(indexes = []) {
indexes.forEach((index) => {
this.tableData.splice(index, 1);
});
},
//根据key删除
removeKey(key, rowKey = this.rowKey) {
this.tableData.splice(
this.tableData.findIndex((item) => item[rowKey] === key),
1
);
},
//根据keys批量删除
removeKeys(keys = [], rowKey = this.rowKey) {
keys.forEach((key) => {
this.tableData.splice(
this.tableData.findIndex((item) => item[rowKey] === key),
1
);
});
},
//原生方法转发
clearSelection() {
this.$refs.scTable.clearSelection();
},
toggleRowSelection(row, selected) {
this.$refs.scTable.toggleRowSelection(row, selected);
},
toggleAllSelection() {
this.$refs.scTable.toggleAllSelection();
},
toggleRowExpansion(row, expanded) {
this.$refs.scTable.toggleRowExpansion(row, expanded);
},
setCurrentRow(row) {
this.$refs.scTable.setCurrentRow(row);
},
clearSort() {
this.$refs.scTable.clearSort();
},
clearFilter(columnKey) {
this.$refs.scTable.clearFilter(columnKey);
},
doLayout() {
this.$refs.scTable.doLayout();
},
sort(prop, order) {
this.$refs.scTable.sort(prop, order);
},
// 控制展开/收起所有行
toggleExpandAll() {
const rows = this.tableData || [];
const key = this.rowKey || "id";
const isExpandAll = rows.length > 0 && rows.every(item => this.expandRowKeys.includes(item[key]));
if (isExpandAll) {
this.expandRowKeys = []; // 收起所有
} else {
this.expandRowKeys = rows.map(item => item[key]); // 展开所有
}
},
// 每次展开状态变化时更新 expandRowKeys
handleExpandChange(row, expandedRows) {
const key = this.rowKey || "id";
this.expandRowKeys = expandedRows.map(item => item[key]); // 根据实际的唯一标识
}
},
};
</script>
<style scoped>
.scTable {}
.scTable-table {
height: calc(100% - 50px);
}
.scTable-page {
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
}
.scTable-do {
white-space: nowrap;
}
.scTable:deep(.el-table__footer) .cell {
font-weight: bold;
}
.scTable:deep(.el-table__body-wrapper) .el-scrollbar__bar.is-horizontal {
height: 12px;
border-radius: 12px;
}
.scTable:deep(.el-table__body-wrapper) .el-scrollbar__bar.is-vertical {
width: 12px;
border-radius: 12px;
}
</style>