forked from opentiny/tiny-vue
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:
parent
6ec2a3cc4f
commit
c4a7391bdd
|
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export default {}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export interface Plugin {
|
||||
// 插件所属宿主
|
||||
host?: 'grid' | 'table'
|
||||
// 插件加载方法
|
||||
install?: (Table: any) => void
|
||||
}
|
Loading…
Reference in New Issue