import { defineComponent, h, ref, Ref, computed, inject, createCommentVNode, resolveComponent, VNode, ComponentOptions, reactive, nextTick, onMounted, onUnmounted, PropType } from 'vue' import XEUtils from 'xe-utils' import GlobalConfig from '../../v-x-e-table/src/conf' import { VXETable } from '../../v-x-e-table' import { useSize } from '../../hooks/size' import { getEventTargetNode } from '../../tools/dom' import { formatText } from '../../tools/utils' import { warnLog, errLog } from '../../tools/log' import { GlobalEvent } from '../../tools/event' import { getSlotVNs } from '../../tools/vn' import { VxeGridConstructor, GridPrivateMethods, ToolbarMethods, VxeToolbarConstructor, VxeToolbarEmits, VxeToolbarPropTypes, VxeTableConstructor, ToolbarPrivateRef, VxeTableMethods, VxeTablePrivateMethods, ToolbarReactData, VxeTableDefines, VxeColumnPropTypes } from '../../../types/all' export default defineComponent({ name: 'VxeToolbar', props: { loading: Boolean, refresh: [Boolean, Object] as PropType, import: [Boolean, Object] as PropType, export: [Boolean, Object] as PropType, print: [Boolean, Object] as PropType, zoom: [Boolean, Object] as PropType< VxeToolbarPropTypes.Zoom>, custom: [Boolean, Object] as PropType, buttons: { type: Array as PropType, default: () => GlobalConfig.toolbar.buttons }, tools: { type: Array as PropType, default: () => GlobalConfig.toolbar.tools }, perfect: { type: Boolean as PropType, default: () => GlobalConfig.toolbar.perfect }, size: { type: String as PropType, default: () => GlobalConfig.toolbar.size || GlobalConfig.size }, className: [String, Function] as PropType }, emits: [ 'button-click', 'tool-click' ] as VxeToolbarEmits, setup (props, context) { const { slots, emit } = context const xID = XEUtils.uniqueId() const computeSize = useSize(props) const reactData = reactive({ isRefresh: false, columns: [] }) const refElem = ref() as Ref const refCustomWrapper = ref() as Ref const customStore = reactive({ isAll: false, isIndeterminate: false, activeBtn: false, activeWrapper: false, visible: false }) const refMaps: ToolbarPrivateRef = { refElem } const $xetoolbar = { xID, props, context, reactData, getRefMaps: () => refMaps } as unknown as VxeToolbarConstructor let toolbarMethods = {} as ToolbarMethods const $xegrid = inject('$xegrid', null as (VxeGridConstructor & GridPrivateMethods) | null) let $xetable: VxeTableConstructor & VxeTableMethods & VxeTablePrivateMethods const computeRefreshOpts = computed(() => { return Object.assign({}, GlobalConfig.toolbar.refresh, props.refresh) as VxeToolbarPropTypes.RefreshOpts }) const computeImportOpts = computed(() => { return Object.assign({}, GlobalConfig.toolbar.import, props.import) as VxeToolbarPropTypes.ImportOpts }) const computeExportOpts = computed(() => { return Object.assign({}, GlobalConfig.toolbar.export, props.export) as VxeToolbarPropTypes.ExportOpts }) const computePrintOpts = computed(() => { return Object.assign({}, GlobalConfig.toolbar.print, props.print) as VxeToolbarPropTypes.PrintOpts }) const computeZoomOpts = computed(() => { return Object.assign({}, GlobalConfig.toolbar.zoom, props.zoom) as VxeToolbarPropTypes.ZoomOpts }) const computeCustomOpts = computed(() => { return Object.assign({}, GlobalConfig.toolbar.custom, props.custom) as VxeToolbarPropTypes.CustomOpts }) const checkTable = () => { if ($xetable) { return true } errLog('vxe.error.barUnableLink') } const checkCustomStatus = () => { const { columns } = reactData const { computeCustomOpts: computeTableCustomOpts } = $xetable.getComputeMaps() const tableCustomOpts = computeTableCustomOpts.value const { checkMethod } = tableCustomOpts customStore.isAll = columns.every((column) => (checkMethod ? !checkMethod({ column }) : false) || column.visible) customStore.isIndeterminate = !customStore.isAll && columns.some((column) => (!checkMethod || checkMethod({ column })) && (column.visible || column.halfVisible)) } const showCustom = () => { customStore.visible = true checkCustomStatus() } const handleTableCustom = () => { $xetable.handleCustom() } const closeCustom = () => { const { custom } = props const customOpts = computeCustomOpts.value if (customStore.visible) { customStore.visible = false if (custom && !customOpts.immediate) { handleTableCustom() } } } const emitCustomEvent = (type: string, evnt: Event) => { const comp = $xegrid || $xetable comp.dispatchEvent('custom', { type }, evnt) } const confirmCustomEvent = (evnt: Event) => { closeCustom() emitCustomEvent('confirm', evnt) } const customOpenEvent = (evnt: Event) => { if (checkTable()) { if (!customStore.visible) { showCustom() emitCustomEvent('open', evnt) } } } const customColseEvent = (evnt: Event) => { if (customStore.visible) { closeCustom() emitCustomEvent('close', evnt) } } const resetCustomEvent = (evnt: Event) => { $xetable.resetColumn(true) closeCustom() emitCustomEvent('reset', evnt) } const handleOptionCheck = (column: VxeTableDefines.ColumnInfo) => { const { columns } = reactData const matchObj = XEUtils.findTree(columns, item => item === column) if (matchObj && matchObj.parent) { const { parent } = matchObj if (parent.children && parent.children.length) { parent.visible = parent.children.every((column) => column.visible) parent.halfVisible = !parent.visible && parent.children.some((column) => column.visible || column.halfVisible) handleOptionCheck(parent) } } } const changeCheckboxOption = (column: VxeTableDefines.ColumnInfo) => { const isChecked = !column.visible const customOpts = computeCustomOpts.value XEUtils.eachTree([column], (item) => { item.visible = isChecked item.halfVisible = false }) handleOptionCheck(column) if (props.custom && customOpts.immediate) { handleTableCustom() } checkCustomStatus() } const changeFixedOption = (column: VxeTableDefines.ColumnInfo, colFixed: VxeColumnPropTypes.Fixed) => { const { computeIsMaxFixedColumn } = $xetable.getComputeMaps() const isMaxFixedColumn = computeIsMaxFixedColumn.value if (column.fixed === colFixed) { $xetable.clearColumnFixed(column) } else { if (!isMaxFixedColumn || column.fixed) { $xetable.setColumnFixed(column, colFixed) } } } const allCustomEvent = () => { const { columns } = reactData const { computeCustomOpts: computeTableCustomOpts } = $xetable.getComputeMaps() const tableCustomOpts = computeTableCustomOpts.value const { checkMethod } = tableCustomOpts const isAll = !customStore.isAll XEUtils.eachTree(columns, (column) => { if (!checkMethod || checkMethod({ column })) { column.visible = isAll column.halfVisible = false } }) customStore.isAll = isAll checkCustomStatus() } const handleGlobalMousedownEvent = (evnt: MouseEvent) => { const customWrapperElem = refCustomWrapper.value if (!getEventTargetNode(evnt, customWrapperElem).flag) { customColseEvent(evnt) } } const handleGlobalBlurEvent = (evnt: Event) => { customColseEvent(evnt) } const handleClickSettingEvent = (evnt: Event) => { if (customStore.visible) { customColseEvent(evnt) } else { customOpenEvent(evnt) } } const handleMouseenterSettingEvent = (evnt: Event) => { customStore.activeBtn = true customOpenEvent(evnt) } const handleMouseleaveSettingEvent = (evnt: Event) => { customStore.activeBtn = false setTimeout(() => { if (!customStore.activeBtn && !customStore.activeWrapper) { customColseEvent(evnt) } }, 300) } const handleWrapperMouseenterEvent = (evnt: Event) => { customStore.activeWrapper = true customOpenEvent(evnt) } const handleWrapperMouseleaveEvent = (evnt: Event) => { customStore.activeWrapper = false setTimeout(() => { if (!customStore.activeBtn && !customStore.activeWrapper) { customColseEvent(evnt) } }, 300) } const refreshEvent = (evnt: KeyboardEvent) => { const { isRefresh } = reactData const refreshOpts = computeRefreshOpts.value if (!isRefresh) { const queryMethod = refreshOpts.queryMethod || refreshOpts.query if (queryMethod) { reactData.isRefresh = true try { Promise.resolve(queryMethod({})).catch((e) => e).then(() => { reactData.isRefresh = false }) } catch (e) { reactData.isRefresh = false } } else if ($xegrid) { reactData.isRefresh = true $xegrid.triggerToolbarCommitEvent({ code: refreshOpts.code || 'reload' }, evnt).catch((e) => e).then(() => { reactData.isRefresh = false }) } } } const zoomEvent = (evnt: Event) => { if ($xegrid) { $xegrid.triggerZoomEvent(evnt) } } const btnEvent = (evnt: Event, item: VxeToolbarPropTypes.ButtonConfig) => { const { code } = item if (code) { if ($xegrid) { $xegrid.triggerToolbarBtnEvent(item, evnt) } else { const gCommandOpts = VXETable.commands.get(code) const params = { code, button: item, $table: $xetable, $grid: $xegrid, $event: evnt } if (gCommandOpts) { if (gCommandOpts.commandMethod) { gCommandOpts.commandMethod(params) } else { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { errLog('vxe.error.notCommands', [code]) } } } $xetoolbar.dispatchEvent('button-click', params, evnt) } } } const tolEvent = (evnt: Event, item: VxeToolbarPropTypes.ButtonConfig) => { const { code } = item if (code) { if ($xegrid) { $xegrid.triggerToolbarTolEvent(item, evnt) } else { const gCommandOpts = VXETable.commands.get(code) const params = { code, tool: item, $table: $xetable, $grid: $xegrid, $event: evnt } if (gCommandOpts) { if (gCommandOpts.commandMethod) { gCommandOpts.commandMethod(params) } else { if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { errLog('vxe.error.notCommands', [code]) } } } $xetoolbar.dispatchEvent('tool-click', params, evnt) } } } const importEvent = () => { if (checkTable()) { $xetable.openImport() } } const exportEvent = () => { if (checkTable()) { $xetable.openExport() } } const printEvent = () => { if (checkTable()) { $xetable.openPrint() } } const renderDropdowns = (item: VxeToolbarPropTypes.ButtonConfig, isBtn: boolean) => { const { dropdowns } = item const downVNs: VNode[] = [] if (dropdowns) { return dropdowns.map((child, index) => { if (child.visible === false) { return createCommentVNode() } return h(resolveComponent('vxe-button') as ComponentOptions, { key: index, disabled: child.disabled, loading: child.loading, type: child.type, icon: child.icon, circle: child.circle, round: child.round, status: child.status, content: child.name, onClick: (evnt: Event) => isBtn ? btnEvent(evnt, child) : tolEvent(evnt, child) }) }) } return downVNs } /** * 渲染按钮 */ const renderBtns = () => { const { buttons } = props const buttonsSlot = slots.buttons if (buttonsSlot) { return getSlotVNs(buttonsSlot({ $grid: $xegrid, $table: $xetable })) } const btnVNs: VNode[] = [] if (buttons) { buttons.forEach((item) => { const { dropdowns, buttonRender } = item if (item.visible !== false) { const compConf = buttonRender ? VXETable.renderer.get(buttonRender.name) : null if (buttonRender && compConf && compConf.renderToolbarButton) { const toolbarButtonClassName = compConf.toolbarButtonClassName const params = { $grid: $xegrid, $table: $xetable, button: item } btnVNs.push( h('span', { class: ['vxe-button--item', toolbarButtonClassName ? (XEUtils.isFunction(toolbarButtonClassName) ? toolbarButtonClassName(params) : toolbarButtonClassName) : ''] }, getSlotVNs(compConf.renderToolbarButton(buttonRender, params))) ) } else { btnVNs.push( h(resolveComponent('vxe-button') as ComponentOptions, { disabled: item.disabled, loading: item.loading, type: item.type, icon: item.icon, circle: item.circle, round: item.round, status: item.status, content: item.name, destroyOnClose: item.destroyOnClose, placement: item.placement, transfer: item.transfer, onClick: (evnt: Event) => btnEvent(evnt, item) }, dropdowns && dropdowns.length ? { dropdowns: () => renderDropdowns(item, true) } : {}) ) } } }) } return btnVNs } /** * 渲染右侧工具 */ const renderRightTools = () => { const { tools } = props const toolsSlot = slots.tools if (toolsSlot) { return getSlotVNs(toolsSlot({ $grid: $xegrid, $table: $xetable })) } const btnVNs: VNode[] = [] if (tools) { tools.forEach((item) => { const { dropdowns, toolRender } = item if (item.visible !== false) { const compConf = toolRender ? VXETable.renderer.get(toolRender.name) : null if (toolRender && compConf && compConf.renderToolbarTool) { const toolbarToolClassName = compConf.toolbarToolClassName const params = { $grid: $xegrid, $table: $xetable, tool: item } btnVNs.push( h('span', { class: ['vxe-tool--item', toolbarToolClassName ? (XEUtils.isFunction(toolbarToolClassName) ? toolbarToolClassName(params) : toolbarToolClassName) : ''] }, getSlotVNs(compConf.renderToolbarTool(toolRender, params))) ) } else { btnVNs.push( h(resolveComponent('vxe-button') as ComponentOptions, { disabled: item.disabled, loading: item.loading, type: item.type, icon: item.icon, circle: item.circle, round: item.round, status: item.status, content: item.name, destroyOnClose: item.destroyOnClose, placement: item.placement, transfer: item.transfer, onClick: (evnt: Event) => tolEvent(evnt, item) }, dropdowns && dropdowns.length ? { dropdowns: () => renderDropdowns(item, false) } : {}) ) } } }) } return btnVNs } const renderCustoms = () => { const { columns } = reactData const customOpts = computeCustomOpts.value let isMaxFixedColumn = true const colVNs: VNode[] = [] const customBtnOns: { onClick?: typeof handleClickSettingEvent; onMouseenter?: typeof handleMouseenterSettingEvent; onMouseleave?: typeof handleMouseleaveSettingEvent; } = {} const customWrapperOns: { onMouseenter?: typeof handleWrapperMouseenterEvent; onMouseleave?: typeof handleWrapperMouseleaveEvent; } = {} let checkMethod: ((params: { column: VxeTableDefines.ColumnInfo }) => boolean) | undefined if ($xetable) { const { computeCustomOpts: computeTableCustomOpts, computeIsMaxFixedColumn } = $xetable.getComputeMaps() const tableCustomOpts = computeTableCustomOpts.value checkMethod = tableCustomOpts.checkMethod isMaxFixedColumn = computeIsMaxFixedColumn.value } if (customOpts.trigger === 'manual') { // 手动触发 } else if (customOpts.trigger === 'hover') { // hover 触发 customBtnOns.onMouseenter = handleMouseenterSettingEvent customBtnOns.onMouseleave = handleMouseleaveSettingEvent customWrapperOns.onMouseenter = handleWrapperMouseenterEvent customWrapperOns.onMouseleave = handleWrapperMouseleaveEvent } else { // 点击触发 customBtnOns.onClick = handleClickSettingEvent } XEUtils.eachTree(columns, (column, index, items, path, parent) => { const colTitle = formatText(column.getTitle(), 1) const colKey = column.getKey() const isColGroup = column.children && column.children.length const isDisabled = checkMethod ? !checkMethod({ column }) : false if (isColGroup || colKey) { const isChecked = column.visible const isIndeterminate = column.halfVisible colVNs.push( h('li', { class: ['vxe-custom--option', `level--${column.level}`, { 'is--group': isColGroup }] }, [ h('div', { title: colTitle, class: ['vxe-custom--checkbox-option', { 'is--checked': isChecked, 'is--indeterminate': isIndeterminate, 'is--disabled': isDisabled }], onClick: () => { if (!isDisabled) { changeCheckboxOption(column) } } }, [ h('span', { class: ['vxe-checkbox--icon', isIndeterminate ? GlobalConfig.icon.TABLE_CHECKBOX_INDETERMINATE : (isChecked ? GlobalConfig.icon.TABLE_CHECKBOX_CHECKED : GlobalConfig.icon.TABLE_CHECKBOX_UNCHECKED)] }), h('span', { class: 'vxe-checkbox--label' }, colTitle) ]), !parent && customOpts.allowFixed ? h('div', { class: 'vxe-custom--fixed-option' }, [ h('span', { class: ['vxe-custom--fixed-left-option', column.fixed === 'left' ? GlobalConfig.icon.TOOLBAR_TOOLS_FIXED_LEFT_ACTIVED : GlobalConfig.icon.TOOLBAR_TOOLS_FIXED_LEFT, { 'is--checked': column.fixed === 'left', 'is--disabled': isMaxFixedColumn && !column.fixed }], title: GlobalConfig.i18n(column.fixed === 'left' ? 'vxe.toolbar.cancelfixed' : 'vxe.toolbar.fixedLeft'), onClick: () => { changeFixedOption(column, 'left') } }), h('span', { class: ['vxe-custom--fixed-right-option', column.fixed === 'right' ? GlobalConfig.icon.TOOLBAR_TOOLS_FIXED_RIGHT_ACTIVED : GlobalConfig.icon.TOOLBAR_TOOLS_FIXED_RIGHT, { 'is--checked': column.fixed === 'right', 'is--disabled': isMaxFixedColumn && !column.fixed }], title: GlobalConfig.i18n(column.fixed === 'right' ? 'vxe.toolbar.cancelfixed' : 'vxe.toolbar.fixedRight'), onClick: () => { changeFixedOption(column, 'right') } }) ]) : null ]) ) } }) const isAllChecked = customStore.isAll const isAllIndeterminate = customStore.isIndeterminate return h('div', { class: ['vxe-custom--wrapper', { 'is--active': customStore.visible }], ref: refCustomWrapper }, [ h(resolveComponent('vxe-button') as ComponentOptions, { circle: true, icon: customOpts.icon || GlobalConfig.icon.TOOLBAR_TOOLS_CUSTOM, title: GlobalConfig.i18n('vxe.toolbar.custom'), ...customBtnOns }), h('div', { class: 'vxe-custom--option-wrapper' }, [ h('ul', { class: 'vxe-custom--header' }, [ h('li', { class: 'vxe-custom--option' }, [ h('div', { class: ['vxe-custom--checkbox-option', { 'is--checked': isAllChecked, 'is--indeterminate': isAllIndeterminate }], title: GlobalConfig.i18n('vxe.table.allTitle'), onClick: allCustomEvent }, [ h('span', { class: ['vxe-checkbox--icon', isAllIndeterminate ? GlobalConfig.icon.TABLE_CHECKBOX_INDETERMINATE : (isAllChecked ? GlobalConfig.icon.TABLE_CHECKBOX_CHECKED : GlobalConfig.icon.TABLE_CHECKBOX_UNCHECKED)] }), h('span', { class: 'vxe-checkbox--label' }, GlobalConfig.i18n('vxe.toolbar.customAll')) ]) ]) ]), h('ul', { class: 'vxe-custom--body', ...customWrapperOns }, colVNs), customOpts.showFooter || customOpts.isFooter ? h('div', { class: 'vxe-custom--footer' }, [ h('button', { class: 'btn--reset', onClick: resetCustomEvent }, customOpts.resetButtonText || GlobalConfig.i18n('vxe.toolbar.customRestore')), h('button', { class: 'btn--confirm', onClick: confirmCustomEvent }, customOpts.confirmButtonText || GlobalConfig.i18n('vxe.toolbar.customConfirm')) ]) : null ]) ]) } toolbarMethods = { dispatchEvent (type, params, evnt) { emit(type, Object.assign({ $toolbar: $xetoolbar, $event: evnt }, params)) }, syncUpdate (params) { const { collectColumn } = params $xetable = params.$table reactData.columns = collectColumn } } Object.assign($xetoolbar, toolbarMethods) onMounted(() => { GlobalEvent.on($xetoolbar, 'mousedown', handleGlobalMousedownEvent) GlobalEvent.on($xetoolbar, 'blur', handleGlobalBlurEvent) }) onUnmounted(() => { GlobalEvent.off($xetoolbar, 'mousedown') GlobalEvent.off($xetoolbar, 'blur') }) nextTick(() => { const { refresh } = props const refreshOpts = computeRefreshOpts.value const queryMethod = refreshOpts.queryMethod || refreshOpts.query if (refresh && !$xegrid && !queryMethod) { warnLog('vxe.error.notFunc', ['queryMethod']) } const customOpts = computeCustomOpts.value if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { if (customOpts.isFooter) { warnLog('vxe.error.notValidators', ['custom.isFooter', 'custom.showFooter']) } } }) const renderVN = () => { const { perfect, loading, refresh, zoom, custom, className } = props const vSize = computeSize.value const refreshOpts = computeRefreshOpts.value const importOpts = computeImportOpts.value const exportOpts = computeExportOpts.value const printOpts = computePrintOpts.value const zoomOpts = computeZoomOpts.value return h('div', { ref: refElem, class: ['vxe-toolbar', className ? (XEUtils.isFunction(className) ? className({ $toolbar: $xetoolbar }) : className) : '', { [`size--${vSize}`]: vSize, 'is--perfect': perfect, 'is--loading': loading }] }, [ h('div', { class: 'vxe-buttons--wrapper' }, renderBtns()), h('div', { class: 'vxe-tools--wrapper' }, renderRightTools()), h('div', { class: 'vxe-tools--operate' }, [ props.import ? h(resolveComponent('vxe-button') as ComponentOptions, { circle: true, icon: importOpts.icon || GlobalConfig.icon.TOOLBAR_TOOLS_IMPORT, title: GlobalConfig.i18n('vxe.toolbar.import'), onClick: importEvent }) : createCommentVNode(), props.export ? h(resolveComponent('vxe-button') as ComponentOptions, { circle: true, icon: exportOpts.icon || GlobalConfig.icon.TOOLBAR_TOOLS_EXPORT, title: GlobalConfig.i18n('vxe.toolbar.export'), onClick: exportEvent }) : createCommentVNode(), props.print ? h(resolveComponent('vxe-button') as ComponentOptions, { circle: true, icon: printOpts.icon || GlobalConfig.icon.TOOLBAR_TOOLS_PRINT, title: GlobalConfig.i18n('vxe.toolbar.print'), onClick: printEvent }) : createCommentVNode(), refresh ? h(resolveComponent('vxe-button') as ComponentOptions, { circle: true, icon: reactData.isRefresh ? (refreshOpts.iconLoading || GlobalConfig.icon.TOOLBAR_TOOLS_REFRESH_LOADING) : (refreshOpts.icon || GlobalConfig.icon.TOOLBAR_TOOLS_REFRESH), title: GlobalConfig.i18n('vxe.toolbar.refresh'), onClick: refreshEvent }) : createCommentVNode(), zoom && $xegrid ? h(resolveComponent('vxe-button') as ComponentOptions, { circle: true, icon: $xegrid.isMaximized() ? (zoomOpts.iconOut || GlobalConfig.icon.TOOLBAR_TOOLS_MINIMIZE) : (zoomOpts.iconIn || GlobalConfig.icon.TOOLBAR_TOOLS_FULLSCREEN), title: GlobalConfig.i18n(`vxe.toolbar.zoom${$xegrid.isMaximized() ? 'Out' : 'In'}`), onClick: zoomEvent }) : createCommentVNode(), custom ? renderCustoms() : createCommentVNode() ]) ]) } $xetoolbar.renderVN = renderVN return $xetoolbar }, render () { return this.renderVN() } })