UNPKG

vxe-pc-ui

Version:
662 lines (661 loc) 26.7 kB
import { defineComponent, h, ref, computed, Teleport, resolveComponent, onUnmounted, reactive, nextTick, onMounted, inject, createCommentVNode } from 'vue'; import XEUtils from 'xe-utils'; import { getConfig, globalEvents, getIcon, createEvent, useSize, usePermission } from '../../ui'; import { getAbsolutePos, getEventTargetNode } from '../../ui/src/dom'; import { getFuncText, getLastZIndex, nextZIndex } from '../../ui/src/utils'; import { warnLog } from '../../ui/src/log'; import VxeTooltipComponent from '../../tooltip/src/tooltip'; export default defineComponent({ name: 'VxeButton', props: { /** * 按钮类型 */ type: String, mode: String, className: [String, Function], popupClassName: [String, Function], /** * 按钮尺寸 */ size: { type: String, default: () => getConfig().button.size || getConfig().size }, /** * 用来标识这一项 */ name: [String, Number], routerLink: Object, /** * 权限码 */ permissionCode: [String, Number], /** * 按钮内容 */ content: String, /** * 固定显示下拉面板的方向 */ placement: String, /** * 按钮状态 */ status: String, /** * 标题 */ title: String, /** * 按钮的图标 */ icon: String, /** * 圆角边框 */ round: Boolean, /** * 圆角按钮 */ circle: Boolean, /** * 是否禁用 */ disabled: Boolean, /** * 是否加载中 */ loading: Boolean, trigger: { type: String, default: () => getConfig().button.trigger }, align: String, prefixTooltip: Object, suffixTooltip: Object, /** * 在下拉面板关闭时销毁内容 */ destroyOnClose: { type: Boolean, default: () => getConfig().button.destroyOnClose }, /** * 是否将弹框容器插入于 body 内 */ transfer: { type: Boolean, default: null } }, emits: [ 'click', 'mouseenter', 'mouseleave', 'dropdown-click' ], setup(props, context) { const { slots, emit } = context; const $xeModal = inject('$xeModal', null); const $xeDrawer = inject('$xeDrawer', null); const $xeTable = inject('$xeTable', null); const $xeForm = inject('$xeForm', null); const $xeButtonGroup = inject('$xeButtonGroup', null); const xID = XEUtils.uniqueId(); const { computeSize } = useSize(props); const { computePermissionInfo } = usePermission(props); const reactData = reactive({ initialized: false, visiblePanel: false, isAniVisible: false, isActivated: false, panelIndex: 0, panelStyle: {}, panelPlacement: '' }); const internalData = { showTime: undefined, tooltipTimeout: undefined }; const refElem = ref(); const refButton = ref(); const refBtnPanel = ref(); const refMaps = { refElem }; const $xeButton = { xID, props, context, reactData, internalData, getRefMaps: () => refMaps }; let buttonMethods = {}; const computeBtnTransfer = computed(() => { const { transfer } = props; if (transfer === null) { const globalTransfer = getConfig().button.transfer; if (XEUtils.isBoolean(globalTransfer)) { return globalTransfer; } if ($xeTable || $xeModal || $xeDrawer || $xeForm) { return true; } } return transfer; }); const computeBtnDisabled = computed(() => { const { disabled } = props; const permissionInfo = computePermissionInfo.value; return disabled || permissionInfo.disabled; }); const computeIsFormBtn = computed(() => { const { type } = props; if (type) { return ['submit', 'reset', 'button'].indexOf(type) > -1; } return false; }); const computeBtnMode = computed(() => { const { type, mode } = props; if (mode === 'text' || type === 'text' || ($xeButtonGroup && $xeButtonGroup.props.mode === 'text')) { return 'text'; } return 'button'; }); const computeBtnStatus = computed(() => { const { status } = props; if (status) { return status; } if ($xeButtonGroup) { return $xeButtonGroup.props.status; } return ''; }); const computeBtnAlign = computed(() => { const { align } = props; if (align) { return align; } if ($xeButtonGroup) { return $xeButtonGroup.props.align; } return false; }); const computeBtnRound = computed(() => { const { round } = props; if (round) { return round; } if ($xeButtonGroup) { return $xeButtonGroup.props.round; } return false; }); const computeBtnCircle = computed(() => { const { circle } = props; if (circle) { return circle; } if ($xeButtonGroup) { return $xeButtonGroup.props.circle; } return false; }); const computePrefixTipOpts = computed(() => { return Object.assign({}, props.prefixTooltip); }); const computeSuffixTipOpts = computed(() => { return Object.assign({}, props.suffixTooltip); }); const updateZindex = () => { if (reactData.panelIndex < getLastZIndex()) { reactData.panelIndex = nextZIndex(); } }; const updatePlacement = () => { return nextTick().then(() => { const { placement } = props; const { panelIndex } = reactData; const targetElem = refButton.value; const panelElem = refBtnPanel.value; const btnTransfer = computeBtnTransfer.value; if (panelElem && targetElem) { const targetHeight = targetElem.offsetHeight; const targetWidth = targetElem.offsetWidth; const panelHeight = panelElem.offsetHeight; const panelWidth = panelElem.offsetWidth; const marginSize = 5; const panelStyle = { zIndex: panelIndex }; const { top, left, boundingTop, visibleHeight, visibleWidth } = getAbsolutePos(targetElem); let panelPlacement = 'bottom'; if (btnTransfer) { let btnLeft = left + targetWidth - panelWidth; let btnTop = top + targetHeight; if (placement === 'top') { panelPlacement = 'top'; btnTop = top - panelHeight; } else if (!placement) { // 如果下面不够放,则向上 if (boundingTop + targetHeight + panelHeight + marginSize > visibleHeight) { panelPlacement = 'top'; btnTop = top - panelHeight; } // 如果上面不够放,则向下(优先) if (btnTop < marginSize) { panelPlacement = 'bottom'; btnTop = top + targetHeight; } } // 如果溢出右边 if (btnLeft + panelWidth + marginSize > visibleWidth) { btnLeft -= btnLeft + panelWidth + marginSize - visibleWidth; } // 如果溢出左边 if (btnLeft < marginSize) { btnLeft = marginSize; } Object.assign(panelStyle, { left: `${btnLeft}px`, right: 'auto', top: `${btnTop}px`, minWidth: `${targetWidth}px` }); } else { if (placement === 'top') { panelPlacement = 'top'; panelStyle.bottom = `${targetHeight}px`; } else if (!placement) { // 如果下面不够放,则向上 if (boundingTop + targetHeight + panelHeight > visibleHeight) { // 如果上面不够放,则向下(优先) if (boundingTop - targetHeight - panelHeight > marginSize) { panelPlacement = 'top'; panelStyle.bottom = `${targetHeight}px`; } } } } reactData.panelStyle = panelStyle; reactData.panelPlacement = panelPlacement; return nextTick(); } }); }; const clickEvent = (evnt) => { if ($xeButtonGroup) { $xeButtonGroup.handleClick({ name: props.name }, evnt); } dispatchEvent('click', { $event: evnt }, evnt); }; const mousedownDropdownEvent = (evnt) => { const isLeftBtn = evnt.button === 0; if (isLeftBtn) { evnt.stopPropagation(); } }; const clickDropdownEvent = (evnt) => { const dropdownElem = evnt.currentTarget; const panelElem = refBtnPanel.value; const { flag, targetElem } = getEventTargetNode(evnt, dropdownElem, 'vxe-button'); if (flag) { if (panelElem) { panelElem.dataset.active = 'N'; } reactData.visiblePanel = false; setTimeout(() => { if (!panelElem || panelElem.dataset.active !== 'Y') { reactData.isAniVisible = false; } }, 350); dispatchEvent('dropdown-click', { name: targetElem.getAttribute('name'), $event: evnt }, evnt); } }; const mouseenterDropdownEvent = () => { const panelElem = refBtnPanel.value; if (panelElem) { panelElem.dataset.active = 'Y'; reactData.isAniVisible = true; setTimeout(() => { if (panelElem.dataset.active === 'Y') { reactData.visiblePanel = true; updateZindex(); updatePlacement(); setTimeout(() => { if (reactData.visiblePanel) { updatePlacement(); } }, 50); } }, 20); } }; const mouseenterTargetEvent = (evnt) => { const { loading } = props; const btnDisabled = computeBtnDisabled.value; if (!(btnDisabled || loading)) { openPanel(); mouseenterEvent(evnt); } }; const mouseleaveTargetEvent = (evnt) => { closePanel(); mouseleaveEvent(evnt); }; const mouseenterEvent = (evnt) => { dispatchEvent('mouseenter', {}, evnt); }; const mouseleaveEvent = (evnt) => { dispatchEvent('mouseleave', {}, evnt); }; const clickTargetEvent = (evnt) => { const { loading, trigger } = props; const btnDisabled = computeBtnDisabled.value; if (!(btnDisabled || loading)) { if (trigger === 'click') { if (reactData.visiblePanel) { closePanel(); } else { openPanel(); } } clickEvent(evnt); } }; const openPanel = () => { const { trigger } = props; const panelElem = refBtnPanel.value; if (panelElem) { panelElem.dataset.active = 'Y'; if (!reactData.initialized) { reactData.initialized = true; } internalData.showTime = setTimeout(() => { if (panelElem.dataset.active === 'Y') { mouseenterDropdownEvent(); } else { reactData.isAniVisible = false; } }, trigger === 'click' ? 50 : 250); } return nextTick(); }; const closePanel = () => { const panelElem = refBtnPanel.value; clearTimeout(internalData.showTime); if (panelElem) { panelElem.dataset.active = 'N'; setTimeout(() => { if (panelElem.dataset.active !== 'Y') { reactData.visiblePanel = false; setTimeout(() => { if (panelElem.dataset.active !== 'Y') { reactData.isAniVisible = false; } }, 350); } }, 100); } else { reactData.isAniVisible = false; reactData.visiblePanel = false; } return nextTick(); }; const mouseleaveDropdownEvent = () => { closePanel(); }; const renderTooltipIcon = (tipOpts, type) => { return h(VxeTooltipComponent, { useHTML: tipOpts.useHTML, content: tipOpts.content, enterable: tipOpts.enterable, theme: tipOpts.theme }, { default() { return h('i', { class: [`vxe-button--tooltip-${type}-icon`, tipOpts.icon || getIcon().BUTTON_TOOLTIP_ICON] }); } }); }; const renderContent = () => { const { content, icon, loading, prefixTooltip, suffixTooltip } = props; const prefixTipOpts = computePrefixTipOpts.value; const suffixTipOpts = computeSuffixTipOpts.value; const iconSlot = slots.icon; const defaultSlot = slots.default; const contVNs = []; if (prefixTooltip) { contVNs.push(renderTooltipIcon(prefixTipOpts, 'prefix')); } if (loading) { contVNs.push(h('i', { class: ['vxe-button--loading-icon', getIcon().BUTTON_LOADING] })); } else if (iconSlot) { contVNs.push(h('span', { class: 'vxe-button--custom-icon' }, iconSlot({}))); } else if (icon) { contVNs.push(h('i', { class: ['vxe-button--icon', icon] })); } if (defaultSlot) { contVNs.push(h('span', { class: 'vxe-button--content' }, defaultSlot({}))); } else if (content) { contVNs.push(h('span', { class: 'vxe-button--content' }, getFuncText(content))); } if (suffixTooltip) { contVNs.push(renderTooltipIcon(suffixTipOpts, 'suffix')); } return contVNs; }; const dispatchEvent = (type, params, evnt) => { emit(type, createEvent(evnt, { $button: $xeButton }, params)); }; buttonMethods = { dispatchEvent, openPanel, closePanel, focus() { const btnElem = refButton.value; if (btnElem) { btnElem.focus(); } return nextTick(); }, blur() { const btnElem = refButton.value; if (btnElem) { btnElem.blur(); } return nextTick(); } }; const handleGlobalMousewheelEvent = (evnt) => { const panelElem = refBtnPanel.value; if (reactData.visiblePanel && !getEventTargetNode(evnt, panelElem).flag) { closePanel(); } }; const handleGlobalMousedownEvent = (evnt) => { const btnDisabled = computeBtnDisabled.value; const { visiblePanel } = reactData; if (!btnDisabled) { const el = refElem.value; const panelElem = refBtnPanel.value; reactData.isActivated = getEventTargetNode(evnt, el).flag || getEventTargetNode(evnt, panelElem).flag; if (visiblePanel && !reactData.isActivated) { closePanel(); } } }; Object.assign($xeButton, buttonMethods); const renderVN = () => { const { className, popupClassName, trigger, title, routerLink, type, destroyOnClose, name, loading } = props; const { initialized, isAniVisible, visiblePanel } = reactData; const isFormBtn = computeIsFormBtn.value; const btnMode = computeBtnMode.value; const btnStatus = computeBtnStatus.value; const btnRound = computeBtnRound.value; const btnAlign = computeBtnAlign.value; const btnCircle = computeBtnCircle.value; const btnTransfer = computeBtnTransfer.value; const btnDisabled = computeBtnDisabled.value; const permissionInfo = computePermissionInfo.value; const vSize = computeSize.value; const downsSlot = slots.dropdowns; if (!permissionInfo.visible) { return createCommentVNode(); } if (downsSlot) { const btnOns = {}; const panelOns = {}; if (trigger === 'hover') { // hover 触发 btnOns.onMouseenter = mouseenterTargetEvent; btnOns.onMouseleave = mouseleaveTargetEvent; panelOns.onMouseenter = mouseenterDropdownEvent; panelOns.onMouseleave = mouseleaveDropdownEvent; } return h('div', { ref: refElem, class: ['vxe-button--dropdown', className ? (XEUtils.isFunction(className) ? className({ $button: $xeButton }) : className) : '', { [`size--${vSize}`]: vSize, 'is--active': visiblePanel }] }, [ routerLink ? h(resolveComponent('router-link'), Object.assign({ ref: refButton, class: ['vxe-button', 'vxe-button--link', `type--${btnMode}`, btnAlign ? `align--${btnAlign}` : '', className ? (XEUtils.isFunction(className) ? className({ $button: $xeButton }) : className) : '', { [`size--${vSize}`]: vSize, [`theme--${btnStatus}`]: btnStatus, 'is--round': btnRound, 'is--circle': btnCircle, 'is--disabled': btnDisabled || loading, 'is--loading': loading }], title, name, type: isFormBtn ? type : 'button', disabled: btnDisabled || loading, to: routerLink, onClick: clickTargetEvent }, btnOns), { default() { return renderContent().concat([ h('i', { class: `vxe-button--dropdown-arrow ${getIcon().BUTTON_DROPDOWN}` }) ]); } }) : h('button', Object.assign({ ref: refButton, class: ['vxe-button', `type--${btnMode}`, btnAlign ? `align--${btnAlign}` : '', className ? (XEUtils.isFunction(className) ? className({ $button: $xeButton }) : className) : '', { [`size--${vSize}`]: vSize, [`theme--${btnStatus}`]: btnStatus, 'is--round': btnRound, 'is--circle': btnCircle, 'is--disabled': btnDisabled || loading, 'is--loading': loading }], title, name, type: isFormBtn ? type : 'button', disabled: btnDisabled || loading, onClick: clickTargetEvent }, btnOns), renderContent().concat([ h('i', { class: `vxe-button--dropdown-arrow ${getIcon().BUTTON_DROPDOWN}` }) ])), h(Teleport, { to: 'body', disabled: btnTransfer ? !initialized : true }, [ h('div', Object.assign({ ref: refBtnPanel, class: ['vxe-button--dropdown-panel', popupClassName ? (XEUtils.isFunction(popupClassName) ? popupClassName({ $button: $xeButton }) : popupClassName) : '', { [`size--${vSize}`]: vSize, 'ani--leave': isAniVisible, 'ani--enter': visiblePanel }], placement: reactData.panelPlacement, style: reactData.panelStyle }, panelOns), initialized && (visiblePanel || isAniVisible) ? [ h('div', { class: 'vxe-button--dropdown-wrapper', onMousedown: mousedownDropdownEvent, onClick: clickDropdownEvent }, destroyOnClose && !visiblePanel ? [] : downsSlot({})) ] : []) ]) ]); } if (routerLink) { return h(resolveComponent('router-link'), { ref: refButton, class: ['vxe-button', 'vxe-button--link', `type--${btnMode}`, btnAlign ? `align--${btnAlign}` : '', className ? (XEUtils.isFunction(className) ? className({ $button: $xeButton }) : className) : '', { [`size--${vSize}`]: vSize, [`theme--${btnStatus}`]: btnStatus, 'is--round': btnRound, 'is--circle': btnCircle, 'is--disabled': btnDisabled || loading, 'is--loading': loading }], title, name, type: isFormBtn ? type : 'button', disabled: btnDisabled || loading, to: routerLink, onClick: clickEvent, onMouseenter: mouseenterEvent, onMouseleave: mouseleaveEvent }, { default() { return renderContent(); } }); } return h('button', { ref: refButton, class: ['vxe-button', `type--${btnMode}`, btnAlign ? `align--${btnAlign}` : '', className ? (XEUtils.isFunction(className) ? className({ $button: $xeButton }) : className) : '', { [`size--${vSize}`]: vSize, [`theme--${btnStatus}`]: btnStatus, 'is--round': btnRound, 'is--circle': btnCircle, 'is--disabled': btnDisabled || loading, 'is--loading': loading }], title, name, type: isFormBtn ? type : 'button', disabled: btnDisabled || loading, onClick: clickEvent, onMouseenter: mouseenterEvent, onMouseleave: mouseleaveEvent }, renderContent()); }; $xeButton.renderVN = renderVN; onMounted(() => { if (process.env.NODE_ENV === 'development') { if (props.type === 'text') { warnLog('vxe.error.delProp', ['type=text', 'mode=text']); } } globalEvents.on($xeButton, 'mousewheel', handleGlobalMousewheelEvent); globalEvents.on($xeButton, 'mousedown', handleGlobalMousedownEvent); }); onUnmounted(() => { globalEvents.off($xeButton, 'mousewheel'); globalEvents.off($xeButton, 'mousedown'); }); return $xeButton; }, render() { return this.renderVN(); } });