refactor(grid): [grid] add grid plugins (#1168)

* refactor(grid): add grid plugins

* refactor(grid): add grid plugins

* refactor(grid): add grid plugins

* refactor(grid): add dragger plugin

* refactor(grid): add tooltip plugin

* refactor(grid): add checkbox plugin

* refactor(grid): add tree plugin

* refactor(grid): add tree plugin

* refactor(grid): add tree plugin
This commit is contained in:
ajaxzheng 2023-12-20 16:10:43 +08:00 committed by GitHub
parent 6ec2a3cc4f
commit c4a7391bdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1592 additions and 1331 deletions

View File

@ -47,7 +47,11 @@ export const getColumnList = (columns) => {
const result = []
columns.forEach((column) => {
result.push.apply(result, column.children && column.children.length ? getColumnList(column.children) : [column])
if (column.children && column.children.length) {
result.push(...getColumnList(column.children))
} else {
result.push(column)
}
})
return result
@ -137,9 +141,10 @@ export const destroyColumn = ($table, { columnConfig }) => {
$table.collectColumn = $table.collectColumn.slice(0)
}
export const emitEvent = (vnode, type, args) => {
if (vnode.tableListeners[type]) {
vnode.$emit.apply(vnode, [type].concat(args))
export const emitEvent = (vm, type, args) => {
if (vm.tableListeners[type]) {
const params = [].concat(args)
vm.$emit(type, ...params)
}
}
@ -182,7 +187,7 @@ export const getListeners = ($attrs, $listeners) => {
const event = $attrs[name]
if (regEventPrefix.test(name) && typeof event === 'function') {
listeners[name.substr(2).replace(regHyphenate, '-$1').toLowerCase()] = event
listeners[name.slice(2).replace(regHyphenate, '-$1').toLowerCase()] = event
}
})

View File

@ -25,18 +25,78 @@ import Filter from './src/filter'
import GridConfig from './src/config'
import GridRadio from './src/radio'
import GridButton from './src/button'
import FetchData from './src/fetch-data'
import Pager from './src/pager'
import Toolbar from './src/toolbar'
import ColumnAnchor from './src/column-anchor'
import Dragger from './src/dragger'
import Sort from './src/sort'
import Tooltip from './src/tooltip'
import Checkbox from './src/checkbox'
import Tree from './src/tree'
import * as GridTools from './src/tools'
import { version } from './package.json'
import type { Plugin } from './src/types/index.type'
// 右键菜单、内置编辑器、导出、键盘操作、校验、响应式改变表格宽高auto-resize、筛选
const components = [Menu, Edit, Export, Keyboard, Validator, Resize, Filter]
/**
* Menu
* Edit
* Export
* Keyboard
* Validator
* Resize auto-resize
* Filter
* FetchData
* Pager
* Toolbar
* ColumnAnchor
* Dragger
* Sort
* Tooltip
* Checkbox
* Tree
*/
const plugins: Plugin[] = [
Menu,
Edit,
Export,
Keyboard,
Validator,
Resize,
Filter,
FetchData,
Pager,
Toolbar,
ColumnAnchor,
Dragger,
Sort,
Tooltip,
Checkbox,
Tree
]
// 设置全局参数,配置GlobalConfig提供比如国际化方法
GridAdapter.setup({ i18n: t })
GridAdapter.t = t
// 把各个插件的方法都合并会$table
components.map((component) => component.install(Table))
// 将每个插件的方法都合并回自己的宿主组件
plugins.map((plugin) => plugin.install(plugin.host === 'grid' ? Grid : Table))
// 让用户可以通过grid组件的方法间接调用内层table组件的方法
const getWrapFunc = (name) =>
function (...args) {
const tinyTable = this.$refs.tinyTable
if (tinyTable) {
return this.$refs.tinyTable[name].apply(tinyTable, args)
}
}
// 将table组件的方法传递给grid组件使用this指向全部指向tinyTable
Object.keys(Table.methods).forEach((name) => {
if (!Grid.methods[name]) {
Grid.methods[name] = getWrapFunc(name)
}
})
Grid.version = version

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'table',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,267 @@
import { hasCheckField, hasNoCheckField } from './handleSelectRow'
import { hasCheckFieldNoStrictly, hasNoCheckFieldNoStrictly, setSelectionNoStrictly } from './setAllSelection'
import { getTableRowKey } from '../../table/src/strategy'
import { emitEvent } from '@opentiny/vue-renderless/grid/utils'
import { isArray, set, get, eachTree, find, toStringJSON, toArray } from '@opentiny/vue-renderless/grid/static/'
export default {
// 处理默认勾选
handleSelectionDefChecked() {
let fullDataRowIdData = this.fullDataRowIdData
let { checkAll, checkRowKeys } = this.selectConfig || {}
if (checkAll) {
this.setAllSelection(true)
return
}
if (checkRowKeys) {
let defCheckedRowids = checkRowKeys.map((key) => encodeURIComponent(key))
let defCheckedRows = []
defCheckedRowids.forEach((rowid) => {
let rowCache = fullDataRowIdData[rowid]
if (rowCache) {
defCheckedRows.push(rowCache.row)
}
})
this.setSelection(defCheckedRows, true)
}
},
setSelection(rows, value) {
if (rows) {
if (!isArray(rows)) {
rows = [rows]
}
rows.forEach((row) => this.handleSelectRow({ row }, !!value))
}
return this.$nextTick()
},
// 多选行选中事件。value选中true、不选false、不确定-1
handleSelectRow({ row }, value) {
hasCheckField({ row }, value, this)
hasNoCheckField({ row }, value, this)
this.checkSelectionStatus()
},
handleToggleCheckRowEvent(params, event) {
let selection = this.selection
let { checkField } = this.selectConfig || {}
let { row } = params
let value = checkField ? !get(row, checkField) : !~selection.indexOf(row)
if (event) {
this.triggerCheckRowEvent(event, params, value)
} else {
this.handleSelectRow(params, value)
}
},
triggerCheckRowEvent(event, params, value) {
let { selectConfig = {} } = this
let { checkMethod } = selectConfig
if (!checkMethod || checkMethod(params)) {
this.handleSelectRow(params, value)
emitEvent(this, 'select-change', [
{
selection: this.getSelectRecords(),
checked: value,
$table: this,
...params
},
event
])
}
},
// 多选,切换某一行的选中状态
toggleRowSelection(row) {
this.handleToggleCheckRowEvent({ row })
return this.$nextTick()
},
setAllSelection(value) {
let { afterFullData, selectConfig = {}, treeConfig, selection } = this
let { checkField: property, reserve, checkStrictly, checkMethod } = selectConfig
hasCheckFieldNoStrictly({ afterFullData, checkMethod, checkStrictly, property, selection, treeConfig, value })
let selectRows = hasNoCheckFieldNoStrictly({
afterFullData,
checkMethod,
checkStrictly,
property,
selection,
treeConfig,
value
})
setSelectionNoStrictly({ _vm: this, checkStrictly, reserve, selectRows, selection, value })
this.treeIndeterminates = []
this.checkSelectionStatus()
},
checkSelectionStatus() {
let { afterFullData, selection, treeIndeterminates } = this
let { checkField, checkStrictly, checkMethod } = this.selectConfig || {}
let { everyHandler, someHandler } = {}
if (checkStrictly) {
return
}
// 包含新增的数据
if (checkField) {
everyHandler = checkMethod
? (row, rowIndex) => !checkMethod({ row, rowIndex }) || get(row, checkField)
: (row) => get(row, checkField)
someHandler = (row) => get(row, checkField) || ~treeIndeterminates.indexOf(row)
this.isAllSelected = false
afterFullData.length && (this.isAllSelected = afterFullData.every(everyHandler))
this.isIndeterminate = !this.isAllSelected && afterFullData.some(someHandler)
} else {
everyHandler = (row, rowIndex) => !checkMethod({ row, rowIndex })
this.headerCheckDisabled = checkMethod && afterFullData.length && afterFullData.every(everyHandler)
everyHandler = checkMethod
? (row, rowIndex) => !checkMethod({ row, rowIndex }) || ~selection.indexOf(row)
: (row) => ~selection.indexOf(row)
someHandler = (row) => ~treeIndeterminates.indexOf(row) || ~selection.indexOf(row)
this.isAllSelected = false
afterFullData.length && (this.isAllSelected = afterFullData.every(everyHandler))
this.isIndeterminate = !this.isAllSelected && afterFullData.some(someHandler)
}
},
// 保留选中状态
reserveCheckSelection() {
let { fullDataRowIdData, selection } = this
let { reserve } = this.selectConfig || {}
let rowkey = getTableRowKey(this)
if (reserve && selection.length) {
this.selection = selection.map((row) => {
let rowCache = fullDataRowIdData[`${get(row, rowkey)}`]
return rowCache ? rowCache.row : row
})
}
},
// 多选,选中所有事件
triggerCheckAllEvent(event, value) {
this.setAllSelection(value)
let eventParams = {
selection: this.getSelectRecords(),
checked: value,
$table: this
}
emitEvent(this, 'select-all', [eventParams, event])
},
// 多选,切换所有行的选中状态
toggleAllSelection() {
this.triggerCheckAllEvent(null, !this.isAllSelected)
return this.$nextTick()
},
clearSelection() {
let { tableFullData, treeConfig } = this
let { checkField } = this.selectConfig || {}
if (checkField) {
treeConfig
? eachTree(tableFullData, (item) => set(item, checkField, false), treeConfig)
: tableFullData.forEach((item) => set(item, checkField, false))
}
Object.assign(this, {
isAllSelected: false,
isIndeterminate: false,
selection: [],
treeIndeterminates: []
})
return this.$nextTick()
},
initMultipleHistory() {
const { isMultipleHistory, toolBarVm } = this.$grid
const {
settingOpts: { storageKey },
id: toolbarId
} = toolBarVm
let remoteSelectedMethod = toolBarVm.setting.multipleHistory.remoteSelectedMethod
let remoteSelectedPromise
if (
isMultipleHistory &&
toolBarVm &&
toolBarVm.setting &&
toolBarVm.setting.multipleHistory &&
remoteSelectedMethod
) {
if (typeof remoteSelectedMethod === 'function') {
remoteSelectedPromise = remoteSelectedMethod()
if (typeof remoteSelectedPromise.then === 'function') {
remoteSelectedPromise.then((storeStr) => {
let storeObj = toStringJSON(storeStr)
storeObj = (storeObj && storeObj[storageKey]) || null
storeObj = (storeObj || {})[toolbarId] || {}
const { columns, pageSize } = storeObj
toolBarVm.applySettings({ columns, pageSize })
})
}
}
}
},
// 显示多选工具栏
showSelectToolbar() {
let {
$grid: { selectToolbar, showHeader },
selectToolbarStore
} = this
if (selectToolbar && showHeader) {
selectToolbarStore.visible = false
let selectColumn = find(this.visibleColumn, (item) => item.type === 'selection')
let selected = this.getSelectRecords()
let position = typeof selectToolbar === 'object' ? selectToolbar.position : ''
if (selectColumn && selected && selected.length) {
let selectTh = this.$el.querySelector('th.tiny-grid-header__column.col__selection')
let headerWrapper = this.$el.querySelector('.tiny-grid>.tiny-grid__header-wrapper')
let tr = selectTh.parentNode
let thArr = toArray(tr.childNodes)
let range = document.createRange()
let rangeBoundingRect
let headerBoundingRect = headerWrapper.getBoundingClientRect()
let layout = { width: 0, height: 0, left: 0, top: 0, zIndex: 1 }
let adjust = 1
if (selectColumn.fixed === 'right') {
range.setStart(tr, thArr.indexOf(selectTh))
range.setEnd(tr, thArr.length)
rangeBoundingRect = range.getBoundingClientRect()
layout.left = `${adjust}px`
} else {
range.setStart(tr, 0)
range.setEnd(tr, thArr.indexOf(selectTh) + 1)
rangeBoundingRect = range.getBoundingClientRect()
layout.left = `${rangeBoundingRect.width + adjust}px`
}
layout.width = `${headerBoundingRect.width - rangeBoundingRect.width - 2 * adjust}px`
if (!selectColumn.fixed && position === 'left') {
range = document.createRange()
range.setStart(tr, 0)
range.setEnd(tr, thArr.indexOf(selectTh))
rangeBoundingRect = range.getBoundingClientRect()
layout.left = `${adjust}px`
layout.width = `${rangeBoundingRect.width - 2 * adjust}px`
}
layout.top = `${headerBoundingRect.height - rangeBoundingRect.height + adjust}px`
layout.height = `${rangeBoundingRect.height - 2 * adjust}px`
return this.$nextTick().then(() => {
selectToolbarStore.layout = layout
selectToolbarStore.visible = true
})
}
}
return this.$nextTick()
},
// 切换多选工具栏的显示
toggleSelectToolbarVisible() {
this.selectToolbarStore.visible = !this.selectToolbarStore.visible
return this.$nextTick()
},
// 在空数据时Selection列表头复选框禁用headerAutoDisabled设置为false就会和旧版本兼容
handleSelectionHeader() {
const { tableFullData, visibleColumn, selectConfig = {} } = this
const { headerAutoDisabled } = selectConfig
const selectionColumn = visibleColumn.find((column) => column.type === 'selection')
if (
(typeof headerAutoDisabled === 'undefined' || (typeof headerAutoDisabled === 'boolean' && headerAutoDisabled)) &&
!tableFullData.length &&
selectionColumn
) {
this.headerCheckDisabled = true
}
}
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'grid',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,104 @@
import { iconMarkOn } from '@opentiny/vue-icon'
import { h } from '@opentiny/vue-common'
export default {
renderColumnAnchor(params, _vm) {
const { anchors = [], action = () => {} } = params || {}
const { viewType } = _vm
return h(
'div',
{
class: ['tiny-grid__column-anchor', _vm.viewCls('columnAnchor')],
style: viewType === 'default' ? 'display:flex' : '',
key: _vm.columnAnchorKey
},
anchors.map((anchor) => {
const { active = false, label = '', field = '', render } = anchor
if (typeof render === 'function') {
return render({ h, anchor, action })
}
const itemClass = { 'tiny-grid__column-anchor-item': true, 'tiny-grid__column-anchor-item--active': active }
const itemOn = { click: (e) => action(field, e) }
const iconVnode = active ? h(iconMarkOn(), { class: 'tiny-grid__column-anchor-item-icon' }) : null
const spanVnode = h('span', label)
return h('div', { class: itemClass, on: itemOn }, [iconVnode, spanVnode])
})
)
},
buildColumnAnchorParams() {
let { columnAnchor } = this
let visibleColumn = this.getColumns()
let anchors = []
let getAnchor = (property, label) => {
const column = visibleColumn.find((col) => !col.type && col.property === property)
let anchorName = ''
let anchorRender = null
if (typeof label !== 'undefined') {
if (typeof label === 'string') {
anchorName = label
} else if (Array.isArray(label) && label.length) {
anchorName = String(label[0])
anchorRender = label[1]
}
}
if (column) {
anchors.push({
label: anchorName || (typeof column.title === 'string' ? column.title : ''),
field: property,
active: false,
render: anchorRender
})
}
}
if (Array.isArray(columnAnchor) && columnAnchor.length) {
columnAnchor.forEach((item) => {
if (typeof item === 'string') {
getAnchor(item)
} else if (Array.isArray(item) && item.length) {
getAnchor(item[0], item[1])
}
})
}
this.columnAnchorParams = { anchors, action: (field, e) => this.anchorAction({ field, anchors, _vm: this, e }) }
},
anchorAction({ field, anchors, _vm }) {
const fromAnchor = anchors.find((anchor) => anchor.active)
const toAnchor = anchors.find((anchor) => anchor.field === field)
if (toAnchor && fromAnchor !== toAnchor) {
if (fromAnchor && fromAnchor.active) {
fromAnchor.active = false
}
if (!toAnchor.active) {
toAnchor.active = true
_vm.columnAnchorKey = field
_vm.$nextTick((found = false) => {
const visibleColumn = _vm.getColumns()
const column = visibleColumn.find((col) => !col.type && col.property === field)
const width = visibleColumn
.filter((col) => !col.fixed)
.map((col) => {
if (col === column) {
found = true
}
return found ? 0 : col.renderWidth
})
.reduce((p, c) => p + c, 0)
if (column) {
_vm.scrollTo(width)
}
})
}
}
}
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'table',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,54 @@
import { createHandlerOnEnd, onEndEvent } from './rowDrop'
export default {
// 处理列拖拽
columnDrop() {
this.$nextTick(() => {
const { plugin, onBeforeMove, filter } = this.dropConfig
this.columnSortable = plugin.create(
this.$el.querySelector('.body__wrapper>.tiny-grid__header .tiny-grid-header__row'),
{
handle: '.tiny-grid-header__column:not(.col__fixed)',
filter,
onEnd: (event) => {
onEndEvent({ event, _this: this })
},
onStart: (event) => {
this.$emit('column-drop-start', event, this)
},
onMove: (event) => {
const cancel = typeof onBeforeMove === 'function' ? onBeforeMove('column', null, event, this) : true
this.$emit('column-drop-move', event, this)
return cancel === undefined || cancel
}
}
)
})
},
// 处理行拖拽
rowDrop() {
this.$nextTick(() => {
const { plugin, onBeforeMove, filter, refresh = true, trigger } = this.dropConfig
this.rowSortable = plugin.create(this.$el.querySelector('.body__wrapper>.tiny-grid__body tbody'), {
handle: trigger || '.tiny-grid-body__row',
filter,
onEnd: createHandlerOnEnd({ _vm: this, refresh }),
onStart: (event) => {
this.$emit('row-drop-start', event, this)
},
onMove: (event) => {
let insertRecords = this.getInsertRecords()
// 包含新增数据的表格不可再拖动行顺序
if (insertRecords.length) return false
let { dragged } = event
let selfRow = this.getRowNode(dragged).item
const cancel = typeof onBeforeMove === 'function' ? onBeforeMove('row', selfRow, event, this) : true
this.$emit('row-drop-move', event, this)
return cancel === undefined || cancel
}
})
})
}
}

View File

@ -24,7 +24,7 @@
*/
import { findTree } from '@opentiny/vue-renderless/grid/static/'
import Modal from '@opentiny/vue-modal'
import GlobalConfig from '../../../config'
import GlobalConfig from '../../config'
function handleIfScrollYLoadTruthy({ isScrollYLoad, _vm, selfRow, prevTrElem, targetTrElem }) {
if (!isScrollYLoad) {
@ -49,7 +49,7 @@ function handleIfScrollYLoadTruthy({ isScrollYLoad, _vm, selfRow, prevTrElem, ta
targetTrElem.remove()
}
function createHandlerOnEnd({ _vm, refresh }) {
export const createHandlerOnEnd = ({ _vm, refresh }) => {
return (event) => {
let insertRecords = _vm.getInsertRecords()
// 包含新增数据的表格不可再拖动行顺序
@ -102,4 +102,62 @@ function createHandlerOnEnd({ _vm, refresh }) {
}
}
export { createHandlerOnEnd }
export const getSortColumns = (columns) => {
const left = []
const right = []
const center = []
columns.forEach((col) => {
const fixed = col.fixed
if (fixed === 'left') {
left.push(col)
} else if (fixed === 'right') {
right.push(col)
} else {
center.push(col)
}
})
return left.concat(center).concat(right)
}
export const onEndEvent = ({ event, _this }) => {
const { item, newIndex, oldIndex } = event
let { fullColumn, tableColumn } = _this.getTableColumn()
const sortVisibleCols = getSortColumns(tableColumn)
let targetThElem = item
let wrapperElem = targetThElem.parentNode
let newColumn = sortVisibleCols[newIndex]
if (newColumn.fixed) {
// 错误的移动
if (newIndex > oldIndex) {
for (let i = newIndex; i >= oldIndex; i--) {
wrapperElem.insertBefore(targetThElem, wrapperElem.children[i])
}
} else {
for (let i = newIndex; i <= oldIndex; i++) {
wrapperElem.insertBefore(targetThElem, wrapperElem.children[i])
}
wrapperElem.insertBefore(wrapperElem.children[oldIndex], targetThElem)
}
return Modal.message({
message: GlobalConfig.i18n('ui.grid.error.dargFixed'),
status: 'error'
})
}
// 转换真实索引
let oldColumnIndex = _this.getColumnIndex(sortVisibleCols[oldIndex])
let newColumnIndex = _this.getColumnIndex(sortVisibleCols[newIndex])
// 移动到目标列
let currCol = fullColumn.splice(oldColumnIndex, 1)[0]
fullColumn.splice(newColumnIndex, 0, currCol)
_this.loadColumn(fullColumn)
_this.$emit('column-drop-end', event, _this)
_this.isDragHeaderSorting && _this.$grid.toolBarVm && _this.$grid.toolBarVm.updateSetting()
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'grid',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,106 @@
import { getObj } from '@opentiny/vue-renderless/common/object'
import { getDataset } from '@opentiny/vue-renderless/common/dataset'
import { error } from '../../tools'
export default {
/**
* fetch-data配置项
* @returns {object}
*/
initFetchOption() {
const { fetchData = {}, dataset = {} } = this as any
if (fetchData.api || dataset.source || dataset.value || dataset.api) {
let { loading, fields, api } = fetchData || dataset.source || dataset.api || {}
return { api, dataset, fields, loading }
}
},
handleFetch(code, sortArg) {
let { pager, sortData, filterData, pagerConfig, fetchOption, fetchData, dataset } = this as any
if (code !== 'prefetch') {
this.clearRadioRow()
this.resetScrollTop()
}
if (!fetchOption) {
error('ui.grid.error.notQuery')
return this.$nextTick()
}
let { args, loading } = fetchData || dataset.source || dataset.api || {}
let { field, order, prop, property } = sortData
let sortByData = { field, order, prop, property }
let params = {
$grid: this,
sort: sortData,
sortBy: sortByData,
filters: filterData,
...args
}
let search
this.tableLoading = loading
if (pagerConfig) {
params.page = pagerConfig
}
if (code === 'reload') {
if (pager || args.page) {
pagerConfig.currentPage = 1
}
this.sortData = params.sort = {}
this.filterData = params.filters = []
this.pendingRecords = []
this.clearAll()
}
if (sortArg && sortArg.length > 0) {
params.sortBy = sortArg
}
if (fetchData && fetchData.api) {
search = fetchData.api.apply(this, [params])
} else {
search = getDataset({ dataset, service: this.$service }, params)
}
return search.then(this.loadFetchData).catch((error) => {
this.tableLoading = false
throw error
})
},
loadFetchData(rest) {
if (!rest) {
this.tableData = []
this.tableLoading = false
return
}
let {
fetchOption: { fields = {} },
pagerConfig,
pagerSlot
} = this as any
if (pagerConfig && !Array.isArray(rest)) {
let total = getObj(rest, fields.total || 'page.total') || rest?.result?.length || 0
let data = getObj(rest, fields.result || fields.data || 'result') || []
this.tableData = data
pagerConfig.total = total
// 内置pager
let setTotal = pagerSlot && pagerSlot.componentInstance.setTotal
setTotal && setTotal(total)
} else {
this.tableData = (fields.list ? getObj(rest, fields.list) : rest) || []
}
if ((this.seqSerial || this.scrollLoad) && pagerConfig) {
this.seqIndex = (pagerConfig.currentPage - 1) * pagerConfig.pageSize
}
this.tableLoading = false
}
}

View File

@ -111,10 +111,10 @@ export function handleFilterCheckStr({ column, relationMethod, relations, row })
value = modifyValueCheckStr(value)
switch (relation) {
case 'equals':
result = value == input
result = value === input
break
case 'unequal':
result = value != input
result = value !== input
break
case 'greaterThan':
result = value > input

View File

@ -26,6 +26,13 @@ import { isArray, isBoolean } from '@opentiny/vue-renderless/grid/static/'
import { getFilters, emitEvent } from '@opentiny/vue-renderless/grid/utils'
import { getDataset } from '@opentiny/vue-renderless/common/dataset'
import { hooks } from '@opentiny/vue-common'
import {
handleFilterConditionCustom,
handleFilterConditionExtend,
handleFilterRelations,
handleFilterCheckStr,
handleFilterCheck
} from './handleLocalFilter'
function getClassName(elem) {
if (elem && elem.nodeType) {
@ -56,7 +63,9 @@ function closest(elem, parentClassName) {
if (elem === document.body) {
break
}
} while ((elem = elem.parentNode))
elem = elem.parentNode
} while (elem)
}
return null
@ -114,6 +123,36 @@ export default {
return Promise.resolve(filters)
},
// 关闭筛选
closeFilter() {
let { filterStore } = this
Object.assign(filterStore, {
visible: false,
targetElem: null,
targetElemParentTr: null
})
return this.$nextTick()
},
handleLocalFilter(row, column) {
let { property } = column
let {
filter: { condition, method, inputFilter }
} = column
let ret = handleFilterConditionCustom({ column, condition, method, property, row })
if (ret.flag) {
return ret.result
}
ret = handleFilterConditionExtend({ column, condition, property, row })
if (ret.flag) {
return ret.result
}
let { empty, input, relation, value, dateList } = condition
let { method: relationMethod } = condition
let relations = handleFilterRelations({ inputFilter })
let checkStr = handleFilterCheckStr({ column, relationMethod, relations, row })
let check = handleFilterCheck({ checkStr, empty, input, property, relation, row, valueList: value, dateList })
return check()
},
getOptions({ property, filter }) {
const { values, value = 'value', label = 'label', dataset } = filter

View File

@ -23,99 +23,16 @@
*
*/
import { getObj } from '@opentiny/vue-renderless/common/object'
import { isBoolean, slice } from '@opentiny/vue-renderless/grid/static/'
import { removeClass, addClass } from '@opentiny/vue-renderless/common/deps/dom'
import { getDataset } from '@opentiny/vue-renderless/common/dataset'
import { isBoolean } from '@opentiny/vue-renderless/grid/static/'
import { getListeners, emitEvent } from '@opentiny/vue-renderless/grid/utils'
import { extend } from '@opentiny/vue-renderless/common/object'
import { h, hooks, emitter, $prefix, $props, setup, defineComponent, resolveMode } from '@opentiny/vue-common'
import Modal from '@opentiny/vue-modal'
import Pager from '@opentiny/vue-pager'
import { Buttons } from '../adapter'
import { error } from '../tools'
import { h, emitter, $prefix, $props, setup, defineComponent, resolveMode } from '@opentiny/vue-common'
import TinyGridTable from '../table'
import GlobalConfig from '../config'
import methods, { setBodyRecords, invokeSaveDataApi, doRemoveOrShowMsg } from './methods'
import { iconMarkOn } from '@opentiny/vue-icon'
import debounce from '@opentiny/vue-renderless/common/deps/debounce'
const propKeys = Object.keys(TinyGridTable.props)
// 表格工具栏渲染器
function getRenderedToolbar({ $slots, _vm, loading, tableLoading, toolbar }) {
return (_vm.renderedToolbar = (() => {
let res = null
if ($slots.toolbar) {
res = $slots.toolbar()
} else if (toolbar) {
res = h(hooks.toRaw(toolbar.component), {
ref: 'toolbar',
props: { loading: loading || tableLoading, ...toolbar },
class: _vm.viewCls('toolbar')
})
}
return res
})())
}
// 表格内置分页渲染器
function renderPager({ $slots, _vm, loading, pager, pagerConfig, tableLoading, vSize }) {
let res = null
if ($slots.pager) {
res = $slots.pager()
} else if (pager) {
pager.component = pager.component || Pager
res = h(hooks.toRaw(pager.component), {
props: {
size: vSize,
loading: loading || tableLoading,
isBeforePageChange: _vm.isBeforePageChange || _vm.showSaveMsg,
accurateJumper: _vm.autoLoad,
...pagerConfig
},
on: {
'size-change': _vm.pageSizeChange,
'current-change': _vm.pageCurrentChange,
'before-page-change': _vm.beforePageChangeHandler
},
ref: 'pager',
class: _vm.viewCls('pager')
})
}
return res
}
function renderColumnAnchor(params, _vm) {
const { anchors = [], action = () => {} } = params || {}
const { viewType } = _vm
return h(
'div',
{
class: ['tiny-grid__column-anchor', _vm.viewCls('columnAnchor')],
style: viewType === 'default' ? 'display:flex' : '',
key: _vm.columnAnchorKey
},
anchors.map((anchor) => {
const { active = false, label = '', field = '', render } = anchor
if (typeof render === 'function') {
return render({ h, anchor, action })
}
const itemClass = { 'tiny-grid__column-anchor-item': true, 'tiny-grid__column-anchor-item--active': active }
const itemOn = { click: (e) => action(field, e) }
const iconVnode = active ? h(iconMarkOn(), { class: 'tiny-grid__column-anchor-item-icon' }) : null
const spanVnode = h('span', label)
return h('div', { class: itemClass, on: itemOn }, [iconVnode, spanVnode])
})
)
}
// 渲染主入口,创建表格最外层节点
function createRender(opt) {
const {
@ -148,10 +65,10 @@ function createRender(opt) {
},
[
selectToolbar ? null : renderedToolbar,
columnAnchor ? renderColumnAnchor(columnAnchorParams, _vm) : null,
columnAnchor ? _vm.renderColumnAnchor(columnAnchorParams, _vm) : null,
// 这里会渲染tiny-grid-column插槽内容从而获取列配置
h(TinyGridTable, { props, on: tableOns, ref: 'tinyTable' }, $slots.default && $slots.default()),
renderPager({
_vm.renderPager({
$slots,
_vm,
loading,
@ -204,6 +121,7 @@ export default defineComponent({
filterData: [],
listeners: {},
pagerConfig: null,
// 存放标记为删除的行数据
pendingRecords: [],
seqIndex: this.startIndex,
sortData: {},
@ -222,16 +140,18 @@ export default defineComponent({
}
},
computed: {
// 工具栏按钮保存和删除时是否弹出提示信息
isMsg() {
return this.proxyOpts.message !== false
},
tableProps() {
let rest = {}
const rest = {}
// 这里收集table组件的props然后提供给下层组件使用
propKeys.forEach((key) => (rest[key] = this[key]))
return rest
},
proxyOpts() {
// 此处需要深拷贝,不然会影响全局配置
return extend(true, {}, GlobalConfig.grid.proxyConfig, this.proxyConfig)
},
vSize() {
@ -348,7 +268,7 @@ export default defineComponent({
remoteFilter,
remoteSort,
selectToolbar
} = this
} = this as any
const {
seqIndex,
slots: $slots,
@ -361,7 +281,7 @@ export default defineComponent({
vSize,
designConfig,
viewType
} = this
} = this as any
const { columnAnchor, columnAnchorParams } = this
// grid全局替换smb图标
@ -370,34 +290,42 @@ export default defineComponent({
}
// 初始化虚拟滚动优化配置
let optimizOpt = { ...GlobalConfig.optimization, ...optimization }
let props = { ...tableProps, optimization: optimizOpt, startIndex: seqIndex }
const optimizOpt = { ...GlobalConfig.optimization, ...optimization }
const props = { ...tableProps, optimization: optimizOpt, startIndex: seqIndex }
// 在用户没有配置stripe时读取design配置
if (designConfig?.stripe !== undefined && !props.stripe) {
// aurora规范默认带斑马条纹
props.stripe = designConfig?.stripe
}
let tableOns = { ...listeners, ...tableListeners }
let { handleRowClassName: rowClassName, sortChangeEvent, filterChangeEvent } = this
const tableOns = { ...listeners, ...tableListeners }
const { handleRowClassName: rowClassName, sortChangeEvent, filterChangeEvent } = this
// fetchApi状态下初始化 loading、remoteSort、remoteFilter
fetchOption && Object.assign(props, { loading: loading || tableLoading, data: tableData, rowClassName })
fetchOption && remoteSort && (tableOns['sort-change'] = sortChangeEvent)
fetchOption && remoteFilter && (tableOns['filter-change'] = filterChangeEvent)
if (fetchOption) {
Object.assign(props, { loading: loading || tableLoading, data: tableData, rowClassName })
remoteSort && (tableOns['sort-change'] = sortChangeEvent)
remoteFilter && (tableOns['filter-change'] = filterChangeEvent)
}
// 处理表格工具栏和个性化数据
toolbar && !(toolbar.setting && toolbar.setting.storage) && (props.customs = tableCustoms)
toolbar && (tableOns['update:customs'] = (value) => (this.tableCustoms = value))
// 初始化表格编辑配置
let editConfigOpt = { trigger: 'click', mode: 'cell', showStatus: true, ...editConfig }
// 这里handleActiveMethod处理一些编辑器的声明周期的拦截用户传递过来的activeMethod优先级最高
editConfig && (props.editConfig = Object.assign(editConfigOpt, { activeMethod: this.handleActiveMethod }))
if (editConfig) {
props.editConfig = {
trigger: 'click',
mode: 'cell',
showStatus: true,
...editConfig,
activeMethod: this.handleActiveMethod
}
}
// 获取工具栏的渲染器
let renderedToolbar = getRenderedToolbar({ $slots, _vm: this, loading, tableLoading, toolbar })
const renderedToolbar = this.getRenderedToolbar({ $slots, _vm: this, loading, tableLoading, toolbar })
// 创建表格最外层容器并加载table组件
return createRender({
@ -419,41 +347,6 @@ export default defineComponent({
})
},
methods: {
...methods,
initPagerConfig() {
let { $slots, fetchOption, scrollLoad = {} } = this
let pagerProps = {}
if (fetchOption) {
let pagerSlot = $slots.pager && $slots.pager[0]
if (pagerSlot) {
let { componentOptions, children } = pagerSlot
if (componentOptions && !children) {
this.pagerSlot = pagerSlot
pagerProps = componentOptions.propsData
}
} else if (this.pager) {
pagerProps = this.pager.attrs
}
if (this.pager || $slots.pager || this.scrollLoad) {
return Object.assign(this.tablePage, { pageSize: scrollLoad.pageSize }, pagerProps)
}
return fetchOption.args && fetchOption.args.page
}
},
initFetchOption() {
let { fetchData = {}, dataset = {} } = this
if (fetchData.api || dataset.source || dataset.value || dataset.api) {
let { loading, fields, api } = fetchData || dataset.source || dataset.api || {}
return { api, dataset, fields, loading }
}
},
updateParentHeight() {
if (!this.tasks.updateParentHeight) {
this.tasks.updateParentHeight = debounce(10, () => {
@ -487,345 +380,6 @@ export default defineComponent({
(!this.editConfig.activeMethod || this.editConfig.activeMethod(params))
)
},
handleFetch(code, sortArg) {
let { pager, sortData, filterData, pagerConfig, fetchOption, fetchData, dataset } = this
if (code !== 'prefetch') {
this.clearRadioRow()
this.resetScrollTop()
}
if (!fetchOption) {
error('ui.grid.error.notQuery')
return this.$nextTick()
}
let { args, loading } = fetchData || dataset.source || dataset.api || {}
let { field, order, prop, property } = sortData
let sortByData = { field, order, prop, property }
let params = {
$grid: this,
sort: sortData,
sortBy: sortByData,
filters: filterData,
...args
}
let search
this.tableLoading = loading
if (pagerConfig) {
params.page = pagerConfig
}
if (code === 'reload') {
if (pager || args.page) {
pagerConfig.currentPage = 1
}
this.sortData = params.sort = {}
this.filterData = params.filters = []
this.pendingRecords = []
this.clearAll()
}
if (sortArg && sortArg.length > 0) {
params.sortBy = sortArg
}
if (fetchData && fetchData.api) {
search = fetchData.api.apply(this, [params])
} else {
search = getDataset({ dataset, service: this.$service }, params)
}
return search.then(this.loadFetchData).catch((error) => {
this.tableLoading = false
throw error
})
},
loadFetchData(rest) {
if (!rest) {
this.tableData = []
this.tableLoading = false
return
}
let {
fetchOption: { fields = {} },
pagerConfig,
pagerSlot
} = this
if (pagerConfig && !Array.isArray(rest)) {
let total = getObj(rest, fields.total || 'page.total') || rest?.result?.length || 0
let data = getObj(rest, fields.result || fields.data || 'result') || []
this.tableData = data
pagerConfig.total = total
// 内置pager
let setTotal = pagerSlot && pagerSlot.componentInstance.setTotal
setTotal && setTotal(total)
} else {
this.tableData = (fields.list ? getObj(rest, fields.list) : rest) || []
}
if ((this.seqSerial || this.scrollLoad) && pagerConfig) {
this.seqIndex = (pagerConfig.currentPage - 1) * pagerConfig.pageSize
}
this.tableLoading = false
},
handleSave(code, args) {
let { saveData, isMsg } = this
if (!saveData) {
error('ui.grid.error.notSave')
return
}
let body = extend(true, { pendingRecords: this.pendingRecords }, this.getRecordset())
let { insertRecords, removeRecords, updateRecords, pendingRecords } = body
let validRows = insertRecords.concat(updateRecords)
let getCallback = (resolve) => (valid) => {
if (!valid) {
resolve(valid)
return
}
let canInvoke = invokeSaveDataApi({
_vm: this,
args,
body,
code,
removeRecords,
resolve,
saveData,
updateRecords,
valid
})
doRemoveOrShowMsg({ _vm: this, canInvoke, code, isMsg, pendingRecords, resolve, valid })
}
// 排除掉新增且标记为删除的数据,排除已标记为删除的数据
setBodyRecords({ body, insertRecords, pendingRecords })
// 只校验新增和修改的数据
return new Promise((resolve) => {
this.validate(validRows, getCallback(resolve))
})
},
handleDelete(code, args) {
let { deleteData, isMsg } = this
if (!deleteData) {
error('ui.grid.error.notDelete')
return
}
let selecteds = this.getSelectRecords(true)
let afterRemove = () => {
let removeds = this.getRemoveRecords()
if (!removeds.length && isMsg && !selecteds.length) {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.selectOneRecord'),
status: 'warning'
})
}
if (removeds.length) {
let apiArgs = [{ $grid: this, changeRecords: { removeRecords: removeds } }, ...args]
let stopLoading = () => {
this.tableLoading = false
}
this.tableLoading = true
return deleteData.api
.apply(this, apiArgs)
.then(stopLoading)
.catch(stopLoading)
.then(() => this.commitProxy('reload'))
}
}
this.remove(selecteds).then(afterRemove)
},
handleFullScreen([show]) {
const cls = 'tiny-fullscreen-full'
show ? addClass(this.$el, cls) : removeClass(this.$el, cls)
this.recalculate()
emitEvent(this, 'fullscreen', show)
this.emitter.emit('fullscreen', show)
},
commitProxy(code) {
let btnMethod = Buttons.get(code)
let args = slice(arguments, 1)
if (code === 'insert') {
this.insert()
} else if (code === 'insert_actived') {
this.insert().then(({ row }) => this.setActiveRow(row))
} else if (code === 'mark_cancel') {
this.triggerPendingEvent(code)
} else if (code === 'delete_selection') {
this.handleDeleteRow(code, 'ui.grid.deleteSelectRecord', () =>
this.commitProxy.apply(this, ['delete'].concat(args))
)
} else if (code === 'remove_selection') {
this.handleDeleteRow(code, 'ui.grid.removeSelectRecord', () => this.removeSelecteds())
} else if (code === 'export') {
this.exportCsv()
} else if (code === 'reset_custom') {
this.resetAll()
} else if (~['reload', 'query', 'prefetch'].indexOf(code)) {
this.handleFetch(code, args)
} else if (code === 'delete') {
this.handleDelete(code, args)
} else if (code === 'save') {
this.handleSave()
} else if (code === 'fullscreen') {
this.handleFullScreen(args)
} else if (btnMethod) {
btnMethod.call(this, { code, $grid: this }, ...args)
}
return this.$nextTick()
},
handleDeleteRow(code, i18nKey, callback) {
let selecteds = this.getSelectRecords()
if (this.isMsg && selecteds.length) {
Modal.confirm(GlobalConfig.i18n(i18nKey)).then((type) => {
type === 'confirm' && callback()
})
}
if (this.isMsg && !selecteds.length) {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.selectOneRecord'),
status: 'warning'
})
}
if (!this.isMsg && selecteds.length) {
callback()
}
},
getPendingRecords() {
return this.pendingRecords
},
triggerToolbarBtnEvent(button, event) {
let { events = {}, tableListeners } = this
let { code } = button
if (!events.toolbarButtonClick && !tableListeners['toolbar-button-click']) {
this.commitProxy(code, event)
}
emitEvent(this, 'toolbar-button-click', [{ code, button, $grid: this }, event])
this.emitter.emit('toolbar-button-click', { code, button, $grid: this }, event)
},
triggerPendingEvent(code) {
let { isMsg, pendingRecords: pendings } = this
let selectColumn = this.getColumns().filter((col) => ~['selection', 'radio'].indexOf(col.type))
let isSelection = selectColumn.length && selectColumn[0].type === 'selection'
let isRadio = selectColumn.length && selectColumn[0].type === 'radio'
let selecteds = isSelection ? this.getSelectRecords(true) : isRadio ? [this.getRadioRow()] : []
if (!selecteds.length && isMsg) {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.selectOneRecord'),
status: 'warning'
})
}
if (selecteds.length) {
let { plus = [], minus = [], tmp } = {}
selecteds.forEach((data) => {
let selectedPending = pendings.includes(data)
tmp = selectedPending ? minus : plus
tmp.push(data)
})
tmp = minus.length ? pendings.filter((item) => !~minus.indexOf(item)) : pendings
this.pendingRecords = tmp.concat(plus)
isSelection && this.clearSelection()
isRadio && this.clearRadioRow()
}
},
pageChangeEvent(params) {
// 这里需要做下防抖操作防止在pageSize从小变大的时候导致fetch-data触发多次
if (!this.tasks.updatePage) {
this.tasks.updatePage = debounce(200, () => {
const eventParams = { $grid: this, ...params }
// 处理标签式监听事件的:@page-change
emitEvent(this, 'page-change', eventParams)
// 处理配置式表格的监听事件
this.emitter.emit('page-change', eventParams)
// 触发fetchData
this.commitProxy('query')
if (this.toolBarVm) {
this.toolBarVm.orderSetting()
}
})
}
this.tasks.updatePage()
},
// size为页大小load为false则触发change事件与查询在个性化初始化时根据autoload控制是否加载数据
pageSizeChange(size, load) {
this.tablePage.pageSize = size
this.tablePage.currentPage = 1
load || this.pageChangeEvent(this.tablePage)
},
pageCurrentChange(current) {
this.tablePage.currentPage = current
this.pageChangeEvent(this.tablePage)
},
beforePageChangeHandler(params) {
if (!this.showSaveMsg) {
let eventParams = extend(false, { $grid: this }, params)
emitEvent(this, 'before-page-change', eventParams)
this.emitter.emit('before-page-change', eventParams)
return
}
let { callback, rollback } = params
let { insertRecords, removeRecords, updateRecords } = this.getRecordset()
if (insertRecords.length || removeRecords.length || updateRecords.length) {
let next = (res) => {
if (res === 'confirm') {
rollback && rollback()
emitEvent(this, 'cancel-page-change', this)
this.emitter.emit('cancel-page-change', this)
} else {
callback && callback()
}
}
Modal.confirm(GlobalConfig.i18n('ui.grid.isSaveMsg')).then(next)
} else {
callback && callback()
}
},
sortChangeEvent(params) {
let remoteSort = this.remoteSort
let column = params.column
@ -843,74 +397,7 @@ export default defineComponent({
viewCls(module) {
return GlobalConfig.viewConfig[module][this.viewType] || ''
},
buildColumnAnchorParams() {
let { columnAnchor } = this
let visibleColumn = this.getColumns()
let anchors = []
let getAnchor = (property, label) => {
const column = visibleColumn.find((col) => !col.type && col.property === property)
let anchorName = ''
let anchorRender = null
if (typeof label !== 'undefined') {
if (typeof label === 'string') {
anchorName = label
} else if (Array.isArray(label) && label.length) {
anchorName = String(label[0])
anchorRender = label[1]
}
}
if (column) {
anchors.push({
label: anchorName || (typeof column.title === 'string' ? column.title : ''),
field: property,
active: false,
render: anchorRender
})
}
}
if (Array.isArray(columnAnchor) && columnAnchor.length) {
columnAnchor.map((item) => {
if (typeof item === 'string') {
getAnchor(item)
} else if (Array.isArray(item) && item.length) {
getAnchor(item[0], item[1])
}
})
}
this.columnAnchorParams = { anchors, action: (field, e) => this.anchorAction({ field, anchors, _vm: this, e }) }
},
anchorAction({ field, anchors, _vm }) {
const fromAnchor = anchors.find((anchor) => anchor.active)
const toAnchor = anchors.find((anchor) => anchor.field === field)
if (toAnchor && fromAnchor !== toAnchor) {
if (fromAnchor && fromAnchor.active) {
fromAnchor.active = false
}
if (!toAnchor.active) {
toAnchor.active = true
_vm.columnAnchorKey = field
_vm.$nextTick((found = false) => {
const visibleColumn = _vm.getColumns()
const column = visibleColumn.find((col) => !col.type && col.property === field)
const width = visibleColumn
.filter((col) => !col.fixed)
.map((col) => (col === column && (found = true), found ? 0 : col.renderWidth))
.reduce((p, c) => p + c, 0)
if (column) {
_vm.scrollTo(width)
}
})
}
}
},
// 监听某个元素是否出现在视口中
addIntersectionObserver() {
if (this.intersectionOption && this.intersectionOption.disabled) return

View File

@ -1,92 +0,0 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Modal from '@opentiny/vue-modal'
import GlobalConfig from '../config'
import Table from '../table'
export function setBodyRecords({ body, insertRecords, pendingRecords }) {
if (insertRecords.length) {
body.pendingRecords = pendingRecords.filter((row) => !insertRecords.includes(row))
}
if (pendingRecords.length) {
body.insertRecords = insertRecords.filter((row) => !pendingRecords.includes(row))
}
}
function canInvokeSaveDataApi(body, removeRecords, updateRecords) {
return body.insertRecords.length || removeRecords.length || updateRecords.length || body.pendingRecords.length
}
export function doRemoveOrShowMsg({ _vm, canInvoke, code, isMsg, pendingRecords, resolve, valid }) {
if (valid && !canInvoke) {
if (isMsg) {
// 直接移除未保存且标记为删除的数据
if (pendingRecords.length) {
_vm.remove(pendingRecords)
} else {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.dataUnchanged'),
status: 'info'
})
}
}
resolve()
}
}
export function invokeSaveDataApi({ _vm, args, body, code, removeRecords, resolve, saveData, updateRecords, valid }) {
let canInvoke = false
if (valid && (canInvoke = canInvokeSaveDataApi(body, removeRecords, updateRecords))) {
_vm.tableLoading = true
resolve(
saveData.api
.apply(_vm, [{ $grid: _vm, changeRecords: body }].concat(args))
.then(() => {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.saveSuccess'),
status: 'success'
})
_vm.tableLoading = false
})
.catch(() => {
_vm.tableLoading = false
})
.then(() => _vm.commitProxy('reload'))
)
}
return canInvoke
}
const getWrapFunc = (name) =>
function () {
const tinyTable = this.$refs.tinyTable
if (tinyTable) {
return this.$refs.tinyTable[name].apply(tinyTable, arguments)
}
}
const methods = {}
const methodNames = Object.keys(Table.methods).concat(['exportCsv', 'clearFilter', 'exportExcel'])
// 将table组件的方法传递给grid组件使用this指向全部指向tinyTable
methodNames.forEach((name) => {
methods[name] = getWrapFunc(name)
})
export default methods

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'grid',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,124 @@
import debounce from '@opentiny/vue-renderless/common/deps/debounce'
import { emitEvent } from '@opentiny/vue-renderless/grid/utils'
import Modal from '@opentiny/vue-modal'
import Pager from '@opentiny/vue-pager'
import { extend } from '@opentiny/vue-renderless/common/object'
import { h, hooks } from '@opentiny/vue-common'
import GlobalConfig from '../../config'
export default {
// 初始化表格分页配置
initPagerConfig() {
let { $slots, fetchOption, scrollLoad = {} } = this as any
let pagerProps = {}
if (fetchOption) {
let pagerSlot = $slots.pager && $slots.pager[0]
if (pagerSlot) {
let { componentOptions, children } = pagerSlot
if (componentOptions && !children) {
this.pagerSlot = pagerSlot
pagerProps = componentOptions.propsData
}
} else if (this.pager) {
pagerProps = this.pager.attrs
}
if (this.pager || $slots.pager || this.scrollLoad) {
return Object.assign(this.tablePage, { pageSize: scrollLoad.pageSize }, pagerProps)
}
return fetchOption.args && fetchOption.args.page
}
},
// 表格内置分页渲染器
renderPager({ $slots, _vm, loading, pager, pagerConfig, tableLoading, vSize }) {
let res = null
if ($slots.pager) {
res = $slots.pager()
} else if (pager) {
pager.component = pager.component || Pager
res = h(hooks.toRaw(pager.component), {
props: {
size: vSize,
loading: loading || tableLoading,
isBeforePageChange: _vm.isBeforePageChange || _vm.showSaveMsg,
accurateJumper: _vm.autoLoad,
...pagerConfig
},
on: {
'size-change': _vm.pageSizeChange,
'current-change': _vm.pageCurrentChange,
'before-page-change': _vm.beforePageChangeHandler
},
ref: 'pager',
class: _vm.viewCls('pager')
})
}
return res
},
pageChangeEvent(params) {
// 这里需要做下防抖操作防止在pageSize从小变大的时候导致fetch-data触发多次
if (!this.tasks.updatePage) {
this.tasks.updatePage = debounce(200, () => {
const eventParams = { $grid: this, ...params }
// 处理标签式监听事件的:@page-change
emitEvent(this, 'page-change', eventParams)
// 处理配置式表格的监听事件
this.emitter.emit('page-change', eventParams)
// 触发fetchData
this.commitProxy('query')
if (this.toolBarVm) {
this.toolBarVm.orderSetting()
}
})
}
this.tasks.updatePage()
},
// size为页大小load为false则触发change事件与查询在个性化初始化时根据autoload控制是否加载数据
pageSizeChange(size, load) {
this.tablePage.pageSize = size
this.tablePage.currentPage = 1
load || this.pageChangeEvent(this.tablePage)
},
pageCurrentChange(current) {
this.tablePage.currentPage = current
this.pageChangeEvent(this.tablePage)
},
beforePageChangeHandler(params) {
if (!this.showSaveMsg) {
let eventParams = extend(false, { $grid: this }, params)
emitEvent(this, 'before-page-change', eventParams)
this.emitter.emit('before-page-change', eventParams)
return
}
let { callback, rollback } = params
let { insertRecords, removeRecords, updateRecords } = this.getRecordset()
if (insertRecords.length || removeRecords.length || updateRecords.length) {
let next = (res) => {
if (res === 'confirm') {
rollback && rollback()
emitEvent(this, 'cancel-page-change', this)
this.emitter.emit('cancel-page-change', this)
} else {
callback && callback()
}
}
Modal.confirm(GlobalConfig.i18n('ui.grid.isSaveMsg')).then(next)
} else {
callback && callback()
}
}
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'table',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1 @@
export default {}

View File

@ -242,40 +242,3 @@ export function handleGlobalKeydownEvent(event) {
export function handleGlobalResizeEvent() {
this.recalculate()
}
// 触发表头 tooltip 事件
export function triggerHeaderTooltipEvent(event, params) {
let { tooltipStore } = this
let { column, showHeaderTip } = params
if (tooltipStore.column !== column || !tooltipStore.visible) {
// 在 v3.0 中废弃 label
this.handleTooltip(event, column, null, showHeaderTip, true)
}
}
// 触发表尾 tooltip 事件
export function triggerFooterTooltipEvent(event, params) {
let { column } = params
let tooltipStore = this.tooltipStore
if (tooltipStore.column !== column || !tooltipStore.visible) {
this.handleTooltip(event, column)
}
}
// 触发 tooltip 事件
export function triggerTooltipEvent(event, params) {
let { editConfig, editStore, tooltipStore } = this
let { actived } = editStore
let { row, column, showTip } = params
if (editConfig) {
if (
(editConfig.mode === 'row' && actived.row === row && column.editor) ||
(actived.row === row && actived.column === column)
) {
return
}
}
if (tooltipStore.column !== column || tooltipStore.row !== row || !tooltipStore.visible) {
this.handleTooltip(event, column, row, showTip)
}
}

View File

@ -9,8 +9,6 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Modal from '@opentiny/vue-modal'
import GlobalConfig from '../../config'
import { warn } from '../../tools'
import { isArray, get } from '@opentiny/vue-renderless/grid/static/'
import { preprocessDataObjectFormat, preventDupRender, handleResolveColumnComplete } from './utils/handleResolveColumn'
@ -53,66 +51,6 @@ export const headerProps = {
children: 'children'
}
export const getSortColumns = (columns) => {
const left = []
const right = []
const center = []
columns.forEach((col) => {
const fixed = col.fixed
if (fixed === 'left') {
left.push(col)
} else if (fixed === 'right') {
right.push(col)
} else {
center.push(col)
}
})
return left.concat(center).concat(right)
}
export const onEndEvent = ({ event, _this }) => {
const { item, newIndex, oldIndex } = event
let { fullColumn, tableColumn } = _this.getTableColumn()
const sortVisibleCols = getSortColumns(tableColumn)
let targetThElem = item
let wrapperElem = targetThElem.parentNode
let newColumn = sortVisibleCols[newIndex]
if (newColumn.fixed) {
// 错误的移动
if (newIndex > oldIndex) {
for (let i = newIndex; i >= oldIndex; i--) {
wrapperElem.insertBefore(targetThElem, wrapperElem.children[i])
}
} else {
for (let i = newIndex; i <= oldIndex; i++) {
wrapperElem.insertBefore(targetThElem, wrapperElem.children[i])
}
wrapperElem.insertBefore(wrapperElem.children[oldIndex], targetThElem)
}
return Modal.message({
message: GlobalConfig.i18n('ui.grid.error.dargFixed'),
status: 'error'
})
}
// 转换真实索引
let oldColumnIndex = _this.getColumnIndex(sortVisibleCols[oldIndex])
let newColumnIndex = _this.getColumnIndex(sortVisibleCols[newIndex])
// 移动到目标列
let currCol = fullColumn.splice(oldColumnIndex, 1)[0]
fullColumn.splice(newColumnIndex, 0, currCol)
_this.loadColumn(fullColumn)
_this.$emit('column-drop-end', event, _this)
_this.isDragHeaderSorting && _this.$grid.toolBarVm && _this.$grid.toolBarVm.updateSetting()
}
export const handleAllColumnPromises = (opt, ctx) => {
let { startIndex, fetchColumns, tableData, asyncRenderMap, isScrollLoad } = opt
return (data) => {

View File

@ -24,13 +24,11 @@
*/
import { getColumnList } from '@opentiny/vue-renderless/grid/utils'
import { toDecimal } from '@opentiny/vue-renderless/common/string'
import { getStyle } from '@opentiny/vue-renderless/common/deps/dom'
import { addClass, removeClass } from '@opentiny/vue-renderless/common/deps/dom'
import debounce from '@opentiny/vue-renderless/common/deps/debounce'
import {
isNumber,
filterTree,
remove,
isArray,
isBoolean,
findTree,
@ -48,9 +46,7 @@ import {
destructuring,
clear,
sum,
find,
toStringJSON,
toArray
find
} from '@opentiny/vue-renderless/grid/static/'
import browser from '@opentiny/vue-renderless/common/browser'
import {
@ -69,8 +65,6 @@ import { error, warn } from '../../tools'
import TINYGrid, { Interceptor } from '../../adapter'
import GlobalConfig from '../../config'
import { handleLayout } from './utils/updateStyle'
import { createTooltipRange, processContentMethod } from './utils/handleTooltip'
import { hasCheckField, hasNoCheckField } from './utils/handleSelectRow'
import {
isTargetRadioOrCheckbox,
onClickExpandColumn,
@ -88,24 +82,12 @@ import {
showGroupFixedError,
onScrollXLoad
} from './utils/refreshColumn'
import {
handleFilterConditionCustom,
handleFilterConditionExtend,
handleFilterRelations,
handleFilterCheckStr,
handleFilterCheck
} from './utils/handleLocalFilter'
import { hasCheckFieldNoStrictly, hasNoCheckFieldNoStrictly, setSelectionNoStrictly } from './utils/setAllSelection'
import { mapFetchColumnPromise } from './utils/handleResolveColumn'
import { computeScrollYLoad, computeScrollXLoad } from './utils/computeScrollLoad'
import { createHandlerOnEnd } from './utils/rowDrop'
import { calcTableWidth, calcFixedStickyPosition } from './utils/autoCellWidth'
import { generateFixedClassName } from './utils/handleFixedColumn'
import { funcs, headerProps, onEndEvent, handleAllColumnPromises } from './funcs'
import { funcs, headerProps, handleAllColumnPromises } from './funcs'
import {
triggerHeaderTooltipEvent,
triggerFooterTooltipEvent,
triggerTooltipEvent,
handleGlobalMousedownEvent,
handleGlobalBlurEvent,
handleGlobalMousewheelEvent,
@ -134,19 +116,20 @@ import {
let run = (names, $table) => names.forEach((name) => $table[name].apply($table))
let isWebkit = browser['-webkit']
let debounceScrollDirDuration = browser.name === 'ie' ? 40 : 20
let debounceScrollDirDuration = 20
let debounceScrollLoadDuration = 200
let AsyncCollectTimeout = 100
let focusSingle = null
// 多字段排序
const sortMultiple = (rows, columns, _vm) => {
const greaterThan = (valueP, valueQ) => {
let typeP
return (typeP = typeof valueP) === typeof valueQ && ['number', 'string', 'boolean'].includes(typeP)
? valueP > valueQ
: String(valueP) > String(valueQ)
const typeP = typeof valueP
const typeQ = typeof valueQ
if (typeP === typeQ && ['number', 'string', 'boolean'].includes(typeP)) {
return valueP > valueQ
} else {
return String(valueP) > String(valueQ)
}
}
const { multipleColumnSort } = _vm.sortOpts
@ -174,57 +157,6 @@ const sortMultiple = (rows, columns, _vm) => {
}
const Methods = {
// 处理列拖拽
columnDrop() {
this.$nextTick(() => {
const { plugin, onBeforeMove, filter } = this.dropConfig
this.columnSortable = plugin.create(
this.$el.querySelector('.body__wrapper>.tiny-grid__header .tiny-grid-header__row'),
{
handle: '.tiny-grid-header__column:not(.col__fixed)',
filter,
onEnd: (event) => {
onEndEvent({ event, _this: this })
},
onStart: (event) => {
this.$emit('column-drop-start', event, this)
},
onMove: (event) => {
const cancel = typeof onBeforeMove === 'function' ? onBeforeMove('column', null, event, this) : true
this.$emit('column-drop-move', event, this)
return cancel === undefined || cancel
}
}
)
})
},
// 处理行拖拽
rowDrop() {
this.$nextTick(() => {
const { plugin, onBeforeMove, filter, refresh = true, trigger } = this.dropConfig
this.rowSortable = plugin.create(this.$el.querySelector('.body__wrapper>.tiny-grid__body tbody'), {
handle: trigger || '.tiny-grid-body__row',
filter,
onEnd: createHandlerOnEnd({ _vm: this, refresh }),
onStart: (event) => {
this.$emit('row-drop-start', event, this)
},
onMove: (event) => {
let insertRecords = this.getInsertRecords()
// 包含新增数据的表格不可再拖动行顺序
if (insertRecords.length) return false
let { dragged } = event
let selfRow = this.getRowNode(dragged).item
const cancel = typeof onBeforeMove === 'function' ? onBeforeMove('row', selfRow, event, this) : true
this.$emit('row-drop-move', event, this)
return cancel === undefined || cancel
}
})
})
},
getParentElem() {
let $el = this.$grid ? this.$grid.$el : this.$el
return $el.parentNode
@ -613,11 +545,6 @@ const Methods = {
collectColumn: collectColumn.slice(0)
}
},
// 在v3.0中废弃getRecords
getRecords() {
warn('ui.grid.error.delGetRecords')
return this.getData.apply(this, arguments)
},
// 获取表格所有数据
getData(rowIndex) {
let tableSynchData = this.data || this.tableSynchData
@ -629,11 +556,6 @@ const Methods = {
}
return undefined
},
// 在v3.0中废弃getAllRecords
getAllRecords() {
warn('ui.grid.error.delGetAllRecords')
return this.getRecordset()
},
// 获取选中数据。notCopy为true不返回数据副本表格内部要继续处理其返回值时设置为true
getSelectRecords(notCopy) {
let { selectConfig = {}, selection } = this
@ -654,26 +576,6 @@ const Methods = {
}
return notCopy ? rowList : clone(rowList, true)
},
handleLocalFilter(row, column) {
let { property } = column
let {
filter: { condition, method, inputFilter }
} = column
let ret = handleFilterConditionCustom({ column, condition, method, property, row })
if (ret.flag) {
return ret.result
}
ret = handleFilterConditionExtend({ column, condition, property, row })
if (ret.flag) {
return ret.result
}
let { empty, input, relation, value, dateList } = condition
let { method: relationMethod } = condition
let relations = handleFilterRelations({ inputFilter })
let checkStr = handleFilterCheckStr({ column, relationMethod, relations, row })
let check = handleFilterCheck({ checkStr, empty, input, property, relation, row, valueList: value, dateList })
return check()
},
// 对数据进行筛选和排序,获取处理后数据。服务端筛选和排序,在接口调用时已传入参数
updateAfterFullData() {
let { remoteFilter, remoteSort, tableFullData, visibleColumn, sortOpts } = this
@ -1108,258 +1010,6 @@ const Methods = {
handleOtherKeyDown,
handleGlobalKeydownEvent,
handleGlobalResizeEvent,
triggerHeaderTooltipEvent,
triggerFooterTooltipEvent,
triggerTooltipEvent,
// 显示 tooltip 依赖的 popper 组件
activateTooltip(tooltip, isValid) {
if (!this.tasks.activateTooltip) {
this.tasks.activateTooltip = debounce(300, () => {
let sign = isValid !== undefined ? isValid : focusSingle
if (sign) {
tooltip.state.popperElm && (tooltip.state.popperElm.style.display = 'none')
tooltip.doDestroy()
tooltip.show()
setTimeout(tooltip.updatePopper)
}
})
}
this.tasks.activateTooltip()
},
// 显示 tooltip 依赖的 popper 组件
activateTooltipValid(tooltip) {
if (!this.tasks.activateTooltipValid) {
this.tasks.activateTooltipValid = debounce(50, () => {
tooltip.handleShowPopper()
setTimeout(() => tooltip.updatePopper())
})
}
this.tasks.activateTooltipValid()
},
// 显示 tooltip
handleTooltip(event, column, row, showTip, isHeader) {
const cell = isHeader
? event.currentTarget.querySelector('.tiny-grid-cell-text')
: event.currentTarget.querySelector('.tiny-grid-cell')
// 当用户悬浮在排序或者筛选图标按钮时不应该显示tooltip
if (isHeader && event.target !== cell) {
return
}
const tooltip = this.$refs.tooltip
const wrapperElem = cell
const content = cell.innerText.trim() || cell.textContent.trim()
const { contentMethod } = this.tooltipConfig
const range = createTooltipRange({ _vm: this, cell, column, isHeader })
const rangeWidth = range.getBoundingClientRect().width
const padding =
(parseInt(getStyle(cell, 'paddingLeft'), 10) || 0) + (parseInt(getStyle(cell, 'paddingRight'), 10) || 0)
const isOverflow = rangeWidth + padding > cell.offsetWidth || wrapperElem.scrollWidth > wrapperElem.clientWidth
// content如果是空字符串但是用户配置了contentMethod则同样也可以触发提示
if ((contentMethod || content) && (showTip || isOverflow)) {
Object.assign(this.tooltipStore, { row, column, visible: true })
if (tooltip) {
processContentMethod({ _vm: this, column, content, contentMethod, event, isHeader, row, showTip })
tooltip.state.referenceElm = cell
tooltip.state.popperElm && (tooltip.state.popperElm.style.display = 'none')
focusSingle = true
this.activateTooltip(tooltip)
}
}
return this.$nextTick()
},
// 提供关闭tips提示的方法
clostTooltip() {
let tooltip = this.$refs.tooltip
Object.assign(this.tooltipStore, {
content: null,
row: null,
visible: false,
column: null
})
focusSingle = false
if (tooltip && typeof tooltip.setExpectedState === 'function') {
tooltip.setExpectedState(false)
this.debounceClose(tooltip)
}
return this.$nextTick()
},
// 添加代码让用户代码可以划入popper
debounceClose(tooltip) {
if (!this.tasks.debounceClose) {
this.tasks.debounceClose = debounce(50, () => {
tooltip.handleClosePopper()
})
}
this.tasks.debounceClose()
},
// 处理默认勾选
handleSelectionDefChecked() {
let fullDataRowIdData = this.fullDataRowIdData
let { checkAll, checkRowKeys } = this.selectConfig || {}
if (checkAll) {
this.setAllSelection(true)
return
}
if (checkRowKeys) {
let defCheckedRowids = checkRowKeys.map((key) => encodeURIComponent(key))
let defCheckedRows = []
defCheckedRowids.forEach((rowid) => {
let rowCache = fullDataRowIdData[rowid]
if (rowCache) {
defCheckedRows.push(rowCache.row)
}
})
this.setSelection(defCheckedRows, true)
}
},
setSelection(rows, value) {
if (rows) {
if (!isArray(rows)) {
rows = [rows]
}
rows.forEach((row) => this.handleSelectRow({ row }, !!value))
}
return this.$nextTick()
},
// 多选行选中事件。value选中true、不选false、不确定-1
handleSelectRow({ row }, value) {
hasCheckField({ row }, value, this)
hasNoCheckField({ row }, value, this)
this.checkSelectionStatus()
},
handleToggleCheckRowEvent(params, event) {
let selection = this.selection
let { checkField } = this.selectConfig || {}
let { row } = params
let value = checkField ? !get(row, checkField) : !~selection.indexOf(row)
if (event) {
this.triggerCheckRowEvent(event, params, value)
} else {
this.handleSelectRow(params, value)
}
},
triggerCheckRowEvent(event, params, value) {
let { selectConfig = {} } = this
let { checkMethod } = selectConfig
if (!checkMethod || checkMethod(params)) {
this.handleSelectRow(params, value)
emitEvent(this, 'select-change', [
{
selection: this.getSelectRecords(),
checked: value,
$table: this,
...params
},
event
])
}
},
// 多选,切换某一行的选中状态
toggleRowSelection(row) {
this.handleToggleCheckRowEvent({ row })
return this.$nextTick()
},
setAllSelection(value) {
let { afterFullData, selectConfig = {}, treeConfig, selection } = this
let { checkField: property, reserve, checkStrictly, checkMethod } = selectConfig
hasCheckFieldNoStrictly({ afterFullData, checkMethod, checkStrictly, property, selection, treeConfig, value })
let selectRows = hasNoCheckFieldNoStrictly({
afterFullData,
checkMethod,
checkStrictly,
property,
selection,
treeConfig,
value
})
setSelectionNoStrictly({ _vm: this, checkStrictly, reserve, selectRows, selection, value })
this.treeIndeterminates = []
this.checkSelectionStatus()
},
checkSelectionStatus() {
let { afterFullData, selection, treeIndeterminates } = this
let { checkField, checkStrictly, checkMethod } = this.selectConfig || {}
let { everyHandler, someHandler } = {}
if (checkStrictly) {
return
}
// 包含新增的数据
if (checkField) {
everyHandler = checkMethod
? (row, rowIndex) => !checkMethod({ row, rowIndex }) || get(row, checkField)
: (row) => get(row, checkField)
someHandler = (row) => get(row, checkField) || ~treeIndeterminates.indexOf(row)
this.isAllSelected = false
afterFullData.length && (this.isAllSelected = afterFullData.every(everyHandler))
this.isIndeterminate = !this.isAllSelected && afterFullData.some(someHandler)
} else {
everyHandler = (row, rowIndex) => !checkMethod({ row, rowIndex })
this.headerCheckDisabled = checkMethod && afterFullData.length && afterFullData.every(everyHandler)
everyHandler = checkMethod
? (row, rowIndex) => !checkMethod({ row, rowIndex }) || ~selection.indexOf(row)
: (row) => ~selection.indexOf(row)
someHandler = (row) => ~treeIndeterminates.indexOf(row) || ~selection.indexOf(row)
this.isAllSelected = false
afterFullData.length && (this.isAllSelected = afterFullData.every(everyHandler))
this.isIndeterminate = !this.isAllSelected && afterFullData.some(someHandler)
}
},
// 保留选中状态
reserveCheckSelection() {
let { fullDataRowIdData, selection } = this
let { reserve } = this.selectConfig || {}
let rowkey = getTableRowKey(this)
if (reserve && selection.length) {
this.selection = selection.map((row) => {
let rowCache = fullDataRowIdData[`${get(row, rowkey)}`]
return rowCache ? rowCache.row : row
})
}
},
// 多选,选中所有事件
triggerCheckAllEvent(event, value) {
this.setAllSelection(value)
let eventParams = {
selection: this.getSelectRecords(),
checked: value,
$table: this
}
emitEvent(this, 'select-all', [eventParams, event])
},
// 多选,切换所有行的选中状态
toggleAllSelection() {
this.triggerCheckAllEvent(null, !this.isAllSelected)
return this.$nextTick()
},
clearSelection() {
let { tableFullData, treeConfig } = this
let { checkField } = this.selectConfig || {}
if (checkField) {
treeConfig
? eachTree(tableFullData, (item) => set(item, checkField, false), treeConfig)
: tableFullData.forEach((item) => set(item, checkField, false))
}
Object.assign(this, {
isAllSelected: false,
isIndeterminate: false,
selection: [],
treeIndeterminates: []
})
return this.$nextTick()
},
// 处理单选框默认勾选
handleRadioDefChecked() {
let { fullDataRowIdData } = this
@ -1573,16 +1223,6 @@ const Methods = {
return this.handleTableData(true).then(this.refreshStyle)
},
// 关闭筛选
closeFilter() {
let { filterStore } = this
Object.assign(filterStore, {
visible: false,
targetElem: null,
targetElemParentTr: null
})
return this.$nextTick()
},
toggleGroupExpansion(row) {
this.groupExpandeds.push(row)
},
@ -1659,116 +1299,6 @@ const Methods = {
this.expandeds = []
return this.$nextTick().then(() => (hasExpand ? this.recalculate() : 0))
},
// 展开树节点事件
triggerTreeExpandEvent(event, { row }) {
let { currentColumn, currentRow } = this
let rest = this.toggleTreeExpansion(row)
let eventParams = { $table: this, row, rowIndex: this.getRowIndex(row) }
emitEvent(this, 'toggle-tree-change', [eventParams, event])
this.$nextTick(() => {
currentRow ? this.setCurrentRow(currentRow) : currentColumn ? this.setCurrentColumn(currentColumn) : ''
})
return rest
},
// 切换/展开树节点
toggleTreeExpansion(row) {
return this.setTreeExpansion(row)
},
// 处理默认展开树节点
handleDefaultTreeExpand() {
let { tableFullData, treeConfig } = this
if (!treeConfig) {
return
}
let { children, expandAll, expandRowKeys: rowids } = treeConfig
let treeExpandeds = []
let rowkey = getTableRowKey(this)
let isNonEmptyArr = (arr) => isArray(arr) && arr.length
// 展开所有行
let doExpandAll = () => {
filterTree(tableFullData, (row) => isNonEmptyArr(row[children]) && treeExpandeds.push(row), treeConfig)
this.treeExpandeds = treeExpandeds
}
// 展开指定行
let doExpandRows = () => {
rowids.forEach((rowid) => {
let matchObj = findTree(tableFullData, (item) => rowid === get(item, rowkey), treeConfig)
matchObj && isNonEmptyArr(matchObj.item[children]) && treeExpandeds.push(matchObj.item)
})
this.treeExpandeds = treeExpandeds
}
expandAll ? doExpandAll() : rowids ? doExpandRows() : ''
setTreeScrollYCache(this)
},
setAllTreeExpansion(expanded) {
let { tableFullData, treeConfig } = this
let children = treeConfig.children
let treeExpandeds = []
if (expanded) {
let rowHandler = (row) => {
if (row[children] && row[children].length) {
treeExpandeds.push(row)
}
}
eachTree(tableFullData, rowHandler, treeConfig)
}
this.treeExpandeds = treeExpandeds
setTreeScrollYCache(this)
return this.$nextTick().then(this.recalculate)
},
// 设置展开树形节点,二个参数设置这一行展开与否:支持单行,支持多行
setTreeExpansion(rows, expanded) {
let { treeConfig, treeExpandeds, tableFullData } = this
let { accordion, children } = treeConfig
let isToggle = arguments.length === 1
if (!rows) {
return this.$nextTick().then(this.recalculate)
}
if (!isArray(rows)) {
rows = [rows]
}
if (accordion) {
rows = rows.slice(rows.length - 1, rows.length)
}
// 这里需要进行一次浅拷贝不能直接操作vue observe的数组不然vue频繁的get、set、toRaw、reactive等操作从而导致卡顿
const treeExpandedsCopy = [...treeExpandeds]
rows.forEach((row) => {
if (row[children] && row[children].length) {
const index = treeExpandedsCopy.indexOf(row)
if (accordion) {
// 同一级只能展开一个
const matchObj = findTree(tableFullData, (item) => item === row, treeConfig)
remove(treeExpandedsCopy, (item) => ~matchObj.items.indexOf(item))
}
if (~index && (isToggle || !expanded)) {
treeExpandedsCopy.splice(index, 1)
return
}
if (!~index && (isToggle || expanded)) {
treeExpandedsCopy.push(row)
}
}
})
this.treeExpandeds = treeExpandedsCopy
setTreeScrollYCache(this)
return this.$nextTick().then(this.recalculate)
},
hasTreeExpand(row) {
return ~this.treeExpandeds.indexOf(row)
},
clearTreeExpand() {
const hasExpand = this.treeExpandeds.length
this.treeExpandeds = []
setTreeScrollYCache(this)
return this.$nextTick().then(() => (hasExpand ? this.recalculate() : 0))
},
// 获取虚拟滚动状态
getVirtualScroller() {
let { scrollXLoad, scrollYLoad } = this
@ -2289,105 +1819,6 @@ const Methods = {
},
// 检查触发源是否属于目标节点
getEventTargetNode,
initMultipleHistory() {
const { isMultipleHistory, toolBarVm } = this.$grid
const {
settingOpts: { storageKey },
id: toolbarId
} = toolBarVm
let remoteSelectedMethod, remoteSelectedPromise
if (
isMultipleHistory &&
toolBarVm &&
toolBarVm.setting &&
toolBarVm.setting.multipleHistory &&
(remoteSelectedMethod = toolBarVm.setting.multipleHistory.remoteSelectedMethod)
) {
if (typeof remoteSelectedMethod === 'function') {
remoteSelectedPromise = remoteSelectedMethod()
if (typeof remoteSelectedPromise.then === 'function') {
remoteSelectedPromise.then((storeStr) => {
let storeObj = toStringJSON(storeStr)
storeObj = (storeObj && storeObj[storageKey]) || null
storeObj = (storeObj || {})[toolbarId] || {}
const { columns, pageSize } = storeObj
toolBarVm.applySettings({ columns, pageSize })
})
}
}
}
},
// 显示多选工具栏
showSelectToolbar() {
let {
$grid: { selectToolbar, showHeader },
selectToolbarStore
} = this
if (selectToolbar && showHeader) {
selectToolbarStore.visible = false
let selectColumn = find(this.visibleColumn, (item) => item.type === 'selection')
let selected = this.getSelectRecords()
let position = typeof selectToolbar === 'object' ? selectToolbar.position : ''
if (selectColumn && selected && selected.length) {
let selectTh = this.$el.querySelector('th.tiny-grid-header__column.col__selection')
let headerWrapper = this.$el.querySelector('.tiny-grid>.tiny-grid__header-wrapper')
let tr = selectTh.parentNode
let thArr = toArray(tr.childNodes)
let range = document.createRange()
let rangeBoundingRect
let headerBoundingRect = headerWrapper.getBoundingClientRect()
let layout = { width: 0, height: 0, left: 0, top: 0, zIndex: 1 }
let adjust = 1
if (selectColumn.fixed === 'right') {
range.setStart(tr, thArr.indexOf(selectTh))
range.setEnd(tr, thArr.length)
rangeBoundingRect = range.getBoundingClientRect()
layout.left = `${adjust}px`
} else {
range.setStart(tr, 0)
range.setEnd(tr, thArr.indexOf(selectTh) + 1)
rangeBoundingRect = range.getBoundingClientRect()
layout.left = `${rangeBoundingRect.width + adjust}px`
}
layout.width = `${headerBoundingRect.width - rangeBoundingRect.width - 2 * adjust}px`
if (!selectColumn.fixed && position === 'left') {
range = document.createRange()
range.setStart(tr, 0)
range.setEnd(tr, thArr.indexOf(selectTh))
rangeBoundingRect = range.getBoundingClientRect()
layout.left = `${adjust}px`
layout.width = `${rangeBoundingRect.width - 2 * adjust}px`
}
layout.top = `${headerBoundingRect.height - rangeBoundingRect.height + adjust}px`
layout.height = `${rangeBoundingRect.height - 2 * adjust}px`
return this.$nextTick().then(() => {
selectToolbarStore.layout = layout
selectToolbarStore.visible = true
})
}
}
return this.$nextTick()
},
// 切换多选工具栏的显示
toggleSelectToolbarVisible() {
this.selectToolbarStore.visible = !this.selectToolbarStore.visible
return this.$nextTick()
},
// 在空数据时Selection列表头复选框禁用headerAutoDisabled设置为false就会和旧版本兼容
handleSelectionHeader() {
const { tableFullData, visibleColumn, selectConfig = {} } = this
const { headerAutoDisabled } = selectConfig
const selectionColumn = visibleColumn.find((column) => column.type === 'selection')
if (
(typeof headerAutoDisabled === 'undefined' || (typeof headerAutoDisabled === 'boolean' && headerAutoDisabled)) &&
!tableFullData.length &&
selectionColumn
) {
this.headerCheckDisabled = true
}
},
// 可见性改变事件处理
handleVisibilityChange(visible, entry) {
if (visible) {
@ -2416,8 +1847,8 @@ const Methods = {
}
}
funcs.forEach((name) => {
Methods[name] = function () {
return this[`_${name}`] ? this[`_${name}`].apply(this, arguments) : null
Methods[name] = function (...args) {
return this[`_${name}`] ? this[`_${name}`](...args) : null
}
})
export default Methods

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'grid',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,280 @@
import { Buttons } from '../../adapter'
import { error } from '../../tools'
import Modal from '@opentiny/vue-modal'
import GlobalConfig from '../../config'
import { emitEvent } from '@opentiny/vue-renderless/grid/utils'
import { h, hooks } from '@opentiny/vue-common'
import { addClass, removeClass } from '@opentiny/vue-renderless/common/deps/dom'
import { extend } from '@opentiny/vue-renderless/common/object'
export function setBodyRecords({ body, insertRecords, pendingRecords }) {
if (insertRecords.length) {
body.pendingRecords = pendingRecords.filter((row) => !insertRecords.includes(row))
}
if (pendingRecords.length) {
body.insertRecords = insertRecords.filter((row) => !pendingRecords.includes(row))
}
}
function canInvokeSaveDataApi(body, removeRecords, updateRecords) {
return body.insertRecords.length || removeRecords.length || updateRecords.length || body.pendingRecords.length
}
export function doRemoveOrShowMsg({ _vm, canInvoke, code, isMsg, pendingRecords, resolve, valid }) {
if (valid && !canInvoke) {
if (isMsg) {
// 直接移除未保存且标记为删除的数据
if (pendingRecords.length) {
_vm.remove(pendingRecords)
} else {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.dataUnchanged'),
status: 'info'
})
}
}
resolve()
}
}
export function invokeSaveDataApi({ _vm, args, body, code, removeRecords, resolve, saveData, updateRecords, valid }) {
let canInvoke = false
if (valid) {
canInvoke = canInvokeSaveDataApi(body, removeRecords, updateRecords)
}
if (valid && canInvoke) {
_vm.tableLoading = true
resolve(
saveData.api
.apply(_vm, [{ $grid: _vm, changeRecords: body }].concat(args))
.then(() => {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.saveSuccess'),
status: 'success'
})
_vm.tableLoading = false
})
.catch(() => {
_vm.tableLoading = false
})
.then(() => _vm.commitProxy('reload'))
)
}
return canInvoke
}
export default {
// 表格工具栏渲染器
getRenderedToolbar({ $slots, _vm, loading, tableLoading, toolbar }) {
return (_vm.renderedToolbar = (() => {
let res = null
if ($slots.toolbar) {
res = $slots.toolbar()
} else if (toolbar) {
res = h(hooks.toRaw(toolbar.component), {
ref: 'toolbar',
props: { loading: loading || tableLoading, ...toolbar },
class: _vm.viewCls('toolbar')
})
}
return res
})())
},
handleSave(code, args) {
let { saveData, isMsg } = this
if (!saveData) {
error('ui.grid.error.notSave')
return
}
let body = extend(true, { pendingRecords: this.pendingRecords }, this.getRecordset())
let { insertRecords, removeRecords, updateRecords, pendingRecords } = body
let validRows = insertRecords.concat(updateRecords)
let getCallback = (resolve) => (valid) => {
if (!valid) {
resolve(valid)
return
}
let canInvoke = invokeSaveDataApi({
_vm: this,
args,
body,
code,
removeRecords,
resolve,
saveData,
updateRecords,
valid
})
doRemoveOrShowMsg({ _vm: this, canInvoke, code, isMsg, pendingRecords, resolve, valid })
}
// 排除掉新增且标记为删除的数据,排除已标记为删除的数据
setBodyRecords({ body, insertRecords, pendingRecords })
// 只校验新增和修改的数据
return new Promise((resolve) => {
this.validate(validRows, getCallback(resolve))
})
},
handleDelete(code, args) {
let { deleteData, isMsg } = this
if (!deleteData) {
error('ui.grid.error.notDelete')
return
}
let selecteds = this.getSelectRecords(true)
let afterRemove = () => {
let removeds = this.getRemoveRecords()
if (!removeds.length && isMsg && !selecteds.length) {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.selectOneRecord'),
status: 'warning'
})
}
if (removeds.length) {
let apiArgs = [{ $grid: this, changeRecords: { removeRecords: removeds } }, ...args]
let stopLoading = () => {
this.tableLoading = false
}
this.tableLoading = true
return deleteData.api
.apply(this, apiArgs)
.then(stopLoading)
.catch(stopLoading)
.then(() => this.commitProxy('reload'))
}
}
this.remove(selecteds).then(afterRemove)
},
handleFullScreen([show]) {
const cls = 'tiny-fullscreen-full'
show ? addClass(this.$el, cls) : removeClass(this.$el, cls)
this.recalculate()
emitEvent(this, 'fullscreen', show)
this.emitter.emit('fullscreen', show)
},
commitProxy(code, ...args) {
let btnMethod = Buttons.get(code)
if (code === 'insert') {
this.insert()
} else if (code === 'insert_actived') {
this.insert().then(({ row }) => this.setActiveRow(row))
} else if (code === 'mark_cancel') {
this.triggerPendingEvent(code)
} else if (code === 'delete_selection') {
this.handleDeleteRow(code, 'ui.grid.deleteSelectRecord', () => this.commitProxy(['delete', ...args]))
} else if (code === 'remove_selection') {
this.handleDeleteRow(code, 'ui.grid.removeSelectRecord', () => this.removeSelecteds())
} else if (code === 'export') {
this.exportCsv()
} else if (code === 'reset_custom') {
this.resetAll()
} else if (~['reload', 'query', 'prefetch'].indexOf(code)) {
this.handleFetch(code, args)
} else if (code === 'delete') {
this.handleDelete(code, args)
} else if (code === 'save') {
this.handleSave()
} else if (code === 'fullscreen') {
this.handleFullScreen(args)
} else if (btnMethod) {
btnMethod.call(this, { code, $grid: this }, ...args)
}
return this.$nextTick()
},
handleDeleteRow(code, i18nKey, callback) {
let selecteds = this.getSelectRecords()
if (this.isMsg && selecteds.length) {
Modal.confirm(GlobalConfig.i18n(i18nKey)).then((type) => {
type === 'confirm' && callback()
})
}
if (this.isMsg && !selecteds.length) {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.selectOneRecord'),
status: 'warning'
})
}
if (!this.isMsg && selecteds.length) {
callback()
}
},
getPendingRecords() {
return this.pendingRecords
},
triggerToolbarBtnEvent(button, event) {
let { events = {}, tableListeners } = this
let { code } = button
if (!events.toolbarButtonClick && !tableListeners['toolbar-button-click']) {
this.commitProxy(code, event)
}
emitEvent(this, 'toolbar-button-click', [{ code, button, $grid: this }, event])
this.emitter.emit('toolbar-button-click', { code, button, $grid: this }, event)
},
triggerPendingEvent(code) {
let { isMsg, pendingRecords: pendings } = this
let selectColumn = this.getColumns().filter((col) => ~['selection', 'radio'].indexOf(col.type))
let isSelection = selectColumn.length && selectColumn[0].type === 'selection'
let isRadio = selectColumn.length && selectColumn[0].type === 'radio'
let selecteds = isSelection ? this.getSelectRecords(true) : isRadio ? [this.getRadioRow()] : []
if (!selecteds.length && isMsg) {
Modal.message({
id: code,
message: GlobalConfig.i18n('ui.grid.selectOneRecord'),
status: 'warning'
})
}
if (selecteds.length) {
let { plus = [], minus = [], tmp } = {}
selecteds.forEach((data) => {
let selectedPending = pendings.includes(data)
tmp = selectedPending ? minus : plus
tmp.push(data)
})
tmp = minus.length ? pendings.filter((item) => !~minus.indexOf(item)) : pendings
this.pendingRecords = tmp.concat(plus)
isSelection && this.clearSelection()
isRadio && this.clearRadioRow()
}
}
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'table',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,133 @@
import debounce from '@opentiny/vue-renderless/common/deps/debounce'
import { getStyle } from '@opentiny/vue-renderless/common/deps/dom'
import { createTooltipRange, processContentMethod } from './handleTooltip'
let focusSingle = null
export default {
// 显示 tooltip 依赖的 popper 组件
activateTooltip(tooltip, isValid) {
if (!this.tasks.activateTooltip) {
this.tasks.activateTooltip = debounce(300, () => {
let sign = isValid !== undefined ? isValid : focusSingle
if (sign) {
tooltip.state.popperElm && (tooltip.state.popperElm.style.display = 'none')
tooltip.doDestroy()
tooltip.show()
setTimeout(tooltip.updatePopper)
}
})
}
this.tasks.activateTooltip()
},
// 显示 tooltip 依赖的 popper 组件
activateTooltipValid(tooltip) {
if (!this.tasks.activateTooltipValid) {
this.tasks.activateTooltipValid = debounce(50, () => {
tooltip.handleShowPopper()
setTimeout(() => tooltip.updatePopper())
})
}
this.tasks.activateTooltipValid()
},
// 显示 tooltip
handleTooltip(event, column, row, showTip, isHeader) {
const cell = isHeader
? event.currentTarget.querySelector('.tiny-grid-cell-text')
: event.currentTarget.querySelector('.tiny-grid-cell')
// 当用户悬浮在排序或者筛选图标按钮时不应该显示tooltip
if (isHeader && event.target !== cell) {
return
}
const tooltip = this.$refs.tooltip
const wrapperElem = cell
const content = cell.innerText.trim() || cell.textContent.trim()
const { contentMethod } = this.tooltipConfig
const range = createTooltipRange({ _vm: this, cell, column, isHeader })
const rangeWidth = range.getBoundingClientRect().width
const padding =
(parseInt(getStyle(cell, 'paddingLeft'), 10) || 0) + (parseInt(getStyle(cell, 'paddingRight'), 10) || 0)
const isOverflow = rangeWidth + padding > cell.offsetWidth || wrapperElem.scrollWidth > wrapperElem.clientWidth
// content如果是空字符串但是用户配置了contentMethod则同样也可以触发提示
if ((contentMethod || content) && (showTip || isOverflow)) {
Object.assign(this.tooltipStore, { row, column, visible: true })
if (tooltip) {
processContentMethod({ _vm: this, column, content, contentMethod, event, isHeader, row, showTip })
tooltip.state.referenceElm = cell
tooltip.state.popperElm && (tooltip.state.popperElm.style.display = 'none')
focusSingle = true
this.activateTooltip(tooltip)
}
}
return this.$nextTick()
},
// 提供关闭tips提示的方法
clostTooltip() {
let tooltip = this.$refs.tooltip
Object.assign(this.tooltipStore, {
content: null,
row: null,
visible: false,
column: null
})
focusSingle = false
if (tooltip && typeof tooltip.setExpectedState === 'function') {
tooltip.setExpectedState(false)
this.debounceClose(tooltip)
}
return this.$nextTick()
},
// 添加代码让用户代码可以划入popper
debounceClose(tooltip) {
if (!this.tasks.debounceClose) {
this.tasks.debounceClose = debounce(50, () => {
tooltip.handleClosePopper()
})
}
this.tasks.debounceClose()
},
// 触发表头 tooltip 事件
triggerHeaderTooltipEvent(event, params) {
let { tooltipStore } = this
let { column, showHeaderTip } = params
if (tooltipStore.column !== column || !tooltipStore.visible) {
// 在 v3.0 中废弃 label
this.handleTooltip(event, column, null, showHeaderTip, true)
}
},
// 触发表尾 tooltip 事件
triggerFooterTooltipEvent(event, params) {
let { column } = params
let tooltipStore = this.tooltipStore
if (tooltipStore.column !== column || !tooltipStore.visible) {
this.handleTooltip(event, column)
}
},
// 触发 tooltip 事件
triggerTooltipEvent(event, params) {
let { editConfig, editStore, tooltipStore } = this
let { actived } = editStore
let { row, column, showTip } = params
if (editConfig) {
if (
(editConfig.mode === 'row' && actived.row === row && column.editor) ||
(actived.row === row && actived.column === column)
) {
return
}
}
if (tooltipStore.column !== column || tooltipStore.row !== row || !tooltipStore.visible) {
this.handleTooltip(event, column, row, showTip)
}
}
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import Methods from './src/methods'
export default {
host: 'table',
install(host) {
Object.assign(host.methods, Methods)
}
}

View File

@ -0,0 +1,126 @@
import { filterTree, remove, isArray, findTree, get, eachTree } from '@opentiny/vue-renderless/grid/static/'
import { getTableRowKey, setTreeScrollYCache } from '../../table/src/strategy'
import { emitEvent } from '@opentiny/vue-renderless/grid/utils'
export default {
// 展开树节点事件
triggerTreeExpandEvent(event, { row }) {
let { currentColumn, currentRow } = this
let rest = this.toggleTreeExpansion(row)
let eventParams = { $table: this, row, rowIndex: this.getRowIndex(row) }
emitEvent(this, 'toggle-tree-change', [eventParams, event])
this.$nextTick(() => {
if (currentRow) {
this.setCurrentRow(currentRow)
} else if (currentColumn) {
this.setCurrentColumn(currentColumn)
}
})
return rest
},
// 切换/展开树节点
toggleTreeExpansion(row) {
return this.setTreeExpansion(row)
},
// 处理默认展开树节点
handleDefaultTreeExpand() {
let { tableFullData, treeConfig } = this
if (!treeConfig) {
return
}
let { children, expandAll, expandRowKeys: rowids } = treeConfig
let treeExpandeds = []
let rowkey = getTableRowKey(this)
let isNonEmptyArr = (arr) => isArray(arr) && arr.length
// 展开所有行
let doExpandAll = () => {
filterTree(tableFullData, (row) => isNonEmptyArr(row[children]) && treeExpandeds.push(row), treeConfig)
this.treeExpandeds = treeExpandeds
}
// 展开指定行
let doExpandRows = () => {
rowids.forEach((rowid) => {
let matchObj = findTree(tableFullData, (item) => rowid === get(item, rowkey), treeConfig)
matchObj && isNonEmptyArr(matchObj.item[children]) && treeExpandeds.push(matchObj.item)
})
this.treeExpandeds = treeExpandeds
}
if (expandAll) {
doExpandAll()
} else if (rowids) {
doExpandRows()
}
setTreeScrollYCache(this)
},
setAllTreeExpansion(expanded) {
let { tableFullData, treeConfig } = this
let children = treeConfig.children
let treeExpandeds = []
if (expanded) {
let rowHandler = (row) => {
if (row[children] && row[children].length) {
treeExpandeds.push(row)
}
}
eachTree(tableFullData, rowHandler, treeConfig)
}
this.treeExpandeds = treeExpandeds
setTreeScrollYCache(this)
return this.$nextTick().then(this.recalculate)
},
// 设置展开树形节点,二个参数设置这一行展开与否:支持单行,支持多行
setTreeExpansion(rows, expanded) {
let { treeConfig, treeExpandeds, tableFullData } = this
let { accordion, children } = treeConfig
let isToggle = arguments.length === 1
if (!rows) {
return this.$nextTick().then(this.recalculate)
}
if (!isArray(rows)) {
rows = [rows]
}
if (accordion) {
rows = rows.slice(rows.length - 1, rows.length)
}
// 这里需要进行一次浅拷贝不能直接操作vue observe的数组不然vue频繁的get、set、toRaw、reactive等操作从而导致卡顿
const treeExpandedsCopy = [...treeExpandeds]
rows.forEach((row) => {
if (row[children] && row[children].length) {
const index = treeExpandedsCopy.indexOf(row)
if (accordion) {
// 同一级只能展开一个
const matchObj = findTree(tableFullData, (item) => item === row, treeConfig)
remove(treeExpandedsCopy, (item) => ~matchObj.items.indexOf(item))
}
if (~index && (isToggle || !expanded)) {
treeExpandedsCopy.splice(index, 1)
return
}
if (!~index && (isToggle || expanded)) {
treeExpandedsCopy.push(row)
}
}
})
this.treeExpandeds = treeExpandedsCopy
setTreeScrollYCache(this)
return this.$nextTick().then(this.recalculate)
},
hasTreeExpand(row) {
return ~this.treeExpandeds.indexOf(row)
},
clearTreeExpand() {
const hasExpand = this.treeExpandeds.length
this.treeExpandeds = []
setTreeScrollYCache(this)
return this.$nextTick().then(() => (hasExpand ? this.recalculate() : 0))
}
}

View File

@ -0,0 +1,6 @@
export interface Plugin {
// 插件所属宿主
host?: 'grid' | 'table'
// 插件加载方法
install?: (Table: any) => void
}