feat: add theme and search

This commit is contained in:
chansee97 2024-03-26 22:30:24 +08:00
parent 1733842331
commit 6af26f2eab
15 changed files with 207 additions and 142 deletions

View File

@ -1,20 +1,20 @@
<script setup lang="ts">
import type { GlobalThemeOverrides } from 'naive-ui'
import { darkTheme, dateZhCN, zhCN } from 'naive-ui'
import { useAppStore } from './store'
import themeConfig from './theme.json'
const locale = zhCN
const dateLocale = dateZhCN
const appStore = useAppStore()
const themeOverrides: GlobalThemeOverrides = themeConfig || {}
</script>
<template>
<n-config-provider
class="wh-full" :theme="appStore.darkMode ? darkTheme : null" :locale="locale" :date-locale="dateLocale"
:theme-overrides="themeOverrides"
class="wh-full"
inline-theme-disabled
:theme="appStore.darkMode ? darkTheme : null"
:locale="locale"
:date-locale="dateLocale"
:theme-overrides="appStore.theme"
>
<naive-provider><router-view /></naive-provider>
</n-config-provider>

View File

@ -5,7 +5,6 @@ import {
CollapaseButton,
DarkMode,
FullScreen,
Github,
Logo,
Menu,
Notices,
@ -51,13 +50,12 @@ const appStore = useAppStore()
<div class="h-60px flex-y-center justify-between">
<div class="flex-y-center h-full">
<CollapaseButton />
<Breadcrumb v-if="appStore.showBreadcrumb" />
<Breadcrumb />
</div>
<div class="flex-y-center h-full">
<Reload />
<Search />
<Reload />
<Notices />
<Github />
<FullScreen />
<DarkMode />
<Setting />
@ -87,7 +85,7 @@ const appStore = useAppStore()
>
<router-view v-slot="{ Component, route }">
<transition
name="fade-slide"
:name="appStore.transitionAnimation"
mode="out-in"
>
<keep-alive :include="routeStore.cacheRoutes">

View File

@ -1,13 +1,16 @@
<script setup lang="ts">
import { useAppStore } from '@/store'
const router = useRouter()
const route = useRoute()
const routes = computed(() => {
return route.matched
})
const appStore = useAppStore()
</script>
<template>
<TransitionGroup name="list" tag="ul" style="display: flex; gap:1em;">
<TransitionGroup v-if="appStore.showBreadcrumb" name="list" tag="ul" style="display: flex; gap:1em;">
<n-el
v-for="(item) in routes"
:key="item.path"
@ -18,7 +21,7 @@ const routes = computed(() => {
class="flex-center gap-2 cursor-pointer split"
@click="router.push(item.path)"
>
<e-icon :icon="item.meta.icon" />
<e-icon v-if="appStore.showBreadcrumbIcon" :icon="item.meta.icon" />
{{ item.meta.title }}
</n-el>
</TransitionGroup>

View File

@ -1,20 +0,0 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
function toMyGithub() {
window.open('https://github.com/chansee97/nova-admin')
}
</script>
<template>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<HeaderButton @click="toMyGithub">
<i-icon-park-outline-github />
</HeaderButton>
</template>
<span>Github</span>
</n-tooltip>
</template>
<style scoped></style>

View File

@ -1,23 +1,58 @@
<script setup lang="ts">
import HeaderButton from '../common/HeaderButton.vue'
import type { SelectOption } from 'naive-ui'
import { NFlex, NTag, NText } from 'naive-ui'
import { useRouteStore } from '@/store'
import { renderIcon } from '@/utils'
function handleSearch() {
window.$message.success('施工中...')
const routeStore = useRouteStore()
const searchValue = ref('')
const options = computed(() => {
return routeStore.rowRoutes.filter((item) => {
const conditions = [
item['meta.title']?.includes(searchValue.value),
item.path?.includes(searchValue.value),
]
return conditions.some(condition => condition)
}).map((item) => {
return {
label: item['meta.title'],
value: item.path,
icon: item['meta.icon'],
}
})
})
function renderLabel(option: any) {
return h(NFlex, {}, { default: () => [
h(NTag, { size: 'small', type: 'primary', bordered: false }, { icon: renderIcon(option.icon), default: () => option.label }),
h(NText, { depth: 3 }, { default: () => option.value }),
] })
}
const router = useRouter()
function handleSelect(value: string) {
router.push(value)
nextTick(() => {
searchValue.value = ''
})
}
</script>
<template>
<n-tooltip
placement="bottom"
trigger="hover"
>
<template #trigger>
<HeaderButton @click="handleSearch">
<i-icon-park-outline-search />
</HeaderButton>
</template>
<span>搜索</span>
</n-tooltip>
<n-auto-complete
v-model:value="searchValue"
class="w-20em m-r-1em"
:input-props="{
autocomplete: 'disabled',
}"
:options="options"
:render-label="renderLabel"
placeholder="搜索页面"
clearable
@select="handleSelect"
/>
</template>
<style scoped></style>

View File

@ -8,98 +8,116 @@ const drawerActive = ref(false)
function openSetting() {
drawerActive.value = !drawerActive.value
}
const transitionSelectorOptions = [
{
label: '无',
value: '',
},
{
label: '侧滑',
value: 'fade-slide',
},
{
label: '下滑',
value: 'fade-bottom',
},
{
label: '收缩',
value: 'fade-scale',
},
{
label: '扩张',
value: 'zoom-fade',
},
{
label: '坍缩',
value: 'zoom-out',
},
{
label: '柔和',
value: 'fade',
},
]
</script>
<template>
<n-tooltip
placement="bottom"
trigger="hover"
>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<HeaderButton @click="openSetting">
<div>
<i-icon-park-outline-setting-two />
<n-drawer
v-model:show="drawerActive"
:width="300"
>
<n-drawer-content title="系统设置">
<n-drawer v-model:show="drawerActive" :width="300">
<n-drawer-content title="系统设置" closable>
<n-space vertical size="large">
<n-divider>主题设置</n-divider>
<n-space vertical>
<n-space justify="space-between">
深色模式
<n-switch
:value="appStore.darkMode"
@update:value="appStore.toggleDarkMode"
/>
<n-switch :value="appStore.darkMode" @update:value="appStore.toggleDarkMode" />
</n-space>
<n-space justify="space-between">
色弱模式
<n-switch
:value="appStore.colorWeak"
@update:value="appStore.toggleColorWeak()"
/>
<n-switch :value="appStore.colorWeak" @update:value="appStore.toggleColorWeak()" />
</n-space>
<n-space justify="space-between">
灰色模式
<n-switch
:value="appStore.grayMode"
@update:value="appStore.toggleGrayMode()"
<n-switch :value="appStore.grayMode" @update:value="appStore.toggleGrayMode()" />
</n-space>
<n-space align="center" justify="space-between">
主题色
<n-color-picker
v-model:value="appStore.theme.common.primaryColor" class="w-7em" :swatches="[
'#18A058',
'#2080F0',
'#F0A020',
'#d03050',
]" :show-alpha="false"
/>
</n-space>
</n-space>
<n-divider>界面显示</n-divider>
<n-space vertical>
<n-space justify="space-between">
LOGO显示
<n-switch
:value="appStore.showLogo"
@update:value="appStore.toggleShowLogo()"
/>
</n-space>
<n-space justify="space-between">
多页签
<n-switch
:value="appStore.showTabs"
@update:value="appStore.toggleShowTabs()"
/>
</n-space>
<n-space justify="space-between">
面包屑
<n-switch
:value="appStore.showBreadcrumb"
@update:value="appStore.toggleShowBreadcrumb()"
/>
</n-space>
<n-space justify="space-between">
固定头部和多页签
<n-switch
:value="appStore.fixedHeader"
@update:value="appStore.toggleFixedHeader()"
/>
<n-space align="center" justify="space-between">
切换动效
<n-select v-model:value="appStore.transitionAnimation" class="w-7em" :options="transitionSelectorOptions" @update:value="appStore.reloadPage" />
</n-space>
<n-space justify="space-between">
侧边栏反转色
<n-switch
:value="appStore.invertedSider"
@update:value="appStore.toggleInvertedSider()"
/>
<n-switch :value="appStore.invertedSider" @update:value="appStore.toggleInvertedSider()" />
</n-space>
<n-space justify="space-between">
头部反转色
<n-switch
:value="appStore.invertedHeader"
@update:value="appStore.toggleInvertedHeader()"
/>
<n-switch :value="appStore.invertedHeader" @update:value="appStore.toggleInvertedHeader()" />
</n-space>
<n-divider>界面显示</n-divider>
<n-space justify="space-between">
LOGO显示
<n-switch :value="appStore.showLogo" @update:value="appStore.toggleShowLogo()" />
</n-space>
<n-space justify="space-between">
多页签
<n-switch :value="appStore.showTabs" @update:value="appStore.toggleShowTabs()" />
</n-space>
<n-space justify="space-between">
面包屑
<n-switch :value="appStore.showBreadcrumb" @update:value="appStore.toggleShowBreadcrumb()" />
</n-space>
<n-space justify="space-between">
面包屑图标
<n-switch :value="appStore.showBreadcrumbIcon" @update:value="appStore.toggleShowBreadcrumbIcon()" />
</n-space>
<n-space justify="space-between">
固定头部和多页签
<n-switch :value="appStore.fixedHeader" @update:value="appStore.toggleFixedHeader()" />
</n-space>
<n-space justify="space-between">
水印
<n-switch
:value="appStore.showWatermark"
@update:value="appStore.toggleShowWatermark()"
/>
<n-switch :value="appStore.showWatermark" @update:value="appStore.toggleShowWatermark()" />
</n-space>
</n-space>
<template #footer>
<n-button type="error" @click="appStore.resetAlltheme">
重置设置
</n-button>
</template>
</n-drawer-content>
</n-drawer>
</div>

View File

@ -16,6 +16,20 @@ const options = [
type: 'divider',
key: 'd1',
},
{
label: 'Github',
key: 'guthub',
icon: renderIcon('icon-park-outline:github'),
},
{
label: 'gitee',
key: 'gitee',
icon: renderIcon('simple-icons:gitee'),
},
{
type: 'divider',
key: 'd1',
},
{
label: '退出登录',
key: 'loginOut',
@ -36,6 +50,12 @@ function handleSelect(key: string | number) {
}
if (key === 'userCenter')
router.push('/userCenter')
if (key === 'guthub')
window.open('https://github.com/chansee97/nova-admin')
if (key === 'gitee')
window.open('https://gitee.com/chansee97/nova-admin')
}
</script>

View File

@ -8,7 +8,6 @@ import CollapaseButton from './header/CollapaseButton.vue'
import FullScreen from './header/FullScreen.vue'
import DarkMode from './header/DarkMode.vue'
import Setting from './header/Setting.vue'
import Github from './header/Github.vue'
import Notices from './header/Notices.vue'
import UserCenter from './header/UserCenter.vue'
import Search from './header/Search.vue'
@ -30,7 +29,6 @@ export {
FullScreen,
DarkMode,
Setting,
Github,
Notices,
UserCenter,
Search,

View File

@ -1,6 +1,5 @@
<script setup lang="ts">
import type { RouteLocationNormalized } from 'vue-router'
import { NIcon } from 'naive-ui'
import { renderIcon } from '@/utils'
import { useAppStore, useTabStore } from '@/store'

View File

@ -1,5 +1,10 @@
import type { GlobalThemeOverrides } from 'naive-ui'
import themeConfig from './theme.json'
type TransitionAnimation = '' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out'
interface AppStatus {
readonly footerText: string
theme: GlobalThemeOverrides
collapsed: boolean
fullScreen: boolean
darkMode: boolean
@ -9,10 +14,12 @@ interface AppStatus {
showLogo: boolean
showTabs: boolean
showBreadcrumb: boolean
showBreadcrumbIcon: boolean
fixedHeader: boolean
invertedSider: boolean
invertedHeader: boolean
showWatermark: boolean
transitionAnimation: TransitionAnimation
}
const docEle = document.documentElement
@ -27,6 +34,7 @@ export const useAppStore = defineStore('app-store', {
state: (): AppStatus => {
return {
footerText: 'Copyright ©2023 Nova Admin',
theme: themeConfig,
collapsed: false,
fullScreen: false,
darkMode: isDark.value,
@ -36,13 +44,18 @@ export const useAppStore = defineStore('app-store', {
showLogo: true,
showTabs: true,
showBreadcrumb: true,
showBreadcrumbIcon: true,
fixedHeader: false,
invertedSider: false,
invertedHeader: false,
showWatermark: false,
transitionAnimation: 'fade-slide',
}
},
actions: {
resetAlltheme() {
this.$reset()
},
/* 切换侧边栏收缩 */
toggleCollapse() {
this.collapsed = !this.collapsed
@ -140,6 +153,10 @@ export const useAppStore = defineStore('app-store', {
toggleShowBreadcrumb() {
this.showBreadcrumb = !this.showBreadcrumb
},
/* 切换显示多页签 */
toggleShowBreadcrumbIcon() {
this.showBreadcrumbIcon = !this.showBreadcrumbIcon
},
/* 切换固定头部和标签页 */
toggleFixedHeader() {
this.fixedHeader = !this.fixedHeader
@ -157,4 +174,7 @@ export const useAppStore = defineStore('app-store', {
this.showWatermark = !this.showWatermark
},
},
persist: {
enabled: true,
},
})

5
src/store/app/theme.json Normal file
View File

@ -0,0 +1,5 @@
{
"common": {
"primaryColor": "#165DFF"
}
}

View File

@ -1,7 +1,7 @@
import type { App } from 'vue'
import piniaPluginPersist from 'pinia-plugin-persist'
export * from './app'
export * from './app/index'
export * from './auth'
export * from './route'
export * from './tab'

View File

@ -74,3 +74,19 @@
opacity: 0;
transform: scale(0);
}
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s, filter 0.2s ease-out;
}
.fade-enter-from {
opacity: 0;
filter: blur(10px);
}
.fade-leave-to {
opacity: 0;
filter: blur(0px);
}

View File

@ -1,24 +0,0 @@
{
"common": {
"primaryColor": "#165DFFFF",
"primaryColorHover": "#4080FFFF",
"primaryColorPressed": "#0E42D2FF",
"primaryColorSuppl": "#4080FFFF",
"successColor": "#00B42AFF",
"successColorHover": "#23C343FF",
"successColorPressed": "#009A29FF",
"successColorSuppl": "#23C343FF",
"warningColor": "#FF7D00FF",
"warningColorHover": "#FF9A2EFF",
"warningColorSuppl": "#FF9A2EFF",
"warningColorPressed": "#D25F00FF",
"errorColor": "#F53F3FFF",
"errorColorHover": "#F76560FF",
"errorColorSuppl": "#F76560FF",
"errorColorPressed": "#CB272DFF",
"textColorBase": "#1D2129FF",
"textColor1": "#4E5969FF",
"textColor2": "#4E5969FF",
"textColor3": "#86909CFF"
}
}

View File

@ -5,10 +5,7 @@ import { createViteProxy, proxyConfig } from './build/proxy'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
// 在开发环境下 command 的值为 serve 生产环境下为 build
// 根据当前工作目录中的 `mode` 加载 .env 文件
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
const env = loadEnv(mode, __dirname, '') as ImportMetaEnv
const envConfig = proxyConfig[mode as ServiceEnvType]