import { Namespace } from '@ibiz-template/core'; import { AppMenuModel, AppMenuItemModel, IPSAppMenuItem, IPSAppIndexView, } from '@ibiz-template/model'; import { useAppMenuController, useNamespace, useRouter, } from '@ibiz-template/vue-util'; import { defineComponent, getCurrentInstance, onMounted, onUnmounted, PropType, Ref, ref, watch, } from 'vue'; import { AppMenuController } from '@ibiz-template/controller'; import '@ibiz-template/theme/style/components/widgets/app-menu/app-menu.scss'; import { AppCounter, CounterService } from '@ibiz-template/service'; /** * 递归生成菜单数据,递给 antd 的 Menu 组件 * * @author chitanda * @date 2022-07-25 10:07:28 * @param {AppMenuItemModel[]} items * @return {*} {any[]} */ function getMenus(items: AppMenuItemModel[]): IData[] { return items.map(item => { const data: IData = { key: item.source.id, label: item.label, image: item.image, counterId: item.counterId, }; if (item.children) { data.children = getMenus(item.children); } return data; }); } /** * 绘制菜单项 * @author lxm * @date 2022-08-16 14:08:20 * @param {IData} menu * @returns {*} */ function renderMenuItem( menu: IData, collapseChange: boolean, ns: Namespace, c: AppMenuController, counterData: IData, ) { if (!c.menuItemsState[menu.key].visible) { return; } return !collapseChange ? ( <i-menu-item class={ns.e('item')} name={menu.key}> <app-icon class={ns.e('icon')} icon={menu.image}></app-icon> {menu.label} {typeof counterData[menu.counterId] === 'number' ? ( <i-badge class={ns.e('counter')} count={counterData[menu.counterId]} ></i-badge> ) : null} </i-menu-item> ) : ( <i-tooltip class={ns.b('tooltip')} content={menu.label} placement={'right'} theme='light' > <i-menu-item class={ns.e('item')} name={menu.key}> <app-icon class={ns.e('icon')} icon={menu.image}></app-icon> {!menu.image ? menu.label.slice(0, 1) : null} {typeof counterData[menu.counterId] === 'number' ? ( <i-badge class={ns.e('counter')} count={counterData[menu.counterId]} ></i-badge> ) : null} </i-menu-item> </i-tooltip> ); } /** * 绘制中间菜单项 * @author fzh * @date 2022-08-29 14:08:20 * @param {IData} menu * @returns {*} */ function renderCenterChildren( menu: IData, ns: Namespace, c: AppMenuController, counterData: IData, click: Function, ) { return ( <div class={ns.e('cchildren')} onClick={() => click(menu.key)}> <app-icon class={ns.e('cchildrenIcon')} icon={menu.image}></app-icon> <div class={ns.em('cchildren', 'title')}> <span>{menu.label}</span> </div> {typeof counterData[menu.counterId] === 'number' ? ( <i-badge class={ns.e('counter')} count={counterData[menu.counterId]} ></i-badge> ) : null} </div> ); } /** * 绘制中间菜单分组 * @author fzh * @date 2022-08-29 14:08:20 * @param {IData} menu * @returns {*} */ function renderCenterMenuItem( menu: IData, ns: Namespace, c: AppMenuController, counterData: IData, click: Function, ) { if (!c.menuItemsState[menu.key].visible) { return; } return ( <div class={ns.e('cmenu')}> <div class={ns.em('cmenu', 'title')}>{menu.label}</div> {typeof counterData[menu.counterId] === 'number' ? ( <i-badge class={ns.e('counter')} count={counterData[menu.counterId]} ></i-badge> ) : null} <div class={ns.e('cmenucontent')}> {menu.children?.length > 0 && menu.children.map((item: IData) => { return renderCenterChildren(item, ns, c, counterData, click); })} </div> </div> ); } /** * 绘制收缩分组菜单项 * @param {IData} menu * @return {*} * @author: zhujiamin * @Date: 2022-09-08 16:39:15 */ function renderDropDownMenuItem( menu: IData, collapseChange: boolean, ns: Namespace, c: AppMenuController, counterData: IData, ) { if (!c.menuItemsState[menu.key].visible) { return; } return ( <i-dropdown-item class={ns.be('submenu', 'item')} name={menu.key}> <i-menu-item name={menu.key}> <app-icon class={ns.e('icon')} icon={menu.image}></app-icon> {menu.label} {typeof counterData[menu.counterId] === 'number' ? ( <i-badge class={ns.e('counter')} count={counterData[menu.counterId]} ></i-badge> ) : null} </i-menu-item> </i-dropdown-item> ); } /** * 绘制子菜单 * @author lxm * @date 2022-08-16 14:08:29 * @param {IData} subMenu * @returns {*} */ function renderSubmenu( isFirst: boolean, subMenu: IData, collapseChange: boolean, ns: Namespace, c: AppMenuController, counterData: IData, ) { if (!c.menuItemsState[subMenu.key].visible) { return; } return !collapseChange ? ( <i-submenu name={subMenu.key}> <template slot='title'> <app-icon class={ns.e('icon')} icon={subMenu.image}></app-icon> {subMenu.label} </template> {subMenu.children.map((item: IData) => { if (item.children) { return renderSubmenu(false, item, collapseChange, ns, c, counterData); } return renderMenuItem(item, collapseChange, ns, c, counterData); })} </i-submenu> ) : ( <i-dropdown placement='right' class={ns.b('submenu')} transfer transfer-class-name={ns.b('submenu-transfer')} > <div class={ns.be('submenu', 'title')}> {isFirst ? [ <app-icon class={ns.e('icon')} icon={subMenu.image}></app-icon>, !subMenu.image ? subMenu.label.slice(0, 1) : null, ] : [ <app-icon class={ns.e('icon')} icon={subMenu.image}></app-icon>, subMenu.label, ]} {isFirst ? null : <i-icon type='ios-arrow-forward' />} </div> <i-dropdown-menu class={ns.be('submenu', 'list')} slot='list'> {subMenu.children.map((item: IData) => { if (item.children) { return renderSubmenu( false, item, collapseChange, ns, c, counterData, ); } return renderDropDownMenuItem( item, collapseChange, ns, c, counterData, ); })} </i-dropdown-menu> </i-dropdown> ); } export const AppMenu = defineComponent({ name: 'AppMenu', props: { modelData: AppMenuModel, context: { type: Object as PropType<IContext>, required: true }, collapseChange: Boolean, currentPath: String, menuModel: { type: Object as PropType<IPSAppIndexView>, required: false }, }, setup(props, { emit }) { const { proxy } = getCurrentInstance()!; const c = useAppMenuController(proxy, props.modelData!, props.context!, {}); const menus = ref<IData[]>([]); // 默认激活菜单项 const defaultActive = ref(''); // 默认展开菜单项数组 const defaultOpens: Ref<string[]> = ref([]); // 路由对象 const route = proxy.$route; // 计数器数据 let counter: AppCounter | null = null; const counterData = ref<IData>({}); const menuDirection = props.menuModel?.mainMenuAlign; c.nerve.self.evt.on('created', () => { menus.value = getMenus(c.model.items); }); let cacheFullPath = ''; const onClick = async (key: string) => { cacheFullPath = proxy.$route.fullPath; await c.onClickMenuItem(key); cacheFullPath = ''; }; const ns = useNamespace('app-menu'); // 手动更新iView菜单 const updateMenu = () => { setTimeout(() => { if (proxy.$refs.menu) { const menu: IData = proxy.$refs.menu; menu.updateActiveName(); menu.updateOpened(); } }, 500); }; const router = useRouter(proxy); router.beforeEach((to, from, next) => { if (from.fullPath === cacheFullPath) { emit('menuRouteChange'); console.log('菜单点击', from); } next(); }); // 菜单选中回显,监听视图传进来的currentPath watch( () => props.currentPath, (newVal, oldVal) => { // 新旧值不一样,且新值不为空时变更 if (newVal !== oldVal && newVal) { const findItem = c.model.allItems.find(item => { return item.viewModelPath === newVal; }); if (findItem) { defaultActive.value = findItem.source.id; updateMenu(); } } }, { deep: true, immediate: true }, ); // 处理默认展开父菜单 const handleDefaultOpen = (parent: IPSAppMenuItem) => { if (parent && parent.itemType === 'MENUITEM') { const findIndex = defaultOpens.value.findIndex(open => { return open === parent.id; }); if (findIndex === -1) { defaultOpens.value.push(parent.id); } const gParent = parent.getParentPSModelObject() as IPSAppMenuItem; if (gParent) { handleDefaultOpen(gParent); } } }; // 回显完成后,遍历展开父菜单 watch( () => defaultActive.value, newVal => { const findItem = c.model.allItems.find(item => { return newVal === item.id; }); if (findItem) { const parent = findItem.source.getParentPSModelObject() as IPSAppMenuItem; // 遍历取父菜单项 handleDefaultOpen(parent); updateMenu(); } }, ); onMounted(async () => { // 默认激活的菜单项 const defaultActiveMenuItem = c.model.allItems.find(item => { return ( item.source.openDefault && !item.source.hidden && c.menuItemsState[item.source.id].permitted ); }); console.log('defaultActiveMenuItem', defaultActiveMenuItem); if (defaultActiveMenuItem && !route.params.view2) { defaultActive.value = defaultActiveMenuItem.id; onClick(defaultActive.value); } // 默认展开的菜单项数组 const defaultOpensArr = c.model.allItems.filter(item => { return item.source.expanded && !item.source.hidden; }); if (defaultOpensArr.length > 0) { defaultOpensArr.forEach(item => { defaultOpens.value.push(item.id); }); } updateMenu(); // 计数器相关 const CounterRef = c.model.source.getPSAppCounterRef(); if (CounterRef) { counter = await CounterService.getCounterByRef( CounterRef, props.context, ); counter.onChange(data => { counterData.value = data; }); } }); onUnmounted(() => { counter?.destroy(); }); return { menus, c, onClick, ns, defaultActive, defaultOpens, counterData, menuDirection, }; }, render() { if (this.menuDirection === 'CENTER') { return ( <div class={[this.ns.b(), this.menuDirection?.toLowerCase()]}> {this.menus.map(item => { return renderCenterMenuItem( item, this.ns, this.c, this.counterData, this.onClick, ); })} </div> ); } return ( <i-menu ref={'menu'} active-name={this.defaultActive} open-names={this.defaultOpens} on-on-select={this.onClick} theme='light' width='auto' class={[this.ns.b(), this.ns.is('collapse', this.collapseChange)]} > {this.menus.map(item => { if (item.children?.length > 0) { return renderSubmenu( true, item, this.collapseChange, this.ns, this.c, this.counterData, ); } return renderMenuItem( item, this.collapseChange, this.ns, this.c, this.counterData, ); })} </i-menu> ); }, });