create-young-proj 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +21 -21
- package/README.md +13 -2
- package/dist/index.mjs +18 -18
- package/index.mjs +3 -3
- package/package.json +10 -12
- package/template-admin-server/.editorconfig +11 -0
- package/template-admin-server/.nvmrc +1 -0
- package/template-admin-server/.vscode/extensions.json +6 -0
- package/template-admin-server/.vscode/settings.json +4 -0
- package/template-admin-server/README.md +73 -0
- package/template-admin-server/_gitignore +15 -0
- package/template-admin-server/boot.mjs +11 -0
- package/template-admin-server/package.json +60 -0
- package/template-admin-server/rome.json +22 -0
- package/template-admin-server/src/config/config.default.ts +56 -0
- package/template-admin-server/src/configuration.ts +47 -0
- package/template-admin-server/src/controller/admin.controller.ts +397 -0
- package/template-admin-server/src/controller/api.controller.ts +98 -0
- package/template-admin-server/src/controller/base.controller.ts +70 -0
- package/template-admin-server/src/controller/dto/api.ts +47 -0
- package/template-admin-server/src/controller/dto/index.ts +36 -0
- package/template-admin-server/src/controller/dto/menu.ts +41 -0
- package/template-admin-server/src/controller/dto/role.ts +41 -0
- package/template-admin-server/src/controller/dto/user.ts +52 -0
- package/template-admin-server/src/controller/menu.controller.ts +138 -0
- package/template-admin-server/src/controller/role.controller.ts +116 -0
- package/template-admin-server/src/controller/user.controller.ts +108 -0
- package/template-admin-server/src/entities/Api.ts +29 -0
- package/template-admin-server/src/entities/BaseCreate.ts +30 -0
- package/template-admin-server/src/entities/Menu.ts +39 -0
- package/template-admin-server/src/entities/Role.ts +36 -0
- package/template-admin-server/src/entities/User.ts +35 -0
- package/template-admin-server/src/entities/index.ts +10 -0
- package/template-admin-server/src/filter/default.filter.ts +22 -0
- package/template-admin-server/src/filter/notfound.filter.ts +23 -0
- package/template-admin-server/src/middleware/helper.middleware.ts +28 -0
- package/template-admin-server/src/middleware/index.ts +9 -0
- package/template-admin-server/src/middleware/jwt.middleware.ts +32 -0
- package/template-admin-server/src/middleware/report.middleware.ts +26 -0
- package/template-admin-server/src/service/api.service.ts +174 -0
- package/template-admin-server/src/service/basic.ts +118 -0
- package/template-admin-server/src/service/index.ts +10 -0
- package/template-admin-server/src/service/menu.service.ts +139 -0
- package/template-admin-server/src/service/role.service.ts +286 -0
- package/template-admin-server/src/service/user.service.ts +124 -0
- package/template-admin-server/src/strategy/jwt.strategy.ts +26 -0
- package/template-admin-server/src/types/index.ts +42 -0
- package/template-admin-server/src/types/types.d.ts +31 -0
- package/template-admin-server/tsconfig.json +24 -0
- package/template-vue-admin/.vscode/extensions.json +10 -0
- package/template-vue-admin/.vscode/list-add.code-snippets +108 -0
- package/template-vue-admin/.vscode/list-export.code-snippets +72 -0
- package/template-vue-admin/.vscode/list.code-snippets +61 -0
- package/template-vue-admin/.vscode/settings.json +7 -0
- package/template-vue-admin/Dockerfile +42 -0
- package/template-vue-admin/README.md +75 -0
- package/template-vue-admin/_env +8 -0
- package/template-vue-admin/_gitignore +30 -0
- package/template-vue-admin/boot.mjs +16 -0
- package/template-vue-admin/config/.devrc +2 -0
- package/template-vue-admin/config/.onlinerc +2 -0
- package/template-vue-admin/config/.testrc +2 -0
- package/template-vue-admin/index.html +21 -0
- package/template-vue-admin/nitro.config.ts +19 -0
- package/template-vue-admin/package.json +50 -0
- package/template-vue-admin/plugins/env.ts +26 -0
- package/template-vue-admin/public/vite.svg +1 -0
- package/template-vue-admin/rome.json +26 -0
- package/template-vue-admin/routes/api/[...all].ts +49 -0
- package/template-vue-admin/routes/get/env.ts +18 -0
- package/template-vue-admin/src/App.vue +14 -0
- package/template-vue-admin/src/apis/delete.ts +36 -0
- package/template-vue-admin/src/apis/get.ts +84 -0
- package/template-vue-admin/src/apis/index.ts +10 -0
- package/template-vue-admin/src/apis/patch.ts +79 -0
- package/template-vue-admin/src/apis/post.ts +77 -0
- package/template-vue-admin/src/assets/img/login_background.jpg +0 -0
- package/template-vue-admin/src/auto-components.d.ts +36 -0
- package/template-vue-admin/src/auto-imports.d.ts +282 -0
- package/template-vue-admin/src/layouts/blank.vue +9 -0
- package/template-vue-admin/src/layouts/default/components/Link.vue +23 -0
- package/template-vue-admin/src/layouts/default/components/Logo.vue +20 -0
- package/template-vue-admin/src/layouts/default/components/Menu.vue +54 -0
- package/template-vue-admin/src/layouts/default/components/NavSearch.vue +52 -0
- package/template-vue-admin/src/layouts/default/components/ScrollPane.vue +79 -0
- package/template-vue-admin/src/layouts/default/components/TagsView.vue +137 -0
- package/template-vue-admin/src/layouts/default/components/TopMenu.vue +21 -0
- package/template-vue-admin/src/layouts/default/components/UserCenter.vue +50 -0
- package/template-vue-admin/src/layouts/default/index.vue +95 -0
- package/template-vue-admin/src/main.ts +44 -0
- package/template-vue-admin/src/modules/1-router.ts +66 -0
- package/template-vue-admin/src/modules/2-pinia.ts +10 -0
- package/template-vue-admin/src/modules/3-net.ts +75 -0
- package/template-vue-admin/src/modules/4-auth.ts +122 -0
- package/template-vue-admin/src/stores/index.ts +9 -0
- package/template-vue-admin/src/stores/local/index.ts +23 -0
- package/template-vue-admin/src/stores/session/index.ts +63 -0
- package/template-vue-admin/src/stores/tags.ts +109 -0
- package/template-vue-admin/src/typings/global.d.ts +70 -0
- package/template-vue-admin/src/typings/index.ts +50 -0
- package/template-vue-admin/src/views/403.vue +32 -0
- package/template-vue-admin/src/views/[...all_404].vue +556 -0
- package/template-vue-admin/src/views/base/login.vue +193 -0
- package/template-vue-admin/src/views/dashboard/[name].vue +23 -0
- package/template-vue-admin/src/views/index.vue +19 -0
- package/template-vue-admin/src/views/system/api.vue +161 -0
- package/template-vue-admin/src/views/system/hooks/useRole.ts +286 -0
- package/template-vue-admin/src/views/system/menuList.vue +195 -0
- package/template-vue-admin/src/views/system/role.vue +132 -0
- package/template-vue-admin/src/views/system/user.vue +193 -0
- package/template-vue-admin/src/vite-env.d.ts +52 -0
- package/template-vue-admin/tsconfig.json +21 -0
- package/template-vue-admin/tsconfig.node.json +9 -0
- package/template-vue-admin/unocss.config.ts +47 -0
- package/template-vue-admin/vite.config.ts +77 -0
- package/template-vue-thin/package.json +14 -13
- package/template-vue-thin/vite.config.ts +1 -6
@@ -0,0 +1,54 @@
|
|
1
|
+
<!--
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2022-10-26 09:24:29
|
4
|
+
* @LastEditTime: 2023-01-06 16:42:20
|
5
|
+
* @Description:
|
6
|
+
-->
|
7
|
+
<script lang="ts" setup>
|
8
|
+
import { useNavStore } from '@/stores';
|
9
|
+
import Link from './Link.vue';
|
10
|
+
const { NavArr } = useNavStore();
|
11
|
+
const activeIndex = ref('0');
|
12
|
+
const haldleSelected = (index: string) => {
|
13
|
+
activeIndex.value = index;
|
14
|
+
};
|
15
|
+
</script>
|
16
|
+
<template>
|
17
|
+
<ElMenu :default-active="activeIndex" background-color="rgb(48, 65, 86)" text-color="#fff" mode="vertical"
|
18
|
+
style="border-right: none;" router @select="haldleSelected">
|
19
|
+
<div v-for="(nav, index) in NavArr" :key="index + 'adskjgkjer'">
|
20
|
+
<ElSubMenu v-if="NavArr[index]?.children?.filter((n) => +n.visible === 1).length"
|
21
|
+
:index="index + 'adskjgkjer' + nav.component">
|
22
|
+
<template #title>
|
23
|
+
<Link v-if="nav.component" :to="nav.component">
|
24
|
+
<span class="ml-1">{{ nav.title }}</span>
|
25
|
+
</Link>
|
26
|
+
<span v-else class="ml-1">{{ nav.title }}</span>
|
27
|
+
</template>
|
28
|
+
<ElMenuItemGroup>
|
29
|
+
<ElMenuItem style="padding: 0;" v-for="(sub, idx) in NavArr[index].children?.filter((n) => +n.visible === 1)"
|
30
|
+
:key="index + '-' + idx + 'fdjahsuy'" :index="sub.component" :route="sub.component">
|
31
|
+
<template #title>
|
32
|
+
<Link class="block w-full text-left box-border pl-[40px] pr-[20px]" :to="sub.component">
|
33
|
+
<span class="ml-1">{{ sub.title }}</span>
|
34
|
+
</Link>
|
35
|
+
</template>
|
36
|
+
</ElMenuItem>
|
37
|
+
</ElMenuItemGroup>
|
38
|
+
</ElSubMenu>
|
39
|
+
<ElMenuItem v-else :index="nav.component">
|
40
|
+
<template #title>
|
41
|
+
<Link :to="nav.component">
|
42
|
+
<span class="ml-1">{{ nav.title }}</span>
|
43
|
+
</Link>
|
44
|
+
</template>
|
45
|
+
</ElMenuItem>
|
46
|
+
</div>
|
47
|
+
</ElMenu>
|
48
|
+
</template>
|
49
|
+
|
50
|
+
<style>
|
51
|
+
.el-menu-item-group__title {
|
52
|
+
display: none;
|
53
|
+
}
|
54
|
+
</style>
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<!--
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2022-10-26 10:08:07
|
4
|
+
* @LastEditTime: 2023-01-09 10:27:03
|
5
|
+
* @Description:
|
6
|
+
-->
|
7
|
+
<script lang="ts" setup>
|
8
|
+
import type { SelectOptionItem } from '@bluesyoung/ui-vue3-element-plus';
|
9
|
+
import { YoungCmdPopup } from '@bluesyoung/ui-vue3';
|
10
|
+
import { useNavStore } from '@/stores';
|
11
|
+
|
12
|
+
const { FlatNavArr } = useNavStore();
|
13
|
+
|
14
|
+
const options = ref<SelectOptionItem<string>[]>([]);
|
15
|
+
const searchStr = ref('');
|
16
|
+
const remoteMethod = (query: string) => {
|
17
|
+
if (query) {
|
18
|
+
options.value = FlatNavArr.value
|
19
|
+
.filter((item) => +item.visible === 1 && item.title?.toLowerCase().includes(query.toLowerCase()))
|
20
|
+
.map((item) => ({ label: item.title || '', value: item.component }));
|
21
|
+
} else {
|
22
|
+
options.value = [];
|
23
|
+
}
|
24
|
+
};
|
25
|
+
|
26
|
+
const router = useRouter();
|
27
|
+
|
28
|
+
const cmdRef = ref()
|
29
|
+
|
30
|
+
const goPage = (url: string) => {
|
31
|
+
if (url) {
|
32
|
+
router.push(url);
|
33
|
+
searchStr.value = '';
|
34
|
+
cmdRef.value?.hide()
|
35
|
+
}
|
36
|
+
};
|
37
|
+
</script>
|
38
|
+
<template>
|
39
|
+
<YoungCmdPopup ref="cmdRef">
|
40
|
+
<template #default="{ el }">
|
41
|
+
<div class="flex flex-col items-center">
|
42
|
+
<div class="text-xl mb-4">
|
43
|
+
快捷菜单搜索:
|
44
|
+
</div>
|
45
|
+
<ElSelect :ref="el" class="w-260px" v-model="searchStr" filterable remote reserve-keyword
|
46
|
+
placeholder="请输入菜单关键字,快捷键 Ctrl + K" :remote-method="remoteMethod" @change="goPage">
|
47
|
+
<ElOption v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
48
|
+
</ElSelect>
|
49
|
+
</div>
|
50
|
+
</template>
|
51
|
+
</YoungCmdPopup>
|
52
|
+
</template>
|
@@ -0,0 +1,79 @@
|
|
1
|
+
<!--
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2020-12-11 11:02:54
|
4
|
+
* @LastEditTime: 2023-01-05 16:07:55
|
5
|
+
* @Description: 滚动容器
|
6
|
+
-->
|
7
|
+
<template>
|
8
|
+
<div ref="scrollWrapper" class="max-w-[calc(90vw)] flex-nowrap whitespace-nowrap overflow-x-auto"
|
9
|
+
@wheel.prevent="scrollHandler">
|
10
|
+
<span>
|
11
|
+
<slot />
|
12
|
+
</span>
|
13
|
+
</div>
|
14
|
+
</template>
|
15
|
+
<script lang="ts" setup>
|
16
|
+
type EL = Element | null;
|
17
|
+
/**
|
18
|
+
* 标签间距
|
19
|
+
*/
|
20
|
+
const tagSpacing = 4;
|
21
|
+
const scrollWrapper = ref<HTMLElement>();
|
22
|
+
// 滚动鼠标滚轮
|
23
|
+
const scrollHandler = (e: WheelEvent) => {
|
24
|
+
const eventData = e.deltaY * 40;
|
25
|
+
const wrap = scrollWrapper.value;
|
26
|
+
if (wrap) {
|
27
|
+
wrap.scrollLeft = wrap.scrollLeft + eventData / 4;
|
28
|
+
}
|
29
|
+
};
|
30
|
+
const moveToTarget = () => {
|
31
|
+
if (scrollWrapper.value) {
|
32
|
+
// 外层包裹
|
33
|
+
const wrap: HTMLElement = scrollWrapper.value as any;
|
34
|
+
// 容器宽度
|
35
|
+
const containerWidth = 1704;
|
36
|
+
// 标签列表
|
37
|
+
const tagList: HTMLCollection = (wrap.firstElementChild as HTMLElement)?.children;
|
38
|
+
|
39
|
+
let firstChild: EL = null, lastChild: EL = null, targetElement: EL = null;
|
40
|
+
if (tagList.length > 0) {
|
41
|
+
firstChild = tagList[0];
|
42
|
+
lastChild = tagList[tagList.length - 1];
|
43
|
+
for (const element of Array.from(tagList)) {
|
44
|
+
if (Array.from(element.classList).includes('el-tag--success')) {
|
45
|
+
targetElement = element;
|
46
|
+
break;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
if (firstChild === targetElement) {
|
51
|
+
wrap.scrollLeft = 0;
|
52
|
+
} else if (lastChild === targetElement) {
|
53
|
+
wrap.scrollLeft = wrap.scrollWidth - containerWidth;
|
54
|
+
} else {
|
55
|
+
const currentIndex = Array.from(tagList).findIndex((item) => item === targetElement);
|
56
|
+
|
57
|
+
const preElement = tagList[currentIndex - 1];
|
58
|
+
const nextElement = tagList[currentIndex + 1];
|
59
|
+
if (!preElement || !nextElement) {
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
|
63
|
+
const afterNextTagOffsetLeft = (nextElement as HTMLElement).offsetLeft + (nextElement as HTMLElement).offsetWidth + tagSpacing;
|
64
|
+
const beforePrevTagOffsetLeft = (preElement as HTMLElement).offsetLeft - tagSpacing;
|
65
|
+
|
66
|
+
if (afterNextTagOffsetLeft > wrap.scrollLeft + containerWidth) {
|
67
|
+
wrap.scrollLeft = afterNextTagOffsetLeft - containerWidth;
|
68
|
+
} else if (beforePrevTagOffsetLeft < wrap.scrollLeft) {
|
69
|
+
wrap.scrollLeft = beforePrevTagOffsetLeft;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
} else {
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
};
|
77
|
+
// 暴露给其父级组件通过 ref 使用
|
78
|
+
defineExpose({ moveToTarget });
|
79
|
+
</script>
|
@@ -0,0 +1,137 @@
|
|
1
|
+
<!--
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2020-12-11 13:35:58
|
4
|
+
* @LastEditTime: 2023-01-05 16:00:11
|
5
|
+
* @Description: 标签选项卡组件
|
6
|
+
-->
|
7
|
+
<template>
|
8
|
+
<div class="tags-view-container px-2">
|
9
|
+
<ScrollPane ref="scrollPane" class="tags-view-wrapper">
|
10
|
+
<ElTag v-for="(tag, index) in visitedViews" :key="index + 'fadsrhewioru'" effect="dark" size="large"
|
11
|
+
class="inline-block mx-1 my-0.2vh hover:cursor-pointer" :type="isActive(tag) ? 'success' : 'info'"
|
12
|
+
:closable="!tag.meta.affix && visitedViews.length > 1" @click="router.push(tag.fullPath)"
|
13
|
+
@close.stop="menuHandlers['closeThis'](tag)" @contextmenu.prevent="openContextMenu(tag, $event)">
|
14
|
+
{{ tag.meta.title }}
|
15
|
+
</ElTag>
|
16
|
+
</ScrollPane>
|
17
|
+
|
18
|
+
<YoungContextMenu v-model="showContextMenu" :menu-list="menuList" @clickItem="clickItemHandler" />
|
19
|
+
</div>
|
20
|
+
</template>
|
21
|
+
<script lang="ts" setup>
|
22
|
+
import { YoungContextMenu } from '@bluesyoung/ui-vue3';
|
23
|
+
import ScrollPane from './ScrollPane.vue';
|
24
|
+
import { storeToRefs } from 'pinia';
|
25
|
+
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
|
26
|
+
import { useTagsStore } from '@/stores';
|
27
|
+
|
28
|
+
const showContextMenu = ref(false);
|
29
|
+
|
30
|
+
const tagsState = useTagsStore();
|
31
|
+
const { visitedViews, cachedViews } = storeToRefs(tagsState);
|
32
|
+
const menuList = computed(() => {
|
33
|
+
let baseMenu = [
|
34
|
+
{ title: '关闭此页面', handlerName: 'closeThis' },
|
35
|
+
{ title: '关闭其他页面', handlerName: 'closeOthers' }
|
36
|
+
];
|
37
|
+
if (visitedViews.value.length === 1) {
|
38
|
+
baseMenu = [];
|
39
|
+
}
|
40
|
+
const { name } = route;
|
41
|
+
const s_name = selectedTag.value?.name ?? '';
|
42
|
+
// 当前页为激活状态才允许刷新
|
43
|
+
const res = (cachedViews.value.includes(s_name) && name === s_name) ? [{ title: '刷新此页面', handlerName: 'refresh' }].concat(baseMenu) : baseMenu;
|
44
|
+
return res;
|
45
|
+
});
|
46
|
+
const router = useRouter();
|
47
|
+
const route = useRoute();
|
48
|
+
/**
|
49
|
+
* 当前选中的标签
|
50
|
+
*/
|
51
|
+
const selectedTag = ref<RouteLocationNormalized | null>(null);
|
52
|
+
/**
|
53
|
+
* 所有固定的标签(禁止关闭)
|
54
|
+
*/
|
55
|
+
const affixTags = ref<RouteLocationNormalized[]>([]);
|
56
|
+
|
57
|
+
const isActive = (view: RouteLocationNormalized) => view.path === route.path;
|
58
|
+
const isAffix = (route: RouteLocationNormalized | RouteRecordRaw) => route?.meta?.affix ?? false;
|
59
|
+
|
60
|
+
const toLastView = (visitedViews: RouteLocationNormalized[], view: RouteLocationNormalized) => {
|
61
|
+
const lastView = visitedViews.slice(-1)[0];
|
62
|
+
if (lastView) {
|
63
|
+
router.push(lastView.fullPath);
|
64
|
+
}
|
65
|
+
};
|
66
|
+
|
67
|
+
const scrollPane = ref<any | null>(null);
|
68
|
+
|
69
|
+
const moveToCurrentTag = () => {
|
70
|
+
nextTick(() => {
|
71
|
+
const SP = scrollPane.value;
|
72
|
+
SP?.moveToTarget?.();
|
73
|
+
});
|
74
|
+
};
|
75
|
+
|
76
|
+
const menuHandlers: Record<string, Function> = {
|
77
|
+
'refresh': () => {
|
78
|
+
const tag = selectedTag.value;
|
79
|
+
tag && tagsState.delCachedView(tag);
|
80
|
+
},
|
81
|
+
'closeThis': (tag = selectedTag.value) => {
|
82
|
+
if (tag) {
|
83
|
+
if (!isAffix(tag)) {
|
84
|
+
tagsState.delView(tag);
|
85
|
+
isActive(tag) && toLastView(visitedViews.value, tag);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
},
|
89
|
+
'closeOthers': () => {
|
90
|
+
const tag = selectedTag.value;
|
91
|
+
if (tag) {
|
92
|
+
router.push(tag);
|
93
|
+
tagsState.delOtherViews(tag);
|
94
|
+
}
|
95
|
+
},
|
96
|
+
'closeAll': () => {
|
97
|
+
tagsState.delAllViews();
|
98
|
+
const sTag = selectedTag.value;
|
99
|
+
if (sTag) {
|
100
|
+
if (affixTags.value.some((tag) => tag.path === sTag.path)) {
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
toLastView(visitedViews.value, sTag);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
};
|
107
|
+
const clickItemHandler = (handler: string) => {
|
108
|
+
menuHandlers?.[handler]?.();
|
109
|
+
showContextMenu.value = false;
|
110
|
+
};
|
111
|
+
const openContextMenu = (tag: RouteLocationNormalized, e: MouseEvent) => {
|
112
|
+
nextTick(() => {
|
113
|
+
if (menuList.value.length === 0) {
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
showContextMenu.value = true;
|
117
|
+
selectedTag.value = tag;
|
118
|
+
});
|
119
|
+
};
|
120
|
+
|
121
|
+
router.afterEach((to) => {
|
122
|
+
tagsState.addView(to);
|
123
|
+
moveToCurrentTag();
|
124
|
+
});
|
125
|
+
|
126
|
+
onMounted(() => {
|
127
|
+
tagsState.addView(route);
|
128
|
+
});
|
129
|
+
</script>
|
130
|
+
|
131
|
+
<style lang="scss" scoped>
|
132
|
+
.tags-view-container {
|
133
|
+
background: #fff;
|
134
|
+
border-bottom: 1px solid #d8dce5;
|
135
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
|
136
|
+
}
|
137
|
+
</style>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<!--
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2022-12-07 14:13:25
|
4
|
+
* @LastEditTime: 2023-01-05 16:08:30
|
5
|
+
* @Description:
|
6
|
+
-->
|
7
|
+
<script lang="ts" setup>
|
8
|
+
import { useNavStore, TopIndex } from '@/stores';
|
9
|
+
|
10
|
+
const { TopMenu } = useNavStore();
|
11
|
+
</script>
|
12
|
+
<template>
|
13
|
+
<div>
|
14
|
+
<ElMenu mode="horizontal" :default-active="TopIndex" :ellipsis="(TopMenu.length > 10)">
|
15
|
+
<ElMenuItem v-for="(nav, index) in TopMenu" :key="index + 'fdsjakr'" :index="index + ''"
|
16
|
+
@click="(TopIndex = index + '')">
|
17
|
+
{{ nav.title }}
|
18
|
+
</ElMenuItem>
|
19
|
+
</ElMenu>
|
20
|
+
</div>
|
21
|
+
</template>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<!--
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2020-12-10 11:30:30
|
4
|
+
* @LastEditTime: 2023-01-05 14:16:52
|
5
|
+
* @Description: 顶部导航栏组件
|
6
|
+
-->
|
7
|
+
<script lang="ts" setup>
|
8
|
+
import NavSearch from './NavSearch.vue';
|
9
|
+
import TopMenu from './TopMenu.vue';
|
10
|
+
import { useUserStore, useNavStore, removeToken } from '@/stores';
|
11
|
+
|
12
|
+
const router = useRouter();
|
13
|
+
const { CurrUserInfo } = useUserStore();
|
14
|
+
|
15
|
+
const loginOut = async () => {
|
16
|
+
removeToken();
|
17
|
+
useUserStore().reset();
|
18
|
+
useNavStore().reset();
|
19
|
+
router.replace('/base/login');
|
20
|
+
};
|
21
|
+
</script>
|
22
|
+
|
23
|
+
<template>
|
24
|
+
<!-- 快捷菜单 -->
|
25
|
+
<NavSearch />
|
26
|
+
<div v-bind="$attrs" class="flex w-full justify-between px-5">
|
27
|
+
<!-- 顶部导航栏 -->
|
28
|
+
<TopMenu />
|
29
|
+
<!-- 右侧个人中心下拉框 -->
|
30
|
+
<ElDropdown class="avatar-container" trigger="click">
|
31
|
+
<div class="inline-flex justify-center items-center mx-2 hover:cursor-pointer">
|
32
|
+
<ElAvatar v-if="CurrUserInfo.avatar" :src="CurrUserInfo.avatar" />
|
33
|
+
<ElAvatar v-else color="transparent">
|
34
|
+
<div class="text-xl i-noto-v1-man-technologist-light-skin-tone" />
|
35
|
+
</ElAvatar>
|
36
|
+
|
37
|
+
<div class="text-sm ml-2">{{ CurrUserInfo.nickname }}</div>
|
38
|
+
<div class="mr-2 text-sm i-ion-md-arrow-dropdown" />
|
39
|
+
</div>
|
40
|
+
<template #dropdown>
|
41
|
+
<ElDropdownMenu class="user-dropdown">
|
42
|
+
<ElDropdownItem @click="loginOut">
|
43
|
+
<div>退出登录</div>
|
44
|
+
</ElDropdownItem>
|
45
|
+
</ElDropdownMenu>
|
46
|
+
</template>
|
47
|
+
</ElDropdown>
|
48
|
+
</div>
|
49
|
+
</template>
|
50
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
<!--
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2022-10-25 17:43:30
|
4
|
+
* @LastEditTime: 2023-01-09 09:08:54
|
5
|
+
* @Description:
|
6
|
+
-->
|
7
|
+
<script lang="ts" setup>
|
8
|
+
import Logo from './components/Logo.vue';
|
9
|
+
import Menu from './components/Menu.vue';
|
10
|
+
import UserCenter from './components/UserCenter.vue';
|
11
|
+
import TagsView from './components/TagsView.vue';
|
12
|
+
import { useTagsStore } from '@/stores';
|
13
|
+
|
14
|
+
const { cachedViews, visitedViews } = storeToRefs(useTagsStore());
|
15
|
+
const route = useRoute();
|
16
|
+
const path = computed(() => route.path);
|
17
|
+
const name = computed(() => route.name);
|
18
|
+
const isCached = computed(() => {
|
19
|
+
// 无需缓存的界面
|
20
|
+
if (route.meta.noCache) {
|
21
|
+
return false;
|
22
|
+
}
|
23
|
+
const arr = visitedViews.value.map((view) => view.name);
|
24
|
+
// 已经缓存,或者首次打开(防止二次初始化)
|
25
|
+
return cachedViews.value.includes(name.value) || !arr.includes(name.value as unknown as string);
|
26
|
+
});
|
27
|
+
</script>
|
28
|
+
<template>
|
29
|
+
<div class="parent">
|
30
|
+
<div class="left">
|
31
|
+
<div class="logo">
|
32
|
+
<Logo />
|
33
|
+
</div>
|
34
|
+
<div class="menu">
|
35
|
+
<Menu />
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
<div class="right">
|
39
|
+
<div class="top">
|
40
|
+
<UserCenter />
|
41
|
+
</div>
|
42
|
+
<div class="tags">
|
43
|
+
<TagsView />
|
44
|
+
</div>
|
45
|
+
<div class="main">
|
46
|
+
<RouterView v-slot="{ Component: PageComponent }">
|
47
|
+
<Transition name="fade-transform" mode="out-in">
|
48
|
+
<div>
|
49
|
+
<KeepAlive>
|
50
|
+
<Component v-if="isCached" :is="PageComponent" :key="path" />
|
51
|
+
</KeepAlive>
|
52
|
+
<Component v-if="!isCached" :is="PageComponent" :key="path" />
|
53
|
+
</div>
|
54
|
+
</Transition>
|
55
|
+
</RouterView>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
</template>
|
60
|
+
|
61
|
+
<style lang="scss" scoped>
|
62
|
+
.parent {
|
63
|
+
@apply w-100vw h-100vh overflow-auto flex;
|
64
|
+
|
65
|
+
.left {
|
66
|
+
@apply h-full flex flex-col w-210px;
|
67
|
+
background-color: rgb(48, 65, 86);
|
68
|
+
|
69
|
+
.logo {
|
70
|
+
@apply w-full h-60px flex justify-center items-center bg-[#2b2f3a] text-white;
|
71
|
+
}
|
72
|
+
|
73
|
+
.menu {
|
74
|
+
@apply flex-1 overflow-y-auto overflow-x-hidden;
|
75
|
+
background-color: rgb(48, 65, 86);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
.right {
|
80
|
+
@apply flex-1;
|
81
|
+
|
82
|
+
.top {
|
83
|
+
@apply h-60px w-full flex items-center;
|
84
|
+
}
|
85
|
+
|
86
|
+
.tags {
|
87
|
+
@apply h-30px w-full relative z-10;
|
88
|
+
}
|
89
|
+
|
90
|
+
.main {
|
91
|
+
@apply p-5 h-[calc(100vh-100px)] w-[calc(100vw-210px)] overflow-auto;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
</style>
|
@@ -0,0 +1,44 @@
|
|
1
|
+
/*
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2022-12-03 15:57:40
|
4
|
+
* @LastEditTime: 2023-01-04 14:30:48
|
5
|
+
* @Description:
|
6
|
+
*/
|
7
|
+
// polyfill
|
8
|
+
import 'core-js/stable';
|
9
|
+
import 'regenerator-runtime/runtime';
|
10
|
+
// 统一浏览器样式
|
11
|
+
import '@unocss/reset/tailwind.css';
|
12
|
+
import 'uno.css';
|
13
|
+
import 'element-plus/dist/index.css';
|
14
|
+
|
15
|
+
import { createApp } from 'vue';
|
16
|
+
import App from './App.vue';
|
17
|
+
|
18
|
+
(async () => {
|
19
|
+
// 获取环境变量
|
20
|
+
let viteEnv;
|
21
|
+
// 注入此环境变量,可以兼容现有的部署方式
|
22
|
+
if (import.meta.env.VITE_USE_DEFAULT_DEPLOY_METHOD) {
|
23
|
+
viteEnv = import.meta.env;
|
24
|
+
console.log('🚀 ~ file: main.ts ~ line 19 ~ viteEnv', viteEnv);
|
25
|
+
} else if (import.meta.env.DEV) {
|
26
|
+
// 开发环境,内网其他设备访问请切换为局域网 ip
|
27
|
+
// 端口号与环境变量的 SERVER_PORT 保持一致
|
28
|
+
viteEnv = await (await fetch(`http://127.0.0.1:3000/get/env`)).json();
|
29
|
+
console.log('🚀 ~ file: main.ts ~ line 24 ~ viteEnv', viteEnv);
|
30
|
+
} else {
|
31
|
+
// 部署环境,需要配合 nginx 使用
|
32
|
+
viteEnv = await (await fetch(`/get/env`)).json();
|
33
|
+
console.log('🚀 ~ file: main.ts ~ line 28 ~ viteEnv', viteEnv);
|
34
|
+
}
|
35
|
+
window.__YOUNG_VITE_ENV__ = viteEnv;
|
36
|
+
|
37
|
+
const app = createApp(App);
|
38
|
+
Object.values(
|
39
|
+
// 模块按数字命名,确保安装的顺序
|
40
|
+
import.meta.glob<{ install: UserModule }>('./modules/*.ts', { eager: true }),
|
41
|
+
).map(({ install }) => install(app));
|
42
|
+
|
43
|
+
app.mount('#app');
|
44
|
+
})();
|
@@ -0,0 +1,66 @@
|
|
1
|
+
/*
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2023-01-04 10:15:03
|
4
|
+
* @LastEditTime: 2023-01-05 14:26:22
|
5
|
+
* @Description: 路由模块
|
6
|
+
*/
|
7
|
+
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router';
|
8
|
+
import { setupLayouts } from 'virtual:generated-layouts';
|
9
|
+
import routes from '~pages';
|
10
|
+
|
11
|
+
/**
|
12
|
+
* 路由元数据类型扩展
|
13
|
+
*/
|
14
|
+
declare module 'vue-router' {
|
15
|
+
interface RouteMeta {
|
16
|
+
/**
|
17
|
+
* 页面标题
|
18
|
+
*/
|
19
|
+
title: string;
|
20
|
+
/**
|
21
|
+
* 是否固定
|
22
|
+
*/
|
23
|
+
affix?: boolean;
|
24
|
+
/**
|
25
|
+
* 是否禁用缓存
|
26
|
+
*/
|
27
|
+
noCache?: boolean;
|
28
|
+
/**
|
29
|
+
* 鉴权路径,不设置则为白名单页面
|
30
|
+
*/
|
31
|
+
authPath?: string;
|
32
|
+
/**
|
33
|
+
* 页面布局,对应 layouts 目录下的布局页面,不写默认为 default/index
|
34
|
+
*/
|
35
|
+
layout?: 'blank';
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
export const finalRoutes = setupLayouts(routes);
|
40
|
+
export const navMap = new Map<string, string>();
|
41
|
+
|
42
|
+
/**
|
43
|
+
* 生成权限节点映射表
|
44
|
+
*/
|
45
|
+
const generateNavMap = (raw: RouteRecordRaw[], base = '') => {
|
46
|
+
for (const route of raw) {
|
47
|
+
if (route.children) {
|
48
|
+
generateNavMap(route.children, route.path);
|
49
|
+
} else {
|
50
|
+
const meta = route.meta;
|
51
|
+
if (meta && meta.authPath) {
|
52
|
+
navMap.set(meta.authPath, `${base}${route.path.startsWith('/') ? '' : '/'}${route.path}`);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
};
|
57
|
+
generateNavMap(finalRoutes);
|
58
|
+
|
59
|
+
export const router = createRouter({
|
60
|
+
history: createWebHashHistory(),
|
61
|
+
routes: finalRoutes,
|
62
|
+
});
|
63
|
+
|
64
|
+
export const install: UserModule = (app) => {
|
65
|
+
app.use(router);
|
66
|
+
};
|
@@ -0,0 +1,75 @@
|
|
1
|
+
/*
|
2
|
+
* @Author: zhangyang
|
3
|
+
* @Date: 2022-03-01 14:01:31
|
4
|
+
* @LastEditTime: 2023-01-09 09:19:53
|
5
|
+
* @Description: 网络请求
|
6
|
+
*/
|
7
|
+
import { useHttp } from '@bluesyoung/http';
|
8
|
+
import { getToken } from '@/stores';
|
9
|
+
import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
|
10
|
+
import { useGetReq, usePostReq, usePatchReq, useDeleteReq } from '@/apis';
|
11
|
+
|
12
|
+
let loading: LoadingInstance;
|
13
|
+
|
14
|
+
const startLoading = () => {
|
15
|
+
loading = ElLoadingService({
|
16
|
+
lock: true,
|
17
|
+
text: '拼命加载中...',
|
18
|
+
background: 'rgba(0, 0, 0, 0.7)',
|
19
|
+
});
|
20
|
+
};
|
21
|
+
|
22
|
+
const endLoading = () => loading?.close?.();
|
23
|
+
|
24
|
+
export const http = useHttp<ResponseMsg>({
|
25
|
+
timeout: -1,
|
26
|
+
loading: {
|
27
|
+
start: startLoading,
|
28
|
+
end: endLoading,
|
29
|
+
},
|
30
|
+
fail: (err) => {
|
31
|
+
console.log('🚀 ~ file: 3-net.ts:60 ~ err', err, typeof err);
|
32
|
+
if (typeof err === 'string') {
|
33
|
+
// 通用失败,弹出提示信息
|
34
|
+
ElMessage.error(err);
|
35
|
+
}
|
36
|
+
|
37
|
+
if (err instanceof Error) {
|
38
|
+
ElMessage.error(
|
39
|
+
// @ts-ignore 接口出错
|
40
|
+
err?.response?.data?.message || err?.response?.data?.msg || err.message || '网络错误!',
|
41
|
+
);
|
42
|
+
}
|
43
|
+
|
44
|
+
throw err;
|
45
|
+
},
|
46
|
+
checkFn: ({ code, msg, data }) => {
|
47
|
+
if (code === 0) {
|
48
|
+
// 通用成功
|
49
|
+
return data;
|
50
|
+
} else if (code === -1) {
|
51
|
+
// 通用失败
|
52
|
+
throw msg;
|
53
|
+
} else {
|
54
|
+
// 特殊状态码
|
55
|
+
throw code;
|
56
|
+
}
|
57
|
+
},
|
58
|
+
headers: {
|
59
|
+
getAuthHeaders: () => {
|
60
|
+
const token = getToken();
|
61
|
+
return {
|
62
|
+
Authorization: `Bearer ${token}`,
|
63
|
+
};
|
64
|
+
},
|
65
|
+
},
|
66
|
+
});
|
67
|
+
|
68
|
+
export const apis = http.__mixin__({
|
69
|
+
get: useGetReq(),
|
70
|
+
post: usePostReq(),
|
71
|
+
patch: usePatchReq(),
|
72
|
+
delete: useDeleteReq(),
|
73
|
+
});
|
74
|
+
|
75
|
+
export const install: UserModule = (ctx) => {};
|