新增多标签页

This commit is contained in:
chuan_wuhao 2022-11-24 14:53:23 +08:00
parent 14ae981efb
commit 75b0f434cf
25 changed files with 250 additions and 79 deletions

View File

@ -2,7 +2,8 @@
"GlobalMenuOptions": {
"Dashboard": "Home",
"Rely": "Rely",
"RelyAbout": "Rely About"
"RelyAbout": "Rely About",
"Error": "Error Page"
},
"LayoutHeaderTooltipOptions": {
"Reload": "Reload Current Page",

View File

@ -2,7 +2,8 @@
"GlobalMenuOptions": {
"Dashboard": "首页",
"Rely": "依赖项",
"RelyAbout": "关于"
"RelyAbout": "关于",
"Error": "错误页"
},
"LayoutHeaderTooltipOptions": {
"Reload": "刷新当前页面",

View File

@ -17,6 +17,7 @@
"crypto-js": "^4.1.1",
"naive-ui": "^2.34.0",
"pinia": "^2.0.17",
"pinia-plugin-persistedstate": "^2.4.0",
"sass": "^1.54.3",
"scrollreveal": "^4.0.9",
"vue": "^3.2.37",

4
src/icons/error.svg Normal file
View File

@ -0,0 +1,4 @@
<svg t="1669270375884" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14105" width="200" height="200">
<path d="M832 160h-96v-64h-64v192h64v-64H832c17.92 0 32 14.08 32 32v96h-704V256c0-17.92 14.08-32 32-32h32v-64H192c-53.12 0-96 42.88-96 96v576c0 53.12 42.88 96 96 96h640c53.12 0 96-42.88 96-96V256c0-53.12-42.88-96-96-96z m0 704H192c-17.92 0-32-14.08-32-32V416h704V832c0 17.92-14.08 32-32 32z" fill="currentColor" p-id="14106"></path>
<path d="M352 224h256v-64h-256v-64h-64v192h64zM575.36 524.8L512 588.16l-63.36-63.36-45.44 45.44 63.36 63.36-63.36 63.36 45.44 45.44 63.36-63.36 63.36 63.36 45.44-45.44-63.36-63.36 63.36-63.36z" fill="currentColor" p-id="14107"></path>
</svg>

After

Width:  |  Height:  |  Size: 726 B

View File

@ -1,3 +0,0 @@
.setting-drawer__space {
width: 100%;
}

View File

@ -6,7 +6,11 @@ const LayoutMenu = defineComponent({
setup() {
const menuStore = useMenu()
const { menuModelValueChange, setupAppRoutes, collapsedMenu } = menuStore
const modelMenuKey = ref(menuStore.menuKey)
const modelMenuKey = computed({
get: () => menuStore.menuKey,
// eslint-disable-next-line @typescript-eslint/no-empty-function
set: () => {},
})
const modelMenuOptions = computed(() => menuStore.options)
const modelCollapsed = computed(() => menuStore.collapsed)

View File

@ -0,0 +1,14 @@
$space: $layoutRouterViewContainer / 2;
.menu-tag {
height: $layoutMenuHeight;
& .menu-tag-sapce {
width: calc(100% - $space * 2);
padding: $layoutRouterViewContainer / 2;
}
& .n-tag {
cursor: pointer;
}
}

View File

@ -0,0 +1,61 @@
import './index.scss'
import { NScrollbar, NTag, NSpace } from 'naive-ui'
import { useMenu } from '@/store'
import type { MenuOption } from 'naive-ui'
const MenuTag = defineComponent({
name: 'MenuTag',
setup() {
const menuStore = useMenu()
const { menuTagOptions, menuKey } = storeToRefs(menuStore)
const { menuModelValueChange, spliceMenTagOptions } = menuStore
const handleCloseTag = (idx: number) => {
spliceMenTagOptions(idx)
if (menuKey.value !== '/dashboard') {
const options = menuTagOptions.value as MenuOption[]
const length = options.length
const tag = options[length - 1]
menuModelValueChange(tag.key as string, tag)
}
}
const handleTagClick = (item: MenuOption) => {
menuModelValueChange(item.key as string, item)
}
return {
menuTagOptions,
menuModelValueChange,
handleCloseTag,
menuKey,
handleTagClick,
}
},
render() {
return (
<NScrollbar class="menu-tag" xScrollable>
<NSpace class="menu-tag-sapce" wrap={false} align="center">
{this.menuTagOptions.map((curr: MenuOption, idx) => (
<NTag
closable={
curr.key !== '/dashboard' && this.menuTagOptions.length > 1
}
onClose={() => this.handleCloseTag(idx)}
type={curr.key === this.menuKey ? 'success' : 'default'}
onClick={this.handleTagClick.bind(this, curr)}
>
{typeof curr.label === 'function' ? curr.label() : curr.label}
</NTag>
))}
</NSpace>
</NScrollbar>
)
},
})
export default MenuTag

View File

@ -0,0 +1,8 @@
.setting-drawer__space {
width: 100%;
& .n-descriptions-table-content {
display: flex !important;
justify-content: space-between;
}
}

View File

@ -7,6 +7,8 @@ import {
NSwitch,
NColorPicker,
NTooltip,
NDescriptions,
NDescriptionsItem,
} from 'naive-ui'
import RayIcon from '@/components/RayIcon/index'
import { useSwatchesColorOptions } from './hook'
@ -35,8 +37,9 @@ const SettingDrawer = defineComponent({
const { t } = useI18n()
const settingStore = useSetting()
const { changeTheme, changePrimaryColor } = settingStore
const { themeValue, primaryColorOverride } = storeToRefs(settingStore)
const { changeTheme, changePrimaryColor, changeMenuTagLog } = settingStore
const { themeValue, primaryColorOverride, menuTagLog } =
storeToRefs(settingStore)
const modelShow = computed({
get: () => props.show,
@ -57,6 +60,8 @@ const SettingDrawer = defineComponent({
changeTheme,
themeValue,
primaryColorOverride,
changeMenuTagLog,
menuTagLog,
}
},
render() {
@ -119,6 +124,15 @@ const SettingDrawer = defineComponent({
v-model:value={this.primaryColorOverride.common.primaryColor}
onUpdateValue={this.changePrimaryColor.bind(this)}
/>
<NDivider titlePlacement="center"></NDivider>
<NDescriptions labelPlacement="left" column={1}>
<NDescriptionsItem label="显示多标签">
<NSwitch
v-model:value={this.menuTagLog}
onUpdateValue={this.changeMenuTagLog.bind(this)}
/>
</NDescriptionsItem>
</NDescriptions>
</NSpace>
</NDrawerContent>
</NDrawer>

View File

@ -1,7 +1,7 @@
import './index.scss'
import { NLayoutHeader, NSpace, NTooltip, NDropdown } from 'naive-ui'
import RayIcon from '@/components/RayIcon/index'
import { useMenu, useSetting } from '@/store'
import { useSetting } from '@/store'
import { useLanguageOptions } from '@/language/index'
import SettingDrawer from './Components/SettingDrawer/index'
import { useAvatarOptions } from './hook'
@ -12,12 +12,10 @@ import type { IconEventMapOptions, IconEventMap } from './type'
const SiderBar = defineComponent({
name: 'SiderBar',
setup() {
const menuStore = useMenu()
const settingStore = useSetting()
const { t } = useI18n()
const { changeReloadLog } = menuStore
const { updateLocale } = settingStore
const { updateLocale, changeReloadLog } = settingStore
const modelDrawerPlacement = ref(settingStore.drawerPlacement)
const showSettings = ref(false)

View File

@ -6,7 +6,8 @@
}
& .layout-content__router-view {
height: calc(100% - $layoutHeaderHeight);
padding: $layoutRouterViewContainer;
// height: calc(100% - $layoutHeaderHeight - $layoutMenuHeight);
height: var(--layout-content-height);
padding: $layoutRouterViewContainer / 2;
}
}

View File

@ -1,31 +1,53 @@
import './index.scss'
import { Transition } from 'vue'
import { NLayout, NLayoutContent } from 'naive-ui'
import RayTransitionComponent from '@/components/RayTransitionComponent/index.vue'
import LayoutMenu from './Menu/index'
import SiderBar from './SiderBar/index'
import { useMenu } from '@/store'
import LayoutMenu from './components/Menu/index'
import SiderBar from './components/SiderBar/index'
import MenuTag from './components/MenuTag/index'
import { useSetting } from '@/store'
const Layout = defineComponent({
name: 'Layout',
props: {},
setup() {
const menuStore = useMenu()
const menuStore = useSetting()
const { height: windowHeight } = useWindowSize()
const modelReloadRoute = computed(() => menuStore.reloadRouteLog)
const modelMenuTagLog = computed(() => menuStore.menuTagLog)
const cssVarsRef = computed(() => {
let cssVar = {}
if (menuStore.menuTagLog) {
cssVar = {
'--layout-content-height': 'calc(100% - 110px)',
}
} else {
cssVar = {
'--layout-content-height': 'calc(100% - 64px)',
}
}
return cssVar
})
return {
windowHeight,
modelReloadRoute,
modelMenuTagLog,
cssVarsRef,
}
},
render() {
return (
<div class="layout" style={[`height: ${this.windowHeight}px`]}>
<div
class="layout"
style={[`height: ${this.windowHeight}px`, this.cssVarsRef]}
>
<NLayout class="layout-full" hasSider>
<LayoutMenu />
<NLayout>
<SiderBar />
{this.modelMenuTagLog ? <MenuTag /> : ''}
<NLayoutContent
class="layout-content__router-view"
nativeScrollbar={false}

View File

@ -0,0 +1,9 @@
export default {
path: '/error',
name: 'error',
component: () => import('@/views/error/index'),
meta: {
i18nKey: 'Error',
icon: 'error',
},
}

View File

@ -1,7 +1,8 @@
import dashboard from './dashboard'
import reyl from './rely'
import error from './error'
const routes = [dashboard, reyl]
const routes = [dashboard, error, reyl]
export default routes

View File

@ -1,3 +1,5 @@
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import type { App } from 'vue'
export { useSetting } from './modules/setting' // import { useSetting } from '@/store' 即可使用
@ -7,4 +9,6 @@ const store = createPinia()
export const setupStore = (app: App<Element>) => {
app.use(store)
store.use(piniaPluginPersistedstate)
}

View File

@ -9,15 +9,28 @@ export const useMenu = defineStore('menu', () => {
const router = useRouter()
const { t } = useI18n()
const cacheMenuKey = getCache('menuKey') === 'no' ? '' : getCache('menuKey')
const cacheMenuKey =
getCache('menuKey') === 'no' ? '/dashboard' : getCache('menuKey')
const menuState = reactive({
menuKey: cacheMenuKey as string | null, // 当前菜单 `key`
options: [] as RouteRecordRaw[], // 菜单列表
collapsed: false, // 是否折叠菜单
reloadRouteLog: true, // 刷新路由开关
menuTagOptions: [] as RouteRecordRaw[],
})
const handleMenuTagOptions = (item: RouteRecordRaw) => {
if (item.path !== menuState.menuKey) {
const tag = menuState.menuTagOptions.find(
(curr) => curr.path === item.path,
)
if (!tag) {
menuState.menuTagOptions.push(item)
}
}
}
/**
*
* @param key `key`
@ -26,6 +39,8 @@ export const useMenu = defineStore('menu', () => {
* `menu key`
*/
const menuModelValueChange = (key: string, item: MenuOption) => {
handleMenuTagOptions(item as unknown as RouteRecordRaw)
menuState.menuKey = key
router.push(`${item.path}`)
@ -69,7 +84,14 @@ export const useMenu = defineStore('menu', () => {
),
}
return curr.meta?.icon ? Object.assign(route, expandIcon) : route
const attr = curr.meta?.icon ? Object.assign(route, expandIcon) : route
// 初始化 `menu tag`
if (curr.path === cacheMenuKey) {
menuState.menuTagOptions.push(attr)
}
return attr
})
}
@ -83,17 +105,14 @@ export const useMenu = defineStore('menu', () => {
const collapsedMenu = (collapsed: boolean) =>
(menuState.collapsed = collapsed)
/**
*
* @param bool
*/
const changeReloadLog = (bool: boolean) => (menuState.reloadRouteLog = bool)
const spliceMenTagOptions = (idx: number) =>
menuState.menuTagOptions.splice(idx, 1)
return {
...toRefs(menuState),
menuModelValueChange,
setupAppRoutes,
collapsedMenu,
changeReloadLog,
spliceMenTagOptions,
}
})

View File

@ -1,49 +1,57 @@
import { setCache, getCache } from '@/utils/cache'
export const useSetting = defineStore('setting', () => {
const cachePrimaryColor: string =
getCache('primaryColor', 'localStorage') === 'no'
? '#18A058'
: getCache('primaryColor', 'localStorage')
const cacheTheme =
getCache('theme', 'localStorage') === 'no'
? false
: getCache('theme', 'localStorage')
const settingState = reactive({
drawerPlacement: 'right' as NaiveDrawerPlacement,
primaryColorOverride: {
common: {
primaryColor: cachePrimaryColor, // 主题色
export const useSetting = defineStore(
'setting',
() => {
const settingState = reactive({
drawerPlacement: 'right' as NaiveDrawerPlacement,
primaryColorOverride: {
common: {
primaryColor: '#18A058', // 主题色
},
},
themeValue: false, // `true` 为黑夜主题, `false` 为白色主题
reloadRouteLog: true, // 刷新路由开关
menuTagLog: true, // 多标签页开关
})
const { locale } = useI18n()
const updateLocale = (key: string) => {
// TODO: 修改语言
locale.value = key
}
const changeTheme = (bool: boolean) => {
settingState.themeValue = bool
}
const changePrimaryColor = (value: string) => {
settingState.primaryColorOverride.common.primaryColor = value
}
/**
*
* @param bool
*/
const changeReloadLog = (bool: boolean) =>
(settingState.reloadRouteLog = bool)
/**
*
* @param bool
*/
const changeMenuTagLog = (bool: boolean) => (settingState.menuTagLog = bool)
return {
...toRefs(settingState),
updateLocale,
changeTheme,
changePrimaryColor,
changeReloadLog,
changeMenuTagLog,
}
},
{
persist: {
key: 'piniaSettingStore',
},
themeValue: cacheTheme, // `true` 为黑夜主题, `false` 为白色主题
})
const { locale } = useI18n()
const updateLocale = (key: string) => {
// TODO: 修改语言
locale.value = key
setCache('localeLanguage', key, 'localStorage')
}
const changeTheme = (bool: boolean) => {
settingState.themeValue = bool
setCache('theme', bool, 'localStorage')
}
const changePrimaryColor = (value: string) => {
settingState.primaryColorOverride.common.primaryColor = value
setCache('primaryColor', value, 'localStorage')
}
return {
...toRefs(settingState),
updateLocale,
changeTheme,
changePrimaryColor,
}
})
},
)

View File

@ -1,2 +1,3 @@
$layoutRouterViewContainer: 18px;
$layoutHeaderHeight: 64px;
$layoutMenuHeight: 46px;

View File

@ -22,13 +22,11 @@ const Login = defineComponent({
const { t } = useI18n()
const { height: windowHeight } = useWindowSize()
const settingStore = useSetting()
const { themeValue } = storeToRefs(settingStore)
const { updateLocale } = settingStore
return {
...toRefs(state),
windowHeight,
themeValue,
updateLocale,
ray: t,
}

View File

@ -3489,6 +3489,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pinia-plugin-persistedstate@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-2.4.0.tgz#fda569b3c397517a0cf8aba83a628283767da620"
integrity sha512-bQcpv47jk3ISl+InuJWsFaS/K7pRZ97kfoD2WCf/suhnlLy48k3BnFM2tI6YZ1xMsDaPv4yOsaPuPAUuSmEO2Q==
pinia@^2.0.17:
version "2.0.17"
resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.17.tgz#f925e5e4f73c15e16dfb4838176a9ca50752f26b"