重构组件结构,优化导入路径并移除不必要的组件,新增多个模态框组件以支持部门、角色、权限和用户管理功能

This commit is contained in:
Chuck1sn
2025-06-16 15:09:52 +08:00
parent 28eed04823
commit ca42fbbda9
51 changed files with 2980 additions and 2881 deletions

View File

@@ -0,0 +1,55 @@
<template>
<nav class="flex mb-4" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 sm:space-x-2 text-sm">
<li class="inline-flex items-center">
<RouterLink :to="Routes.HOME.fullPath()"
class="inline-flex items-center font-medium text-gray-500 hover:text-blue-600">
<svg class="w-3.5 h-3.5 mr-1.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 20 20">
<path
d="m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z" />
</svg>
首页
</RouterLink>
</li>
<li v-for="(item, index) in breadcrumbs" :key="index">
<div class="flex items-center">
<svg class="w-3 h-3 text-gray-400 mx-1.5 sm:mx-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 6 10">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m1 9 4-4-4-4" />
</svg>
<RouterLink v-if="item.route" :to="item.route" class="font-medium text-gray-500 hover:text-blue-600 truncate">
{{ item.name }}
</RouterLink>
<span v-else class="font-medium text-gray-500 truncate">{{ item.name }}</span>
</div>
</li>
</ol>
</nav>
</template>
<script setup lang="ts">
import { Routes } from "@/router/constants";
import { computed } from "vue";
import type { RouteLocationRaw } from "vue-router";
interface BreadcrumbItem {
name: string;
route?: RouteLocationRaw;
}
const props = defineProps<{
names: string[];
routes?: RouteLocationRaw[];
}>();
const breadcrumbs = computed<BreadcrumbItem[]>(() => {
return props.names.map((name, index) => {
return {
name,
route: props.routes?.[index],
};
});
});
</script>

View File

@@ -0,0 +1,38 @@
<template>
<Headbar :changeAssistantVisible="changeAssistantVisible" :onSidebarToggle="toggleSidebar"></Headbar>
<Sidebar ref="sidebarRef" @menu-click="handleMenuClick"></Sidebar>
<div class="flex flex-row h-[calc(100vh-3.5rem)] mt-14">
<article class="flex-1 sm:ml-44 overflow-y-auto">
<RouterView></RouterView>
</article>
<Assistant v-if="isAssistantVisible"></Assistant>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { RouterView } from "vue-router";
import Assistant from "@/components/common/Assistant.vue";
import Headbar from "./Headbar.vue";
import Sidebar from "./Sidebar.vue";
const isAssistantVisible = ref(false);
const sidebarRef = ref();
const changeAssistantVisible = () => {
isAssistantVisible.value = !isAssistantVisible.value;
};
const toggleSidebar = () => {
if (sidebarRef.value) {
sidebarRef.value.toggleSidebar();
}
};
const handleMenuClick = () => {
if (sidebarRef.value) {
sidebarRef.value.closeSidebar();
}
};
</script>

View File

@@ -0,0 +1,130 @@
<template>
<nav class="fixed top-0 w-full bg-white border-b border-gray-200 z-40">
<div class="px-3 py-3 lg:px-5 lg:pl-3">
<div class="flex items-center justify-between">
<div class="flex items-center justify-start rtl:justify-end">
<button type="button" @click="handleSidebarToggle"
class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100">
<span class="sr-only">Open sidebar</span>
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" fill-rule="evenodd"
d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z">
</path>
</svg>
</button>
<a href="https://github.com/ccmjga/zhilu-admin" target="_blank" class="flex items-center ms-2 md:me-24 ">
<img class="me-3" src="/logo.svg" alt="logo">
<span class="self-center text-lg sm:text-xl md:text-2xl font-semibold whitespace-nowrap">知路后台管理</span>
</a>
</div>
<div class="flex items-center space-x-2 sm:space-x-3">
<a href="https://github.com/ccmjga/zhilu-admin" target="_blank"
class="hidden sm:flex items-center border rounded-sm border-gray-300">
<span class="bg-gray-200 rounded-r-none border-r border-r-gray-300">
<svg class="me-0.5 inline pl-1.5 pb-1 w-6 h-6 text-gray-800 bg-gray-200" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-width="2"
d="M11.083 5.104c.35-.8 1.485-.8 1.834 0l1.752 4.022a1 1 0 0 0 .84.597l4.463.342c.9.069 1.255 1.2.556 1.771l-3.33 2.723a1 1 0 0 0-.337 1.016l1.03 4.119c.214.858-.71 1.552-1.474 1.106l-3.913-2.281a1 1 0 0 0-1.008 0L7.583 20.8c-.764.446-1.688-.248-1.474-1.106l1.03-4.119A1 1 0 0 0 6.8 14.56l-3.33-2.723c-.698-.571-.342-1.702.557-1.771l4.462-.342a1 1 0 0 0 .84-.597l1.753-4.022Z" />
</svg>
<span class="text-sm pl-0.5 pr-2 font-medium">Star</span>
</span>
<span class="text-sm py-0.5 px-2 font-medium">0.2k</span>
</a>
<button class="cursor-pointer pt-1" @click="changeAssistantVisible">
<AiChatIcon />
</button>
<div class="flex items-center ms-2 sm:ms-3">
<div>
<button type="button" id="dropdown-button" class="flex text-sm rounded-full cursor-pointer"
aria-expanded="false" data-dropdown-toggle="dropdown-user">
<span class="sr-only">打开用户菜单</span>
<Avatar :src="user.avatar" size="sm" />
</button>
</div>
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-sm shadow-sm "
id="dropdown-user">
<div class="px-4 py-3" role="none">
<p class="text-sm font-medium text-gray-900 truncate " role="none">
{{ user.username }}
</p>
</div>
<ul class="py-1" role="none">
<li>
<button @click="() => {
userDropDownMenu?.toggle()
router.push(Routes.SETTINGS.fullPath())
}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 "
role="menuitem">Settings</button>
</li>
<li>
<button @click="() => {
userDropDownMenu?.toggle()
router.push(Routes.USERVIEW.fullPath())
}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 "
role="menuitem">Dashboard</button>
</li>
<li>
<button @click="handleLogoutClick"
class="flex items-center space-x-1 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 "
role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-log-out-icon w-4 h-4 lucide-log-out">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" x2="9" y1="12" y2="12" />
</svg><span>
Sign out
</span>
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { AiChatIcon } from "@/components/icons";
import Avatar from "@/components/ui/Avatar.vue";
import useUserAuth from "@/composables/auth/useUserAuth";
import useUserStore from "@/composables/store/useUserStore";
import { Routes } from "@/router/constants";
import { Dropdown, type DropdownInterface, initFlowbite } from "flowbite";
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
const props = defineProps<{
changeAssistantVisible: () => void;
onSidebarToggle: () => void;
}>();
const handleSidebarToggle = () => {
props.onSidebarToggle();
};
const userDropDownMenu = ref<DropdownInterface>();
const { user } = useUserStore();
const { signOut } = useUserAuth();
const router = useRouter();
const handleLogoutClick = () => {
signOut();
router.push(Routes.LOGIN.path);
};
onMounted(() => {
initFlowbite();
const $dropdownUser = document.getElementById("dropdown-user");
const $dropdownButton = document.getElementById("dropdown-button");
userDropDownMenu.value = new Dropdown(
$dropdownUser,
$dropdownButton,
{},
{ id: "dropdownMenu", override: true },
);
});
</script>

View File

@@ -0,0 +1,148 @@
<template>
<aside id="logo-sidebar"
class="fixed top-0 left-0 px-1 w-44 min-h-screen overflow-y-auto pt-20 transform transition-transform duration-300 ease-in-out bg-white border-r border-gray-200"
:class="[
isDrawerVisible ? 'translate-x-0' : '-translate-x-full sm:translate-x-0',
isDrawerVisible ? 'z-30' : ''
]" aria-label="Sidebar">
<div class="h-full px-3 pb-4 overflow-y-auto bg-white">
<ul class="space-y-2 font-medium">
<li v-for="item in menuItems" :key="item.path">
<RouterLink :to="item.path"
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg hover:bg-gray-100 group"
:class="{ 'bg-gray-100': isActive(item.path) }" @click="handleMenuClick">
<component :is="item.icon"
class="shrink-0 text-gray-500 transition duration-75 group-hover:text-gray-900" />
<span>{{ item.title }}</span>
</RouterLink>
</li>
</ul>
</div>
</aside>
<!-- 遮罩层 -->
<div class="fixed inset-0 bg-gray-900/50 transition-all duration-300 sm:hidden z-20" :class="[
isDrawerVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'
]" @click="closeSidebar">
</div>
</template>
<script setup lang="ts">
import { Routes } from "@/router/constants";
import { initFlowbite } from "flowbite";
import { onMounted, ref } from "vue";
import { RouterLink, useRoute } from "vue-router";
import {
DepartmentIcon,
LlmConfigIcon,
PermissionIcon,
PositionIcon,
RoleIcon,
SchedulerIcon,
SettingsIcon,
UsersIcon,
} from "@/components/icons";
const isDrawerVisible = ref(false);
const emit = defineEmits(["menu-click"]);
// 菜单点击处理
const handleMenuClick = () => {
emit("menu-click");
};
const toggleSidebar = () => {
isDrawerVisible.value = !isDrawerVisible.value;
};
const openSidebar = () => {
isDrawerVisible.value = true;
};
const closeSidebar = () => {
isDrawerVisible.value = false;
};
defineExpose({
toggleSidebar,
openSidebar,
closeSidebar,
isDrawerVisible,
});
// 菜单配置
const menuItems = [
{
title: "用户管理",
path: Routes.USERVIEW.fullPath(),
icon: UsersIcon,
},
{
title: "角色管理",
path: Routes.ROLEVIEW.fullPath(),
icon: RoleIcon,
},
{
title: "权限管理",
path: Routes.PERMISSIONVIEW.fullPath(),
icon: PermissionIcon,
},
{
title: "部门管理",
path: Routes.DEPARTMENTVIEW.fullPath(),
icon: DepartmentIcon,
},
{
title: "岗位管理",
path: Routes.POSITIONVIEW.fullPath(),
icon: PositionIcon,
},
{
title: "个人中心",
path: Routes.SETTINGS.fullPath(),
icon: SettingsIcon,
},
{
title: "定时任务",
path: Routes.SCHEDULERVIEW.fullPath(),
icon: SchedulerIcon,
},
{
title: "大模型管理",
path: Routes.LLMCONFIGVIEW.fullPath(),
icon: LlmConfigIcon,
},
];
const route = useRoute();
const isActive = (path: string) => {
return route.path === path;
};
onMounted(() => {
initFlowbite();
});
</script>
<style scoped>
.invisible {
visibility: hidden;
}
.visible {
visibility: visible;
}
/* 添加移动端样式 */
@media (max-width: 640px) {
.translate-x-0 {
transform: translateX(0);
}
.-translate-x-full {
transform: translateX(-100%);
}
}
</style>