refactor:路由模块优化重构

environments/test/deployments/1
saatana 2 years ago
parent 7bde556c8a
commit 48c8b708da

@ -10,5 +10,5 @@ export default {
/**
* 是否使用本地路由
*/
useLocalRouter: false,
useLocalRouter: true,
};

@ -10,7 +10,6 @@
</template>
<script setup>
const router = useRouter();
const store = useStore();
const asideList = computed(() => store.getters['layout/asideList']);
@ -29,6 +28,7 @@
);
const handleClick = (item) => {
store.commit('layout/setAutoRouter', true);
store.commit('layout/setActiveAside', item.name);
};
</script>

@ -9,6 +9,9 @@
<style lang="less" scoped>
.layout-footer {
width: 100%;
height: @layout-footer-height;
line-height: @layout-footer-height;
flex-shrink: 0;
padding: @layout-space-small 0;
display: flex;
align-items: center;

@ -25,6 +25,7 @@
<style lang="less" scoped>
.layout-header {
width: 100%;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;

@ -1,5 +1,5 @@
<template>
<el-scrollbar class="layout-main" always>
<el-scrollbar class="layout-main" ref="refsScrollbar" max-height="100%">
<RouterView />
</el-scrollbar>
</template>
@ -10,6 +10,7 @@
<style lang="less" scoped>
.layout-main {
flex: 1;
margin: @layout-space-large;
background-color: @color-white;
border-radius: @layout-border-radius;
@ -17,11 +18,11 @@
> :deep(.el-scrollbar__wrap) {
> .el-scrollbar__view {
width: 100%;
height: 100%;
min-height: 100%;
> * {
// display: inline-block; // BFC
width: 100%;
height: 100%;
height: @layout-main-height;
padding: @layout-space-large;
}
}

@ -1,5 +1,5 @@
<template>
<el-menu-item v-if="!menuItem.children?.length" :index="props.menuItem.name" :route="{ name: props.menuItem.name }">
<el-menu-item v-if="!hasChildren" :index="props.menuItem.name" :route="{ name: props.menuItem.name }">
<el-icon :name="props.menuItem.meta.icon" size="20" />
<p>{{ props.menuItem.meta.title }}</p>
</el-menu-item>
@ -19,6 +19,9 @@
required: true,
},
});
const hasChildren = computed(
() => (props.menuItem.children || []).filter((item) => item.meta.hidden !== true).length > 0
);
</script>
<style lang="less" scoped>

@ -14,9 +14,10 @@
const store = useStore();
const activeAsideName = computed(() => store.getters['layout/activeAsideName']);
const activeAside = computed(() => store.state.layout.activeAside);
const menuList = computed(() => store.state.layout.menuList);
const menuList = computed(() => store.getters['layout/menuList']);
const activeMenu = computed(() => store.state.layout.activeMenu);
const collapseMenu = computed(() => store.getters['layout/collapseMenu']);
const autoRouter = computed(() => store.state.layout.autoRouter);
const handleSelect = (index) => {
// TODO
router.push({ name: index });
@ -25,10 +26,15 @@
watch(
() => unref(menuList),
(value) => {
if (value.length === 1) {
handleSelect(value[0].name);
} else if (value.length === 0) {
handleSelect(unref(activeAside));
if (unref(autoRouter)) {
if (value.length === 1) {
console.info('[router] menu push 1 ' + value[0].name);
handleSelect(value[0].name);
} else if (value.length === 0) {
console.info('[router] menu push 0 ' + unref(activeAside));
handleSelect(unref(activeAside));
}
store.commit('layout/setAutoRouter', false);
}
},
{

@ -32,7 +32,8 @@
() => unref(tabList),
(value) => {
if (!unref(activeTab) || value.findIndex((item) => item.name === unref(activeTab)) === -1) {
router.push({ name: value[0].name });
console.info('[router] tabs push ' + value[0].fullPath);
router.push(value[0].fullPath);
}
},
{ immediate: true, deep: true }
@ -67,6 +68,7 @@
<style lang="less" scoped>
.layout-tabs {
width: 100%;
flex-shrink: 0;
height: @layout-tabs-height;
padding: 0 @layout-space-large;
display: flex;
@ -145,7 +147,7 @@
}
}
.operation {
height: 80%;
height: 100%;
margin-left: @layout-space;
display: flex;
align-items: center;

@ -6,6 +6,7 @@ export default [
meta: {
title: '组件示例',
icon: 'home-fill',
layout: true,
},
children: [
{

@ -22,10 +22,10 @@ export const globalRoutes = [
// 示例模块
import demoModule from './demo';
export const demeRoutes = import.meta.env.DEV ? demoModule : [];
export const demoRoutes = import.meta.env.DEV ? demoModule : [];
// 动态模块
const dynamicRoutes = [];
export const dynamicRoutes = [];
const modules = import.meta.globEager('./modules/*.js');
Object.values(modules).forEach((mod) => {
dynamicRoutes.push(...mod.default);
@ -41,6 +41,8 @@ export const routes = [
component: () => import('@/layouts/default.vue'),
meta: {
layout: true,
title: '首页',
icon: 'home-fill',
},
children: [
{
@ -55,6 +57,7 @@ export const routes = [
],
},
...dynamicRoutes,
...demoRoutes,
{
path: '/:pathMatch(.*)*',
redirect: '/404',
@ -100,7 +103,7 @@ router.beforeEach(async (to, from, next) => {
next({ name: childName });
} else {
next();
store.commit('layout/setActiveAside', to.matched.find((item) => !item.meta?.layout).name);
store.commit('layout/setActiveAside', to.matched[0].name);
store.commit('layout/setActiveMenu', to.name);
store.commit('layout/setActiveTab', to.name);
store.commit('layout/setBreakcrumbList', to.matched);
@ -115,23 +118,56 @@ router.beforeEach(async (to, from, next) => {
});
export default router;
export const reset = (routers) => {
/**
* 多级嵌套路由时由于父级路由页面中没有router-view组件所以子级路由页面无法显示
* 所以需要将路由规则展平处理让所有页面路由规则都是平级
* @param {*} routes
* @param {*} parent
*/
const flatRoutes = (routes, parent = { path: '/', meta: {} }) =>
routes.flatMap((route) => {
let res = [];
if (!route.path.startsWith('/')) {
if (!parent.path.endsWith('/')) {
parent.path += '/';
}
route.path = parent.path + route.path;
}
route.meta.menu = route.path.replaceAll(/\/:[^?]+\?/g, '');
let activeMenu = route.meta.activeMenu;
if (!activeMenu && route.meta.hidden) {
activeMenu = parent.meta.menu;
}
route.meta.activeMenu = activeMenu || route.meta.menu;
const children = flatRoutes(route.children || [], route);
route.redirect = children.find((item) => !item.meta.hidden)?.menu;
if (route.meta.layout) {
route.children = children;
res = [route];
} else {
res = [route, ...children];
}
return res;
});
export const reset = (routes) => {
router.getRoutes().forEach((item) => {
router.removeRoute(item.name);
});
routers = [
...globalRoutes,
...routers,
{
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'NotFound',
meta: {
global: true,
if (!config.useLocalRouter) {
routes = [
...globalRoutes,
...routes,
...demoRoutes,
{
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'NotFound',
meta: {
global: true,
},
},
},
];
routers.forEach(router.addRoute);
];
}
flatRoutes(routes).forEach(router.addRoute);
console.info('[router] reset', router.getRoutes());
};

@ -1,286 +0,0 @@
export default [
{
path: '/education',
name: 'Education',
component: () => import('@/layouts/default.vue'),
meta: {
title: '教务教学',
icon: 'book-open-fill',
},
children: [
{
path: 'teacher',
name: 'TeacherManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '老师管理',
icon: 'user-shared-fill',
},
},
{
path: 'student',
name: 'StudentManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '学生管理',
icon: 'user-received-fill',
},
children: [
{
path: 'list',
name: 'StudentManagementList',
component: () => import('@/views/home/index.vue'),
meta: {
title: '学生列表',
icon: 'user-received-line',
},
},
{
path: 'grade',
name: 'GradeManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '年级管理',
icon: 'account-pin-box-fill',
},
},
{
path: 'class',
name: 'ClassManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '班级管理',
icon: 'account-pin-circle-fill',
},
},
],
},
{
path: 'admission',
name: 'AdmissionManagement',
component: () => import('@/views/home/index.vue'),
component: () => import('@/views/home/index.vue'),
meta: {
title: '入学',
icon: 'award-fill',
},
children: [
{
path: 'evaluation',
name: 'AdmissionEvaluation',
component: () => import('@/views/home/index.vue'),
meta: {
title: '入学评测',
icon: 'file-paper-2-fill',
},
},
{
path: 'way',
name: 'StudyWay',
component: () => import('@/views/home/index.vue'),
meta: {
title: '学习路线',
icon: 'send-plane-fill',
},
},
],
},
{
path: 'answer',
name: 'Answer',
component: () => import('@/views/home/index.vue'),
component: () => import('@/views/home/index.vue'),
meta: {
title: '答疑',
icon: 'question-answer-fill',
},
children: [
{
path: 'management',
name: 'AnswerManagement',
component: () => import('@/views/home/index.vue'),
component: () => import('@/views/home/index.vue'),
meta: {
title: '答疑管理',
icon: 'question-answer-fill',
},
children: [
{
path: 'list',
name: 'AnswerManagementList',
component: () => import('@/views/home/index.vue'),
meta: {
title: '答疑管理列表',
icon: 'question-answer-line',
},
},
{
path: 'invalid',
name: 'InvalidQuestionManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '无效追问管理',
icon: 'questionnaire-fill',
},
},
{
path: 'lock',
name: 'UserLockManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '用户锁定管理',
icon: 'lock-fill',
},
},
],
},
{
path: 'assign/:questionId',
name: 'AssignAnswerTeacher',
component: () => import('@/views/home/index.vue'),
meta: {
title: '指派老师',
icon: 'account-box-fill',
hidden: true,
activeMenu: '/education/answer/management',
},
},
{
path: 'teacher',
name: 'AnswerTeacher',
component: () => import('@/views/home/index.vue'),
component: () => import('@/views/home/index.vue'),
meta: {
title: '答疑老师管理',
icon: 'book-line',
},
children: [
{
path: 'summary',
name: 'AnswerTeacherSummary',
component: () => import('@/views/home/index.vue'),
meta: {
title: '答疑老师统计',
icon: 'book-2-line',
},
},
{
path: 'management',
name: 'AnswerTeacherManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '答疑老师管理',
icon: 'book-3-line',
},
},
{
path: 'bind/:teacher?',
name: 'AnswerTeacherUpdateBind',
component: () => import('@/views/home/index.vue'),
meta: {
title: '编辑课程绑定',
icon: 'book-fill',
hidden: true,
activeMenu: '/education/answer/teacher/management',
},
},
{
path: 'course',
name: 'AnswerTeacherBindCourse',
component: () => import('@/views/home/index.vue'),
meta: {
title: '绑定课程管理',
icon: 'book-fill',
},
},
],
},
],
},
{
path: 'question',
name: 'QuestionManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '题库管理',
icon: 'brush-fill',
},
},
{
path: 'note',
name: 'NoteManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '笔记管理',
icon: 'sticky-note-2-fill',
},
},
{
path: 'research',
name: 'CourseResearch',
component: () => import('@/views/home/index.vue'),
component: () => import('@/views/home/index.vue'),
meta: {
title: '课研更新',
icon: 'contacts-book-upload-fill',
},
children: [
{
path: 'plan',
name: 'CourseResearchPlan',
component: () => import('@/views/home/index.vue'),
meta: {
title: '课研计划',
icon: 'contacts-book-upload-line',
},
},
{
path: 'log',
name: 'CourseResearchLog',
component: () => import('@/views/home/index.vue'),
meta: {
title: '课研更新日志',
icon: 'file-copy-2-line',
},
},
],
},
{
path: 'live',
name: 'LiveManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '直播管理',
icon: 'live-fill',
},
},
{
path: 'material',
name: 'MaterialManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '老师资料管理',
icon: 'database-2-fill',
},
},
{
path: 'shift',
name: 'ShiftTable',
component: () => import('@/views/home/index.vue'),
meta: {
title: '排班表',
icon: 'calendar-check-line',
},
},
{
path: 'course',
name: 'CourseTable',
component: () => import('@/views/home/index.vue'),
meta: {
title: '课程表管理',
icon: 'calendar-todo-fill',
},
},
],
},
];

@ -6,43 +6,39 @@ export default [
meta: {
title: '系统管理',
icon: 'settings-3-fill',
layout: true,
},
children: [
{
path: 'log',
name: 'LogManagement',
component: () => import('@/views/home/index.vue'),
path: 'user',
name: 'UserManagement',
component: () => import('@/views/system/user/index.vue'),
meta: {
title: '日志管理',
icon: 'file-chart-fill',
},
},
{
path: 'app',
name: 'AppManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: 'APP包管理',
icon: 'app-store-fill',
},
},
{
path: 'notify',
name: 'NotifyManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '通知管理',
icon: 'alarm-warning-fill',
},
},
{
path: 'file',
name: 'FileManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '文件中台',
icon: 'file-copy-fill',
title: '用户管理',
icon: 'user-1-fill',
},
children: [
{
path: 'create',
name: 'CreateUser',
component: () => import('@/views/system/user/form.vue'),
meta: {
title: '创建用户',
icon: 'user-1-fill',
hidden: true,
},
},
{
path: 'update/:id',
name: 'UpdateUser',
component: () => import('@/views/system/user/form.vue'),
meta: {
title: '编辑用户',
icon: 'user-1-fill',
hidden: true,
},
},
],
},
],
},

@ -1,49 +0,0 @@
export default [
{
path: '/user',
name: 'UserManagement',
component: () => import('@/layouts/default.vue'),
meta: {
title: '用户管理',
icon: 'folder-user-fill',
},
children: [
{
path: 'consult',
name: 'ConsultManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '咨询管理',
icon: 'kakao-talk-fill',
},
},
{
path: 'course',
name: 'CourseConsultManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '咨询课程',
icon: 'chat-smile-2-fill',
},
},
{
path: 'account',
name: 'AccountManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '账号课程',
icon: 'account-box-fill',
},
},
{
path: 'feedback',
name: 'FeedbackManagement',
component: () => import('@/views/home/index.vue'),
meta: {
title: '意见反馈',
icon: 'feedback-fill',
},
},
],
},
];

@ -1,6 +1,6 @@
import { getUserInfo, getPermission, sendSmsCode, login } from '@/api/auth';
import { ElMessage } from '@/plugins/element-plus';
import router, { reset as resetRoutes, demeRoutes } from '@/router';
import router, { reset as resetRoutes } from '@/router';
import config from '@/configs';
const viewComponents = import.meta.glob('../../views/**/*.vue');
const state = () => ({
@ -39,6 +39,7 @@ const mutations = {
if (route.component) {
if (route.component === 'Layout') {
route.component = () => import('@/layouts/default.vue');
route.meta.layout = true;
} else {
let path = `../../views/${route.component}`;
if (!path.endsWith('.vue')) {
@ -57,7 +58,6 @@ const mutations = {
});
};
data = config.useLocalRouter ? data : convert(data);
data.push(...demeRoutes);
state.permission = data;
resetRoutes(state.permission);
},

@ -7,14 +7,16 @@ const state = () => ({
tabList: [],
activeTab: null,
breakcrumbList: [],
autoRouter: false,
});
const getters = {
asideList: (state, getters, rootState) => {
const deep = (arr) =>
arr.flatMap((item) => (item.meta?.layout ? deep(item.children || []) : item.meta?.global ? [] : item));
return deep(rootState.auth.permission);
return rootState.auth.permission.filter((item) => item.meta.global !== true);
},
collapseMenu: (state) => state.collapseMenu || state.menuList.length < 2,
menuList: (state) => {
return state.menuList.filter((item) => item.meta.hidden !== true);
},
collapseMenu: (state, getters) => state.collapseMenu || getters.menuList.length < 2,
activeAsideName: (state, getters) => getters.asideList.find((item) => item.name === state.activeAside)?.meta.title,
};
const mutations = {
@ -26,6 +28,7 @@ const mutations = {
let oldIndex = state.tabList.findIndex((item) => item.name === data.name);
let tab = null;
if (oldIndex !== -1) {
tab = state.tabList[oldIndex];
tab.fullPath = data.fullPath;
} else {
tab = _.cloneDeep(data);
@ -38,16 +41,17 @@ const mutations = {
} else {
if (reverse) {
let tab = state.tabList[index];
router.push({ name: tab.name });
router.push(tab.fullPath);
state.tabList = [tab];
} else if (state.tabList.length > 1) {
state.tabList.splice(index, 1);
router.push({ name: state.tabList[Math.min(index, state.tabList.length - 1)].name });
router.push(state.tabList[Math.min(index, state.tabList.length - 1)].fullPath);
}
}
},
setActiveTab: (state, data) => (state.activeTab = data),
setBreakcrumbList: (state, data) => (state.breakcrumbList = data),
setAutoRouter: (state, data) => (state.autoRouter = data),
};
const actions = {};
export default {

@ -47,11 +47,16 @@
@layout-header-bgc: @color-white;
@layout-header-fc: @color-black;
@layout-tabs-height: 50px;
@layout-tabs-height: 40px;
@layout-footer-height: @layout-h1;
@layout-footer-bgc: @color-white;
@layout-footer-fc: @color-black;
@layout-main-height: calc(
100vh - @layout-header-height - @layout-tabs-height - @layout-space-large * 2 - @layout-footer-height
);
:export {
layoutAsideWidth: @layout-aside-width;
layoutLogoSize: calc(@layout-header-height * 0.72);

@ -0,0 +1,20 @@
<template>
<div class="form-container">
<el-form :model="form" :rules="rules" label-width="100px">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" />
</el-form-item>
</el-form>
</div>
</template>
<script setup>
const form = reactive({
username: null,
});
const rules = reactive({
username: [{ required: true }],
});
</script>
<style lang="less" scoped></style>

@ -21,6 +21,7 @@
</template>
<script setup lang="jsx">
const router = useRouter();
const store = useStore();
const loading = computed(() => store.state.user.loading);
const list = computed(() => store.state.user.list);
@ -34,11 +35,11 @@
const handleSearch = (page) => {
store.dispatch('user/search', { ...page, ...state.condition });
};
const handleCreate = (row) => {
alert('create ' + row.id);
const handleCreate = () => {
router.push({ name: 'CreateUser' });
};
const handleUpdate = (row) => {
alert('update ' + row.id);
router.push({ name: 'UpdateUser', params: { id: row.id } });
};
const handleRemove = (rows) => {
alert('delete ' + rows.map((item) => item.id).join(','));

Loading…
Cancel
Save