mirror of https://github.com/halo-dev/halo-admin
feat: add menu component (#534)
parent
4e3f11829b
commit
bd446449c0
|
@ -15,6 +15,7 @@ module.exports = {
|
|||
},
|
||||
rules: {
|
||||
"vue/multi-word-component-names": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
|
20
package.json
20
package.json
|
@ -17,37 +17,39 @@
|
|||
"story:build": "histoire build"
|
||||
},
|
||||
"dependencies": {
|
||||
"pinia": "^2.0.12",
|
||||
"pinia": "^2.0.13",
|
||||
"vue": "^3.2.31",
|
||||
"vue-router": "^4.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/ri": "^1.1.1",
|
||||
"@rushstack/eslint-patch": "^1.1.1",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/node": "^16.11.26",
|
||||
"@vitejs/plugin-vue": "^2.2.4",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.8",
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.9",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^10.0.0",
|
||||
"@vue/test-utils": "^2.0.0-rc.18",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"c8": "^7.11.0",
|
||||
"cypress": "^9.5.2",
|
||||
"eslint": "^8.11.0",
|
||||
"cypress": "^9.5.3",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-vue": "^8.5.0",
|
||||
"histoire": "^0.2.0",
|
||||
"histoire": "^0.2.1",
|
||||
"husky": "^7.0.4",
|
||||
"jsdom": "^19.0.0",
|
||||
"postcss": "^8.4.12",
|
||||
"prettier": "^2.6.0",
|
||||
"sass": "^1.49.9",
|
||||
"prettier": "^2.6.1",
|
||||
"sass": "^1.49.10",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"tailwindcss-themeable": "^1.3.0",
|
||||
"typescript": "~4.5.5",
|
||||
"vite": "^2.8.6",
|
||||
"unplugin-icons": "^0.14.1",
|
||||
"vite": "^2.9.1",
|
||||
"vitest": "^0.5.9",
|
||||
"vue-tsc": "^0.31.4"
|
||||
}
|
||||
|
|
545
pnpm-lock.yaml
545
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
69
src/App.vue
69
src/App.vue
|
@ -1,7 +1,74 @@
|
|||
<script lang="ts" setup>
|
||||
import { RouterView } from "vue-router";
|
||||
import { VMenu, VMenuItem, VMenuLabel } from "@/components/base/menu";
|
||||
import { IconDashboard } from "@/core/icons";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
<div class="flex">
|
||||
<div class="navbar h-screen w-72 p-4" style="background: #fff">
|
||||
<VMenu :open-ids="['dashboard']">
|
||||
<VMenuLabel>首页</VMenuLabel>
|
||||
<VMenuItem id="dashboard" title="仪表盘">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
<VMenuItem title="子菜单">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
<VMenuItem title="子菜单">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
<VMenuItem title="子菜单">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
</VMenuItem>
|
||||
<VMenuItem title="子菜单">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
</VMenuItem>
|
||||
<VMenuItem title="仪表盘">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
<VMenuItem title="仪表盘">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
<VMenuItem title="子菜单1">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
<VMenuItem title="子菜单1">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
<VMenuItem title="子菜单1">
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
</VMenuItem>
|
||||
</VMenu>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<RouterView />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
body {
|
||||
background: #eff4f9;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<Story title="Menu">
|
||||
<Variant title="Playground">
|
||||
<template #default>
|
||||
<VMenu>
|
||||
<VMenuItem>
|
||||
<template #icon>
|
||||
<Component :is="IconDashboard" />
|
||||
</template>
|
||||
仪表盘
|
||||
</VMenuItem>
|
||||
</VMenu>
|
||||
</template>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { VMenu, VMenuItem } from "./index";
|
||||
import { IconDashboard } from "@/core/icons";
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import { provide } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
openIds: {
|
||||
type: Object as PropType<string[]>,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
provide<string[] | undefined>("openIds", props.openIds);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="menu-container w-full h-full">
|
||||
<ul>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,121 @@
|
|||
<script lang="ts" setup>
|
||||
import { IconArrowRight } from "@/core/icons";
|
||||
import { computed, inject, ref, useSlots } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["select"]);
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
const openIds = inject<string[] | undefined>("openIds");
|
||||
|
||||
if (openIds?.includes(props.id)) {
|
||||
open.value = true;
|
||||
}
|
||||
|
||||
const hasSubmenus = computed(() => {
|
||||
return slots.default;
|
||||
});
|
||||
|
||||
function handleClick() {
|
||||
if (hasSubmenus.value) {
|
||||
open.value = !open.value;
|
||||
return;
|
||||
}
|
||||
emit("select", props.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
class="menu-item"
|
||||
:class="{ 'has-submenus': hasSubmenus }"
|
||||
@click.stop="handleClick"
|
||||
>
|
||||
<div class="menu-item-title">
|
||||
<span v-if="$slots.icon" class="menu-icon self-center mr-3">
|
||||
<slot name="icon" />
|
||||
</span>
|
||||
<span class="menu-title self-center flex-1">
|
||||
{{ title }}
|
||||
</span>
|
||||
<span
|
||||
v-if="$slots.default"
|
||||
:class="{ open }"
|
||||
class="menu-icon-collapse self-center transition-all"
|
||||
>
|
||||
<IconArrowRight />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Transition name="submenus-show">
|
||||
<ul v-show="$slots.default && open" class="sub-menu-items transition-all">
|
||||
<slot />
|
||||
</ul>
|
||||
</Transition>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.menu-item {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
.menu-item-title {
|
||||
@apply transition-all;
|
||||
@apply text-base;
|
||||
@apply flex;
|
||||
@apply select-none;
|
||||
@apply relative;
|
||||
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background: #f4f5f7;
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
&.active::after {
|
||||
@apply absolute;
|
||||
top: calc(50% - 13px);
|
||||
left: -8px;
|
||||
width: 3px;
|
||||
height: 26px;
|
||||
content: "";
|
||||
background: #242e41;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon-collapse.open {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.submenus-show-enter-active {
|
||||
transition: all 0.1s ease-out;
|
||||
}
|
||||
|
||||
.submenus-show-leave-active {
|
||||
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
|
||||
.submenus-show-enter-from,
|
||||
.submenus-show-enter-to {
|
||||
transform: translateY(-10px);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<li class="menu-label flex flex-col">
|
||||
<slot />
|
||||
</li>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.menu-label {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 13px;
|
||||
color: #847e7e;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { VMenu, VMenuItem } from "../index";
|
||||
|
||||
describe("Menu", () => {
|
||||
it("should render", () => {
|
||||
expect(VMenu).toBeDefined();
|
||||
expect(VMenuItem).toBeDefined();
|
||||
expect(mount(VMenu).html()).toMatchSnapshot();
|
||||
expect(mount(VMenuItem).html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should work with sub menus", async () => {
|
||||
const wrapper = await mount({
|
||||
setup() {
|
||||
return () => (
|
||||
<VMenu openIds={["3"]}>
|
||||
<VMenuItem id="1" title="Menu Item 1" />
|
||||
<VMenuItem id="2" title="Menu Item 2" />
|
||||
<VMenuItem id="3" title="Menu Item 3">
|
||||
<VMenuItem key="4" title="Menu Item 4" />
|
||||
</VMenuItem>
|
||||
</VMenu>
|
||||
);
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
||||
// toggling sub menu
|
||||
expect(
|
||||
wrapper.find(".has-submenus .sub-menu-items").attributes().style
|
||||
).toBeUndefined(); // visible
|
||||
|
||||
await wrapper.find(".has-submenus").trigger("click");
|
||||
expect(
|
||||
wrapper.find(".has-submenus .sub-menu-items").attributes().style
|
||||
).toBe("display: none;");
|
||||
|
||||
await wrapper.find(".has-submenus").trigger("click");
|
||||
expect(
|
||||
wrapper.find(".has-submenus .sub-menu-items").attributes().style
|
||||
).toBeUndefined(); // visible
|
||||
});
|
||||
|
||||
it("should work with openIds prop", function () {
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => (
|
||||
<VMenu openIds={["3"]}>
|
||||
<VMenuItem id="1" title="Menu Item 1" />
|
||||
<VMenuItem id="2" title="Menu Item 2" />
|
||||
<VMenuItem id="3" title="Menu Item 3">
|
||||
<VMenuItem key="4" title="Menu Item 4" />
|
||||
</VMenuItem>
|
||||
</VMenu>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
||||
expect(wrapper.find(".sub-menu-items .menu-title").text()).contain(
|
||||
"Menu Item 4"
|
||||
);
|
||||
});
|
||||
|
||||
it("should work with select emit", async () => {
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => (
|
||||
<VMenu openIds={["3"]}>
|
||||
<VMenuItem id="1" title="Menu Item 1" />
|
||||
<VMenuItem id="2" title="Menu Item 2" />
|
||||
<VMenuItem id="3" title="Menu Item 3">
|
||||
<VMenuItem id="4" title="Menu Item 4" />
|
||||
</VMenuItem>
|
||||
</VMenu>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.findAllComponents(VMenuItem).forEach((item) => {
|
||||
// has not sub menu
|
||||
if (item.props().id === "1") {
|
||||
item.trigger("click");
|
||||
expect(item.emitted().select[0]).toEqual(["1"]);
|
||||
}
|
||||
// has sub menu
|
||||
if (item.props().id === "3") {
|
||||
item.trigger("click");
|
||||
expect(item.emitted().select).toBeUndefined();
|
||||
|
||||
expect(item.vm.open).toBe(false);
|
||||
|
||||
item.trigger("click");
|
||||
expect(item.vm.open).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`Menu > should render 1`] = `
|
||||
"<div class=\\"menu-container w-full h-full\\">
|
||||
<ul></ul>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`Menu > should render 2`] = `
|
||||
"<li class=\\"menu-item\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\"></span>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\" style=\\"display: none;\\"></ul>
|
||||
</transition-stub>
|
||||
</li>"
|
||||
`;
|
||||
|
||||
exports[`Menu > should work with openIds prop 1`] = `
|
||||
"<div class=\\"menu-container w-full h-full\\">
|
||||
<ul>
|
||||
<li class=\\"menu-item\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 1</span>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\" style=\\"display: none;\\"></ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
<li class=\\"menu-item\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 2</span>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\" style=\\"display: none;\\"></ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
<li class=\\"menu-item has-submenus\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 3</span><span class=\\"open menu-icon-collapse self-center transition-all\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"m13.172 12l-4.95-4.95l1.414-1.414L16 12l-6.364 6.364l-1.414-1.414z\\"></path></svg></span>
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\">
|
||||
<li class=\\"menu-item\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 4</span>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\" style=\\"display: none;\\"></ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
</ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
</ul>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`Menu > should work with sub menus 1`] = `
|
||||
"<div class=\\"menu-container w-full h-full\\">
|
||||
<ul>
|
||||
<li class=\\"menu-item\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 1</span>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\" style=\\"display: none;\\"></ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
<li class=\\"menu-item\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 2</span>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\" style=\\"display: none;\\"></ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
<li class=\\"menu-item has-submenus\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 3</span><span class=\\"open menu-icon-collapse self-center transition-all\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"m13.172 12l-4.95-4.95l1.414-1.414L16 12l-6.364 6.364l-1.414-1.414z\\"></path></svg></span>
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\">
|
||||
<li class=\\"menu-item\\">
|
||||
<div class=\\"menu-item-title\\">
|
||||
<!--v-if--><span class=\\"menu-title self-center flex-1\\">Menu Item 4</span>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<transition-stub>
|
||||
<ul class=\\"sub-menu-items transition-all\\" style=\\"display: none;\\"></ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
</ul>
|
||||
</transition-stub>
|
||||
</li>
|
||||
</ul>
|
||||
</div>"
|
||||
`;
|
|
@ -0,0 +1,3 @@
|
|||
export { default as VMenu } from "./Menu.vue";
|
||||
export { default as VMenuItem } from "./MenuItem.vue";
|
||||
export { default as VMenuLabel } from "./MenuLabel.vue";
|
|
@ -0,0 +1,6 @@
|
|||
// @ts-ignore
|
||||
import IconDashboard from "~icons/ri/dashboard-3-line";
|
||||
// @ts-ignore
|
||||
import IconArrowRight from "~icons/ri/arrow-right-s-line";
|
||||
|
||||
export { IconDashboard, IconArrowRight };
|
|
@ -1,20 +1,10 @@
|
|||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import HomeView from "../views/HomeView.vue";
|
||||
import routesConfig from "@/router/routes.config";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "about",
|
||||
component: () => import("../views/AboutView.vue"),
|
||||
},
|
||||
],
|
||||
routes: routesConfig,
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import { IconDashboard } from "@/core/icons";
|
||||
|
||||
declare interface MenuGroupType {
|
||||
name?: string;
|
||||
items: MenuItemType[];
|
||||
}
|
||||
|
||||
declare interface MenuItemType {
|
||||
name: string;
|
||||
path: string;
|
||||
icon?: string;
|
||||
meta?: Record<string, unknown>;
|
||||
children?: MenuItemType[];
|
||||
}
|
||||
|
||||
export const menus: MenuGroupType[] = [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
name: "仪表盘",
|
||||
path: "/dashboard",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "内容",
|
||||
items: [
|
||||
{
|
||||
name: "文章",
|
||||
path: "/posts",
|
||||
icon: IconDashboard,
|
||||
children: [
|
||||
{
|
||||
name: "分类",
|
||||
path: "/categories",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
name: "标签",
|
||||
path: "/tags",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "页面",
|
||||
path: "/sheets",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
name: "评论",
|
||||
path: "/comment",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
name: "附件",
|
||||
path: "/attachment",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "外观",
|
||||
items: [
|
||||
{
|
||||
name: "主题",
|
||||
path: "/themes",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
name: "菜单",
|
||||
path: "/menus",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
name: "可视化",
|
||||
path: "/visual",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "系统",
|
||||
items: [
|
||||
{
|
||||
name: "插件",
|
||||
path: "/plugins",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
name: "用户",
|
||||
path: "/users",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
{
|
||||
name: "设置",
|
||||
path: "/settings",
|
||||
icon: IconDashboard,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export type { MenuItemType, MenuGroupType };
|
||||
|
||||
export default menus;
|
|
@ -0,0 +1,18 @@
|
|||
import type { RouteRecordRaw } from "vue-router";
|
||||
|
||||
export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: () => import("../views/HomeView.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "/about",
|
||||
name: "about",
|
||||
component: () => import("../views/AboutView.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
|
@ -3,10 +3,11 @@ import { fileURLToPath, URL } from "url";
|
|||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import icons from "unplugin-icons/vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()],
|
||||
plugins: [vue(), vueJsx(), icons()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
|
|
Loading…
Reference in New Issue