UNPKG

vxe-pc-ui

Version:
434 lines (433 loc) 17.2 kB
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(); } });