first commit
This commit is contained in:
33
resources/admin/layout/components/header/index.vue
Normal file
33
resources/admin/layout/components/header/index.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="flex flex-row h-16 w-full drop-shadow border-l dark:border-l-0 border-gray-200" style="background-color: var(--header-bg-color)">
|
||||
<div class="flex flex-row justify-between w-full">
|
||||
<div class="flex flex-row min-w-[17rem]">
|
||||
<div class="h-full flex items-center w-8 ml-2 hover:cursor-pointer" @click="store.changeExpaned">
|
||||
<Icon name="list-bullet" class="w-6 h-8" />
|
||||
</div>
|
||||
<div class="w-96 flex items-center pl-3 sm:pl-0">
|
||||
<Breadcrumbs />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-52 sm:min-w-[18rem] flex-row item-center pl-1 sm:pl-0 justify-end sm:justify-between mr-4">
|
||||
<div class="w-3/5 hidden sm:flex">
|
||||
<!-- 搜索 -->
|
||||
<Search />
|
||||
<!-- 多语言 -->
|
||||
<Lang />
|
||||
<!-- 暗黑主题 -->
|
||||
<Theme />
|
||||
<Notification />
|
||||
</div>
|
||||
<Profile />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import Notification from './notification.vue'
|
||||
|
||||
const store = useAppStore()
|
||||
</script>
|
38
resources/admin/layout/components/header/lang.vue
Normal file
38
resources/admin/layout/components/header/lang.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer">
|
||||
<div class="flex hover:cursor-pointer pl-1 pr-1">
|
||||
<el-dropdown size="large" class="flex items-center justify-center hover:cursor-pointer w-full" @command="selectLanguage">
|
||||
<Icon name="language" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="lang in langs" :key="lang.value" :command="lang.value" :disabled="lang.value == defaultLang">
|
||||
{{ $t('system.' + lang.label) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
|
||||
const langs = reactive([
|
||||
{ label: 'chinese', value: 'zh' },
|
||||
{ label: 'english', value: 'en' },
|
||||
])
|
||||
|
||||
const appStore = useAppStore()
|
||||
// select default languages
|
||||
const defaultLang = computed(() => {
|
||||
return appStore.getLocale
|
||||
})
|
||||
|
||||
// select language
|
||||
const selectLanguage = (value: 'zh' | 'en') => {
|
||||
appStore.changeLocale(value)
|
||||
location.reload()
|
||||
}
|
||||
</script>
|
22
resources/admin/layout/components/header/logo.vue
Normal file
22
resources/admin/layout/components/header/logo.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="h-16 flex items-center justify-center logo-bg">
|
||||
<img :src="logo" class="h-9 w-9" />
|
||||
<div class="text-md logo-text pl-3" v-if="store.isExpand">CatchAdmin</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import logo from '/admin/assets/logo.png'
|
||||
|
||||
const store = useAppStore()
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.logo-bg {
|
||||
background-color: var(--header-logo-bg-color);
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
color: var(--header-logo-text-color);
|
||||
}
|
||||
</style>
|
49
resources/admin/layout/components/header/notification.vue
Normal file
49
resources/admin/layout/components/header/notification.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<!-- 通知 -->
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer" ref="messageRef" v-click-outside="onClickOutside">
|
||||
<el-badge :value="3">
|
||||
<Icon name="bell" />
|
||||
</el-badge>
|
||||
<el-popover ref="popoverRef" :virtual-ref="messageRef" trigger="hover" virtual-triggering :width="300">
|
||||
<el-tabs model-value="message">
|
||||
<el-tab-pane label="消息(8)" name="message">
|
||||
<div>
|
||||
<div class="flex flex-row w-full border-b border-b-slate-300 dark:border-b-slate-500 mt-2" v-for="(message, key) in messages" :key="key">
|
||||
<div>
|
||||
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" class="w-2 h-2" />
|
||||
</div>
|
||||
<div class="ml-2 h-10 mt-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="通知(1)" name="notice">
|
||||
<div>
|
||||
<div class="flex flex-row w-full border-b border-b-slate-300 dark:border-b-slate-500 mt-2" v-for="(message, key) in messages" :key="key">
|
||||
<div>
|
||||
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" class="w-2 h-2" />
|
||||
</div>
|
||||
<div class="ml-2 h-10 mt-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, unref } from 'vue'
|
||||
import { ClickOutside as vClickOutside } from 'element-plus'
|
||||
const messageRef = ref()
|
||||
const popoverRef = ref()
|
||||
const onClickOutside = () => {
|
||||
unref(popoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
|
||||
const messages = ref()
|
||||
|
||||
messages.value = ['你收到 catchadmin 的好友申请', '你收到 catchadmin pro 的 license 授权', '你收到 catchadmin 通知']
|
||||
</script>
|
34
resources/admin/layout/components/header/profile.vue
Normal file
34
resources/admin/layout/components/header/profile.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="flex w-2/5 hover:cursor-pointer pl-1 pr-1">
|
||||
<el-dropdown size="large" placement="bottom-end" class="flex items-center justify-center hover:cursor-pointer w-full">
|
||||
<div class="flex lg:items-center">
|
||||
<img :src="userStore.getAvatar" class="w-7 h-7 rounded-full" />
|
||||
<div class="ml-2 hidden lg:block">{{ userStore.getNickname }}</div>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>Action 1</el-dropdown-item>
|
||||
<el-dropdown-item>Action 2</el-dropdown-item>
|
||||
<el-dropdown-item>Action 3</el-dropdown-item>
|
||||
<el-dropdown-item divided @click="logout">
|
||||
<Icon name="logout" class="mr-1" />
|
||||
退 出
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '/admin/stores/modules/user'
|
||||
import Message from '/admin/support/message'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const logout = () => {
|
||||
Message.confirm('确定退出系统吗?', () => {
|
||||
userStore.logout()
|
||||
})
|
||||
}
|
||||
</script>
|
58
resources/admin/layout/components/header/search.vue
Normal file
58
resources/admin/layout/components/header/search.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer">
|
||||
<div class="flex flex-row w-96">
|
||||
<Icon name="magnifying-glass" class="hidden sm:block" @click="serachMenuVisable = true" />
|
||||
|
||||
<Teleport to="body">
|
||||
<el-dialog v-model="serachMenuVisable" width="30%" draggable>
|
||||
<el-cascader :filterable="true" :options="options" @change="toWhere" placeholder="请输入菜单名称" clearable class="w-full" :show-all-levels="false" />
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePermissionsStore } from '/admin/stores/modules/user/permissions'
|
||||
import { Menu } from '/admin/types/Menu'
|
||||
import router from '/admin/router'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const serachMenuVisable = ref(false)
|
||||
|
||||
const permissionStore = usePermissionsStore()
|
||||
const options = computed(() => {
|
||||
return filterMenus(permissionStore.getMenus)
|
||||
})
|
||||
const toWhere = (value: string[]) => {
|
||||
if (value.length) {
|
||||
router.push({ path: value[value.length - 1] })
|
||||
}
|
||||
|
||||
serachMenuVisable.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* filter menus
|
||||
*
|
||||
* @param menus
|
||||
*/
|
||||
function filterMenus(menus: Menu[] | undefined): Object[] {
|
||||
const cascaderMenus: Object[] = []
|
||||
|
||||
menus?.forEach(menu => {
|
||||
if (menu.meta === undefined) {
|
||||
const child = menu.children?.pop()
|
||||
cascaderMenus.push(Object.assign({ label: child?.meta?.title, value: child?.path }))
|
||||
} else {
|
||||
const cascaderMenu = Object.assign({ label: menu.meta?.title, value: menu.path, children: [] })
|
||||
|
||||
cascaderMenu.children = filterMenus(menu.children)
|
||||
|
||||
cascaderMenus.push(cascaderMenu)
|
||||
}
|
||||
})
|
||||
|
||||
return cascaderMenus
|
||||
}
|
||||
</script>
|
22
resources/admin/layout/components/header/theme.vue
Normal file
22
resources/admin/layout/components/header/theme.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="w-10 h-10 grid place-items-center rounded-full mt-3 hover:cursor-pointer">
|
||||
<Icon name="moon" @click="changeTheme()" v-if="isDark" />
|
||||
<Icon name="sun" @click="changeTheme()" v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
import { useAppStore } from '/admin/stores/modules/app'
|
||||
import { unref } from 'vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = useDark()
|
||||
|
||||
const toggleDark = useToggle(isDark)
|
||||
|
||||
function changeTheme() {
|
||||
appStore.setDarkMode(!unref(isDark))
|
||||
toggleDark()
|
||||
}
|
||||
</script>
|
Reference in New Issue
Block a user