vxe-pc-ui
Version:
A vue based PC component library
434 lines (433 loc) • 17.2 kB
JavaScript
import { defineComponent, ref, h, reactive, inject, resolveComponent, createCommentVNode, watch, computed, nextTick, onBeforeUnmount, onMounted } from 'vue';
import XEUtils from 'xe-utils';
import { getConfig, getIcon, createEvent, permission, useSize, globalEvents } from '../../ui';
import { toCssUnit } from '../../ui/src/dom';
import { getLastZIndex, nextZIndex } from '../../ui/src/utils';
import { getSlotVNs } from '../../ui/src/vn';
import VxeLoadingComponent from '../../loading/src/loading';
export default defineComponent({
name: 'VxeMenu',
props: {
modelValue: [String, Number],
expandAll: Boolean,
collapsed: {
type: Boolean,
default: null
},
collapseFixed: Boolean,
loading: Boolean,
options: {
type: Array,
default: () => []
},
size: {
type: String,
default: () => getConfig().image.size || getConfig().size
}
},
emits: [
'update:modelValue',
'click'
],
setup(props, context) {
const { emit, slots } = context;
const xID = XEUtils.uniqueId();
const $xeLayoutAside = inject('$xeLayoutAside', null);
const refElem = ref();
const refCollapseElem = ref();
const { computeSize } = useSize(props);
const reactData = reactive({
initialized: !!props.collapsed,
isEnterCollapse: false,
collapseStyle: {},
collapseZindex: 0,
activeName: props.modelValue,
menuList: [],
itemHeight: 1
});
const refMaps = {
refElem
};
const computeIsCollapsed = computed(() => {
const { collapsed } = props;
if (XEUtils.isBoolean(collapsed)) {
return collapsed;
}
if ($xeLayoutAside) {
return !!$xeLayoutAside.props.collapsed;
}
return false;
});
const computeCollapseWidth = computed(() => {
let collapseWidth = '';
if ($xeLayoutAside) {
collapseWidth = $xeLayoutAside.props.collapseWidth || '';
}
return collapseWidth;
});
const computeCollapseEnterWidth = computed(() => {
let width = '';
if ($xeLayoutAside) {
width = $xeLayoutAside.props.width || '';
}
return width;
});
const computeMaps = {
computeSize
};
const $xeMenu = {
xID,
props,
context,
reactData,
getRefMaps: () => refMaps,
getComputeMaps: () => computeMaps
};
const getMenuTitle = (item) => {
return `${item.title || item.name}`;
};
const updateZindex = () => {
if (reactData.collapseZindex < getLastZIndex()) {
reactData.collapseZindex = nextZIndex();
}
};
const updateActiveMenu = (isDefExpand) => {
const { activeName } = reactData;
XEUtils.eachTree(reactData.menuList, (item, index, items, path, parent, nodes) => {
if (item.itemKey === activeName) {
nodes.forEach(obj => {
obj.isActive = true;
if (isDefExpand) {
obj.isExpand = true;
}
});
item.isExactActive = true;
}
else {
item.isExactActive = false;
item.isActive = false;
}
}, { children: 'childList' });
};
const updateMenuConfig = () => {
const { options, expandAll } = props;
reactData.menuList = XEUtils.mapTree(options, (item, index, items, path, parent) => {
const objItem = Object.assign(Object.assign({}, item), { parentKey: parent ? (parent.name || path.slice(0, path.length - 1).join(',')) : '', level: path.length, itemKey: item.name || path.join(','), isExactActive: false, isActive: false, isExpand: XEUtils.isBoolean(item.expanded) ? item.expanded : !!expandAll, hasChild: item.children && item.children.length > 0 });
return objItem;
}, { children: 'children', mapChildren: 'childList' });
};
const updateCollapseStyle = () => {
const { collapseFixed } = props;
if (collapseFixed) {
nextTick(() => {
const { isEnterCollapse } = reactData;
const isCollapsed = computeIsCollapsed.value;
const collapseEnterWidth = computeCollapseEnterWidth.value;
const collapseWidth = computeCollapseWidth.value;
const el = refElem.value;
if (el) {
const clientRect = el.getBoundingClientRect();
const parentNode = el.parentNode;
reactData.collapseStyle = isCollapsed
? {
top: toCssUnit(clientRect.top),
left: toCssUnit(clientRect.left),
height: toCssUnit(parentNode.clientHeight),
width: isEnterCollapse ? (collapseEnterWidth ? toCssUnit(collapseEnterWidth) : '') : (collapseWidth ? toCssUnit(collapseWidth) : ''),
zIndex: reactData.collapseZindex
}
: {};
}
});
}
};
const handleCollapseMenu = () => {
const { collapseFixed } = props;
if (collapseFixed) {
const { initialized } = reactData;
const isCollapsed = computeIsCollapsed.value;
if (isCollapsed) {
if (!initialized) {
reactData.initialized = true;
nextTick(() => {
const collapseEl = refCollapseElem.value;
if (collapseEl) {
document.body.appendChild(collapseEl);
}
});
}
}
reactData.isEnterCollapse = false;
updateZindex();
updateCollapseStyle();
}
};
const handleClickIconCollapse = (evnt, item) => {
const { hasChild, isExpand } = item;
if (hasChild) {
evnt.stopPropagation();
evnt.preventDefault();
item.isExpand = !isExpand;
}
};
const emitModel = (value) => {
reactData.activeName = value;
emit('update:modelValue', value);
};
const handleClickMenu = (evnt, item) => {
const { itemKey, routerLink, hasChild } = item;
if (routerLink) {
emitModel(itemKey);
handleMenuMouseleave();
}
else {
if (hasChild) {
handleClickIconCollapse(evnt, item);
}
else {
emitModel(itemKey);
handleMenuMouseleave();
}
}
dispatchEvent('click', { menu: item }, evnt);
};
const handleMenuMouseenter = () => {
const { collapseStyle } = reactData;
const collapseEnterWidth = computeCollapseEnterWidth.value;
reactData.collapseStyle = Object.assign({}, collapseStyle, {
width: collapseEnterWidth ? toCssUnit(collapseEnterWidth) : ''
});
reactData.isEnterCollapse = true;
};
const handleMenuMouseleave = () => {
const { collapseStyle } = reactData;
const el = refElem.value;
reactData.collapseStyle = Object.assign({}, collapseStyle, {
width: el ? toCssUnit(el.offsetWidth) : ''
});
reactData.isEnterCollapse = false;
};
const callSlot = (slotFunc, params) => {
if (slotFunc) {
if (XEUtils.isString(slotFunc)) {
slotFunc = slots[slotFunc] || null;
}
if (XEUtils.isFunction(slotFunc)) {
return getSlotVNs(slotFunc(params));
}
}
return [];
};
const dispatchEvent = (type, params, evnt) => {
emit(type, createEvent(evnt, { $menu: $xeMenu }, params));
};
const menuMethods = {
dispatchEvent
};
const menuPrivateMethods = {};
Object.assign($xeMenu, menuMethods, menuPrivateMethods);
const renderMenuTitle = (item) => {
const { icon, isExpand, hasChild, slots: itemSlots } = item;
const optionSlot = itemSlots ? itemSlots.default : slots.option;
const title = getMenuTitle(item);
const isCollapsed = computeIsCollapsed.value;
return [
h('div', {
class: 'vxe-menu--item-link-icon'
}, icon
? [
h('i', {
class: icon
})
]
: []),
optionSlot
? h('div', {
class: 'vxe-menu--item-custom-title'
}, callSlot(optionSlot, {
option: item,
collapsed: isCollapsed
}))
: h('div', {
class: 'vxe-menu--item-link-title',
title
}, title),
hasChild
? h('div', {
class: 'vxe-menu--item-link-collapse',
onClick(evnt) {
handleClickIconCollapse(evnt, item);
}
}, [
h('i', {
class: isExpand ? getIcon().MENU_ITEM_EXPAND_OPEN : getIcon().MENU_ITEM_EXPAND_CLOSE
})
])
: createCommentVNode()
];
};
const renderDefaultChildren = (item) => {
const { itemKey, level, hasChild, isActive, isExactActive, isExpand, routerLink, childList } = item;
const { isEnterCollapse } = reactData;
const isCollapsed = computeIsCollapsed.value;
if (item.permissionCode) {
if (!permission.checkVisible(item.permissionCode)) {
return createCommentVNode();
}
}
return h('div', {
key: itemKey,
class: ['vxe-menu--item-wrapper', `vxe-menu--item-level${level}`, {
'is--exact-active': isExactActive,
'is--active': isActive,
'is--expand': (!isCollapsed || isEnterCollapse) && isExpand
}]
}, [
routerLink
? h(resolveComponent('router-link'), {
class: 'vxe-menu--item-link',
to: routerLink,
onClick(evnt) {
handleClickMenu(evnt, item);
}
}, {
default: () => renderMenuTitle(item)
})
: h('div', {
class: 'vxe-menu--item-link',
onClick(evnt) {
handleClickMenu(evnt, item);
}
}, renderMenuTitle(item)),
hasChild
? h('div', {
class: 'vxe-menu--item-group'
}, childList.map(child => renderDefaultChildren(child)))
: createCommentVNode()
]);
};
const renderCollapseChildren = (item) => {
const { itemKey, level, hasChild, isActive, isExactActive, routerLink, childList } = item;
if (item.permissionCode) {
if (!permission.checkVisible(item.permissionCode)) {
return createCommentVNode();
}
}
return h('div', {
key: itemKey,
class: ['vxe-menu--item-wrapper', `vxe-menu--item-level${level}`, {
'is--exact-active': isExactActive,
'is--active': isActive
}]
}, [
routerLink
? h(resolveComponent('router-link'), {
class: 'vxe-menu--item-link',
to: routerLink,
onClick(evnt) {
handleClickMenu(evnt, item);
}
}, {
default: () => renderMenuTitle(item)
})
: h('div', {
class: 'vxe-menu--item-link',
onClick(evnt) {
handleClickMenu(evnt, item);
}
}, renderMenuTitle(item)),
hasChild
? h('div', {
class: 'vxe-menu--item-group'
}, childList.map(child => renderDefaultChildren(child)))
: createCommentVNode()
]);
};
const renderVN = () => {
const { loading } = props;
const { initialized, menuList, collapseStyle, isEnterCollapse } = reactData;
const vSize = computeSize.value;
const isCollapsed = computeIsCollapsed.value;
return h('div', {
ref: refElem,
class: ['vxe-menu', {
[`size--${vSize}`]: vSize,
'is--collapsed': isCollapsed,
'is--loading': loading
}]
}, [
h('div', {
class: 'vxe-menu--item-list'
}, menuList.map(child => isCollapsed ? renderCollapseChildren(child) : renderDefaultChildren(child))),
initialized
? h('div', {
ref: refCollapseElem,
class: ['vxe-menu--collapse-wrapper', {
[`size--${vSize}`]: vSize,
'is--collapsed': isCollapsed,
'is--enter': isEnterCollapse,
'is--loading': loading
}],
style: collapseStyle,
onMouseenter: handleMenuMouseenter,
onMouseleave: handleMenuMouseleave
}, [
isCollapsed
? h('div', {
class: 'vxe-menu--item-list'
}, menuList.map(child => renderDefaultChildren(child)))
: createCommentVNode()
])
: createCommentVNode(),
/**
* 加载中
*/
h(VxeLoadingComponent, {
class: 'vxe-list-view--loading',
modelValue: loading
})
]);
};
const optFlag = ref(0);
watch(() => props.options ? props.options.length : -1, () => {
optFlag.value++;
});
watch(() => props.options, () => {
optFlag.value++;
});
watch(optFlag, () => {
updateMenuConfig();
updateActiveMenu(true);
});
watch(() => props.modelValue, (val) => {
reactData.activeName = val;
});
watch(() => reactData.activeName, () => {
updateActiveMenu(true);
});
watch(computeIsCollapsed, () => {
handleCollapseMenu();
});
onMounted(() => {
globalEvents.on($xeMenu, 'resize', updateCollapseStyle);
updateCollapseStyle();
});
onBeforeUnmount(() => {
globalEvents.off($xeMenu, 'resize');
const collapseEl = refCollapseElem.value;
if (collapseEl) {
const parentNode = collapseEl.parentNode;
if (parentNode) {
parentNode.removeChild(collapseEl);
}
}
});
updateMenuConfig();
updateActiveMenu(true);
$xeMenu.renderVN = renderVN;
return $xeMenu;
},
render() {
return this.renderVN();
}
});