vue-easytable
Version:
943 lines (851 loc) • 31.4 kB
JSX
import BodyTr from "./body-tr";
import BodyTrScrolling from "./body-tr-scrolling";
import ExpandTr from "./expand-tr";
import VueDomResizeObserver from "../../../src/comps/resize-observer";
import {
getDomResizeObserverCompKey,
getFixedTotalWidthByColumnKey,
clsName,
} from "../util";
import { getValByUnit } from "../../../src/utils/index.js";
import emitter from "../../../src/mixins/emitter";
import {
COMPS_NAME,
EMIT_EVENTS,
COLUMN_TYPES,
EXPAND_TRIGGER_TYPES,
} from "../util/constant";
export default {
name: COMPS_NAME.VE_TABLE_BODY,
mixins: [emitter],
props: {
tableViewportWidth: {
type: Number,
default: 0,
},
columnsOptionResetTime: {
type: Number,
default: 0,
},
colgroups: {
type: Array,
required: true,
},
actualRenderTableData: {
type: Array,
required: true,
},
hasFixedColumn: {
type: Boolean,
default: false,
},
allRowKeys: {
type: Array,
required: true,
},
// expand row option
expandOption: {
type: Object,
default: function () {
return null;
},
},
// checkbox option
checkboxOption: {
type: Object,
default: function () {
return null;
},
},
// radio option
radioOption: {
type: Object,
default: function () {
return null;
},
},
// virual scroll
virtualScrollOption: {
type: Object,
default: null,
},
// is virtual scroll
isVirtualScroll: {
type: Boolean,
default: false,
},
// is scrolling
showVirtualScrollingPlaceholder: {
type: Boolean,
default: false,
},
rowKeyFieldName: {
type: String,
default: null,
},
// cell style option
cellStyleOption: {
type: Object,
default: function () {
return null;
},
},
// cell span option
cellSpanOption: {
type: Object,
default: function () {
return null;
},
},
// highlight row key
highlightRowKey: {
type: [String, Number],
default: null,
},
// event custom option
eventCustomOption: {
type: Object,
default: function () {
return null;
},
},
// cell selection option
cellSelectionOption: {
type: Object,
default: function () {
return null;
},
},
// cell selection data
cellSelectionData: {
type: Object,
default: function () {
return null;
},
},
// cell selection range data
cellSelectionRangeData: {
type: Object,
default: function () {
return null;
},
},
bodyIndicatorRowKeys: {
type: Object,
default: function () {
return null;
},
},
// edit option
editOption: {
type: Object,
default: function () {
return null;
},
},
},
data() {
return {
// columns widths map
colsWidths: new Map(),
/*
internal expand row keys
1、当没有设置 expandedRowKeys 时生效
*/
internalExpandRowkeys: [],
/*
1、存储当前多选功能的rowkey 信息
*/
internalCheckboxSelectedRowKeys: [],
/*
1、存储当前单选功能的rowkey 信息
*/
internalRadioSelectedRowKey: null,
// virtual scroll preview rendered rowKey
virtualScrollPreviewRenderedRowKeys: [],
// virtual scroll repeat rendered rowKey
virtualScrollRepeatRenderedRowKeys: [],
};
},
computed: {
/*
column collenction info
1、style of each column
2、class of each column
*/
columnCollection() {
let columnCollection = [];
const { colgroups } = this;
colgroups.forEach((col) => {
const colKey = col.key;
let columnCollectionItem = {
colKey: colKey,
class: {
[clsName("last-left-fixed-column")]:
this.isLastLeftFixedColumn(col),
[clsName("first-right-fixed-column")]:
this.isfirstRightFixedColumn(col),
},
style: {},
};
const { fixed, align } = col;
columnCollectionItem.style["text-align"] = align || "center";
if (fixed) {
let totalWidth = 0;
// column index
const columnIndex = colgroups.findIndex(
(x) => x.key === colKey,
);
if (
(fixed === "left" && columnIndex > 0) ||
(fixed === "right" &&
columnIndex < colgroups.length - 1)
) {
totalWidth = getFixedTotalWidthByColumnKey({
colgroups,
colKey,
fixed,
});
totalWidth = getValByUnit(totalWidth);
}
columnCollectionItem.style["left"] =
fixed === "left" ? totalWidth : "";
columnCollectionItem.style["right"] =
fixed === "right" ? totalWidth : "";
}
columnCollection.push(columnCollectionItem);
});
return columnCollection;
},
// expand column
expandColumn() {
return this.colgroups.find((x) => x.type === COLUMN_TYPES.EXPAND);
},
/*
是否是可控行展开
1、当设置了 expandedRowKeys 属性时则为可控行展开
*/
isControlledExpand() {
return (
this.expandOption &&
Array.isArray(this.expandOption.expandedRowKeys)
);
},
// expanded row keys
expandedRowkeys() {
return this.isControlledExpand
? this.expandOption.expandedRowKeys
: this.internalExpandRowkeys;
},
// disable row selected row keys
disableCheckboxSelectedRowKeys() {
let result = [];
const { checkboxOption, internalCheckboxSelectedRowKeys } = this;
if (!checkboxOption) {
return result;
}
const { disableSelectedRowKeys } = checkboxOption;
if (
internalCheckboxSelectedRowKeys.length > 0 &&
Array.isArray(disableSelectedRowKeys) &&
disableSelectedRowKeys.length > 0
) {
disableSelectedRowKeys.forEach((rowkey) => {
if (internalCheckboxSelectedRowKeys.includes(rowkey)) {
result.push(rowkey);
}
});
}
return result;
},
// disable row unselected row keys
disableCheckboxUnselectedRowKeys() {
let result = [];
const { checkboxOption, internalCheckboxSelectedRowKeys } = this;
if (!checkboxOption) {
return result;
}
const { disableSelectedRowKeys } = checkboxOption;
if (
Array.isArray(disableSelectedRowKeys) &&
disableSelectedRowKeys.length > 0
) {
disableSelectedRowKeys.forEach((rowkey) => {
if (!internalCheckboxSelectedRowKeys.includes(rowkey)) {
result.push(rowkey);
}
});
}
return result;
},
/*
is row keys selected all
为 true 的条件:选中数量 + 禁用选中数量 === 总量
*/
isCheckboxSelectedAll() {
if (this.allRowKeys.length > 0) {
if (
this.internalCheckboxSelectedRowKeys.length +
this.disableCheckboxUnselectedRowKeys.length ===
this.allRowKeys.length
) {
return true;
}
}
return false;
},
// is checkbox indeterminate
isCheckboxIndeterminate() {
const { internalCheckboxSelectedRowKeys, allRowKeys } = this;
return (
internalCheckboxSelectedRowKeys.length > 0 &&
internalCheckboxSelectedRowKeys.length < allRowKeys.length
);
},
// 是否是受控属性(取决于selectedRowKey)
isControlledRadio() {
const { radioOption } = this;
return (
radioOption &&
Object.keys(radioOption).includes("selectedRowKey")
);
},
},
watch: {
// watch expand Option
expandOption: {
handler: function () {
this.initInternalExpandRowKeys();
},
immediate: true,
},
// watch expandOption expandedRowKeys
"expandOption.expandedRowKeys": {
handler: function () {
this.initInternalExpandRowKeys();
},
},
// watch checkbox option
checkboxOption: {
handler: function () {
this.initInternalCheckboxSelectedRowKeys();
},
immediate: true,
},
// watch selectedRowKeys
"checkboxOption.selectedRowKeys": {
handler: function () {
this.resetInternalCheckboxSelectedRowKeys();
},
},
// watch internalCheckboxSelectedRowKeys
internalCheckboxSelectedRowKeys: {
handler: function () {
// send to checkbox all(in header)
this.sendToCheckboxAll();
},
},
// watch checkbox option
radioOption: {
handler: function () {
this.initInternalRadioSelectedRowKey();
},
immediate: true,
},
// watch selectedRowKeys
"radioOption.selectedRowKey": {
handler: function () {
this.initInternalRadioSelectedRowKey();
},
},
},
methods: {
// is last left fixed column
isLastLeftFixedColumn(column) {
let result = false;
const { colgroups } = this;
const { fixed } = column;
if (fixed === "left") {
const { field } = column;
const leftFixedColumns = colgroups.filter(
(x) => x.fixed === "left",
);
const index = leftFixedColumns.findIndex(
(x) => x.field === field,
);
if (index === leftFixedColumns.length - 1) {
result = true;
}
}
return result;
},
// is first right fixed column
isfirstRightFixedColumn(column) {
let result = false;
const { colgroups } = this;
const { fixed } = column;
if (fixed === "right") {
const { field } = column;
const rightFixedColumns = colgroups.filter(
(x) => x.fixed === "right",
);
if (rightFixedColumns[0].field === field) {
result = true;
}
}
return result;
},
/*
* @expandRowChange
* @desc row expand change
* @param {object} rowData - row data
* @param {number} rowIndex - row index
*/
expandRowChange(rowData, rowIndex) {
const {
expandOption,
internalExpandRowkeys,
expandedRowkeys,
rowKeyFieldName,
} = this;
// deal before expand row method
if (typeof expandOption.beforeExpandRowChange === "function") {
const beforeExpandRowResult =
expandOption.beforeExpandRowChange({
beforeExpandedRowKeys: expandedRowkeys,
row: rowData,
rowIndex,
});
// interrupt execute
if (beforeExpandRowResult === false) {
return false;
}
}
const rowKey = rowData[rowKeyFieldName];
const rowKeyIndex = internalExpandRowkeys.indexOf(rowKey);
if (rowKeyIndex > -1) {
internalExpandRowkeys.splice(rowKeyIndex, 1);
} else {
internalExpandRowkeys.push(rowKey);
}
// deal after expand row method
if (typeof expandOption.afterExpandRowChange === "function") {
expandOption.afterExpandRowChange({
afterExpandedRowKeys: internalExpandRowkeys,
row: rowData,
rowIndex,
});
}
},
/*
* @rowClick
* @desc row expand click event
* @param {object} rowData - row data
* @param {number} rowIndex - row index
*/
rowClick({ rowData, rowIndex }) {
const {
expandOption,
isExpandRow,
expandRowChange,
rowKeyFieldName,
} = this;
// 行高亮功能
if (rowKeyFieldName) {
const rowKey = rowData[rowKeyFieldName];
this.$emit(EMIT_EVENTS.HIGHLIGHT_ROW_CHANGE, { rowKey });
}
// 行展开功能
if (!isExpandRow({ rowData, rowIndex })) {
return false;
}
const trigger = expandOption.trigger;
// expand row by click row
if (trigger === EXPAND_TRIGGER_TYPES.ROW) {
expandRowChange(rowData, rowIndex);
}
},
/*
* @isExpandRow
* @desc is expand row
* @param {object} rowData - row data
* @param {number} rowIndex - row index
*/
isExpandRow({ rowData, rowIndex }) {
let result = false;
const { expandColumn, expandOption } = this;
if (expandColumn && expandOption) {
// 是否允许展开
let expandable = true;
if (typeof expandOption.expandable === "function") {
expandable = expandOption.expandable({
row: rowData,
column: expandColumn,
rowIndex,
});
}
if (expandable !== false) {
result = true;
}
}
return result;
},
/*
* @tdSizeChange
* @desc td size change
* @param {any} key - column key
* @param {number|string} width - column real width
*/
tdSizeChange({ key, width }) {
const { colsWidths } = this;
colsWidths.set(key, width);
this.$emit(EMIT_EVENTS.BODY_CELL_WIDTH_CHANGE, colsWidths);
},
// init internal expand row keys
initInternalExpandRowKeys() {
const { expandOption, isControlledExpand, allRowKeys } = this;
if (!expandOption) {
return false;
}
if (isControlledExpand) {
this.internalExpandRowkeys =
expandOption.expandedRowKeys.slice(0);
} else if (expandOption.defaultExpandAllRows) {
this.internalExpandRowkeys = allRowKeys;
} else if (expandOption.defaultExpandedRowKeys) {
this.internalExpandRowkeys =
expandOption.defaultExpandedRowKeys.slice(0);
}
},
// get expand row
getExpandRowComp({ rowData, rowIndex }) {
if (this.isExpandRow({ rowData, rowIndex })) {
const expandTrProps = {
props: {
tableViewportWidth: this.tableViewportWidth,
colgroups: this.colgroups,
expandOption: this.expandOption,
expandedRowkeys: this.expandedRowkeys,
expandColumn: this.expandColumn,
rowKeyFieldName: this.rowKeyFieldName,
rowData,
rowIndex,
},
};
return <ExpandTr {...expandTrProps} />;
}
return null;
},
// send to checkbox all
sendToCheckboxAll() {
const { isCheckboxSelectedAll, isCheckboxIndeterminate } = this;
this.dispatch(
COMPS_NAME.VE_TABLE,
EMIT_EVENTS.CHECKBOX_SELECTED_ALL_INFO,
{
isIndeterminate: isCheckboxIndeterminate,
isSelected: isCheckboxSelectedAll,
},
);
},
// init internal Radio SelectedRowKey
initInternalRadioSelectedRowKey() {
const { radioOption, isControlledRadio } = this;
if (!radioOption) {
return false;
}
const { selectedRowKey, defaultSelectedRowKey } = radioOption;
this.internalRadioSelectedRowKey = isControlledRadio
? selectedRowKey
: defaultSelectedRowKey;
},
// init internal Checkbox SelectedRowKeys
initInternalCheckboxSelectedRowKeys() {
let result = [];
const { checkboxOption, allRowKeys } = this;
if (!checkboxOption) {
return false;
}
const {
selectedRowKeys,
defaultSelectedAllRows,
defaultSelectedRowKeys,
} = checkboxOption;
if (Array.isArray(selectedRowKeys)) {
result = selectedRowKeys;
} else if (defaultSelectedAllRows) {
result = allRowKeys;
} else if (Array.isArray(defaultSelectedRowKeys)) {
result = defaultSelectedRowKeys;
}
this.internalCheckboxSelectedRowKeys = result;
},
// reset internalCheckboxSelectedRowKeys by selectedRowKeys
resetInternalCheckboxSelectedRowKeys() {
this.internalCheckboxSelectedRowKeys =
this.checkboxOption.selectedRowKeys.slice(0);
},
/*
* @checkboxSelectedRowChange
* @desc selected row change
* @param {number|string} rowKey - rowKey
* @param {bool} isSelected
*/
checkboxSelectedRowChange({ rowKey, isSelected }) {
const {
checkboxOption,
internalCheckboxSelectedRowKeys,
rowKeyFieldName,
} = this;
const { selectedRowChange, selectedRowKeys } = checkboxOption;
let internalCheckboxSelectedRowKeysTemp =
internalCheckboxSelectedRowKeys.slice(0);
// will selected
const rowKeyIndex =
internalCheckboxSelectedRowKeysTemp.indexOf(rowKey);
if (isSelected) {
// bug fixed:通过行点击触发,导致key重复的问题
if (rowKeyIndex === -1) {
internalCheckboxSelectedRowKeysTemp.push(rowKey);
}
} else {
if (rowKeyIndex > -1) {
internalCheckboxSelectedRowKeysTemp.splice(rowKeyIndex, 1);
}
}
// 非可控才改变 internalCheckboxSelectedRowKeys
if (!Array.isArray(selectedRowKeys)) {
this.internalCheckboxSelectedRowKeys =
internalCheckboxSelectedRowKeysTemp;
}
selectedRowChange({
row: this.actualRenderTableData.find(
(x) => x[rowKeyFieldName] === rowKey,
),
isSelected,
selectedRowKeys: internalCheckboxSelectedRowKeysTemp,
});
},
/*
* @checkboxSelectedAllChange
* @desc selected all change
* @param {bool} isSelected - is selected
*/
checkboxSelectedAllChange({ isSelected }) {
const {
checkboxOption,
internalCheckboxSelectedRowKeys,
allRowKeys,
disableCheckboxSelectedRowKeys,
disableCheckboxUnselectedRowKeys,
} = this;
const { selectedAllChange, selectedRowKeys } = checkboxOption;
let internalCheckboxSelectedRowKeysTemp =
internalCheckboxSelectedRowKeys.slice(0);
// selected all
if (isSelected) {
// except disable Row Unselected keys
let allSelectedKeys = allRowKeys.slice(0);
if (disableCheckboxUnselectedRowKeys.length > 0) {
disableCheckboxUnselectedRowKeys.forEach((rowkey) => {
let index = allSelectedKeys.indexOf(rowkey);
if (index > -1) {
allSelectedKeys.splice(index, 1);
}
});
}
internalCheckboxSelectedRowKeysTemp = allSelectedKeys;
} else {
// except disable Row Selected keys
internalCheckboxSelectedRowKeysTemp =
disableCheckboxSelectedRowKeys;
}
// 非可控才改变 internalCheckboxSelectedRowKeys
if (!Array.isArray(selectedRowKeys)) {
this.internalCheckboxSelectedRowKeys =
internalCheckboxSelectedRowKeysTemp;
}
selectedAllChange &&
selectedAllChange({
isSelected,
selectedRowKeys: internalCheckboxSelectedRowKeysTemp,
//changeRowKeys:
});
},
/*
* @radioSelectedRowChange
* @desc selected all change
* @param {number|string} rowKey - rowKey
*/
radioSelectedRowChange({ rowKey }) {
const { radioOption, rowKeyFieldName, isControlledRadio } = this;
const { selectedRowChange } = radioOption;
// 非受控
if (!isControlledRadio) {
this.internalRadioSelectedRowKey = rowKey;
}
selectedRowChange({
row: this.actualRenderTableData.find(
(x) => x[rowKeyFieldName] === rowKey,
),
});
},
// get tr key
getTrKey({ rowData, rowIndex }) {
let result = rowIndex;
const { rowKeyFieldName } = this;
if (rowKeyFieldName) {
result = rowData[rowKeyFieldName];
}
return result;
},
/*
rendering row keys
virtual scrolling will invoke
*/
renderingRowKeys(rowKeys) {
const {
virtualScrollPreviewRenderedRowKeys: previewRenderedRowKeys,
} = this;
this.virtualScrollRepeatRenderedRowKeys = rowKeys.filter(
(rowKey) => {
return previewRenderedRowKeys.indexOf(rowKey) != -1;
},
);
this.virtualScrollPreviewRenderedRowKeys = rowKeys;
},
},
mounted() {
// receive checkbox row selected change from VE_TABLE_BODY_CHECKBOX_CONTENT
this.$on(EMIT_EVENTS.CHECKBOX_SELECTED_ROW_CHANGE, (params) => {
this.checkboxSelectedRowChange(params);
});
// receive checkbox row selected change from VE_TABLE_BODY_CHECKBOX_CONTENT
this.$on(EMIT_EVENTS.CHECKBOX_SELECTED_ALL_CHANGE, (params) => {
this.checkboxSelectedAllChange(params);
});
// receive radio row selected change from VE_TABLE_BODY_RADIO_CONTENT
this.$on(EMIT_EVENTS.RADIO_SELECTED_ROW_CHANGE, (params) => {
this.radioSelectedRowChange(params);
});
// recieve tr click
this.$on(EMIT_EVENTS.BODY_ROW_CLICK, (params) => {
this.rowClick(params);
});
if (this.checkboxOption) {
// 这里 nextTick 解决由于子组件先初始化,导致父组件无法接收消息的问题
this.$nextTick(() => {
this.sendToCheckboxAll();
});
}
},
render() {
const {
colgroups,
actualRenderTableData,
expandOption,
expandRowChange,
isExpandRow,
getExpandRowComp,
expandedRowkeys,
checkboxOption,
radioOption,
rowKeyFieldName,
tdSizeChange,
internalCheckboxSelectedRowKeys,
internalRadioSelectedRowKey,
isVirtualScroll,
cellStyleOption,
showVirtualScrollingPlaceholder,
} = this;
const { virtualScrollRepeatRenderedRowKeys } = this;
return (
<tbody>
{/* Measure each column width with additional hidden col */}
<tr style="height:0;">
{colgroups.map((column) => {
const measureTdProps = {
key: getDomResizeObserverCompKey(
column.key,
this.columnsOptionResetTime,
),
props: {
tagName: "td",
id: column.key,
},
on: {
"on-dom-resize-change": tdSizeChange,
},
style: {
padding: 0,
border: 0,
height: 0,
},
};
return <VueDomResizeObserver {...measureTdProps} />;
})}
</tr>
{actualRenderTableData.map((rowData, rowIndex) => {
const trProps = {
key: this.getTrKey({ rowData, rowIndex }),
props: {
rowIndex,
rowData,
colgroups,
expandOption,
expandedRowkeys,
checkboxOption,
radioOption,
rowKeyFieldName,
allRowKeys: this.allRowKeys,
expandRowChange,
internalCheckboxSelectedRowKeys,
internalRadioSelectedRowKey,
isVirtualScroll,
isExpandRow: isExpandRow({
rowData,
rowIndex,
}),
cellStyleOption,
cellSpanOption: this.cellSpanOption,
highlightRowKey: this.highlightRowKey,
eventCustomOption: this.eventCustomOption,
cellSelectionData: this.cellSelectionData,
editOption: this.editOption,
columnCollection: this.columnCollection,
cellSelectionRangeData: this.cellSelectionRangeData,
bodyIndicatorRowKeys: this.bodyIndicatorRowKeys,
},
};
if (showVirtualScrollingPlaceholder) {
const trPropsScrolling = {
key: this.getTrKey({ rowData, rowIndex }),
props: {
colgroups,
},
};
if (
virtualScrollRepeatRenderedRowKeys.indexOf(
rowData[this.rowKeyFieldName],
) != -1
) {
return [
// body tr
<BodyTr {...trProps} />,
];
} else {
return <BodyTrScrolling {...trPropsScrolling} />;
}
} else {
return [
// body tr
<BodyTr {...trProps} />,
// expand row
getExpandRowComp({ rowData, rowIndex }),
];
}
})}
</tbody>
);
},
};