mixin.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import XEUtils from 'xe-utils'
  2. import { VxeUI } from '../../../ui'
  3. import { getDomNode, getAbsolutePos, getEventTargetNode } from '../../../ui/src/dom'
  4. import { isEnableConf, hasChildrenList } from '../../../ui/src/utils'
  5. import { warnLog } from '../../../ui/src/log'
  6. import type { VxeTableConstructor, VxeTablePrivateMethods, TableInternalData, TableReactData } from '../../../../types'
  7. const { menus, globalEvents, GLOBAL_EVENT_KEYS } = VxeUI
  8. export default {
  9. methods: {
  10. /**
  11. * 关闭快捷菜单
  12. */
  13. _closeMenu () {
  14. const $xeTable = this as unknown as VxeTableConstructor & VxeTablePrivateMethods
  15. const reactData = $xeTable as unknown as TableReactData
  16. Object.assign(reactData.ctxMenuStore, {
  17. visible: false,
  18. selected: null,
  19. selectChild: null,
  20. showChild: false
  21. })
  22. return $xeTable.$nextTick()
  23. },
  24. // 处理菜单的移动
  25. moveCtxMenu (evnt: KeyboardEvent, ctxMenuStore: any, property: 'selectChild' | 'selected', hasOper: boolean, operRest: any, menuList: any[]) {
  26. const $xeTable = this as unknown as VxeTableConstructor & VxeTablePrivateMethods
  27. let selectItem
  28. const selectIndex = XEUtils.findIndexOf(menuList, item => ctxMenuStore[property] === item)
  29. if (hasOper) {
  30. if (operRest && hasChildrenList(ctxMenuStore.selected)) {
  31. ctxMenuStore.showChild = true
  32. } else {
  33. ctxMenuStore.showChild = false
  34. ctxMenuStore.selectChild = null
  35. }
  36. } else if (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP)) {
  37. for (let len = selectIndex - 1; len >= 0; len--) {
  38. if (menuList[len].visible !== false) {
  39. selectItem = menuList[len]
  40. break
  41. }
  42. }
  43. ctxMenuStore[property] = selectItem || menuList[menuList.length - 1]
  44. } else if (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN)) {
  45. for (let index = selectIndex + 1; index < menuList.length; index++) {
  46. if (menuList[index].visible !== false) {
  47. selectItem = menuList[index]
  48. break
  49. }
  50. }
  51. ctxMenuStore[property] = selectItem || menuList[0]
  52. } else if (ctxMenuStore[property] && (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ENTER) || globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.SPACEBAR))) {
  53. $xeTable.ctxMenuLinkEvent(evnt, ctxMenuStore[property])
  54. }
  55. },
  56. /**
  57. * 快捷菜单事件处理
  58. */
  59. handleGlobalContextmenuEvent (evnt: any) {
  60. const $xeTable = this as unknown as VxeTableConstructor & VxeTablePrivateMethods
  61. const $xeGrid = $xeTable.$xeGrid
  62. const $xeGantt = $xeTable.$xeGantt
  63. const props = $xeTable
  64. const reactData = $xeTable as unknown as TableReactData
  65. const internalData = $xeTable as unknown as TableInternalData
  66. const { xID } = $xeTable
  67. const { mouseConfig, menuConfig } = props
  68. const { editStore, ctxMenuStore } = reactData
  69. const { visibleColumn } = internalData
  70. const tableFilter = $xeTable.$refs.refTableFilter
  71. const tableMenu = $xeTable.$refs.refTableMenu
  72. const mouseOpts = $xeTable.computeMouseOpts
  73. const menuOpts = $xeTable.computeMenuOpts
  74. const el = $xeTable.$refs.refElem as HTMLDivElement
  75. const { selected } = editStore
  76. const layoutList = ['header', 'body', 'footer']
  77. if (isEnableConf(menuConfig)) {
  78. if (ctxMenuStore.visible && tableMenu && getEventTargetNode(evnt, (tableMenu as any).$el).flag) {
  79. evnt.preventDefault()
  80. return
  81. }
  82. if (internalData._keyCtx) {
  83. const type = 'body'
  84. const params: any = { source: 'table', type, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, keyboard: true, columns: visibleColumn.slice(0), $event: evnt }
  85. // 如果开启单元格区域
  86. if (mouseConfig && mouseOpts.area) {
  87. const activeArea = $xeTable.getActiveCellArea()
  88. if (activeArea && activeArea.row && activeArea.column) {
  89. params.row = activeArea.row
  90. params.column = activeArea.column
  91. $xeTable.handleOpenMenuEvent(evnt, type, params)
  92. return
  93. }
  94. } else if (mouseConfig && mouseOpts.selected) {
  95. // 如果启用键盘导航且已选中单元格
  96. if (selected.row && selected.column) {
  97. params.row = selected.row
  98. params.column = selected.column
  99. $xeTable.handleOpenMenuEvent(evnt, type, params)
  100. return
  101. }
  102. }
  103. }
  104. // 分别匹配表尾、内容、表尾的快捷菜单
  105. for (let index = 0; index < layoutList.length; index++) {
  106. const layout = layoutList[index] as 'header' | 'body' | 'footer'
  107. const columnTargetNode = getEventTargetNode(evnt, el, `vxe-${layout}--column`, (target: any) => {
  108. // target=td|th,直接向上找 table 去匹配即可
  109. return target.parentNode.parentNode.parentNode.getAttribute('xid') === xID
  110. })
  111. const params: any = { source: 'table', type: layout, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, columns: visibleColumn.slice(0), $event: evnt }
  112. if (columnTargetNode.flag) {
  113. const cell = columnTargetNode.targetElem
  114. const columnNodeRest = $xeTable.getColumnNode(cell)
  115. const column = columnNodeRest ? columnNodeRest.item : null
  116. let typePrefix = `${layout}-`
  117. if (column) {
  118. Object.assign(params, { column, columnIndex: $xeTable.getColumnIndex(column), cell })
  119. }
  120. if (layout === 'body') {
  121. const rowNodeRest = $xeTable.getRowNode(cell.parentNode)
  122. const row = rowNodeRest ? rowNodeRest.item : null
  123. typePrefix = ''
  124. if (row) {
  125. params.row = row
  126. params.rowIndex = $xeTable.getRowIndex(row)
  127. }
  128. }
  129. const eventType = `${typePrefix}cell-menu` as 'cell-menu' | 'header-cell-menu' | 'footer-cell-menu'
  130. $xeTable.handleOpenMenuEvent(evnt, layout, params)
  131. // 在 v4 中废弃事件 cell-context-menu、header-cell-context-menu、footer-cell-context-menu
  132. if ($xeTable.$listeners[`${typePrefix}cell-context-menu`]) {
  133. warnLog('vxe.error.delEvent', [`${typePrefix}cell-context-menu`, `${typePrefix}cell-menu`])
  134. $xeTable.dispatchEvent(`${typePrefix}cell-context-menu` as any, params, evnt)
  135. } else {
  136. $xeTable.dispatchEvent(eventType, params, evnt)
  137. }
  138. return
  139. } else if (getEventTargetNode(evnt, $xeTable.$el, `vxe-table--${layout}-wrapper`, target => target.getAttribute('xid') === xID).flag) {
  140. if (menuOpts.trigger === 'cell') {
  141. evnt.preventDefault()
  142. } else {
  143. $xeTable.handleOpenMenuEvent(evnt, layout, params)
  144. }
  145. return
  146. }
  147. }
  148. }
  149. if (tableFilter && !getEventTargetNode(evnt, (tableFilter as any).$el).flag) {
  150. $xeTable.closeFilter()
  151. }
  152. $xeTable.closeMenu()
  153. },
  154. /**
  155. * 显示快捷菜单
  156. */
  157. handleOpenMenuEvent (evnt: any, type: 'header' | 'body' | 'footer', params: any) {
  158. const $xeTable = this as unknown as VxeTableConstructor & VxeTablePrivateMethods
  159. const reactData = $xeTable as unknown as TableReactData
  160. const internalData = $xeTable as unknown as TableInternalData
  161. const { ctxMenuStore } = reactData
  162. const isContentMenu = $xeTable.computeIsContentMenu
  163. const menuOpts = $xeTable.computeMenuOpts
  164. const config = menuOpts[type]
  165. const { transfer, visibleMethod } = menuOpts
  166. if (config) {
  167. const { options, disabled } = config
  168. if (disabled) {
  169. evnt.preventDefault()
  170. } else if (isContentMenu && options && options.length) {
  171. params.options = options
  172. $xeTable.preventEvent(evnt, 'event.showMenu', params, () => {
  173. if (!visibleMethod || visibleMethod(params)) {
  174. evnt.preventDefault()
  175. $xeTable.updateZindex()
  176. const el = $xeTable.$refs.refElem as HTMLDivElement
  177. const tableRect = el.getBoundingClientRect()
  178. const { scrollTop, scrollLeft, visibleHeight, visibleWidth } = getDomNode()
  179. let top = evnt.clientY - tableRect.y
  180. let left = evnt.clientX - tableRect.x
  181. if (transfer) {
  182. top = evnt.clientY + scrollTop
  183. left = evnt.clientX + scrollLeft
  184. }
  185. const handleVisible = () => {
  186. internalData._currMenuParams = params
  187. Object.assign(ctxMenuStore, {
  188. visible: true,
  189. list: options,
  190. selected: null,
  191. selectChild: null,
  192. showChild: false,
  193. style: {
  194. zIndex: internalData.tZindex,
  195. top: `${top}px`,
  196. left: `${left}px`
  197. }
  198. })
  199. $xeTable.$nextTick(() => {
  200. const tableMenu = $xeTable.$refs.refTableMenu
  201. const ctxElem = (tableMenu as any).$el
  202. const clientHeight = ctxElem.clientHeight
  203. const clientWidth = ctxElem.clientWidth
  204. const { boundingTop, boundingLeft } = getAbsolutePos(ctxElem)
  205. const offsetTop = boundingTop + clientHeight - visibleHeight
  206. const offsetLeft = boundingLeft + clientWidth - visibleWidth
  207. if (offsetTop > -10) {
  208. ctxMenuStore.style.top = `${Math.max(scrollTop + 2, top - clientHeight - 2)}px`
  209. }
  210. if (offsetLeft > -10) {
  211. ctxMenuStore.style.left = `${Math.max(scrollLeft + 2, left - clientWidth - 2)}px`
  212. }
  213. })
  214. }
  215. const { keyboard, row, column } = params
  216. if (keyboard && row && column) {
  217. $xeTable.scrollToRow(row, column).then(() => {
  218. const cell = $xeTable.getCellElement(row, column)
  219. if (cell) {
  220. const { boundingTop, boundingLeft } = getAbsolutePos(cell)
  221. top = boundingTop + scrollTop + Math.floor(cell.offsetHeight / 2)
  222. left = boundingLeft + scrollLeft + Math.floor(cell.offsetWidth / 2)
  223. }
  224. handleVisible()
  225. })
  226. } else {
  227. handleVisible()
  228. }
  229. } else {
  230. $xeTable.closeMenu()
  231. }
  232. })
  233. }
  234. }
  235. $xeTable.closeFilter()
  236. },
  237. ctxMenuMouseoverEvent (evnt: any, item: any, child: any) {
  238. const $xeTable = this as unknown as VxeTableConstructor & VxeTablePrivateMethods
  239. const reactData = $xeTable as unknown as TableReactData
  240. const menuElem = evnt.currentTarget
  241. const { ctxMenuStore } = reactData
  242. evnt.preventDefault()
  243. evnt.stopPropagation()
  244. ctxMenuStore.selected = item
  245. ctxMenuStore.selectChild = child
  246. if (!child) {
  247. ctxMenuStore.showChild = hasChildrenList(item)
  248. if (ctxMenuStore.showChild) {
  249. $xeTable.$nextTick(() => {
  250. const childWrapperElem = menuElem.nextElementSibling
  251. if (childWrapperElem) {
  252. const { boundingTop, boundingLeft, visibleHeight, visibleWidth } = getAbsolutePos(menuElem)
  253. const posTop = boundingTop + menuElem.offsetHeight
  254. const posLeft = boundingLeft + menuElem.offsetWidth
  255. let left = ''
  256. let right = ''
  257. // 是否超出右侧
  258. if (posLeft + childWrapperElem.offsetWidth > visibleWidth - 10) {
  259. left = 'auto'
  260. right = `${menuElem.offsetWidth}px`
  261. }
  262. // 是否超出底部
  263. let top = ''
  264. let bottom = ''
  265. if (posTop + childWrapperElem.offsetHeight > visibleHeight - 10) {
  266. top = 'auto'
  267. bottom = '0'
  268. }
  269. childWrapperElem.style.left = left
  270. childWrapperElem.style.right = right
  271. childWrapperElem.style.top = top
  272. childWrapperElem.style.bottom = bottom
  273. }
  274. })
  275. }
  276. }
  277. },
  278. ctxMenuMouseoutEvent (evnt: any, item: any) {
  279. const $xeTable = this as unknown as VxeTableConstructor & VxeTablePrivateMethods
  280. const reactData = $xeTable as unknown as TableReactData
  281. const { ctxMenuStore } = reactData
  282. if (!item.children) {
  283. ctxMenuStore.selected = null
  284. }
  285. ctxMenuStore.selectChild = null
  286. },
  287. /**
  288. * 快捷菜单点击事件
  289. */
  290. ctxMenuLinkEvent (evnt: any, menu: any) {
  291. const $xeTable = this as unknown as VxeTableConstructor & VxeTablePrivateMethods
  292. const $xeGrid = $xeTable.$xeGrid
  293. const $xeGantt = $xeTable.$xeGantt
  294. const internalData = $xeTable as unknown as TableInternalData
  295. // 如果一级菜单有配置 code 则允许点击,否则不能点击
  296. if (!menu.disabled && (menu.code || !menu.children || !menu.children.length)) {
  297. const gMenuOpts = menus.get(menu.code)
  298. const params = Object.assign({}, internalData._currMenuParams, { menu, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, $event: evnt })
  299. if (gMenuOpts && gMenuOpts.menuMethod) {
  300. gMenuOpts.menuMethod(params, evnt)
  301. }
  302. // 在 v4 中废弃事件 context-menu-click
  303. if ($xeTable.$listeners['context-menu-click']) {
  304. warnLog('vxe.error.delEvent', ['context-menu-click', 'menu-click'])
  305. $xeTable.dispatchEvent('context-menu-click' as any, params, evnt)
  306. } else {
  307. $xeTable.dispatchEvent('menu-click', params, evnt)
  308. }
  309. $xeTable.closeMenu()
  310. }
  311. }
  312. }
  313. }