| | |
| | | </ul> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | <script lang="ts" setup> |
| | | import type { PropType } from 'vue'; |
| | | import type { SubMenuProvider } from './types'; |
| | | import { |
| | | defineComponent, |
| | | ref, |
| | | computed, |
| | | onMounted, |
| | |
| | | getCurrentInstance, |
| | | provide, |
| | | } from 'vue'; |
| | | import { useDesign } from '@/hooks/web/useDesign'; |
| | | import { propTypes } from '@/utils/propTypes'; |
| | | import { createSimpleRootMenuContext, type MenuEmitterEvents } from './useSimpleMenuContext'; |
| | | import { mitt } from '@/utils/mitt'; |
| | | |
| | | import { useDesign } from '/@/hooks/web/useDesign'; |
| | | import { propTypes } from '/@/utils/propTypes'; |
| | | import { createSimpleRootMenuContext } from './useSimpleMenuContext'; |
| | | import { mitt } from '/@/utils/mitt'; |
| | | defineOptions({ name: 'Menu' }); |
| | | |
| | | export default defineComponent({ |
| | | name: 'Menu', |
| | | props: { |
| | | theme: propTypes.oneOf(['light', 'dark']).def('light'), |
| | | activeName: propTypes.oneOfType([propTypes.string, propTypes.number]), |
| | | openNames: { |
| | | type: Array as PropType<string[]>, |
| | | default: () => [], |
| | | }, |
| | | accordion: propTypes.bool.def(true), |
| | | width: propTypes.string.def('100%'), |
| | | collapsedWidth: propTypes.string.def('48px'), |
| | | indentSize: propTypes.number.def(16), |
| | | collapse: propTypes.bool.def(true), |
| | | activeSubMenuNames: { |
| | | type: Array as PropType<(string | number)[]>, |
| | | default: () => [], |
| | | }, |
| | | const props = defineProps({ |
| | | theme: propTypes.oneOf(['light', 'dark']).def('light'), |
| | | activeName: propTypes.oneOfType([propTypes.string, propTypes.number]), |
| | | openNames: { |
| | | type: Array as PropType<string[]>, |
| | | default: () => [], |
| | | }, |
| | | emits: ['select', 'open-change'], |
| | | setup(props, { emit }) { |
| | | const rootMenuEmitter = mitt(); |
| | | const instance = getCurrentInstance(); |
| | | accordion: propTypes.bool.def(true), |
| | | width: propTypes.string.def('100%'), |
| | | collapsedWidth: propTypes.string.def('48px'), |
| | | indentSize: propTypes.number.def(16), |
| | | collapse: propTypes.bool.def(true), |
| | | activeSubMenuNames: { |
| | | type: Array as PropType<(string | number)[]>, |
| | | default: () => [], |
| | | }, |
| | | }); |
| | | |
| | | const currentActiveName = ref<string | number>(''); |
| | | const openedNames = ref<string[]>([]); |
| | | const emit = defineEmits(['select', 'open-change']); |
| | | |
| | | const { prefixCls } = useDesign('menu'); |
| | | const rootMenuEmitter = mitt<MenuEmitterEvents>(); |
| | | const instance = getCurrentInstance(); |
| | | |
| | | const isRemoveAllPopup = ref(false); |
| | | const currentActiveName = ref<string | number>(''); |
| | | const openedNames = ref<(string | number)[]>([]); |
| | | |
| | | createSimpleRootMenuContext({ |
| | | rootMenuEmitter: rootMenuEmitter, |
| | | activeName: currentActiveName, |
| | | const { prefixCls } = useDesign('menu'); |
| | | |
| | | const isRemoveAllPopup = ref(false); |
| | | |
| | | createSimpleRootMenuContext({ |
| | | rootMenuEmitter: rootMenuEmitter, |
| | | activeName: currentActiveName, |
| | | }); |
| | | |
| | | const getClass = computed(() => { |
| | | const { theme } = props; |
| | | return [ |
| | | prefixCls, |
| | | `${prefixCls}-${theme}`, |
| | | `${prefixCls}-vertical`, |
| | | { |
| | | [`${prefixCls}-collapse`]: props.collapse, |
| | | }, |
| | | ]; |
| | | }); |
| | | |
| | | watchEffect(() => { |
| | | openedNames.value = props.openNames; |
| | | }); |
| | | |
| | | watchEffect(() => { |
| | | if (props.activeName) { |
| | | currentActiveName.value = props.activeName; |
| | | } |
| | | }); |
| | | |
| | | watch( |
| | | () => props.openNames, |
| | | () => { |
| | | nextTick(() => { |
| | | updateOpened(); |
| | | }); |
| | | }, |
| | | ); |
| | | |
| | | const getClass = computed(() => { |
| | | const { theme } = props; |
| | | return [ |
| | | prefixCls, |
| | | `${prefixCls}-${theme}`, |
| | | `${prefixCls}-vertical`, |
| | | { |
| | | [`${prefixCls}-collapse`]: props.collapse, |
| | | }, |
| | | ]; |
| | | function updateOpened() { |
| | | rootMenuEmitter.emit('on-update-opened', openedNames.value); |
| | | } |
| | | |
| | | function addSubMenu(name: string | number) { |
| | | if (openedNames.value.includes(name)) return; |
| | | openedNames.value.push(name); |
| | | updateOpened(); |
| | | } |
| | | |
| | | function removeSubMenu(name: string | number) { |
| | | openedNames.value = openedNames.value.filter((item) => item !== name); |
| | | updateOpened(); |
| | | } |
| | | |
| | | function removeAll() { |
| | | openedNames.value = []; |
| | | updateOpened(); |
| | | } |
| | | |
| | | function sliceIndex(index: number) { |
| | | if (index === -1) return; |
| | | openedNames.value = openedNames.value.slice(0, index + 1); |
| | | updateOpened(); |
| | | } |
| | | |
| | | provide<SubMenuProvider>(`subMenu:${instance?.uid}`, { |
| | | addSubMenu, |
| | | removeSubMenu, |
| | | getOpenNames: () => openedNames.value, |
| | | removeAll, |
| | | isRemoveAllPopup, |
| | | sliceIndex, |
| | | level: 0, |
| | | props: props as any, |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | openedNames.value = !props.collapse ? [...props.openNames] : []; |
| | | updateOpened(); |
| | | rootMenuEmitter.on('on-menu-item-select', (name: string | number) => { |
| | | currentActiveName.value = name; |
| | | |
| | | nextTick(() => { |
| | | props.collapse && removeAll(); |
| | | }); |
| | | emit('select', name); |
| | | }); |
| | | |
| | | watchEffect(() => { |
| | | openedNames.value = props.openNames; |
| | | }); |
| | | |
| | | watchEffect(() => { |
| | | if (props.activeName) { |
| | | currentActiveName.value = props.activeName; |
| | | } |
| | | }); |
| | | |
| | | watch( |
| | | () => props.openNames, |
| | | () => { |
| | | nextTick(() => { |
| | | updateOpened(); |
| | | }); |
| | | }, |
| | | ); |
| | | |
| | | function updateOpened() { |
| | | rootMenuEmitter.emit('on-update-opened', openedNames.value); |
| | | } |
| | | |
| | | function addSubMenu(name: string) { |
| | | if (openedNames.value.includes(name)) return; |
| | | rootMenuEmitter.on('open-name-change', ({ name, opened }) => { |
| | | if (opened && !openedNames.value.includes(name)) { |
| | | openedNames.value.push(name); |
| | | updateOpened(); |
| | | } else if (!opened) { |
| | | const index = openedNames.value.findIndex((item) => item === name); |
| | | index !== -1 && openedNames.value.splice(index, 1); |
| | | } |
| | | |
| | | function removeSubMenu(name: string) { |
| | | openedNames.value = openedNames.value.filter((item) => item !== name); |
| | | updateOpened(); |
| | | } |
| | | |
| | | function removeAll() { |
| | | openedNames.value = []; |
| | | updateOpened(); |
| | | } |
| | | |
| | | function sliceIndex(index: number) { |
| | | if (index === -1) return; |
| | | openedNames.value = openedNames.value.slice(0, index + 1); |
| | | updateOpened(); |
| | | } |
| | | |
| | | provide<SubMenuProvider>(`subMenu:${instance?.uid}`, { |
| | | addSubMenu, |
| | | removeSubMenu, |
| | | getOpenNames: () => openedNames.value, |
| | | removeAll, |
| | | isRemoveAllPopup, |
| | | sliceIndex, |
| | | level: 0, |
| | | props: props as any, |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | openedNames.value = !props.collapse ? [...props.openNames] : []; |
| | | updateOpened(); |
| | | rootMenuEmitter.on('on-menu-item-select', (name: string) => { |
| | | currentActiveName.value = name; |
| | | |
| | | nextTick(() => { |
| | | props.collapse && removeAll(); |
| | | }); |
| | | emit('select', name); |
| | | }); |
| | | |
| | | rootMenuEmitter.on('open-name-change', ({ name, opened }) => { |
| | | if (opened && !openedNames.value.includes(name)) { |
| | | openedNames.value.push(name); |
| | | } else if (!opened) { |
| | | const index = openedNames.value.findIndex((item) => item === name); |
| | | index !== -1 && openedNames.value.splice(index, 1); |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | return { getClass, openedNames }; |
| | | }, |
| | | }); |
| | | }); |
| | | </script> |
| | | <style lang="less"> |