commit.
parent
e49e21f841
commit
6d9fb30bbd
|
@ -12,4 +12,7 @@ module.exports = {
|
|||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
export const useAccountStore = defineStore('account', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<h1>404</h1>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
|
@ -1,12 +1,20 @@
|
|||
<template>
|
||||
<n-layout position="absolute" style="top: 0; right: 0; bottom: 0; left: 0">
|
||||
<n-layout-header position="absolute" style="z-index: 9999">
|
||||
<LogoComponent :collapsed="collapsed" style="width: 224px" />
|
||||
<div class="header">颐和园路</div>
|
||||
<n-layout-header position="absolute" :inverted="dark" :style="invertedStyle" :bordered="!dark">
|
||||
<Logo :collapsed="false" style="width: 224px" />
|
||||
<div class="header">
|
||||
<n-button type="primary" @click="dark = !dark">{{ dark ? '亮色' : '暗色' }}</n-button>
|
||||
颐和园路
|
||||
</div>
|
||||
</n-layout-header>
|
||||
<n-layout has-sider position="absolute" style="top: 72px; right: 0; bottom: 0; left: 0">
|
||||
<n-layout
|
||||
has-sider
|
||||
position="absolute"
|
||||
style="right: 0; bottom: 0; left: 0"
|
||||
:style="dark ? { top: '72px' } : { top: '73px' }"
|
||||
>
|
||||
<n-layout-sider
|
||||
bordered
|
||||
:bordered="!dark"
|
||||
:collapsed-width="64"
|
||||
:width="256"
|
||||
show-trigger="arrow-circle"
|
||||
|
@ -15,16 +23,10 @@
|
|||
@collapse="collapsed = true"
|
||||
@expand="collapsed = false"
|
||||
:native-scrollbar="false"
|
||||
inverted
|
||||
:inverted="dark"
|
||||
:style="invertedStyle"
|
||||
>
|
||||
<n-menu
|
||||
inverted
|
||||
v-model:value="activeKey"
|
||||
:collapsed="collapsed"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menuOptions"
|
||||
/>
|
||||
<SiderMenu :collapsed="collapsed" invertedBackgroundColor="#282c34" :inverted="dark" />
|
||||
</n-layout-sider>
|
||||
<n-layout-content content-style="padding: 24px;" :native-scrollbar="false">
|
||||
平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />平山道<br />
|
||||
|
@ -37,133 +39,48 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, h, ref, type Component } from 'vue'
|
||||
import { defineComponent, h, ref, computed, type Component } from 'vue'
|
||||
|
||||
import { NIcon } from 'naive-ui'
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
import {
|
||||
BookOpen20Filled as BookIcon,
|
||||
Person20Regular as PersonIcon,
|
||||
DrinkWine20Regular as WineIcon,
|
||||
} from '@vicons/fluent'
|
||||
|
||||
import LogoComponent from './components/LogoComponent.vue'
|
||||
|
||||
function renderIcon(icon: Component) {
|
||||
return () => h(NIcon, null, { default: () => h(icon) })
|
||||
}
|
||||
|
||||
const menuOptions: MenuOption[] = [
|
||||
{
|
||||
label: '且听风吟',
|
||||
key: 'hear-the-wind-sing',
|
||||
icon: renderIcon(BookIcon),
|
||||
},
|
||||
{
|
||||
label: '1973年的弹珠玩具',
|
||||
key: 'pinball-1973',
|
||||
icon: renderIcon(BookIcon),
|
||||
disabled: true,
|
||||
children: [
|
||||
{
|
||||
label: '鼠',
|
||||
key: 'rat',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '寻羊冒险记',
|
||||
key: 'a-wild-sheep-chase',
|
||||
disabled: true,
|
||||
icon: renderIcon(BookIcon),
|
||||
},
|
||||
{
|
||||
label: '舞,舞,舞',
|
||||
key: 'dance-dance-dance',
|
||||
icon: renderIcon(BookIcon),
|
||||
children: [
|
||||
{
|
||||
type: 'group',
|
||||
label: '人物',
|
||||
key: 'people',
|
||||
children: [
|
||||
{
|
||||
label: '叙事者',
|
||||
key: 'narrator',
|
||||
icon: renderIcon(PersonIcon),
|
||||
},
|
||||
{
|
||||
label: '羊男',
|
||||
key: 'sheep-man',
|
||||
icon: renderIcon(PersonIcon),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '饮品',
|
||||
key: 'beverage',
|
||||
icon: renderIcon(WineIcon),
|
||||
children: [
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
{ label: '威士忌', key: 'whisky' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '食物',
|
||||
key: 'food',
|
||||
children: [
|
||||
{
|
||||
label: '三明治',
|
||||
key: 'sandwich',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '过去增多,未来减少',
|
||||
key: 'the-past-increases-the-future-recedes',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
import Logo from './components/Logo.vue'
|
||||
import SiderMenu from './components/SiderMenu.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HomeView',
|
||||
components: {
|
||||
LogoComponent,
|
||||
Logo,
|
||||
SiderMenu,
|
||||
},
|
||||
setup() {
|
||||
const collapsed = ref(false)
|
||||
const dark = ref(true)
|
||||
const invertedStyle = computed(() => {
|
||||
return dark.value ? { '--n-color': '#282c34' } : { '--n-color': '#ffffff' }
|
||||
})
|
||||
return {
|
||||
activeKey: ref<string | null>(null),
|
||||
collapsed,
|
||||
menuOptions,
|
||||
dark,
|
||||
invertedStyle,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:root {
|
||||
--sider-width: 256px;
|
||||
}
|
||||
|
||||
.n-layout-header {
|
||||
display: flex;
|
||||
}
|
||||
.header {
|
||||
padding: 24px;
|
||||
border-bottom: solid 1px rgb(239, 239, 245);
|
||||
}
|
||||
|
||||
.n-scrollbar > .n-scrollbar-rail.n-scrollbar-rail--vertical {
|
||||
--n-scrollbar-color: rgba(255, 255, 255, 0.25);
|
||||
--n-scrollbar-color-hover: rgba(255, 255, 255, 0.4);
|
||||
width: 25px;
|
||||
.n-layout-header > .header {
|
||||
padding: 24px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 256px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
<template>
|
||||
<div class="logo">
|
||||
<div class="logo" :style="style">
|
||||
<img alt="logo" src="@/assets/logo.svg" />
|
||||
<span class="title" v-if="!collapsed">Plusone Admin</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
collapsed: Boolean,
|
||||
inverted: Boolean,
|
||||
})
|
||||
|
||||
const style = computed(() => {
|
||||
return props.inverted ? { color: '#ffffff' } : { color: 'unset' }
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -15,8 +22,9 @@ const props = defineProps({
|
|||
.logo {
|
||||
padding: 20px 16px;
|
||||
display: inline-flex;
|
||||
background-color: rgb(0, 20, 40);
|
||||
background-color: var(--n-color);
|
||||
color: #ffffff;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.logo > img {
|
|
@ -0,0 +1,113 @@
|
|||
import type { MenuOption } from 'naive-ui'
|
||||
import { type RouteRecordRaw, RouterLink } from 'vue-router'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import * as iconComponents from '@vicons/fluent'
|
||||
import { type Component, h } from 'vue'
|
||||
|
||||
export interface MenuVO {
|
||||
type: number
|
||||
typeName: string
|
||||
id: number
|
||||
parentId: number
|
||||
name: string
|
||||
// 若 type 为 MENU_ITEM 且 path 以 http:// 或 https:// 开头则被识别为外链
|
||||
path: string
|
||||
title: string
|
||||
icon: string
|
||||
hidden: boolean
|
||||
orderNumber: number
|
||||
status: number
|
||||
remarks: string
|
||||
|
||||
// MENU_ITEM
|
||||
component?: string
|
||||
cache?: boolean
|
||||
resource?: string
|
||||
actions?: ActionVO[]
|
||||
|
||||
// MENU_LIST
|
||||
children?: MenuVO[]
|
||||
}
|
||||
|
||||
interface ActionVO {
|
||||
id: number
|
||||
identifier: string
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export enum MenuType {
|
||||
MENU_LIST,
|
||||
MENU_ITEM,
|
||||
}
|
||||
|
||||
export function convertToRoutes(menuVOTree: MenuVO[]): RouteRecordRaw[] {
|
||||
const routes: RouteRecordRaw[] = menuVOTree.map((menuVO) => {
|
||||
if (menuVO.type == MenuType.MENU_LIST) {
|
||||
return {
|
||||
path: menuVO.path,
|
||||
name: menuVO.name,
|
||||
component:
|
||||
menuVO.component !== undefined ? () => import('@/views' + menuVO.component) : undefined,
|
||||
children: convertToRoutes(menuVO.children ? menuVO.children : []),
|
||||
meta: {
|
||||
title: menuVO.title,
|
||||
icon: menuVO.icon,
|
||||
hidden: menuVO.hidden,
|
||||
order: menuVO.orderNumber,
|
||||
status: menuVO.status,
|
||||
remarks: menuVO.remarks,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
path: menuVO.path,
|
||||
name: menuVO.name,
|
||||
component: import('@/views' + menuVO.component),
|
||||
meta: {
|
||||
title: menuVO.title,
|
||||
icon: menuVO.icon,
|
||||
hidden: menuVO.hidden,
|
||||
order: menuVO.orderNumber,
|
||||
status: menuVO.status,
|
||||
remarks: menuVO.remarks,
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
export function convertToMenuOptions(menuVOTree: MenuVO[]): MenuOption[] {
|
||||
const menuOptions: MenuOption[] = menuVOTree.map((menuVO) => {
|
||||
if (menuVO.type == MenuType.MENU_ITEM) {
|
||||
return {
|
||||
key: menuVO.name,
|
||||
icon: renderIcon(menuVO.icon),
|
||||
label:
|
||||
menuVO.path.startsWith('http://') || menuVO.path.startsWith('https://')
|
||||
? () => h('a', { href: menuVO.path, target: '_blank' }, menuVO.title)
|
||||
: () => h(RouterLink, { to: menuVO.path }, menuVO.title),
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
key: menuVO.name,
|
||||
icon: renderIcon(menuVO.icon),
|
||||
label: menuVO.title,
|
||||
children: convertToMenuOptions(menuVO.children ? menuVO.children : []),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return menuOptions
|
||||
}
|
||||
|
||||
const icons: Map<string, Component> = new Map(Object.entries(iconComponents))
|
||||
export function renderIcon(icon: string) {
|
||||
const iconComponent = icons.get(icon)
|
||||
return () =>
|
||||
h(NIcon, null, {
|
||||
default: () => h(iconComponent ? iconComponent : iconComponents.QuestionCircle20Regular),
|
||||
})
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<n-menu
|
||||
:inverted="inverted"
|
||||
:style="style"
|
||||
v-model:value="activeKey"
|
||||
:collapsed="collapsed"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menuOptions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h, computed, type Component } from 'vue'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
|
||||
import { NIcon } from 'naive-ui'
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
|
||||
import * as iconComponents from '@vicons/fluent'
|
||||
|
||||
const props = defineProps({
|
||||
inverted: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
invertedBackgroundColor: {
|
||||
type: String,
|
||||
default: '#282c34',
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const style = computed(() => {
|
||||
return props.inverted ? { '--n-color': props.invertedBackgroundColor } : { '--n-color': '#ffffff' }
|
||||
})
|
||||
|
||||
// MenuOptions
|
||||
const icons: Map<string, Component> = new Map(Object.entries(iconComponents))
|
||||
|
||||
function renderIcon(icon: string) {
|
||||
const iconComponent = icons.get(icon)
|
||||
return () =>
|
||||
h(NIcon, null, {
|
||||
default: () => h(iconComponent ? iconComponent : iconComponents.QuestionCircle20Regular),
|
||||
})
|
||||
}
|
||||
|
||||
const menuOptions: MenuOption[] = [
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'home',
|
||||
params: {
|
||||
lang: 'zh-CN',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ default: () => '回家' }
|
||||
),
|
||||
key: '/',
|
||||
icon: renderIcon('Home20Regular'),
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: '/login',
|
||||
},
|
||||
{ default: () => '上班' }
|
||||
),
|
||||
key: '/login',
|
||||
icon: renderIcon('Desktop20Regular'),
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
href: 'http://zhouxy.xyz',
|
||||
target: '_blank',
|
||||
rel: 'noopenner noreferrer',
|
||||
},
|
||||
'ZhouXY'
|
||||
),
|
||||
key: 'hear-the-wind-sing',
|
||||
icon: renderIcon('BookOpen20Filled'),
|
||||
},
|
||||
]
|
||||
|
||||
// ActiveKey
|
||||
const route = useRoute()
|
||||
const activeKey = computed(() => {
|
||||
return route.path
|
||||
})
|
||||
</script>
|
|
@ -2,8 +2,11 @@
|
|||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2017"],
|
||||
"target": "ES2015",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@": ["src"],
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -11,4 +11,7 @@ export default defineConfig({
|
|||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 8888,
|
||||
},
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue