引用vxe-table@4.5.21表格功能
目录
一. 效果展示
编辑操作
选中单元格
选中行
新增、标记删除、修改、复制
二. template
<vxe-table
ref="tableRef"
:data="tableData"
keep-source
:edit-config="{ trigger: 'dblclick', mode: 'cell', showStatus: true, enabled: canEdit }"
style="width: 100%; user-select: text"
height="100%"
class="editable-vxe"
:row-class-name="setRowClassName"
:cell-class-name="setCellClassName"
:row-config="{ useKey: true }"
:tooltip-config="{ enterable: true, theme: 'light' }"
v-loading="loading"
border
:scroll-x="{ enabled: true, gt: 10 }"
:scroll-y="{ enabled: true, gt: 100 }"
@cell-click="cellClickEvent"
>
<vxe-column
v-for="(item, index) in tableColumn"
:key="index"
:field="item.prop"
:title="item.label"
:show-header-overflow="true"
min-width="180"
:show-overflow="true"
:edit-render="{ enabled: !!item.label, autofocus: '.el-input__inner' }"
>
<template #edit="{ row }">
<el-input v-model="row[item.prop]" type="text" :placeholder="setDefaultPlaceHolder(row, item)"></el-input>
</template>
</vxe-column>
</vxe-table>
1. vxe-table属性解析
keep-source:需区分编辑样式、做临时删除及还原数据操作的要加上
edit-config:此处选用单元格编辑模式cell,视情况可选行编辑row;showStatus配合keep-source属性可实现区分新增行、修改单元格或行与原数据的样式区分
row-class-name:给原选中行附加类名,修改默认样式
cell-class-name:给原选中单元格附加类名,修改默认样式
row-config:useKey可默认表格每行数据的唯一标识
scroll-x/scroll-y:虚拟滚动配置,大数据量可配
2. vxe-column属性解析
edit-render:autofocus实现打开编辑立刻聚焦文本
placeholder:可设置编辑文本区的默认提示值
三. script
1. data
import { VxeTableInstance, VxeTableEvents } from 'vxe-table';
const initTableData = ref<any[]>([]); // 初始数据(用于还原)
const tableData = ref<any[]>([]); // 表格绑定数据
const tableColumn = ref<any[]>([]); // 表格每列相关值(表头、默认提示值、是否可编辑等)
const previewUpdateParams = ref<any>({}); // 预览需携带的相关参数
const tableRef = ref<VxeTableInstance>(); // 表格ref绑定
const currSelectedCell = ref<any[]>([]); // 当前选中(可单一单元格,单行,多单元格,多行)
2. computed
// 是否可编辑
const canEdit = computed(() => {
// 接口返回是否可编辑状态
return ...;
});
// 是否可复制
const copyDisabled = computed(() => {
return !currSelectedCell.value[0]?.row;
});
// 是否可删除
const delDisabled = computed(() => {
return !currSelectedCell.value[0]?.row;
});
// 是否可撤销所选条目
const cancelDisabled = computed(() => {
const $table = tableRef.value;
if ($table && currSelectedCell.value[0]) {
// isInsertByRow isUpdateByRow可能存在性能问题
// 复制或新增行
for (let index = 0; index < currSelectedCell.value.length; index++) {
if (!!$table.isInsertByRow(currSelectedCell.value[index].row)) return false;
if (
currSelectedCell.value[index].field &&
$table.isUpdateByRow(currSelectedCell.value[index].row, currSelectedCell.value[index].field)
) {
// cell
return false;
}
if (!currSelectedCell.value[index].field && $table.isUpdateByRow(currSelectedCell.value[index].row)) {
// row
return false;
}
// 标记删除
if (isPendingRecords(currSelectedCell.value[index].row)) {
return false;
}
}
}
return true;
});
3. methods
表格属性及事件绑定
onMounted(() => {
window.addEventListener('dblclick', dblclickEventListenerFn);
});
onUnmounted(() => {
window.removeEventListener('dblclick', dblclickEventListenerFn);
});
// 此处采取双击表格外区域取消选中
const dblclickEventListenerFn = (event?: any) => {
currSelectedCell.value = [];
};
const cellClickEvent: VxeTableEvents.CellClick = ({ row, rowIndex, column, columnIndex, $event }) => {
if (!canEdit.value) return;
if (currSelectedCell.value.length === 1) {
// 取消选中操作(cell与row)
const selected = currSelectedCell.value[0];
if (
rowIndex === selected.rowIndex &&
(columnIndex === selected.columnIndex || (!columnIndex && !selected.columnIndex))
) {
currSelectedCell.value = [];
return;
}
}
// 默认选中行
const cellOrRow = {
row,
rowIndex,
};
if (columnIndex != 0) {
// 选中单元格
Object.assign(cellOrRow, { field: column.field, columnIndex });
}
if ($event.ctrlKey) {
// ctrl多选功能
ctrlSelectFn(row, rowIndex);
return;
}
if ($event.shiftKey) {
// shift多选功能
shiftSelectFn(rowIndex);
return;
}
currSelectedCell.value = [cellOrRow];
};
const setRowClassName = ({ rowIndex }: any) => {
const filterCurrSelectedCell = currSelectedCell.value.filter((item: any) => item.rowIndex === rowIndex);
if (!filterCurrSelectedCell[0]?.columnIndex && filterCurrSelectedCell[0]?.rowIndex == rowIndex) {
return '选中行类名';
}
return '';
};
const setCellClassName = ({ rowIndex, columnIndex }: any) => {
const filterCurrSelectedCell = currSelectedCell.value.filter((item: any) => item.rowIndex === rowIndex);
if (filterCurrSelectedCell[0]?.rowIndex == rowIndex && filterCurrSelectedCell[0]?.columnIndex == columnIndex) {
return '选中单元格类名';
}
return '';
};
const setDefaultPlaceHolder = (row: any, item: any) => {
let result = '';
const $table = tableRef.value;
if ($table) {
// 此处设置默认提示值
...
}
return result;
};
多选功能参考(默认行选中)
const ctrlSelectFn = (row: anyObj, rowIndex: number) => {
// 纯row操作
currSelectedCell.value = currSelectedCell.value.map((item: any) => {
return { row: item.row, rowIndex: item.rowIndex };
});
const selecteds = currSelectedCell.value.map((item: any) => item.rowIndex);
if (selecteds.includes(rowIndex)) {
currSelectedCell.value = currSelectedCell.value.filter((item: any) => item.rowIndex !== rowIndex);
} else {
currSelectedCell.value.push({ row, rowIndex });
currSelectedCell.value.sort((a: any, b: any) => a.rowIndex - b.rowIndex);
}
};
const shiftSelectFn = (rowIndex: number) => {
// 纯row操作
currSelectedCell.value = currSelectedCell.value.map((item: any) => {
return { row: item.row, rowIndex: item.rowIndex };
});
const selecteds = currSelectedCell.value.map((item: any) => item.rowIndex);
// 此处规则取上一次选中项的最后一行数据为基准点
const lastRowIndex = selecteds[selecteds.length - 1] || rowIndex;
const $table = tableRef.value;
if ($table) {
currSelectedCell.value = [];
const fullData = $table.getTableData().fullData || [];
if (rowIndex > lastRowIndex) {
for (let index = lastRowIndex; index < rowIndex + 1; index++) {
currSelectedCell.value.push({ row: fullData[index], rowIndex: index });
}
} else {
for (let index = rowIndex; index < lastRowIndex + 1; index++) {
currSelectedCell.value.push({ row: fullData[index], rowIndex: index });
}
}
}
};
表格数据获取及操作方法
/**
* 表格数据获取
* @param headers 表头数据
* @param datas 数据映射
*/
const getTableData = (headers: any[], datas: any[]) => {
const tablePropsMap = {} as any;
for (let i in headers) {
tablePropsMap[i] = headers[i].prop;
}
let result = [] as any[];
for (let i in datas) {
let temp = {} as any;
for (let j in datas[i]) {
temp[tablePropsMap[j]] = datas[i][j];
}
result.push(temp);
}
tableData.value = result;
initTableData.value = JSON.parse(JSON.stringify(result));
}
/**
* 表格数据是否新增、编辑、复制及临时删除
*/
const isEdited = () => {
const $table = tableRef.value;
let res = false;
if ($table) {
const records = $table.getRecordset();
const { insertRecords, updateRecords, pendingRecords } = records;
res = [...insertRecords, ...updateRecords, ...pendingRecords].length > 0;
}
return res;
};
/**
* 新增行
* @param row 指定位置、null从第一行插入、-1 从最后插入
*/
const insertEvent = async (row?: number) => {
const $table = tableRef.value;
if ($table) {
const insertRecords = $table.getInsertRecords();
const record: any = {};
tableColumn.value.map((item: any) => {
record[item.prop] = null;
})
record['Row Number'] = tableData.value[tableData.value.length - 1]['Row Number'] * 1 + insertRecords.length + 1
const { row: newRow } = await $table.insertAt(record, row);
await $table.setEditCell(newRow, tableColumn.value[1].prop);
}
};
/**
* 复制行
*/
const copyEvent = async () => {
const $table = tableRef.value;
if ($table && currSelectedCell.value[0]?.row) {
currSelectedCell.value.map(async (item: any) => {
const insertRecords = $table.getInsertRecords();
const record = {
...item.row,
'Row Number': tableData.value[tableData.value.length - 1]['Row Number'] * 1 + insertRecords.length + 1,
};
record['_X_ROW_KEY'] && delete record['_X_ROW_KEY'];
const { row: newRow } = await $table.insertAt(record, -1);
await $table.setEditCell(newRow, tableColumn.value[1].prop);
});
}
};
/**
* 删除行(原数据做临时删除,新增或复制则直接删除)
* @param status true:标记为删除,false:还原
*/
const delEvent = (status: boolean) => {
const $table = tableRef.value;
if ($table) {
// 删除复制或新增行
currSelectedCell.value.map((item: any) => {
if (!!$table.isInsertByRow(item.row)) {
$table.remove(item.row);
} else {
$table.revertData(item.row);
$table.setPendingRow(item.row, status);
}
});
}
};
/**
* 撤销所选项目
*/
const cancelSelectedEvent = () => {
const $table = tableRef.value;
if ($table) {
currSelectedCell.value.map((item: any) => {
if (isPendingRecords(item.row)) {
// 还原临时删除
delEvent(false);
} else if (!!$table.isInsertByRow(item.row)) {
// 删除复制或新增行
$table.remove(item.row);
} else if (item.field) {
// cell
$table.revertData(item.row, item.field);
} else {
// row
$table.revertData(item.row);
}
});
}
};
/**
* 预览修改
*/
const previewModify = () => {
if (!isEdited()) return;
const $table = tableRef.value;
if ($table) {
const records = $table.getRecordset();
// 新增、临时删除、编辑数据
const { insertRecords, pendingRecords, updateRecords } = records;
...
}
};
/**
* 提交修改
*/
const submitModify = () => {
if (!isEdited()) return;
...
};
四. style
以下为参考样式,除选中样式需添加,其余可沿用默认样式
.editable-vxe {
.selected-row {
.vxe-body--column {
// 选中行样式
box-shadow: inset 0 -0.5px 0 2px #00f;
user-select: none;
}
}
.selected-cell {
// 选中单元格样式
box-shadow: inset 0 0 0 2px #00f;
}
.col--dirty {
background-color: 编辑后的背景色;
&::before {
// 屏蔽原样式
display: none;
}
}
.row--new {
background-color: 新增或复制后的背景色;
.vxe-body--column {
&::before {
// 屏蔽原样式
display: none;
}
}
}
.row--pending {
background-color: 临时删除的背景色;
// 覆盖原样式
color: inherit;
text-decoration: none;
cursor: default;
.vxe-body--column::after {
display: none;
}
}
}