vue-fantable
Version:
Vue table component for huge data
948 lines (858 loc) • 26.3 kB
JSX
import {
getDomResizeObserverCompKey,
getFixedTotalWidthByColumnKey,
clsName,
} from '../util/index.js'
import { getValByUnit } from '../../../src/utils/index.js'
import {
COMPS_NAME,
COLUMN_TYPES,
EXPAND_TRIGGER_TYPES,
} from '../util/constant'
// import eventCenter from '@P/events/event-center'
import VueDomResizeObserver from '@P/src/components/resize-observer/index.js'
import BodyTr from './body-tr.jsx'
import BodyTrScrolling from './body-tr-scrolling.jsx'
import ExpandTr from './expand-tr.jsx'
import { GLOBAL_EVENT } from '@P/events/global-events.js'
import { defineComponent } from 'vue'
export default defineComponent({
name: COMPS_NAME.FAN_TABLE_BODY,
components: {
BodyTr
},
inject: ['eventCenter'],
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
},
},
},
emits: ['highlightRowChange', 'bodyCellWidthChange'],
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() {
const columnCollection = []
const { colgroups } = this
colgroups.forEach((col) => {
const colKey = col.key
const columnCollectionItem = {
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() {
const 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() {
const 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()
},
},
},
mounted() {
// receive checkbox row selected change from FAN_TABLE_BODY_CHECKBOX_CONTENT
this.eventCenter.on(GLOBAL_EVENT.CHECKBOX_SELECTED_ROW_CHANGE, (params) => {
this.checkboxSelectedRowChange(params)
})
// receive checkbox row selected change from FAN_TABLE_BODY_CHECKBOX_CONTENT
this.eventCenter.on(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_CHANGE_BODY, (params) => {
this.checkboxSelectedAllChange(params)
})
// receive radio row selected change from FAN_TABLE_BODY_RADIO_CONTENT
this.eventCenter.on(GLOBAL_EVENT.RADIO_SELECTED_ROW_CHANGE, (params) => {
this.radioSelectedRowChange(params)
})
// recieve tr click
this.eventCenter.on(GLOBAL_EVENT.BODY_ROW_CLICK, (params) => {
this.rowClick(params)
})
if (this.checkboxOption) {
// 这里 nextTick 解决由于子组件先初始化,导致父组件无法接收消息的问题
this.$nextTick(() => {
this.sendToCheckboxAll()
})
}
},
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 })
this.$emit('highlightRowChange', { 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)
this.$emit('bodyCellWidthChange', 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 = {
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.eventCenter.emit(GLOBAL_EVENT.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
let selectedRowChange = null
let selectedRowKeys = null
if (!checkboxOption) return
if (checkboxOption.selectedRowChange) {
selectedRowChange = checkboxOption.selectedRowChange
}
if (checkboxOption.selectedRowKeys) {
selectedRowKeys = checkboxOption.selectedRowKeys
}
const 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
if (!checkboxOption) return
const { selectedAllChange, selectedRowKeys } = checkboxOption
let internalCheckboxSelectedRowKeysTemp =
internalCheckboxSelectedRowKeys.slice(0)
// selected all
if (isSelected) {
// except disable Row Unselected keys
const allSelectedKeys = allRowKeys.slice(0)
if (disableCheckboxUnselectedRowKeys.length > 0) {
disableCheckboxUnselectedRowKeys.forEach((rowkey) => {
const 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
let selectedRowChange = null
if (!radioOption) {
return
}
if (radioOption.selectedRowChange) {
selectedRowChange = radioOption.selectedRowChange
}
// 非受控
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
},
},
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,
),
tagName: 'td',
id: column.key,
onDomResizeChange: tdSizeChange,
style: {
padding: 0,
border: 0,
height: 0,
},
}
return <VueDomResizeObserver {...measureTdProps} />
})}
</tr>
{actualRenderTableData.map((rowData, rowIndex) => {
const trProps = {
key: this.getTrKey({ rowData, rowIndex }),
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 }),
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>
)
},
}
)