319 lines
13 KiB
TypeScript
319 lines
13 KiB
TypeScript
import { nextTick } from 'vue'
|
||
import XEUtils from 'xe-utils'
|
||
import { VxeUI } from '../../../ui'
|
||
import { getDomNode, getAbsolutePos, getEventTargetNode } from '../../../ui/src/dom'
|
||
import { isEnableConf, hasChildrenList } from '../../../ui/src/utils'
|
||
|
||
import type { TableMenuMethods, TableMenuPrivateMethods } from '../../../../types'
|
||
|
||
const { menus, hooks, globalEvents, GLOBAL_EVENT_KEYS } = VxeUI
|
||
|
||
const tableMenuMethodKeys: (keyof TableMenuMethods)[] = ['closeMenu']
|
||
|
||
hooks.add('tableMenuModule', {
|
||
setupTable ($xeTable) {
|
||
const { xID, props, reactData, internalData } = $xeTable
|
||
const { refElem, refTableFilter, refTableMenu } = $xeTable.getRefMaps()
|
||
const { computeMouseOpts, computeIsContentMenu, computeMenuOpts } = $xeTable.getComputeMaps()
|
||
|
||
let menuMethods = {} as TableMenuMethods
|
||
let menuPrivateMethods = {} as TableMenuPrivateMethods
|
||
|
||
/**
|
||
* 显示快捷菜单
|
||
*/
|
||
const handleOpenMenuEvent = (evnt: any, type: 'header' | 'body' | 'footer', params: any) => {
|
||
const { ctxMenuStore } = reactData
|
||
const isContentMenu = computeIsContentMenu.value
|
||
const menuOpts = computeMenuOpts.value
|
||
const config = menuOpts[type]
|
||
const { transfer, visibleMethod } = menuOpts
|
||
if (config) {
|
||
const { options, disabled } = config
|
||
if (disabled) {
|
||
evnt.preventDefault()
|
||
} else if (isContentMenu && options && options.length) {
|
||
params.options = options
|
||
$xeTable.preventEvent(evnt, 'event.showMenu', params, () => {
|
||
if (!visibleMethod || visibleMethod(params)) {
|
||
evnt.preventDefault()
|
||
$xeTable.updateZindex()
|
||
const el = refElem.value
|
||
const tableRect = el.getBoundingClientRect()
|
||
const { scrollTop, scrollLeft, visibleHeight, visibleWidth } = getDomNode()
|
||
|
||
let top = evnt.clientY - tableRect.y
|
||
let left = evnt.clientX - tableRect.x
|
||
if (transfer) {
|
||
top = evnt.clientY + scrollTop
|
||
left = evnt.clientX + scrollLeft
|
||
}
|
||
|
||
const handleVisible = () => {
|
||
internalData._currMenuParams = params
|
||
Object.assign(ctxMenuStore, {
|
||
visible: true,
|
||
list: options,
|
||
selected: null,
|
||
selectChild: null,
|
||
showChild: false,
|
||
style: {
|
||
zIndex: internalData.tZindex,
|
||
top: `${top}px`,
|
||
left: `${left}px`
|
||
}
|
||
})
|
||
nextTick(() => {
|
||
const tableMenu = refTableMenu.value
|
||
const ctxElem = tableMenu.getRefMaps().refElem.value
|
||
const clientHeight = ctxElem.clientHeight
|
||
const clientWidth = ctxElem.clientWidth
|
||
const { boundingTop, boundingLeft } = getAbsolutePos(ctxElem)
|
||
const offsetTop = boundingTop + clientHeight - visibleHeight
|
||
const offsetLeft = boundingLeft + clientWidth - visibleWidth
|
||
if (offsetTop > -10) {
|
||
ctxMenuStore.style.top = `${Math.max(scrollTop + 2, top - clientHeight - 2)}px`
|
||
}
|
||
if (offsetLeft > -10) {
|
||
ctxMenuStore.style.left = `${Math.max(scrollLeft + 2, left - clientWidth - 2)}px`
|
||
}
|
||
})
|
||
}
|
||
const { keyboard, row, column } = params
|
||
if (keyboard && row && column) {
|
||
$xeTable.scrollToRow(row, column).then(() => {
|
||
const cell = $xeTable.getCellElement(row, column)
|
||
if (cell) {
|
||
const { boundingTop, boundingLeft } = getAbsolutePos(cell)
|
||
top = boundingTop + scrollTop + Math.floor(cell.offsetHeight / 2)
|
||
left = boundingLeft + scrollLeft + Math.floor(cell.offsetWidth / 2)
|
||
}
|
||
handleVisible()
|
||
})
|
||
} else {
|
||
handleVisible()
|
||
}
|
||
} else {
|
||
menuMethods.closeMenu()
|
||
}
|
||
})
|
||
}
|
||
}
|
||
$xeTable.closeFilter()
|
||
}
|
||
|
||
menuMethods = {
|
||
/**
|
||
* 关闭快捷菜单
|
||
*/
|
||
closeMenu () {
|
||
Object.assign(reactData.ctxMenuStore, {
|
||
visible: false,
|
||
selected: null,
|
||
selectChild: null,
|
||
showChild: false
|
||
})
|
||
return nextTick()
|
||
}
|
||
}
|
||
|
||
menuPrivateMethods = {
|
||
/**
|
||
* 处理菜单的移动
|
||
*/
|
||
moveCtxMenu (evnt, ctxMenuStore, property, hasOper, operRest, menuList) {
|
||
let selectItem
|
||
const selectIndex = XEUtils.findIndexOf(menuList, item => ctxMenuStore[property] === item)
|
||
if (hasOper) {
|
||
if (operRest && hasChildrenList(ctxMenuStore.selected)) {
|
||
ctxMenuStore.showChild = true
|
||
} else {
|
||
ctxMenuStore.showChild = false
|
||
ctxMenuStore.selectChild = null
|
||
}
|
||
} else if (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP)) {
|
||
for (let len = selectIndex - 1; len >= 0; len--) {
|
||
if (menuList[len].visible !== false) {
|
||
selectItem = menuList[len]
|
||
break
|
||
}
|
||
}
|
||
ctxMenuStore[property] = selectItem || menuList[menuList.length - 1]
|
||
} else if (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN)) {
|
||
for (let index = selectIndex + 1; index < menuList.length; index++) {
|
||
if (menuList[index].visible !== false) {
|
||
selectItem = menuList[index]
|
||
break
|
||
}
|
||
}
|
||
ctxMenuStore[property] = selectItem || menuList[0]
|
||
} else if (ctxMenuStore[property] && (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ENTER) || globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.SPACEBAR))) {
|
||
$xeTable.ctxMenuLinkEvent(evnt, ctxMenuStore[property])
|
||
}
|
||
},
|
||
handleOpenMenuEvent,
|
||
/**
|
||
* 快捷菜单事件处理
|
||
*/
|
||
handleGlobalContextmenuEvent (evnt) {
|
||
const $xeGrid = $xeTable.xeGrid
|
||
const $xeGantt = $xeTable.xeGantt
|
||
|
||
const { mouseConfig, menuConfig } = props
|
||
const { editStore, ctxMenuStore } = reactData
|
||
const { visibleColumn } = internalData
|
||
const tableFilter = refTableFilter.value
|
||
const tableMenu = refTableMenu.value
|
||
const mouseOpts = computeMouseOpts.value
|
||
const menuOpts = computeMenuOpts.value
|
||
const el = refElem.value
|
||
const { selected } = editStore
|
||
const layoutList = ['header', 'body', 'footer']
|
||
if (isEnableConf(menuConfig)) {
|
||
if (ctxMenuStore.visible && tableMenu && getEventTargetNode(evnt, tableMenu.getRefMaps().refElem.value).flag) {
|
||
evnt.preventDefault()
|
||
return
|
||
}
|
||
if (internalData._keyCtx) {
|
||
const type = 'body'
|
||
const params: any = { type, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, keyboard: true, columns: visibleColumn.slice(0), $event: evnt }
|
||
// 如果开启单元格区域
|
||
if (mouseConfig && mouseOpts.area) {
|
||
const activeArea = $xeTable.getActiveCellArea()
|
||
if (activeArea && activeArea.row && activeArea.column) {
|
||
params.row = activeArea.row
|
||
params.column = activeArea.column
|
||
handleOpenMenuEvent(evnt, type, params)
|
||
return
|
||
}
|
||
} else if (mouseConfig && mouseOpts.selected) {
|
||
// 如果启用键盘导航且已选中单元格
|
||
if (selected.row && selected.column) {
|
||
params.row = selected.row
|
||
params.column = selected.column
|
||
handleOpenMenuEvent(evnt, type, params)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
// 分别匹配表尾、内容、表尾的快捷菜单
|
||
for (let index = 0; index < layoutList.length; index++) {
|
||
const layout = layoutList[index] as 'header' | 'body' | 'footer'
|
||
const columnTargetNode = getEventTargetNode(evnt, el, `vxe-${layout}--column`, (target: any) => {
|
||
// target=td|th,直接向上找 table 去匹配即可
|
||
return target.parentNode.parentNode.parentNode.getAttribute('xid') === xID
|
||
})
|
||
const params: any = { type: layout, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, columns: visibleColumn.slice(0), $event: evnt }
|
||
if (columnTargetNode.flag) {
|
||
const cell = columnTargetNode.targetElem
|
||
const columnNodeRest = $xeTable.getColumnNode(cell)
|
||
const column = columnNodeRest ? columnNodeRest.item : null
|
||
let typePrefix = `${layout}-`
|
||
if (column) {
|
||
Object.assign(params, { column, columnIndex: $xeTable.getColumnIndex(column), cell })
|
||
}
|
||
if (layout === 'body') {
|
||
const rowNodeRest = $xeTable.getRowNode(cell.parentNode)
|
||
const row = rowNodeRest ? rowNodeRest.item : null
|
||
typePrefix = ''
|
||
if (row) {
|
||
params.row = row
|
||
params.rowIndex = $xeTable.getRowIndex(row)
|
||
}
|
||
}
|
||
const eventType = `${typePrefix}cell-menu` as 'cell-menu' | 'header-cell-menu' | 'footer-cell-menu'
|
||
handleOpenMenuEvent(evnt, layout, params)
|
||
$xeTable.dispatchEvent(eventType, params, evnt)
|
||
return
|
||
} else if (getEventTargetNode(evnt, el, `vxe-table--${layout}-wrapper`, target => target.getAttribute('xid') === xID).flag) {
|
||
if (menuOpts.trigger === 'cell') {
|
||
evnt.preventDefault()
|
||
} else {
|
||
handleOpenMenuEvent(evnt, layout, params)
|
||
}
|
||
return
|
||
}
|
||
}
|
||
}
|
||
if (tableFilter && !getEventTargetNode(evnt, tableFilter.getRefMaps().refElem.value).flag) {
|
||
$xeTable.closeFilter()
|
||
}
|
||
menuMethods.closeMenu()
|
||
},
|
||
ctxMenuMouseoverEvent (evnt, item, child) {
|
||
const menuElem = evnt.currentTarget
|
||
const { ctxMenuStore } = reactData
|
||
evnt.preventDefault()
|
||
evnt.stopPropagation()
|
||
ctxMenuStore.selected = item
|
||
ctxMenuStore.selectChild = child
|
||
if (!child) {
|
||
ctxMenuStore.showChild = hasChildrenList(item)
|
||
if (ctxMenuStore.showChild) {
|
||
nextTick(() => {
|
||
const childWrapperElem = menuElem.nextElementSibling
|
||
if (childWrapperElem) {
|
||
const { boundingTop, boundingLeft, visibleHeight, visibleWidth } = getAbsolutePos(menuElem)
|
||
const posTop = boundingTop + menuElem.offsetHeight
|
||
const posLeft = boundingLeft + menuElem.offsetWidth
|
||
let left = ''
|
||
let right = ''
|
||
// 是否超出右侧
|
||
if (posLeft + childWrapperElem.offsetWidth > visibleWidth - 10) {
|
||
left = 'auto'
|
||
right = `${menuElem.offsetWidth}px`
|
||
}
|
||
// 是否超出底部
|
||
let top = ''
|
||
let bottom = ''
|
||
if (posTop + childWrapperElem.offsetHeight > visibleHeight - 10) {
|
||
top = 'auto'
|
||
bottom = '0'
|
||
}
|
||
childWrapperElem.style.left = left
|
||
childWrapperElem.style.right = right
|
||
childWrapperElem.style.top = top
|
||
childWrapperElem.style.bottom = bottom
|
||
}
|
||
})
|
||
}
|
||
}
|
||
},
|
||
ctxMenuMouseoutEvent (evnt, item) {
|
||
const { ctxMenuStore } = reactData
|
||
if (!item.children) {
|
||
ctxMenuStore.selected = null
|
||
}
|
||
ctxMenuStore.selectChild = null
|
||
},
|
||
/**
|
||
* 快捷菜单点击事件
|
||
*/
|
||
ctxMenuLinkEvent (evnt, menu) {
|
||
const $xeGrid = $xeTable.xeGrid
|
||
const $xeGantt = $xeTable.xeGantt
|
||
|
||
// 如果一级菜单有配置 code 则允许点击,否则不能点击
|
||
if (!menu.disabled && (menu.code || !menu.children || !menu.children.length)) {
|
||
const gMenuOpts = menus.get(menu.code)
|
||
const params = Object.assign({}, internalData._currMenuParams, { menu, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, $event: evnt })
|
||
const tmMethod = gMenuOpts ? (gMenuOpts.tableMenuMethod || gMenuOpts.menuMethod) : null
|
||
if (tmMethod) {
|
||
tmMethod(params, evnt)
|
||
}
|
||
$xeTable.dispatchEvent('menu-click', params, evnt)
|
||
menuMethods.closeMenu()
|
||
}
|
||
}
|
||
}
|
||
|
||
return { ...menuMethods, ...menuPrivateMethods }
|
||
},
|
||
setupGrid ($xeGrid) {
|
||
return $xeGrid.extendTableMethods(tableMenuMethodKeys)
|
||
},
|
||
setupGantt ($xeGantt) {
|
||
return $xeGantt.extendTableMethods(tableMenuMethodKeys)
|
||
}
|
||
})
|