mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-04-06 03:57:49 +08:00
新增多标签页
This commit is contained in:
parent
14ae981efb
commit
75b0f434cf
@ -2,7 +2,8 @@
|
||||
"GlobalMenuOptions": {
|
||||
"Dashboard": "Home",
|
||||
"Rely": "Rely",
|
||||
"RelyAbout": "Rely About"
|
||||
"RelyAbout": "Rely About",
|
||||
"Error": "Error Page"
|
||||
},
|
||||
"LayoutHeaderTooltipOptions": {
|
||||
"Reload": "Reload Current Page",
|
||||
|
@ -2,7 +2,8 @@
|
||||
"GlobalMenuOptions": {
|
||||
"Dashboard": "首页",
|
||||
"Rely": "依赖项",
|
||||
"RelyAbout": "关于"
|
||||
"RelyAbout": "关于",
|
||||
"Error": "错误页"
|
||||
},
|
||||
"LayoutHeaderTooltipOptions": {
|
||||
"Reload": "刷新当前页面",
|
||||
|
@ -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
4
src/icons/error.svg
Normal 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 |
@ -1,3 +0,0 @@
|
||||
.setting-drawer__space {
|
||||
width: 100%;
|
||||
}
|
@ -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)
|
||||
|
14
src/layout/components/MenuTag/index.scss
Normal file
14
src/layout/components/MenuTag/index.scss
Normal 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;
|
||||
}
|
||||
}
|
61
src/layout/components/MenuTag/index.tsx
Normal file
61
src/layout/components/MenuTag/index.tsx
Normal 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
|
@ -0,0 +1,8 @@
|
||||
.setting-drawer__space {
|
||||
width: 100%;
|
||||
|
||||
& .n-descriptions-table-content {
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
@ -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>
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
|
9
src/router/modules/error.ts
Normal file
9
src/router/modules/error.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
path: '/error',
|
||||
name: 'error',
|
||||
component: () => import('@/views/error/index'),
|
||||
meta: {
|
||||
i18nKey: 'Error',
|
||||
icon: 'error',
|
||||
},
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
})
|
||||
|
@ -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,
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
@ -1,2 +1,3 @@
|
||||
$layoutRouterViewContainer: 18px;
|
||||
$layoutHeaderHeight: 64px;
|
||||
$layoutMenuHeight: 46px;
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user