main
ZhouXY108 2022-12-28 17:40:30 +08:00
parent e49e21f841
commit 6d9fb30bbd
9 changed files with 279 additions and 124 deletions

View File

@ -12,4 +12,7 @@ module.exports = {
parserOptions: {
ecmaVersion: 'latest',
},
rules: {
'vue/multi-word-component-names': 'off',
},
}

View File

@ -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() {

View File

@ -0,0 +1,5 @@
<template>
<h1>404</h1>
</template>
<script setup lang="ts"></script>

View File

@ -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>

View File

@ -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 {

View File

@ -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),
})
}

View File

@ -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>

View File

@ -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/*"]
}
},

View File

@ -11,4 +11,7 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 8888,
},
})