vxe-pc-ui
Version:
A vue based PC component library
662 lines (661 loc) • 26.7 kB
JavaScript
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();
}
});