vue3-admin-gpt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/.env.development +14 -0
  2. package/.env.production +14 -0
  3. package/LICENSE +21 -0
  4. package/README.en.md +106 -0
  5. package/README.md +104 -0
  6. package/build-zip.cjs +53 -0
  7. package/cli.js +110 -0
  8. package/jsconfig.json +9 -0
  9. package/package.json +92 -0
  10. package/public/index.html +20 -0
  11. package/public/robots.txt +2 -0
  12. package/rspack.config.js +282 -0
  13. package/rspack.js +162 -0
  14. package/src/App.vue +9 -0
  15. package/src/api/icon.js +9 -0
  16. package/src/api/router.js +9 -0
  17. package/src/api/table.js +25 -0
  18. package/src/api/tree.js +9 -0
  19. package/src/api/user.js +34 -0
  20. package/src/assets/error_images/401.png +0 -0
  21. package/src/assets/error_images/404.png +0 -0
  22. package/src/assets/error_images/cloud.png +0 -0
  23. package/src/assets/login_images/background.jpg +0 -0
  24. package/src/assets/logo.png +0 -0
  25. package/src/assets/qr_logo/lqr_logo.png +0 -0
  26. package/src/assets/vuejs-fill.svg +4 -0
  27. package/src/components/VabPageHeader/index.vue +133 -0
  28. package/src/config/index.js +7 -0
  29. package/src/config/net.config.js +20 -0
  30. package/src/config/permission.js +136 -0
  31. package/src/config/setting.config.js +62 -0
  32. package/src/config/settings.js +6 -0
  33. package/src/config/theme.config.js +14 -0
  34. package/src/layouts/EmptyLayout.vue +3 -0
  35. package/src/layouts/components/VabAppMain/index.vue +109 -0
  36. package/src/layouts/components/VabAvatar/index.vue +255 -0
  37. package/src/layouts/components/VabBreadcrumb/index.vue +61 -0
  38. package/src/layouts/components/VabFullScreen/index.vue +61 -0
  39. package/src/layouts/components/VabLogo/index.vue +94 -0
  40. package/src/layouts/components/VabNav/index.vue +176 -0
  41. package/src/layouts/components/VabSide/components/VabMenuItem.vue +80 -0
  42. package/src/layouts/components/VabSide/components/VabSideItem.vue +100 -0
  43. package/src/layouts/components/VabSide/components/VabSubmenu.vue +56 -0
  44. package/src/layouts/components/VabSide/index.vue +123 -0
  45. package/src/layouts/components/VabTabs/index.vue +500 -0
  46. package/src/layouts/components/VabTheme/index.vue +603 -0
  47. package/src/layouts/components/VabTop/index.vue +286 -0
  48. package/src/layouts/export.js +29 -0
  49. package/src/layouts/index.vue +339 -0
  50. package/src/main.js +40 -0
  51. package/src/plugins/echarts.js +4 -0
  52. package/src/plugins/index.js +44 -0
  53. package/src/plugins/support.js +16 -0
  54. package/src/router/index.js +400 -0
  55. package/src/store/index.js +26 -0
  56. package/src/store/modules/errorLog.js +27 -0
  57. package/src/store/modules/routes.js +60 -0
  58. package/src/store/modules/settings.js +73 -0
  59. package/src/store/modules/table.js +22 -0
  60. package/src/store/modules/tabsBar.js +109 -0
  61. package/src/store/modules/user.js +131 -0
  62. package/src/styles/element-variables.scss +13 -0
  63. package/src/styles/loading.scss +345 -0
  64. package/src/styles/nav-icons.scss +52 -0
  65. package/src/styles/normalize.scss +353 -0
  66. package/src/styles/spinner/dots.css +68 -0
  67. package/src/styles/spinner/gauge.css +104 -0
  68. package/src/styles/spinner/inner-circles.css +51 -0
  69. package/src/styles/spinner/plus.css +341 -0
  70. package/src/styles/themes/default.scss +1 -0
  71. package/src/styles/transition.scss +18 -0
  72. package/src/styles/vab.scss +476 -0
  73. package/src/styles/variables.scss +69 -0
  74. package/src/utils/accessToken.js +56 -0
  75. package/src/utils/eventBus.js +8 -0
  76. package/src/utils/handleRoutes.js +100 -0
  77. package/src/utils/index.js +231 -0
  78. package/src/utils/message.js +67 -0
  79. package/src/utils/pageTitle.js +11 -0
  80. package/src/utils/password.js +43 -0
  81. package/src/utils/permission.js +19 -0
  82. package/src/utils/request.js +187 -0
  83. package/src/utils/static.js +81 -0
  84. package/src/utils/vab.js +218 -0
  85. package/src/utils/validate.js +48 -0
  86. package/src/views/401.vue +302 -0
  87. package/src/views/404.vue +302 -0
  88. package/src/views/demo/index.vue +591 -0
  89. package/src/views/index/index.vue +1489 -0
  90. package/src/views/login/index.vue +456 -0
  91. package/src/views/register/index.vue +524 -0
  92. package/src/views/vab/calendar.vue +488 -0
  93. package/src/views/vab/campaign.vue +1006 -0
  94. package/src/views/vab/chart.vue +189 -0
  95. package/src/views/vab/customer.vue +666 -0
  96. package/src/views/vab/editor.vue +84 -0
  97. package/src/views/vab/form.vue +151 -0
  98. package/src/views/vab/help.vue +390 -0
  99. package/src/views/vab/icon.vue +113 -0
  100. package/src/views/vab/knowledge.vue +820 -0
  101. package/src/views/vab/nested/menu1/menu2/menu3.vue +29 -0
  102. package/src/views/vab/nested/menu1/menu2.vue +33 -0
  103. package/src/views/vab/nested/menu1.vue +33 -0
  104. package/src/views/vab/nested.vue +97 -0
  105. package/src/views/vab/notification.vue +416 -0
  106. package/src/views/vab/order.vue +507 -0
  107. package/src/views/vab/permissions.vue +214 -0
  108. package/src/views/vab/product.vue +724 -0
  109. package/src/views/vab/project.vue +559 -0
  110. package/src/views/vab/settings.vue +319 -0
  111. package/src/views/vab/statistics.vue +431 -0
  112. package/src/views/vab/table.vue +110 -0
  113. package/src/views/vab/task.vue +613 -0
  114. package/src/views/vab/team.vue +662 -0
  115. package/src/views/vab/tree.vue +44 -0
  116. package/src/views/vab/upload.vue +180 -0
  117. package/src/views/vab/vue3Demo/index.vue +103 -0
  118. package/src/views/vab/workflow.vue +863 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @description 3个子配置,通用配置|主题配置|网络配置导出
3
+ */
4
+ const setting = require('./setting.config')
5
+ const theme = require('./theme.config')
6
+ const network = require('./net.config')
7
+ module.exports = Object.assign({}, setting, theme, network)
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @description 导出默认网路配置
3
+ **/
4
+ const network = {
5
+ // 默认的接口地址,支持通过环境变量配置
6
+ baseURL: process.env.VUE_APP_BASE_API || "/api",
7
+ //配后端数据的接收方式application/json;charset=UTF-8或者application/x-www-form-urlencoded;charset=UTF-8
8
+ contentType: "application/json;charset=UTF-8",
9
+ //消息框消失时间
10
+ messageDuration: 3000,
11
+ //最长请求时间
12
+ requestTimeout: 15000,
13
+ //操作正常code,支持String、Array、int多种类型
14
+ successCode: [200, 0],
15
+ //登录失效code
16
+ invalidCode: 402,
17
+ //无权限code
18
+ noPermissionCode: 401,
19
+ };
20
+ module.exports = network;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @description 路由守卫,目前两种模式:all模式与intelligence模式
3
+ */
4
+ import router from "@/router";
5
+ import store from "@/store";
6
+ import VabProgress from "nprogress";
7
+ import "nprogress/nprogress.css";
8
+ import getPageTitle from "@/utils/pageTitle";
9
+ import {
10
+ authentication,
11
+ loginInterception,
12
+ progressBar,
13
+ recordRoute,
14
+ routesWhiteList,
15
+ } from "@/config";
16
+ import { ElMessage } from "element-plus";
17
+
18
+ VabProgress.configure({
19
+ easing: "ease",
20
+ speed: 500,
21
+ trickleSpeed: 200,
22
+ showSpinner: false,
23
+ });
24
+
25
+ router.beforeEach(async (to, from, next) => {
26
+ if (progressBar) VabProgress.start();
27
+ let hasToken = store.getters["user/accessToken"];
28
+
29
+ if (!loginInterception) hasToken = true;
30
+
31
+ if (hasToken) {
32
+ if (to.path === "/login") {
33
+ next({ path: "/" });
34
+ if (progressBar) VabProgress.done();
35
+ } else {
36
+ const hasPermissions =
37
+ store.getters["user/permissions"] &&
38
+ store.getters["user/permissions"].length > 0;
39
+ if (hasPermissions) {
40
+ next();
41
+ } else {
42
+ try {
43
+ let permissions;
44
+ if (!loginInterception) {
45
+ //settings.js loginInterception为false时,创建虚拟权限
46
+ await store.dispatch("user/setPermissions", ["admin"]);
47
+ permissions = ["admin"];
48
+ } else {
49
+ permissions = await store.dispatch("user/getUserInfo");
50
+ if (!permissions) {
51
+ throw new Error("获取用户权限失败");
52
+ }
53
+ }
54
+
55
+ let accessRoutes = [];
56
+ if (authentication === "intelligence") {
57
+ accessRoutes = await store.dispatch(
58
+ "routes/setRoutes",
59
+ permissions
60
+ );
61
+ } else if (authentication === "all") {
62
+ accessRoutes = await store.dispatch("routes/setAllRoutes");
63
+ }
64
+
65
+ // 确保accessRoutes是数组
66
+ if (!Array.isArray(accessRoutes)) {
67
+ console.error("路由数据格式错误:", accessRoutes);
68
+ accessRoutes = [];
69
+ }
70
+
71
+ // 添加路由
72
+ accessRoutes.forEach((item) => {
73
+ router.addRoute(item);
74
+ });
75
+
76
+ // 确保路由添加完成后,跳转到目标页面
77
+ next({ ...to, replace: true });
78
+ } catch (error) {
79
+ console.error("路由守卫错误:", error);
80
+ ElMessage.error(error.message || "发生错误,请重新登录");
81
+ await store.dispatch("user/resetAccessToken");
82
+ next(`/login?redirect=${to.path}`);
83
+ if (progressBar) VabProgress.done();
84
+ }
85
+ }
86
+ }
87
+ } else {
88
+ // 检查是否有保存的路由信息(页面刷新场景)
89
+ const savedRoute = sessionStorage.getItem('currentRoute');
90
+ if (savedRoute) {
91
+ try {
92
+ const routeInfo = JSON.parse(savedRoute);
93
+ // 如果目标路由不在白名单中,仍然需要登录
94
+ if (routesWhiteList.indexOf(to.path) !== -1) {
95
+ next();
96
+ } else {
97
+ // 检查当前路径是否需要登录
98
+ if (recordRoute) {
99
+ next(`/login?redirect=${to.path}`);
100
+ } else {
101
+ next("/login");
102
+ }
103
+ }
104
+ } catch (e) {
105
+ console.error('解析保存的路由信息失败:', e);
106
+ // 继续正常的登录检查流程
107
+ if (routesWhiteList.indexOf(to.path) !== -1) {
108
+ next();
109
+ } else {
110
+ if (recordRoute) {
111
+ next(`/login?redirect=${to.path}`);
112
+ } else {
113
+ next("/login");
114
+ }
115
+ }
116
+ }
117
+ } else {
118
+ if (routesWhiteList.indexOf(to.path) !== -1) {
119
+ next();
120
+ } else {
121
+ if (recordRoute) {
122
+ next(`/login?redirect=${to.path}`);
123
+ } else {
124
+ next("/login");
125
+ }
126
+ }
127
+ }
128
+
129
+ if (progressBar) VabProgress.done();
130
+ }
131
+ document.title = getPageTitle(to.meta.title);
132
+ });
133
+
134
+ router.afterEach(() => {
135
+ if (progressBar) VabProgress.done();
136
+ });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @description 导出默认通用配置
3
+ */
4
+ const setting = {
5
+ // 生产环境构建文件的目录名
6
+ outputDir: "dist",
7
+ // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
8
+ assetsDir: "static",
9
+ // 开发环境每次保存时是否输出为eslint编译警告
10
+ lintOnSave: true,
11
+ // 进行编译的依赖
12
+ transpileDependencies: [],
13
+ //标题 (包括初次加载雪花屏的标题 页面的标题 浏览器的标题)
14
+ title: "管理系统",
15
+ //简写
16
+ abbreviation: "admin",
17
+ //开发环境端口号
18
+ devPort: "8091",
19
+ //copyright
20
+ copyright: "",
21
+ //是否显示页面底部自定义版权信息
22
+ footerCopyright: true,
23
+ //是否显示顶部进度条
24
+ progressBar: true,
25
+ //缓存路由的最大数量
26
+ keepAliveMaxNum: 99,
27
+ // 路由模式,可选值为 history 或 hash
28
+ routerMode: "hash",
29
+ //不经过token校验的路由
30
+ routesWhiteList: ["/login", "/register", "/404", "/401"],
31
+ //加载时显示文字
32
+ loadingText: "正在加载中...",
33
+ //token名称
34
+ tokenName: "accessToken",
35
+ //token在localStorage、sessionStorage存储的key的名称
36
+ tokenTableName: "admin-scaffold-token",
37
+ //token存储位置localStorage sessionStorage
38
+ storage: "localStorage",
39
+ //token失效回退到登录页时是否记录本次的路由
40
+ recordRoute: true,
41
+ //是否显示logo,不显示时设置false,显示时请填写remixIcon图标名称,暂时只支持设置remixIcon
42
+ logo: "vuejs-fill",
43
+ //是否显示在页面高亮错误
44
+ errorLog: ["development"],
45
+ //是否开启登录拦截
46
+ loginInterception: false,
47
+ //intelligence和all两种方式,前者后端权限只控制permissions不控制view文件的import(前后端配合,减轻后端工作量),all方式完全交给后端前端只负责加载
48
+ authentication: "intelligence",
49
+ //vertical布局时是否只保持一个子菜单的展开
50
+ uniqueOpened: false,
51
+ //vertical布局时默认展开的菜单path,使用逗号隔开建议只展开一个
52
+ defaultOopeneds: ["/vab"],
53
+ //需要加loading层的请求,防止重复提交
54
+ debounce: ["doEdit"],
55
+ //需要自动注入并加载的模块
56
+ providePlugin: {},
57
+ //代码生成机生成在view下的文件夹名称
58
+ templateFolder: "project",
59
+ //是否显示终端donation打印
60
+ donation: false,
61
+ };
62
+ module.exports = setting;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @description 3个子配置,通用配置|主题配置|网络配置
3
+ */
4
+ //默认配置
5
+ const { setting, theme, network } = require('./')
6
+ module.exports = Object.assign({}, setting, theme, network)
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @description 导出默认主题配置
3
+ */
4
+ const theme = {
5
+ //是否国定头部 固定fixed 不固定noFixed
6
+ header: 'fixed',
7
+ //横纵布局 horizontal vertical
8
+ layout: 'vertical',
9
+ //是否开启主题配置按钮
10
+ themeBar: true,
11
+ //是否显示多标签页
12
+ tabsBar: true,
13
+ }
14
+ module.exports = theme
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <router-view />
3
+ </template>
@@ -0,0 +1,109 @@
1
+ <template>
2
+ <div class="app-main-container">
3
+ <router-view v-slot="{ Component }">
4
+ <transition mode="out-in" name="fade-transform">
5
+ <keep-alive :include="cachedRoutes" :max="keepAliveMaxNum">
6
+ <component :is="Component" class="app-main-height" />
7
+ </keep-alive>
8
+ </transition>
9
+ </router-view>
10
+ <!-- <footer v-show="footerCopyright" class="footer-copyright">
11
+ </footer> -->
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ import { mapActions, mapGetters } from "vuex";
17
+ import { copyright, footerCopyright, keepAliveMaxNum, title } from "@/config";
18
+ import { CopyDocument } from "@element-plus/icons-vue";
19
+ import eventBus from "@/utils/eventBus";
20
+
21
+ export default {
22
+ name: "VabAppMain",
23
+ components: {
24
+ CopyDocument,
25
+ },
26
+ data() {
27
+ return {
28
+ show: false,
29
+ fullYear: new Date().getFullYear(),
30
+ copyright,
31
+ title,
32
+ keepAliveMaxNum,
33
+ routerView: true,
34
+ footerCopyright,
35
+ };
36
+ },
37
+ computed: {
38
+ ...mapGetters({
39
+ visitedRoutes: "tabsBar/visitedRoutes",
40
+ device: "settings/device",
41
+ }),
42
+ cachedRoutes() {
43
+ const cachedRoutesArr = [];
44
+ this.visitedRoutes.forEach((item) => {
45
+ if (!item.meta.noKeepAlive) {
46
+ cachedRoutesArr.push(item.name);
47
+ }
48
+ });
49
+ return cachedRoutesArr;
50
+ },
51
+ key() {
52
+ return this.$route.path;
53
+ },
54
+ },
55
+ watch: {
56
+ $route: {
57
+ handler(route) {
58
+ if ("mobile" === this.device) this.foldSideBar();
59
+ },
60
+ immediate: true,
61
+ },
62
+ },
63
+ created() {
64
+ // 监听事件总线中的reload-router-view事件
65
+ eventBus.on("reload-router-view", this.reloadRouterView);
66
+ },
67
+ beforeUnmount() {
68
+ // 组件销毁前移除事件监听
69
+ eventBus.off("reload-router-view", this.reloadRouterView);
70
+ },
71
+ mounted() {},
72
+ methods: {
73
+ ...mapActions({
74
+ foldSideBar: "settings/foldSideBar",
75
+ }),
76
+ // 重新加载路由视图
77
+ reloadRouterView() {
78
+ this.routerView = false;
79
+ this.$nextTick(() => {
80
+ this.routerView = true;
81
+ });
82
+ },
83
+ },
84
+ };
85
+ </script>
86
+
87
+ <style lang="scss" scoped>
88
+ .app-main-container {
89
+ position: relative;
90
+ width: 100%;
91
+ overflow: hidden;
92
+
93
+ .vab-keel {
94
+ margin: $base-padding;
95
+ }
96
+
97
+ .app-main-height {
98
+ min-height: $base-app-main-height;
99
+ }
100
+
101
+ .footer-copyright {
102
+ min-height: 55px;
103
+ line-height: 55px;
104
+ color: rgba(0, 0, 0, 0.45);
105
+ text-align: center;
106
+ border-top: 1px dashed $base-border-color;
107
+ }
108
+ }
109
+ </style>
@@ -0,0 +1,255 @@
1
+ <template>
2
+ <el-dropdown @command="handleCommand" trigger="click">
3
+ <div
4
+ class="avatar-container"
5
+ :class="{ 'horizontal-layout': isHorizontalLayout }"
6
+ >
7
+ <div class="avatar-wrapper">
8
+ <img :src="avatar" alt="用户头像" class="user-avatar" />
9
+ </div>
10
+ <div class="user-info">
11
+ <div class="username">{{ username }}</div>
12
+ <div class="user-role">管理员</div>
13
+ </div>
14
+ <!-- 直接使用图标组件 -->
15
+ <ArrowDown class="avatar-dropdown-icon" />
16
+ </div>
17
+
18
+ <template #dropdown>
19
+ <el-dropdown-menu class="custom-dropdown">
20
+ <div class="dropdown-header">
21
+ <img :src="avatar" alt="用户头像" class="header-avatar" />
22
+ <div class="header-info">
23
+ <div class="header-username">{{ username }}</div>
24
+ <div class="header-email">admin@example.com</div>
25
+ </div>
26
+ </div>
27
+
28
+ <el-dropdown-item command="personalCenter" class="dropdown-item">
29
+ <!-- 直接使用图标组件 -->
30
+ <User class="dropdown-icon" />
31
+ <span>个人中心</span>
32
+ </el-dropdown-item>
33
+
34
+ <el-dropdown-item command="settings" class="dropdown-item">
35
+ <!-- 直接使用图标组件 -->
36
+ <Setting class="dropdown-icon" />
37
+ <span>系统设置</span>
38
+ </el-dropdown-item>
39
+
40
+
41
+ <el-dropdown-item command="logout" class="dropdown-item logout-item">
42
+ <!-- 直接使用图标组件 -->
43
+ <SwitchButton class="dropdown-icon" />
44
+ <span>退出登录</span>
45
+ </el-dropdown-item>
46
+ </el-dropdown-menu>
47
+ </template>
48
+ </el-dropdown>
49
+ </template>
50
+
51
+ <script setup>
52
+ import { computed } from "vue";
53
+ import { useStore } from "vuex";
54
+ import { useRouter, useRoute } from "vue-router";
55
+ import { ElMessage } from "element-plus";
56
+ import { recordRoute } from "@/config";
57
+ import {
58
+ ArrowDown,
59
+ User,
60
+ Setting,
61
+ Link,
62
+ SwitchButton,
63
+ } from "@element-plus/icons-vue";
64
+
65
+ defineOptions({
66
+ name: "VabAvatar",
67
+ });
68
+
69
+ const store = useStore();
70
+ const router = useRouter();
71
+ const route = useRoute();
72
+
73
+ // 计算属性
74
+ const avatar = computed(() => store.getters["user/avatar"]);
75
+ const username = computed(() => store.getters["user/username"]);
76
+ const layout = computed(() => store.getters["settings/layout"]);
77
+ const isHorizontalLayout = computed(() => layout.value === "horizontal");
78
+
79
+ // 方法
80
+ const handleCommand = (command) => {
81
+ switch (command) {
82
+ case "logout":
83
+ logout();
84
+ break;
85
+ case "personalCenter":
86
+ personalCenter();
87
+ break;
88
+ case "settings":
89
+ settings();
90
+ break;
91
+ }
92
+ };
93
+
94
+ const personalCenter = () => {
95
+ router.push("/personalCenter/personalCenter");
96
+ };
97
+
98
+ const settings = () => {
99
+ ElMessage.info("系统设置功能开发中...");
100
+ };
101
+
102
+ const logout = () => {
103
+ store.dispatch("user/logout");
104
+ if (recordRoute) {
105
+ const fullPath = route.fullPath;
106
+ router.push(`/login?redirect=${fullPath}`);
107
+ }
108
+ };
109
+ </script>
110
+
111
+ <style lang="scss" scoped>
112
+ .avatar-container {
113
+ display: flex;
114
+ align-items: center;
115
+ padding: 8px 12px;
116
+ border-radius: 8px;
117
+ cursor: pointer;
118
+
119
+ &.horizontal-layout {
120
+ .user-info {
121
+ .username,
122
+ .user-role {
123
+ color: rgba(255, 255, 255, 0.9) !important;
124
+ }
125
+ }
126
+
127
+ .avatar-dropdown-icon {
128
+ color: rgba(255, 255, 255, 0.9) !important;
129
+ }
130
+ }
131
+
132
+ .avatar-wrapper {
133
+ position: relative;
134
+ margin-right: 12px;
135
+
136
+ .user-avatar {
137
+ width: 40px;
138
+ height: 40px;
139
+ border-radius: 50%;
140
+ object-fit: cover;
141
+ border: 2px solid rgba(255, 255, 255, 0.3);
142
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
143
+ }
144
+ }
145
+
146
+ .user-info {
147
+ flex: 1;
148
+ min-width: 0;
149
+
150
+ .username {
151
+ font-size: 14px;
152
+ font-weight: 600;
153
+ color: #333;
154
+ margin-bottom: 2px;
155
+ white-space: nowrap;
156
+ overflow: hidden;
157
+ text-overflow: ellipsis;
158
+ }
159
+
160
+ .user-role {
161
+ font-size: 12px;
162
+ color: #666;
163
+ opacity: 0.8;
164
+ }
165
+ }
166
+
167
+ .avatar-dropdown-icon {
168
+ margin-left: 5px;
169
+ color: #666;
170
+ }
171
+ }
172
+
173
+ .custom-dropdown {
174
+ border-radius: 12px;
175
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
176
+ border: 1px solid rgba(255, 255, 255, 0.2);
177
+ backdrop-filter: blur(10px);
178
+ background: rgba(255, 255, 255, 0.95);
179
+ padding: 0;
180
+ min-width: 220px;
181
+
182
+ .dropdown-header {
183
+ display: flex;
184
+ align-items: center;
185
+ padding: 16px;
186
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
187
+ background: linear-gradient(135deg, #409EFF 0%, #69C0FF 100%);
188
+ border-radius: 12px 12px 0 0;
189
+ color: white;
190
+
191
+ .header-avatar {
192
+ width: 48px;
193
+ height: 48px;
194
+ border-radius: 50%;
195
+ border: 2px solid rgba(255, 255, 255, 0.3);
196
+ margin-right: 12px;
197
+ object-fit: cover;
198
+ }
199
+
200
+ .header-info {
201
+ flex: 1;
202
+
203
+ .header-username {
204
+ font-size: 16px;
205
+ font-weight: 600;
206
+ margin-bottom: 4px;
207
+ }
208
+
209
+ .header-email {
210
+ font-size: 12px;
211
+ opacity: 0.8;
212
+ }
213
+ }
214
+ }
215
+
216
+ .dropdown-item {
217
+ display: flex;
218
+ align-items: center;
219
+ padding: 6px 12px;
220
+ transition: background-color 0.2s;
221
+
222
+ &.logout-item {
223
+ color: #f56c6c;
224
+ }
225
+ }
226
+
227
+ .el-divider {
228
+ margin: 8px 0;
229
+ }
230
+ }
231
+
232
+ // 响应式设计
233
+ @media (max-width: 768px) {
234
+ .avatar-container {
235
+ padding: 6px 8px;
236
+
237
+ .user-info {
238
+ display: none;
239
+ }
240
+
241
+ .dropdown-icon {
242
+ display: none;
243
+ }
244
+ }
245
+
246
+ .custom-dropdown {
247
+ min-width: 200px;
248
+ }
249
+ }
250
+
251
+ // 隐藏下拉菜单箭头
252
+ :deep() .popper__arrow {
253
+ display: none !important;
254
+ }
255
+ </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <el-breadcrumb class="breadcrumb-container" separator=">">
3
+ <el-breadcrumb-item v-for="item in list" :key="item.path">
4
+ {{ item.meta.title }}
5
+ </el-breadcrumb-item>
6
+ </el-breadcrumb>
7
+ </template>
8
+
9
+ <script>
10
+ export default {
11
+ name: "VabBreadcrumb",
12
+ data() {
13
+ return {
14
+ list: this.getBreadcrumb(),
15
+ };
16
+ },
17
+ watch: {
18
+ $route() {
19
+ this.list = this.getBreadcrumb();
20
+ },
21
+ },
22
+ methods: {
23
+ getBreadcrumb() {
24
+ return this.$route.matched.filter((item) => item.name && item.meta.title);
25
+ },
26
+ },
27
+ };
28
+ </script>
29
+
30
+ <style lang="scss" scoped>
31
+ .breadcrumb-container {
32
+ height: $base-nav-bar-height;
33
+ font-size: $base-font-size-default;
34
+ line-height: $base-nav-bar-height;
35
+
36
+ :deep() {
37
+ .el-breadcrumb__item {
38
+ .el-breadcrumb__inner {
39
+ a {
40
+ display: flex;
41
+ float: left;
42
+ font-weight: normal;
43
+ color: #515a6e;
44
+
45
+ i {
46
+ margin-right: 3px;
47
+ }
48
+ }
49
+ }
50
+
51
+ &:last-child {
52
+ .el-breadcrumb__inner {
53
+ a {
54
+ color: #999;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ </style>