mirror of
				https://github.com/zclzone/vue-naive-admin.git
				synced 2025-11-04 20:58:59 +08:00 
			
		
		
		
	wip: 主题设置
This commit is contained in:
		
							parent
							
								
									c8616ebbf3
								
							
						
					
					
						commit
						4f0fbf6107
					
				
							
								
								
									
										10
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/App.vue
									
									
									
									
									
								
							@ -12,7 +12,7 @@
 | 
			
		||||
    :locale="zhCN"
 | 
			
		||||
    :date-locale="dateZhCN"
 | 
			
		||||
    :theme="appStore.isDark ? darkTheme : undefined"
 | 
			
		||||
    :theme-overrides="settings.naiveThemeOverrides"
 | 
			
		||||
    :theme-overrides="appStore.naiveThemeOverrides"
 | 
			
		||||
  >
 | 
			
		||||
    <router-view v-if="Layout" v-slot="{ Component, route: curRoute }">
 | 
			
		||||
      <component :is="Layout">
 | 
			
		||||
@ -20,16 +20,18 @@
 | 
			
		||||
          <component :is="Component" v-if="!tabStore.reloading" :key="curRoute.fullPath" />
 | 
			
		||||
        </KeepAlive>
 | 
			
		||||
      </component>
 | 
			
		||||
 | 
			
		||||
      <ThemeSetting class="fixed bottom-12 right-12" />
 | 
			
		||||
    </router-view>
 | 
			
		||||
  </n-config-provider>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { zhCN, dateZhCN, darkTheme } from 'naive-ui'
 | 
			
		||||
import { ThemeSetting } from '@/components'
 | 
			
		||||
import { useCssVar } from '@vueuse/core'
 | 
			
		||||
import { kebabCase } from 'lodash-es'
 | 
			
		||||
import { useAppStore, useTabStore } from '@/store'
 | 
			
		||||
import settings from '@/settings'
 | 
			
		||||
 | 
			
		||||
const layouts = new Map()
 | 
			
		||||
function getLayout(name) {
 | 
			
		||||
@ -44,11 +46,11 @@ const route = useRoute()
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const Layout = computed(() => {
 | 
			
		||||
  if (!route.matched?.length) return null
 | 
			
		||||
  return getLayout(route.meta?.layout || appStore.layout || settings.defaultLayout)
 | 
			
		||||
  return getLayout(route.meta?.layout || appStore.layout)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function setupCssVar() {
 | 
			
		||||
  const common = settings.naiveThemeOverrides?.common || {}
 | 
			
		||||
  const common = appStore.naiveThemeOverrides?.common || {}
 | 
			
		||||
  for (const key in common) {
 | 
			
		||||
    useCssVar(`--${kebabCase(key)}`, document.documentElement).value = common[key] || ''
 | 
			
		||||
    if (key === 'primaryColor') window.localStorage.setItem('__THEME_COLOR__', common[key] || '')
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								src/assets/icons/isme/theme.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/assets/icons/isme/theme.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
<svg
 | 
			
		||||
  t="1702480351321"
 | 
			
		||||
  class="icon"
 | 
			
		||||
  viewBox="0 0 1024 1024"
 | 
			
		||||
  version="1.1"
 | 
			
		||||
  xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
  p-id="11122"
 | 
			
		||||
  width="200"
 | 
			
		||||
  height="200"
 | 
			
		||||
>
 | 
			
		||||
  <path
 | 
			
		||||
    d="M509.9008 519.8336m-450.816 0a450.816 450.816 0 1 0 901.632 0 450.816 450.816 0 1 0-901.632 0Z"
 | 
			
		||||
    fill="#C65EDB"
 | 
			
		||||
    p-id="11123"
 | 
			
		||||
  ></path>
 | 
			
		||||
  <path
 | 
			
		||||
    d="M798.1568 512.512l-113.3056-78.3872a47.4112 47.4112 0 0 1-20.4288-39.0656l0.3584-137.7792c0.1024-39.2704-44.9024-61.5936-76.0832-37.7856l-109.568 83.5072a47.2832 47.2832 0 0 1-43.4688 7.3216l-130.9184-42.9056c-37.3248-12.2368-72.448 23.6544-59.4432 60.7232l45.568 129.9968A47.3088 47.3088 0 0 1 284.416 501.76l-81.2544 111.2576c-23.1424 31.6928 0.1024 76.2368 39.3728 75.3152l137.728-3.1744a47.3344 47.3344 0 0 1 39.4752 19.6096l80.6912 111.6672c22.9888 31.8464 72.4992 23.4496 83.7632-14.1824l37.9392-126.6176 126.5664 118.272a27.648 27.648 0 0 0 17.7664 7.4752c7.8848 0.3584 15.872-2.6112 21.7088-8.8064a27.91936 27.91936 0 0 0-1.3312-39.4752l-124.8768-116.6848 123.8016-39.8848c37.376-11.9808 44.6976-61.6448 12.3904-84.0192z m-389.6832-6.5024l-42.8032 77.824a27.86816 27.86816 0 0 1-37.9392 11.008 27.93984 27.93984 0 0 1-11.008-37.9392l40.0384-72.7552-19.0464-63.6928c-4.4032-14.7968 3.9936-30.3616 18.7392-34.7648 14.7456-4.4032 30.3616 3.9936 34.7648 18.7392l20.6848 69.2224c3.2256 10.752 1.9968 22.528-3.4304 32.3584z"
 | 
			
		||||
    fill="#FFFFFF"
 | 
			
		||||
    p-id="11124"
 | 
			
		||||
  ></path>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										38
									
								
								src/components/common/ThemeSetting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/components/common/ThemeSetting.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <n-popover trigger="click">
 | 
			
		||||
      <template #trigger>
 | 
			
		||||
        <i class="i-me-theme cursor-pointer text-40" />
 | 
			
		||||
      </template>
 | 
			
		||||
      <div class="h-600 w-260">
 | 
			
		||||
        <h3 class="font-normal">主题设置</h3>
 | 
			
		||||
 | 
			
		||||
        <n-divider>布局</n-divider>
 | 
			
		||||
        <ul class="h-32 flex items-center justify-between">
 | 
			
		||||
          <li
 | 
			
		||||
            v-for="(item, index) in layouts"
 | 
			
		||||
            :key="index"
 | 
			
		||||
            class="cursor-pointer opacity-70"
 | 
			
		||||
            @click="appStore.setLayout(item.value)"
 | 
			
		||||
          >
 | 
			
		||||
            {{ item.desc }}
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
 | 
			
		||||
        <n-divider>主题色</n-divider>
 | 
			
		||||
      </div>
 | 
			
		||||
    </n-popover>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const layouts = [
 | 
			
		||||
  { value: 'simple', desc: '简约' },
 | 
			
		||||
  { value: 'normal', desc: '通用' },
 | 
			
		||||
  { value: 'full', desc: '全面' },
 | 
			
		||||
  { value: 'empty', desc: '空白' },
 | 
			
		||||
]
 | 
			
		||||
</script>
 | 
			
		||||
@ -2,3 +2,4 @@ export { default as AppCard } from './AppCard.vue'
 | 
			
		||||
export { default as TheFooter } from './TheFooter.vue'
 | 
			
		||||
export { default as AppPage } from './AppPage.vue'
 | 
			
		||||
export { default as CommonPage } from './CommonPage.vue'
 | 
			
		||||
export { default as ThemeSetting } from './ThemeSetting.vue'
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:24:19
 | 
			
		||||
 - @LastEditTime: 2023/12/13 20:54:55
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
    </aside>
 | 
			
		||||
 | 
			
		||||
    <article class="w-0 flex-col flex-1">
 | 
			
		||||
      <AppHeader :class="`h-${header.height}`" class="flex-shrink-0" />
 | 
			
		||||
      <AppHeader class="h-60 flex-shrink-0" />
 | 
			
		||||
      <slot />
 | 
			
		||||
    </article>
 | 
			
		||||
  </div>
 | 
			
		||||
@ -28,9 +28,6 @@ import { useAppStore } from '@/store'
 | 
			
		||||
import SideBar from './sidebar/index.vue'
 | 
			
		||||
import AppHeader from './header/index.vue'
 | 
			
		||||
 | 
			
		||||
import settings from '@/settings'
 | 
			
		||||
const { header } = settings
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										83
									
								
								src/layouts/normal/header/components/UserAvatar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/layouts/normal/header/components/UserAvatar.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:46
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <n-dropdown :options="options" @select="handleSelect">
 | 
			
		||||
    <div class="flex cursor-pointer items-center">
 | 
			
		||||
      <n-avatar round :size="36" :src="userStore.avatar" class="mr-12" />
 | 
			
		||||
      <div v-if="userStore.userInfo" class="flex-col items-center">
 | 
			
		||||
        <span class="text-14">{{ userStore.nickName ?? userStore.username }}</span>
 | 
			
		||||
        <span class="text-12 opacity-50">[{{ userStore.currentRole?.name }}]</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </n-dropdown>
 | 
			
		||||
 | 
			
		||||
  <RoleSelect ref="roleSelectRef" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useUserStore, useAuthStore, usePermissionStore } from '@/store'
 | 
			
		||||
import { RoleSelect } from '@/layouts/components'
 | 
			
		||||
import { initUserAndPermissions } from '@/router'
 | 
			
		||||
import api from '@/api'
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const userStore = useUserStore()
 | 
			
		||||
const authStore = useAuthStore()
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
 | 
			
		||||
const options = reactive([
 | 
			
		||||
  {
 | 
			
		||||
    label: '个人资料',
 | 
			
		||||
    key: 'profile',
 | 
			
		||||
    icon: () => h('i', { class: 'i-material-symbols:person-outline text-14' }),
 | 
			
		||||
    show: computed(() => permissionStore.accessRoutes?.some((item) => item.path === '/profile')),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '切换角色',
 | 
			
		||||
    key: 'toggleRole',
 | 
			
		||||
    icon: () => h('i', { class: 'i-basil:exchange-solid text-14' }),
 | 
			
		||||
    show: computed(() => userStore.roles.length > 1),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '退出登录',
 | 
			
		||||
    key: 'logout',
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:exit-to-app text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
const roleSelectRef = ref(null)
 | 
			
		||||
function handleSelect(key) {
 | 
			
		||||
  switch (key) {
 | 
			
		||||
    case 'profile':
 | 
			
		||||
      router.push('/profile')
 | 
			
		||||
      break
 | 
			
		||||
    case 'toggleRole':
 | 
			
		||||
      roleSelectRef.value?.open({
 | 
			
		||||
        onOk() {
 | 
			
		||||
          initUserAndPermissions().then(() => {
 | 
			
		||||
            router.replace('/')
 | 
			
		||||
          })
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      break
 | 
			
		||||
    case 'logout':
 | 
			
		||||
      $dialog.confirm({
 | 
			
		||||
        title: '提示',
 | 
			
		||||
        type: 'info',
 | 
			
		||||
        content: '确认退出?',
 | 
			
		||||
        async confirm() {
 | 
			
		||||
          await api.logout()
 | 
			
		||||
          authStore.logout()
 | 
			
		||||
          $message.success('已退出登录')
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      break
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										2
									
								
								src/layouts/normal/header/components/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/layouts/normal/header/components/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
export { default as UserAvatar } from './UserAvatar.vue'
 | 
			
		||||
export { default as AppTab } from './tab/index.vue'
 | 
			
		||||
							
								
								
									
										125
									
								
								src/layouts/normal/header/components/tab/ContextMenu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/layouts/normal/header/components/tab/ContextMenu.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:32
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <n-dropdown
 | 
			
		||||
    :show="show"
 | 
			
		||||
    :options="options"
 | 
			
		||||
    :x="x"
 | 
			
		||||
    :y="y"
 | 
			
		||||
    placement="bottom-start"
 | 
			
		||||
    @clickoutside="handleHideDropdown"
 | 
			
		||||
    @select="handleSelect"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useTabStore } from '@/store'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  currentPath: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  x: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 0,
 | 
			
		||||
  },
 | 
			
		||||
  y: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 0,
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:show'])
 | 
			
		||||
 | 
			
		||||
const tabStore = useTabStore()
 | 
			
		||||
 | 
			
		||||
const options = computed(() => [
 | 
			
		||||
  {
 | 
			
		||||
    label: '重新加载',
 | 
			
		||||
    key: 'reload',
 | 
			
		||||
    disabled: props.currentPath !== tabStore.activeTab,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:refresh text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭',
 | 
			
		||||
    key: 'close',
 | 
			
		||||
    disabled: tabStore.tabs.length <= 1,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:close text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭其他',
 | 
			
		||||
    key: 'close-other',
 | 
			
		||||
    disabled: tabStore.tabs.length <= 1,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:arrow-expand-horizontal text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭左侧',
 | 
			
		||||
    key: 'close-left',
 | 
			
		||||
    disabled: tabStore.tabs.length <= 1 || props.currentPath === tabStore.tabs[0].path,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:arrow-expand-left text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭右侧',
 | 
			
		||||
    key: 'close-right',
 | 
			
		||||
    disabled:
 | 
			
		||||
      tabStore.tabs.length <= 1 ||
 | 
			
		||||
      props.currentPath === tabStore.tabs[tabStore.tabs.length - 1].path,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:arrow-expand-right text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
const actionMap = new Map([
 | 
			
		||||
  [
 | 
			
		||||
    'reload',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.reloadTab(route.fullPath, route.meta?.keepAlive)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeTab(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close-other',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeOther(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close-left',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeLeft(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close-right',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeRight(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
function handleHideDropdown() {
 | 
			
		||||
  emit('update:show', false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleSelect(key) {
 | 
			
		||||
  const actionFn = actionMap.get(key)
 | 
			
		||||
  actionFn && actionFn()
 | 
			
		||||
  handleHideDropdown()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										101
									
								
								src/layouts/normal/header/components/tab/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/layouts/normal/header/components/tab/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:38
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <n-tabs
 | 
			
		||||
      :value="tabStore.activeTab"
 | 
			
		||||
      :closable="tabStore.tabs.length > 1"
 | 
			
		||||
      :style="`--selected-bg: ${appStore.isDark ? '#1b2429' : '#eaf0f1'}`"
 | 
			
		||||
      type="card"
 | 
			
		||||
      @close="(path) => tabStore.removeTab(path)"
 | 
			
		||||
    >
 | 
			
		||||
      <n-tab
 | 
			
		||||
        v-for="item in tabStore.tabs"
 | 
			
		||||
        :key="item.path"
 | 
			
		||||
        :name="item.path"
 | 
			
		||||
        @click="handleItemClick(item.path)"
 | 
			
		||||
        @contextmenu.prevent="handleContextMenu($event, item)"
 | 
			
		||||
      >
 | 
			
		||||
        {{ item.title }}
 | 
			
		||||
      </n-tab>
 | 
			
		||||
    </n-tabs>
 | 
			
		||||
 | 
			
		||||
    <ContextMenu
 | 
			
		||||
      v-if="contextMenuOption.show"
 | 
			
		||||
      v-model:show="contextMenuOption.show"
 | 
			
		||||
      :current-path="contextMenuOption.currentPath"
 | 
			
		||||
      :x="contextMenuOption.x"
 | 
			
		||||
      :y="contextMenuOption.y"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import ContextMenu from './ContextMenu.vue'
 | 
			
		||||
import { useTabStore, useAppStore } from '@/store'
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const tabStore = useTabStore()
 | 
			
		||||
 | 
			
		||||
const contextMenuOption = reactive({
 | 
			
		||||
  show: false,
 | 
			
		||||
  x: 0,
 | 
			
		||||
  y: 0,
 | 
			
		||||
  currentPath: '',
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const handleItemClick = (path) => {
 | 
			
		||||
  tabStore.setActiveTab(path)
 | 
			
		||||
  router.push(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showContextMenu() {
 | 
			
		||||
  contextMenuOption.show = true
 | 
			
		||||
}
 | 
			
		||||
function hideContextMenu() {
 | 
			
		||||
  contextMenuOption.show = false
 | 
			
		||||
}
 | 
			
		||||
function setContextMenu(x, y, currentPath) {
 | 
			
		||||
  Object.assign(contextMenuOption, { x, y, currentPath })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 右击菜单
 | 
			
		||||
async function handleContextMenu(e, tagItem) {
 | 
			
		||||
  const { clientX, clientY } = e
 | 
			
		||||
  hideContextMenu()
 | 
			
		||||
  setContextMenu(clientX, clientY, tagItem.path)
 | 
			
		||||
  await nextTick()
 | 
			
		||||
  showContextMenu()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
:deep(.n-tabs) {
 | 
			
		||||
  .n-tabs-tab {
 | 
			
		||||
    padding-left: 16px;
 | 
			
		||||
    height: 36px;
 | 
			
		||||
    background: transparent !important;
 | 
			
		||||
    border-radius: 4px !important;
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
    &:hover {
 | 
			
		||||
      border: 1px solid var(--primary-color) !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .n-tabs-tab--active {
 | 
			
		||||
    border: 1px solid var(--primary-color) !important;
 | 
			
		||||
    background-color: var(--selected-bg) !important;
 | 
			
		||||
  }
 | 
			
		||||
  .n-tabs-pad,
 | 
			
		||||
  .n-tabs-tab-pad,
 | 
			
		||||
  .n-tabs-scroll-padding {
 | 
			
		||||
    border: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										64
									
								
								src/layouts/normal/header/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/layouts/normal/header/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:23
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <AppCard class="flex items-center px-12" border-b="1px solid light_border dark:dark_border">
 | 
			
		||||
    <div
 | 
			
		||||
      class="f-c-c cursor-pointer rounded-4 p-6 text-22 transition-all-300 auto-bg-hover"
 | 
			
		||||
      @click="appStore.switchCollapsed"
 | 
			
		||||
    >
 | 
			
		||||
      <i :class="appStore.collapsed ? 'i-line-md-menu-unfold-left' : 'i-line-md-menu-fold-left'" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <AppTab class="w-0 flex-1 px-12" />
 | 
			
		||||
 | 
			
		||||
    <span class="mx-6 opacity-20">|</span>
 | 
			
		||||
 | 
			
		||||
    <div class="flex flex-shrink-0 items-center px-12 text-18">
 | 
			
		||||
      <i
 | 
			
		||||
        class="mr-16 cursor-pointer"
 | 
			
		||||
        :class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
 | 
			
		||||
        @click="toggleDark"
 | 
			
		||||
      />
 | 
			
		||||
      <i
 | 
			
		||||
        class="mr-16 cursor-pointer"
 | 
			
		||||
        :class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
 | 
			
		||||
        @click="toggle"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <i
 | 
			
		||||
        class="i-fe:github mr-16 cursor-pointer"
 | 
			
		||||
        @click="handleLinkClick('https://github.com/zclzone/vue-naive-admin/tree/2.x-beta')"
 | 
			
		||||
      />
 | 
			
		||||
      <i
 | 
			
		||||
        class="i-me:gitee mr-16 cursor-pointer"
 | 
			
		||||
        @click="handleLinkClick('https://gitee.com/isme-admin/vue-naive-admin/tree/2.x-beta')"
 | 
			
		||||
      />
 | 
			
		||||
      <UserAvatar />
 | 
			
		||||
    </div>
 | 
			
		||||
  </AppCard>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { AppTab, UserAvatar } from './components'
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
import { useDark, useToggle, useFullscreen } from '@vueuse/core'
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const isDark = useDark()
 | 
			
		||||
const toggleDark = () => {
 | 
			
		||||
  appStore.toggleDark()
 | 
			
		||||
  useToggle(isDark)()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const { isFullscreen, toggle } = useFullscreen()
 | 
			
		||||
 | 
			
		||||
function handleLinkClick(link) {
 | 
			
		||||
  window.open(link)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										38
									
								
								src/layouts/normal/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/layouts/normal/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/13 20:54:55
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="wh-full flex">
 | 
			
		||||
    <aside
 | 
			
		||||
      class="flex-col flex-shrink-0 transition-width-300"
 | 
			
		||||
      :class="appStore.collapsed ? 'w-64' : 'w-220'"
 | 
			
		||||
      border-r="1px solid light_border dark:dark_border"
 | 
			
		||||
    >
 | 
			
		||||
      <SideBar />
 | 
			
		||||
    </aside>
 | 
			
		||||
 | 
			
		||||
    <article class="w-0 flex-col flex-1">
 | 
			
		||||
      <AppHeader class="h-60 flex-shrink-0" />
 | 
			
		||||
      <slot />
 | 
			
		||||
    </article>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
import SideBar from './sidebar/index.vue'
 | 
			
		||||
import AppHeader from './header/index.vue'
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.collapsed {
 | 
			
		||||
  width: 64px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										26
									
								
								src/layouts/normal/sidebar/components/SideLogo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/layouts/normal/sidebar/components/SideLogo.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:55
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <router-link class="h-60 f-c-c" to="/">
 | 
			
		||||
    <img src="@/assets/images/logo.png" class="h-40" />
 | 
			
		||||
    <h2
 | 
			
		||||
      v-show="!appStore.collapsed"
 | 
			
		||||
      class="ml-10 max-w-140 flex-shrink-0 text-16 font-bold color-primary"
 | 
			
		||||
    >
 | 
			
		||||
      {{ title }}
 | 
			
		||||
    </h2>
 | 
			
		||||
  </router-link>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
const title = import.meta.env.VITE_TITLE
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										62
									
								
								src/layouts/normal/sidebar/components/SideMenu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/layouts/normal/sidebar/components/SideMenu.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:24:02
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <n-menu
 | 
			
		||||
    ref="menu"
 | 
			
		||||
    class="side-menu"
 | 
			
		||||
    accordion
 | 
			
		||||
    :indent="18"
 | 
			
		||||
    :collapsed-icon-size="22"
 | 
			
		||||
    :collapsed-width="64"
 | 
			
		||||
    :collapsed="appStore.collapsed"
 | 
			
		||||
    :options="permissionStore.menus"
 | 
			
		||||
    :value="activeKey"
 | 
			
		||||
    @update:value="handleMenuSelect"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useAppStore, usePermissionStore } from '@/store'
 | 
			
		||||
import { isExternal } from '@/utils'
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
 | 
			
		||||
const activeKey = computed(() => route.meta?.parentKey || route.name)
 | 
			
		||||
 | 
			
		||||
const menu = ref(null)
 | 
			
		||||
watch(route, async () => {
 | 
			
		||||
  await nextTick()
 | 
			
		||||
  menu.value?.showOption()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function handleMenuSelect(key, item) {
 | 
			
		||||
  if (isExternal(item.path)) {
 | 
			
		||||
    window.open(item.path)
 | 
			
		||||
  } else {
 | 
			
		||||
    router.push(item.path)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.side-menu:not(.n-menu--collapsed) {
 | 
			
		||||
  .n-menu-item-content {
 | 
			
		||||
    &::before {
 | 
			
		||||
      left: 8px;
 | 
			
		||||
      right: 8px;
 | 
			
		||||
    }
 | 
			
		||||
    &.n-menu-item-content--selected::before {
 | 
			
		||||
      border-left: 4px solid var(--primary-color);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										17
									
								
								src/layouts/normal/sidebar/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/layouts/normal/sidebar/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:24:09
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <SideLogo border-b="1px solid light_border dark:dark_border" />
 | 
			
		||||
  <SideMenu class="cus-scroll-y mt-4 h-0 flex-1" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import SideLogo from './components/SideLogo.vue'
 | 
			
		||||
import SideMenu from './components/SideMenu.vue'
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										83
									
								
								src/layouts/simple/header/components/UserAvatar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/layouts/simple/header/components/UserAvatar.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:46
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <n-dropdown :options="options" @select="handleSelect">
 | 
			
		||||
    <div class="flex cursor-pointer items-center">
 | 
			
		||||
      <n-avatar round :size="36" :src="userStore.avatar" class="mr-12" />
 | 
			
		||||
      <div v-if="userStore.userInfo" class="flex-col items-center">
 | 
			
		||||
        <span class="text-14">{{ userStore.nickName ?? userStore.username }}</span>
 | 
			
		||||
        <span class="text-12 opacity-50">[{{ userStore.currentRole?.name }}]</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </n-dropdown>
 | 
			
		||||
 | 
			
		||||
  <RoleSelect ref="roleSelectRef" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useUserStore, useAuthStore, usePermissionStore } from '@/store'
 | 
			
		||||
import { RoleSelect } from '@/layouts/components'
 | 
			
		||||
import { initUserAndPermissions } from '@/router'
 | 
			
		||||
import api from '@/api'
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const userStore = useUserStore()
 | 
			
		||||
const authStore = useAuthStore()
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
 | 
			
		||||
const options = reactive([
 | 
			
		||||
  {
 | 
			
		||||
    label: '个人资料',
 | 
			
		||||
    key: 'profile',
 | 
			
		||||
    icon: () => h('i', { class: 'i-material-symbols:person-outline text-14' }),
 | 
			
		||||
    show: computed(() => permissionStore.accessRoutes?.some((item) => item.path === '/profile')),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '切换角色',
 | 
			
		||||
    key: 'toggleRole',
 | 
			
		||||
    icon: () => h('i', { class: 'i-basil:exchange-solid text-14' }),
 | 
			
		||||
    show: computed(() => userStore.roles.length > 1),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '退出登录',
 | 
			
		||||
    key: 'logout',
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:exit-to-app text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
const roleSelectRef = ref(null)
 | 
			
		||||
function handleSelect(key) {
 | 
			
		||||
  switch (key) {
 | 
			
		||||
    case 'profile':
 | 
			
		||||
      router.push('/profile')
 | 
			
		||||
      break
 | 
			
		||||
    case 'toggleRole':
 | 
			
		||||
      roleSelectRef.value?.open({
 | 
			
		||||
        onOk() {
 | 
			
		||||
          initUserAndPermissions().then(() => {
 | 
			
		||||
            router.replace('/')
 | 
			
		||||
          })
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      break
 | 
			
		||||
    case 'logout':
 | 
			
		||||
      $dialog.confirm({
 | 
			
		||||
        title: '提示',
 | 
			
		||||
        type: 'info',
 | 
			
		||||
        content: '确认退出?',
 | 
			
		||||
        async confirm() {
 | 
			
		||||
          await api.logout()
 | 
			
		||||
          authStore.logout()
 | 
			
		||||
          $message.success('已退出登录')
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      break
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										2
									
								
								src/layouts/simple/header/components/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/layouts/simple/header/components/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
export { default as UserAvatar } from './UserAvatar.vue'
 | 
			
		||||
export { default as AppTab } from './tab/index.vue'
 | 
			
		||||
							
								
								
									
										125
									
								
								src/layouts/simple/header/components/tab/ContextMenu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/layouts/simple/header/components/tab/ContextMenu.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:32
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <n-dropdown
 | 
			
		||||
    :show="show"
 | 
			
		||||
    :options="options"
 | 
			
		||||
    :x="x"
 | 
			
		||||
    :y="y"
 | 
			
		||||
    placement="bottom-start"
 | 
			
		||||
    @clickoutside="handleHideDropdown"
 | 
			
		||||
    @select="handleSelect"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useTabStore } from '@/store'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  currentPath: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  x: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 0,
 | 
			
		||||
  },
 | 
			
		||||
  y: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 0,
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:show'])
 | 
			
		||||
 | 
			
		||||
const tabStore = useTabStore()
 | 
			
		||||
 | 
			
		||||
const options = computed(() => [
 | 
			
		||||
  {
 | 
			
		||||
    label: '重新加载',
 | 
			
		||||
    key: 'reload',
 | 
			
		||||
    disabled: props.currentPath !== tabStore.activeTab,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:refresh text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭',
 | 
			
		||||
    key: 'close',
 | 
			
		||||
    disabled: tabStore.tabs.length <= 1,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:close text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭其他',
 | 
			
		||||
    key: 'close-other',
 | 
			
		||||
    disabled: tabStore.tabs.length <= 1,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:arrow-expand-horizontal text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭左侧',
 | 
			
		||||
    key: 'close-left',
 | 
			
		||||
    disabled: tabStore.tabs.length <= 1 || props.currentPath === tabStore.tabs[0].path,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:arrow-expand-left text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '关闭右侧',
 | 
			
		||||
    key: 'close-right',
 | 
			
		||||
    disabled:
 | 
			
		||||
      tabStore.tabs.length <= 1 ||
 | 
			
		||||
      props.currentPath === tabStore.tabs[tabStore.tabs.length - 1].path,
 | 
			
		||||
    icon: () => h('i', { class: 'i-mdi:arrow-expand-right text-14' }),
 | 
			
		||||
  },
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
const actionMap = new Map([
 | 
			
		||||
  [
 | 
			
		||||
    'reload',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.reloadTab(route.fullPath, route.meta?.keepAlive)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeTab(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close-other',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeOther(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close-left',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeLeft(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    'close-right',
 | 
			
		||||
    () => {
 | 
			
		||||
      tabStore.removeRight(props.currentPath)
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
function handleHideDropdown() {
 | 
			
		||||
  emit('update:show', false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleSelect(key) {
 | 
			
		||||
  const actionFn = actionMap.get(key)
 | 
			
		||||
  actionFn && actionFn()
 | 
			
		||||
  handleHideDropdown()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										101
									
								
								src/layouts/simple/header/components/tab/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/layouts/simple/header/components/tab/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:38
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <n-tabs
 | 
			
		||||
      :value="tabStore.activeTab"
 | 
			
		||||
      :closable="tabStore.tabs.length > 1"
 | 
			
		||||
      :style="`--selected-bg: ${appStore.isDark ? '#1b2429' : '#eaf0f1'}`"
 | 
			
		||||
      type="card"
 | 
			
		||||
      @close="(path) => tabStore.removeTab(path)"
 | 
			
		||||
    >
 | 
			
		||||
      <n-tab
 | 
			
		||||
        v-for="item in tabStore.tabs"
 | 
			
		||||
        :key="item.path"
 | 
			
		||||
        :name="item.path"
 | 
			
		||||
        @click="handleItemClick(item.path)"
 | 
			
		||||
        @contextmenu.prevent="handleContextMenu($event, item)"
 | 
			
		||||
      >
 | 
			
		||||
        {{ item.title }}
 | 
			
		||||
      </n-tab>
 | 
			
		||||
    </n-tabs>
 | 
			
		||||
 | 
			
		||||
    <ContextMenu
 | 
			
		||||
      v-if="contextMenuOption.show"
 | 
			
		||||
      v-model:show="contextMenuOption.show"
 | 
			
		||||
      :current-path="contextMenuOption.currentPath"
 | 
			
		||||
      :x="contextMenuOption.x"
 | 
			
		||||
      :y="contextMenuOption.y"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import ContextMenu from './ContextMenu.vue'
 | 
			
		||||
import { useTabStore, useAppStore } from '@/store'
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const tabStore = useTabStore()
 | 
			
		||||
 | 
			
		||||
const contextMenuOption = reactive({
 | 
			
		||||
  show: false,
 | 
			
		||||
  x: 0,
 | 
			
		||||
  y: 0,
 | 
			
		||||
  currentPath: '',
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const handleItemClick = (path) => {
 | 
			
		||||
  tabStore.setActiveTab(path)
 | 
			
		||||
  router.push(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showContextMenu() {
 | 
			
		||||
  contextMenuOption.show = true
 | 
			
		||||
}
 | 
			
		||||
function hideContextMenu() {
 | 
			
		||||
  contextMenuOption.show = false
 | 
			
		||||
}
 | 
			
		||||
function setContextMenu(x, y, currentPath) {
 | 
			
		||||
  Object.assign(contextMenuOption, { x, y, currentPath })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 右击菜单
 | 
			
		||||
async function handleContextMenu(e, tagItem) {
 | 
			
		||||
  const { clientX, clientY } = e
 | 
			
		||||
  hideContextMenu()
 | 
			
		||||
  setContextMenu(clientX, clientY, tagItem.path)
 | 
			
		||||
  await nextTick()
 | 
			
		||||
  showContextMenu()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
:deep(.n-tabs) {
 | 
			
		||||
  .n-tabs-tab {
 | 
			
		||||
    padding-left: 16px;
 | 
			
		||||
    height: 36px;
 | 
			
		||||
    background: transparent !important;
 | 
			
		||||
    border-radius: 4px !important;
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
    &:hover {
 | 
			
		||||
      border: 1px solid var(--primary-color) !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .n-tabs-tab--active {
 | 
			
		||||
    border: 1px solid var(--primary-color) !important;
 | 
			
		||||
    background-color: var(--selected-bg) !important;
 | 
			
		||||
  }
 | 
			
		||||
  .n-tabs-pad,
 | 
			
		||||
  .n-tabs-tab-pad,
 | 
			
		||||
  .n-tabs-scroll-padding {
 | 
			
		||||
    border: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										64
									
								
								src/layouts/simple/header/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/layouts/simple/header/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:23
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <AppCard class="flex items-center px-12" border-b="1px solid light_border dark:dark_border">
 | 
			
		||||
    <div
 | 
			
		||||
      class="f-c-c cursor-pointer rounded-4 p-6 text-22 transition-all-300 auto-bg-hover"
 | 
			
		||||
      @click="appStore.switchCollapsed"
 | 
			
		||||
    >
 | 
			
		||||
      <i :class="appStore.collapsed ? 'i-line-md-menu-unfold-left' : 'i-line-md-menu-fold-left'" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <AppTab class="w-0 flex-1 px-12" />
 | 
			
		||||
 | 
			
		||||
    <span class="mx-6 opacity-20">|</span>
 | 
			
		||||
 | 
			
		||||
    <div class="flex flex-shrink-0 items-center px-12 text-18">
 | 
			
		||||
      <i
 | 
			
		||||
        class="mr-16 cursor-pointer"
 | 
			
		||||
        :class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
 | 
			
		||||
        @click="toggleDark"
 | 
			
		||||
      />
 | 
			
		||||
      <i
 | 
			
		||||
        class="mr-16 cursor-pointer"
 | 
			
		||||
        :class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
 | 
			
		||||
        @click="toggle"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <i
 | 
			
		||||
        class="i-fe:github mr-16 cursor-pointer"
 | 
			
		||||
        @click="handleLinkClick('https://github.com/zclzone/vue-naive-admin/tree/2.x-beta')"
 | 
			
		||||
      />
 | 
			
		||||
      <i
 | 
			
		||||
        class="i-me:gitee mr-16 cursor-pointer"
 | 
			
		||||
        @click="handleLinkClick('https://gitee.com/isme-admin/vue-naive-admin/tree/2.x-beta')"
 | 
			
		||||
      />
 | 
			
		||||
      <UserAvatar />
 | 
			
		||||
    </div>
 | 
			
		||||
  </AppCard>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { AppTab, UserAvatar } from './components'
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
import { useDark, useToggle, useFullscreen } from '@vueuse/core'
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const isDark = useDark()
 | 
			
		||||
const toggleDark = () => {
 | 
			
		||||
  appStore.toggleDark()
 | 
			
		||||
  useToggle(isDark)()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const { isFullscreen, toggle } = useFullscreen()
 | 
			
		||||
 | 
			
		||||
function handleLinkClick(link) {
 | 
			
		||||
  window.open(link)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										38
									
								
								src/layouts/simple/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/layouts/simple/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/13 20:54:55
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="wh-full flex">
 | 
			
		||||
    <aside
 | 
			
		||||
      class="flex-col flex-shrink-0 transition-width-300"
 | 
			
		||||
      :class="appStore.collapsed ? 'w-64' : 'w-220'"
 | 
			
		||||
      border-r="1px solid light_border dark:dark_border"
 | 
			
		||||
    >
 | 
			
		||||
      <SideBar />
 | 
			
		||||
    </aside>
 | 
			
		||||
 | 
			
		||||
    <article class="w-0 flex-col flex-1">
 | 
			
		||||
      <AppHeader class="h-60 flex-shrink-0" />
 | 
			
		||||
      <slot />
 | 
			
		||||
    </article>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
import SideBar from './sidebar/index.vue'
 | 
			
		||||
import AppHeader from './header/index.vue'
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.collapsed {
 | 
			
		||||
  width: 64px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										26
									
								
								src/layouts/simple/sidebar/components/SideLogo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/layouts/simple/sidebar/components/SideLogo.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:23:55
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <router-link class="h-60 f-c-c" to="/">
 | 
			
		||||
    <img src="@/assets/images/logo.png" class="h-40" />
 | 
			
		||||
    <h2
 | 
			
		||||
      v-show="!appStore.collapsed"
 | 
			
		||||
      class="ml-10 max-w-140 flex-shrink-0 text-16 font-bold color-primary"
 | 
			
		||||
    >
 | 
			
		||||
      {{ title }}
 | 
			
		||||
    </h2>
 | 
			
		||||
  </router-link>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useAppStore } from '@/store'
 | 
			
		||||
const title = import.meta.env.VITE_TITLE
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										62
									
								
								src/layouts/simple/sidebar/components/SideMenu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/layouts/simple/sidebar/components/SideMenu.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:24:02
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <n-menu
 | 
			
		||||
    ref="menu"
 | 
			
		||||
    class="side-menu"
 | 
			
		||||
    accordion
 | 
			
		||||
    :indent="18"
 | 
			
		||||
    :collapsed-icon-size="22"
 | 
			
		||||
    :collapsed-width="64"
 | 
			
		||||
    :collapsed="appStore.collapsed"
 | 
			
		||||
    :options="permissionStore.menus"
 | 
			
		||||
    :value="activeKey"
 | 
			
		||||
    @update:value="handleMenuSelect"
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { useAppStore, usePermissionStore } from '@/store'
 | 
			
		||||
import { isExternal } from '@/utils'
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
const permissionStore = usePermissionStore()
 | 
			
		||||
 | 
			
		||||
const activeKey = computed(() => route.meta?.parentKey || route.name)
 | 
			
		||||
 | 
			
		||||
const menu = ref(null)
 | 
			
		||||
watch(route, async () => {
 | 
			
		||||
  await nextTick()
 | 
			
		||||
  menu.value?.showOption()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function handleMenuSelect(key, item) {
 | 
			
		||||
  if (isExternal(item.path)) {
 | 
			
		||||
    window.open(item.path)
 | 
			
		||||
  } else {
 | 
			
		||||
    router.push(item.path)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.side-menu:not(.n-menu--collapsed) {
 | 
			
		||||
  .n-menu-item-content {
 | 
			
		||||
    &::before {
 | 
			
		||||
      left: 8px;
 | 
			
		||||
      right: 8px;
 | 
			
		||||
    }
 | 
			
		||||
    &.n-menu-item-content--selected::before {
 | 
			
		||||
      border-left: 4px solid var(--primary-color);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										17
									
								
								src/layouts/simple/sidebar/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/layouts/simple/sidebar/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
<!--------------------------------
 | 
			
		||||
 - @Author: Ronnie Zhang
 | 
			
		||||
 - @LastEditor: Ronnie Zhang
 | 
			
		||||
 - @LastEditTime: 2023/12/05 21:24:09
 | 
			
		||||
 - @Email: zclzone@outlook.com
 | 
			
		||||
 - Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 --------------------------------->
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import SideLogo from './components/SideLogo.vue'
 | 
			
		||||
import SideMenu from './components/SideMenu.vue'
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <SideLogo border-b="1px solid light_border dark:dark_border" />
 | 
			
		||||
  <SideMenu class="cus-scroll-y mt-4 h-0 flex-1" />
 | 
			
		||||
</template>
 | 
			
		||||
@ -1,21 +1,14 @@
 | 
			
		||||
/**********************************
 | 
			
		||||
 * @Author: Ronnie Zhang
 | 
			
		||||
 * @LastEditor: Ronnie Zhang
 | 
			
		||||
 * @LastEditTime: 2023/12/05 21:30:24
 | 
			
		||||
 * @LastEditTime: 2023/12/13 20:54:36
 | 
			
		||||
 * @Email: zclzone@outlook.com
 | 
			
		||||
 * Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
 | 
			
		||||
 **********************************/
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  defaultLayout: 'default',
 | 
			
		||||
  header: {
 | 
			
		||||
    height: 60,
 | 
			
		||||
  },
 | 
			
		||||
  tab: {
 | 
			
		||||
    visible: true,
 | 
			
		||||
    height: 50,
 | 
			
		||||
  },
 | 
			
		||||
  naiveThemeOverrides: {
 | 
			
		||||
export const defaultLayout = 'normal'
 | 
			
		||||
 | 
			
		||||
export const naiveThemeOverrides = {
 | 
			
		||||
  common: {
 | 
			
		||||
    primaryColor: '#316C72FF',
 | 
			
		||||
    primaryColorHover: '#316C72E3',
 | 
			
		||||
@ -42,7 +35,6 @@ export default {
 | 
			
		||||
    errorColorPressed: '#AB1F3FFF',
 | 
			
		||||
    errorColorSuppl: '#DE576DFF',
 | 
			
		||||
  },
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const basePermissions = [
 | 
			
		||||
 | 
			
		||||
@ -8,13 +8,14 @@
 | 
			
		||||
 | 
			
		||||
import { defineStore } from 'pinia'
 | 
			
		||||
import { useDark } from '@vueuse/core'
 | 
			
		||||
import settings from '@/settings'
 | 
			
		||||
import { defaultLayout, naiveThemeOverrides } from '@/settings'
 | 
			
		||||
 | 
			
		||||
export const useAppStore = defineStore('app', {
 | 
			
		||||
  state: () => ({
 | 
			
		||||
    collapsed: false,
 | 
			
		||||
    isDark: useDark(),
 | 
			
		||||
    layout: settings.defaultLayout,
 | 
			
		||||
    layout: defaultLayout,
 | 
			
		||||
    naiveThemeOverrides,
 | 
			
		||||
  }),
 | 
			
		||||
  actions: {
 | 
			
		||||
    switchCollapsed() {
 | 
			
		||||
@ -26,12 +27,12 @@ export const useAppStore = defineStore('app', {
 | 
			
		||||
    toggleDark() {
 | 
			
		||||
      this.isDark = !this.isDark
 | 
			
		||||
    },
 | 
			
		||||
    setDeaultLayout(v) {
 | 
			
		||||
    setLayout(v) {
 | 
			
		||||
      this.layout = v
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  persist: {
 | 
			
		||||
    paths: ['layout', 'collapsed'],
 | 
			
		||||
    paths: ['layout', 'collapsed', 'naiveThemeOverrides'],
 | 
			
		||||
    storage: localStorage,
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -64,7 +64,7 @@ export const usePermissionStore = defineStore('permission', {
 | 
			
		||||
        meta: {
 | 
			
		||||
          icon: item.icon,
 | 
			
		||||
          title: item.name,
 | 
			
		||||
          layout: item.layout || 'default',
 | 
			
		||||
          layout: item.layout,
 | 
			
		||||
          keepAlive: !!item.keepAlive,
 | 
			
		||||
          parentKey,
 | 
			
		||||
          btns: item.children
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,6 @@
 | 
			
		||||
 | 
			
		||||
import * as NaiveUI from 'naive-ui'
 | 
			
		||||
import { isNullOrUndef } from '@/utils'
 | 
			
		||||
import settings from '@/settings'
 | 
			
		||||
import { useAppStore } from '@/store/modules/app'
 | 
			
		||||
 | 
			
		||||
export function setupMessage(NMessage) {
 | 
			
		||||
@ -104,10 +103,9 @@ export function setupDialog(NDialog) {
 | 
			
		||||
 | 
			
		||||
export function setupNaiveDiscreteApi() {
 | 
			
		||||
  const appStore = useAppStore()
 | 
			
		||||
  const { naiveThemeOverrides: themeOverrides } = settings
 | 
			
		||||
  const configProviderProps = computed(() => ({
 | 
			
		||||
    theme: appStore.isDark ? NaiveUI.darkTheme : undefined,
 | 
			
		||||
    themeOverrides,
 | 
			
		||||
    themeOverrides: useAppStore().naiveThemeOverrides,
 | 
			
		||||
  }))
 | 
			
		||||
  const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
 | 
			
		||||
    ['message', 'dialog', 'notification', 'loadingBar'],
 | 
			
		||||
 | 
			
		||||
@ -155,7 +155,10 @@ const iconOptions = icons.map((item) => ({
 | 
			
		||||
  value: item,
 | 
			
		||||
}))
 | 
			
		||||
const layoutOptions = [
 | 
			
		||||
  { label: '默认-default', value: 'default' },
 | 
			
		||||
  { label: '跟随系统', value: '' },
 | 
			
		||||
  { label: '简约-simple', value: 'simple' },
 | 
			
		||||
  { label: '通用-normal', value: 'normal' },
 | 
			
		||||
  { label: '全面-full', value: 'full' },
 | 
			
		||||
  { label: '空白-empty', value: 'empty' },
 | 
			
		||||
]
 | 
			
		||||
const required = {
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,7 @@
 | 
			
		||||
              <span v-else>无</span>
 | 
			
		||||
            </n-descriptions-item>
 | 
			
		||||
            <n-descriptions-item label="layout">
 | 
			
		||||
              {{ currentMenu.layout ?? 'default' }}
 | 
			
		||||
              {{ currentMenu.layout || '跟随系统' }}
 | 
			
		||||
            </n-descriptions-item>
 | 
			
		||||
            <n-descriptions-item label="是否显示">
 | 
			
		||||
              {{ currentMenu.show ? '是' : '否' }}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user