element-plus
Version: 
A Component Library for Vue3.0
529 lines (490 loc) • 14.2 kB
text/typescript
import { ref, getCurrentInstance, unref, watch, Ref } from 'vue'
import { hasOwn } from '@vue/shared'
import {
  getKeysMap,
  getRowIdentity,
  getColumnById,
  getColumnByKey,
  orderBy,
  toggleRowStatus,
} from '../util'
import useExpand from './expand'
import useCurrent from './current'
import useTree from './tree'
import { TableColumnCtx } from '../table-column/defaults'
import { Table, TableRefs } from '../table/defaults'
import { StoreFilter } from './index'
const sortData = (data, states) => {
  const sortingColumn = states.sortingColumn
  if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
    return data
  }
  return orderBy(
    data,
    states.sortProp,
    states.sortOrder,
    sortingColumn.sortMethod,
    sortingColumn.sortBy,
  )
}
const doFlattenColumns = columns => {
  const result = []
  columns.forEach(column => {
    if (column.children) {
      // eslint-disable-next-line prefer-spread
      result.push.apply(result, doFlattenColumns(column.children))
    } else {
      result.push(column)
    }
  })
  return result
}
function useWatcher<T>() {
  const instance = getCurrentInstance() as Table<T>
  const rowKey: Ref<string> = ref(null)
  const data: Ref<T[]> = ref([])
  const _data: Ref<T[]> = ref([])
  const isComplex = ref(false)
  const _columns: Ref<TableColumnCtx<T>[]> = ref([])
  const originColumns: Ref<TableColumnCtx<T>[]> = ref([])
  const columns: Ref<TableColumnCtx<T>[]> = ref([])
  const fixedColumns: Ref<TableColumnCtx<T>[]> = ref([])
  const rightFixedColumns: Ref<TableColumnCtx<T>[]> = ref([])
  const leafColumns: Ref<TableColumnCtx<T>[]> = ref([])
  const fixedLeafColumns: Ref<TableColumnCtx<T>[]> = ref([])
  const rightFixedLeafColumns: Ref<TableColumnCtx<T>[]> = ref([])
  const leafColumnsLength = ref(0)
  const fixedLeafColumnsLength = ref(0)
  const rightFixedLeafColumnsLength = ref(0)
  const isAllSelected = ref(false)
  const selection: Ref<T[]> = ref([])
  const reserveSelection = ref(false)
  const selectOnIndeterminate = ref(false)
  const selectable: Ref<(row: T, index: number) => boolean> = ref(null)
  const filters: Ref<StoreFilter> = ref({})
  const filteredData = ref(null)
  const sortingColumn = ref(null)
  const sortProp = ref(null)
  const sortOrder = ref(null)
  const hoverRow = ref(null)
  watch(data, () => instance.state && scheduleLayout(false), {
    deep: true,
  })
  // 检查 rowKey 是否存在
  const assertRowKey = () => {
    if (!rowKey.value) throw new Error('[ElTable] prop row-key is required')
  }
  // 更新列
  const updateColumns = () => {
    fixedColumns.value = _columns.value.filter(
      column => column.fixed === true || column.fixed === 'left',
    )
    rightFixedColumns.value = _columns.value.filter(
      column => column.fixed === 'right',
    )
    if (
      fixedColumns.value.length > 0 &&
      _columns.value[0] &&
      _columns.value[0].type === 'selection' &&
      !_columns.value[0].fixed
    ) {
      _columns.value[0].fixed = true
      fixedColumns.value.unshift(_columns.value[0])
    }
    const notFixedColumns = _columns.value.filter(column => !column.fixed)
    originColumns.value = []
      .concat(fixedColumns.value)
      .concat(notFixedColumns)
      .concat(rightFixedColumns.value)
    const leafColumns = doFlattenColumns(notFixedColumns)
    const fixedLeafColumns = doFlattenColumns(fixedColumns.value)
    const rightFixedLeafColumns = doFlattenColumns(rightFixedColumns.value)
    leafColumnsLength.value = leafColumns.length
    fixedLeafColumnsLength.value = fixedLeafColumns.length
    rightFixedLeafColumnsLength.value = rightFixedLeafColumns.length
    columns.value = []
      .concat(fixedLeafColumns)
      .concat(leafColumns)
      .concat(rightFixedLeafColumns)
    isComplex.value =
      fixedColumns.value.length > 0 || rightFixedColumns.value.length > 0
  }
  // 更新 DOM
  const scheduleLayout = (needUpdateColumns?: boolean, immediate = false) => {
    if (needUpdateColumns) {
      updateColumns()
    }
    if (immediate) {
      instance.state.doLayout()
    } else {
      instance.state.debouncedUpdateLayout()
    }
  }
  // 选择
  const isSelected = row => {
    return selection.value.indexOf(row) > -1
  }
  const clearSelection = () => {
    isAllSelected.value = false
    const oldSelection = selection.value
    if (oldSelection.length) {
      selection.value = []
      instance.emit('selection-change', [])
    }
  }
  const cleanSelection = () => {
    let deleted
    if (rowKey.value) {
      deleted = []
      const selectedMap = getKeysMap(selection.value, rowKey.value)
      const dataMap = getKeysMap(data.value, rowKey.value)
      for (const key in selectedMap) {
        if (hasOwn(selectedMap, key) && !dataMap[key]) {
          deleted.push(selectedMap[key].row)
        }
      }
    } else {
      deleted = selection.value.filter(item => data.value.indexOf(item) === -1)
    }
    if (deleted.length) {
      const newSelection = selection.value.filter(
        item => deleted.indexOf(item) === -1,
      )
      selection.value = newSelection
      instance.emit('selection-change', newSelection.slice())
    }
  }
  const toggleRowSelection = (
    row: T,
    selected = undefined,
    emitChange = true,
  ) => {
    const changed = toggleRowStatus(selection.value, row, selected)
    if (changed) {
      const newSelection = (selection.value || []).slice()
      // 调用 API 修改选中值,不触发 select 事件
      if (emitChange) {
        instance.emit('select', newSelection, row)
      }
      instance.emit('selection-change', newSelection)
    }
  }
  const _toggleAllSelection = () => {
    // when only some rows are selected (but not all), select or deselect all of them
    // depending on the value of selectOnIndeterminate
    const value = selectOnIndeterminate.value
      ? !isAllSelected.value
      : !(isAllSelected.value || selection.value.length)
    isAllSelected.value = value
    let selectionChanged = false
    let childrenCount = 0
    const rowKey = instance?.store?.states?.rowKey.value
    data.value.forEach((row, index) => {
      const rowIndex = index + childrenCount
      if (selectable.value) {
        if (
          selectable.value.call(null, row, rowIndex) &&
          toggleRowStatus(selection.value, row, value)
        ) {
          selectionChanged = true
        }
      } else {
        if (toggleRowStatus(selection.value, row, value)) {
          selectionChanged = true
        }
      }
      childrenCount += getChildrenCount(getRowIdentity(row, rowKey))
    })
    if (selectionChanged) {
      instance.emit(
        'selection-change',
        selection.value ? selection.value.slice() : [],
      )
    }
    instance.emit('select-all', selection.value)
  }
  const updateSelectionByRowKey = () => {
    const selectedMap = getKeysMap(selection.value, rowKey.value)
    data.value.forEach(row => {
      const rowId = getRowIdentity(row, rowKey.value)
      const rowInfo = selectedMap[rowId]
      if (rowInfo) {
        selection.value[rowInfo.index] = row
      }
    })
  }
  const updateAllSelected = () => {
    // data 为 null 时,解构时的默认值会被忽略
    if (data.value?.length === 0) {
      isAllSelected.value = false
      return
    }
    let selectedMap
    if (rowKey.value) {
      selectedMap = getKeysMap(selection.value, rowKey.value)
    }
    const isSelected = function(row) {
      if (selectedMap) {
        return !!selectedMap[getRowIdentity(row, rowKey.value)]
      } else {
        return selection.value.indexOf(row) !== -1
      }
    }
    let isAllSelected_ = true
    let selectedCount = 0
    let childrenCount = 0
    for (let i = 0, j = (data.value || []).length; i < j; i++) {
      const keyProp = instance?.store?.states?.rowKey.value
      const rowIndex = i + childrenCount
      const item = data.value[i]
      const isRowSelectable =
        selectable.value && selectable.value.call(null, item, rowIndex)
      if (!isSelected(item)) {
        if (!selectable.value || isRowSelectable) {
          isAllSelected_ = false
          break
        }
      } else {
        selectedCount++
      }
      childrenCount += getChildrenCount(getRowIdentity(item, keyProp))
    }
    if (selectedCount === 0) isAllSelected_ = false
    isAllSelected.value = isAllSelected_
  }
  // gets the number of all child nodes by rowKey
  const getChildrenCount = (rowKey: string) => {
    if (!instance || !instance.store) return 0
    const {
      treeData,
    } = instance.store.states
    let count = 0
    const children = treeData.value[rowKey]?.children
    if (children) {
      count += children.length
      children.forEach(childKey => {
        count += getChildrenCount(childKey)
      })
    }
    return count
  }
  // 过滤与排序
  const updateFilters = (columns, values) => {
    if (!Array.isArray(columns)) {
      columns = [columns]
    }
    const filters_ = {}
    columns.forEach(col => {
      filters.value[col.id] = values
      filters_[col.columnKey || col.id] = values
    })
    return filters_
  }
  const updateSort = (column, prop, order) => {
    if (sortingColumn.value && sortingColumn.value !== column) {
      sortingColumn.value.order = null
    }
    sortingColumn.value = column
    sortProp.value = prop
    sortOrder.value = order
  }
  const execFilter = () => {
    let sourceData = unref(_data)
    Object.keys(filters.value).forEach(columnId => {
      const values = filters.value[columnId]
      if (!values || values.length === 0) return
      const column = getColumnById(
        {
          columns: columns.value,
        },
        columnId,
      )
      if (column && column.filterMethod) {
        sourceData = sourceData.filter(row => {
          return values.some(value =>
            column.filterMethod.call(null, value, row, column),
          )
        })
      }
    })
    filteredData.value = sourceData
  }
  const execSort = () => {
    data.value = sortData(filteredData.value, {
      sortingColumn: sortingColumn.value,
      sortProp: sortProp.value,
      sortOrder: sortOrder.value,
    })
  }
  // 根据 filters 与 sort 去过滤 data
  const execQuery = (ignore = undefined) => {
    if (!(ignore && ignore.filter)) {
      execFilter()
    }
    execSort()
  }
  const clearFilter = columnKeys => {
    const {
      tableHeader,
      fixedTableHeader,
      rightFixedTableHeader,
    } = instance.refs as TableRefs
    let panels = {}
    if (tableHeader) panels = Object.assign(panels, tableHeader.filterPanels)
    if (fixedTableHeader)
      panels = Object.assign(panels, fixedTableHeader.filterPanels)
    if (rightFixedTableHeader)
      panels = Object.assign(panels, rightFixedTableHeader.filterPanels)
    const keys = Object.keys(panels)
    if (!keys.length) return
    if (typeof columnKeys === 'string') {
      columnKeys = [columnKeys]
    }
    if (Array.isArray(columnKeys)) {
      const columns_ = columnKeys.map(key =>
        getColumnByKey(
          {
            columns: columns.value,
          },
          key,
        ),
      )
      keys.forEach(key => {
        const column = columns_.find(col => col.id === key)
        if (column) {
          column.filteredValue = []
        }
      })
      instance.store.commit('filterChange', {
        column: columns_,
        values: [],
        silent: true,
        multi: true,
      })
    } else {
      keys.forEach(key => {
        const column = columns.value.find(col => col.id === key)
        if (column) {
          column.filteredValue = []
        }
      })
      filters.value = {}
      instance.store.commit('filterChange', {
        column: {},
        values: [],
        silent: true,
      })
    }
  }
  const clearSort = () => {
    if (!sortingColumn.value) return
    updateSort(null, null, null)
    instance.store.commit('changeSortCondition', {
      silent: true,
    })
  }
  const {
    setExpandRowKeys,
    toggleRowExpansion,
    updateExpandRows,
    states: expandStates,
    isRowExpanded,
  } = useExpand({
    data,
    rowKey,
  })
  const {
    updateTreeExpandKeys,
    toggleTreeExpansion,
    loadOrToggle,
    states: treeStates,
  } = useTree({
    data,
    rowKey,
  })
  const {
    updateCurrentRowData,
    updateCurrentRow,
    setCurrentRowKey,
    states: currentData,
  } = useCurrent({
    data,
    rowKey,
  })
  // 适配层,expand-row-keys 在 Expand 与 TreeTable 中都有使用
  const setExpandRowKeysAdapter = (val: string[]) => {
    // 这里会触发额外的计算,但为了兼容性,暂时这么做
    setExpandRowKeys(val)
    updateTreeExpandKeys(val)
  }
  // 展开行与 TreeTable 都要使用
  const toggleRowExpansionAdapter = (row: T, expanded: boolean) => {
    const hasExpandColumn = columns.value.some(({ type }) => type === 'expand')
    if (hasExpandColumn) {
      toggleRowExpansion(row, expanded)
    } else {
      toggleTreeExpansion(row, expanded)
    }
  }
  return {
    assertRowKey,
    updateColumns,
    scheduleLayout,
    isSelected,
    clearSelection,
    cleanSelection,
    toggleRowSelection,
    _toggleAllSelection,
    toggleAllSelection: null,
    updateSelectionByRowKey,
    updateAllSelected,
    updateFilters,
    updateCurrentRow,
    updateSort,
    execFilter,
    execSort,
    execQuery,
    clearFilter,
    clearSort,
    toggleRowExpansion,
    setExpandRowKeysAdapter,
    setCurrentRowKey,
    toggleRowExpansionAdapter,
    isRowExpanded,
    updateExpandRows,
    updateCurrentRowData,
    loadOrToggle,
    states: {
      rowKey,
      data,
      _data,
      isComplex,
      _columns,
      originColumns,
      columns,
      fixedColumns,
      rightFixedColumns,
      leafColumns,
      fixedLeafColumns,
      rightFixedLeafColumns,
      leafColumnsLength,
      fixedLeafColumnsLength,
      rightFixedLeafColumnsLength,
      isAllSelected,
      selection,
      reserveSelection,
      selectOnIndeterminate,
      selectable,
      filters,
      filteredData,
      sortingColumn,
      sortProp,
      sortOrder,
      hoverRow,
      ...expandStates,
      ...treeStates,
      ...currentData,
    },
  }
}
export default useWatcher