feat:导航模块

main
向文可 4 years ago
parent a263a36692
commit 30cd6b483d

@ -1,7 +1,12 @@
<template> <template>
<el-scrollbar class="layout-aside"> <el-scrollbar class="layout-aside">
<el-menu collapse> <el-menu collapse :default-active="activeAside" router>
<el-menu-item v-for="(item, index) in handleRoutes" :key="index" :index="item.name"> <el-menu-item
v-for="(item, index) in asideList"
:key="index"
:index="item.name"
:route="{ name: item.name }"
>
<XIcon :name="item.meta.icon" size="30" /> <XIcon :name="item.meta.icon" size="30" />
<p>{{ item.meta.title }}</p> <p>{{ item.meta.title }}</p>
</el-menu-item> </el-menu-item>
@ -10,11 +15,11 @@
</template> </template>
<script setup> <script setup>
import routes from '@/router/route.json';
const router = useRouter(); const router = useRouter();
router.getRoutes(); const store = useStore();
const handleRoutes = computed(() => routes);
const handleClick = () => {}; const asideList = computed(() => store.getters['layout/asideList']);
const activeAside = computed(() => store.state.layout.activeAside);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

@ -1,5 +1,5 @@
<template> <template>
<el-menu-item v-if="!menuItem.children?.length" :index="props.menuItem.name"> <el-menu-item v-if="!menuItem.children?.length" :index="props.menuItem.name" :route="{ name: props.menuItem.name }">
<XIcon :name="props.menuItem.meta.icon" size="20" /> <XIcon :name="props.menuItem.meta.icon" size="20" />
<p>{{ props.menuItem.meta.title }}</p> <p>{{ props.menuItem.meta.title }}</p>
</el-menu-item> </el-menu-item>

@ -2,16 +2,17 @@
<el-scrollbar class="layout-menu"> <el-scrollbar class="layout-menu">
<div class="title">马士兵严选后台管理平台</div> <div class="title">马士兵严选后台管理平台</div>
<el-divider>test</el-divider> <el-divider>test</el-divider>
<el-menu> <el-menu router :default-active="activeMenu">
<MenuItem v-for="(item, index) in handleRoutes" :key="index" :menu-item="item" /> <MenuItem v-for="(item, index) in menuList" :key="index" :menu-item="item" />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
</template> </template>
<script setup> <script setup>
import routes from '@/router/route.json';
import MenuItem from './menu-item.vue'; import MenuItem from './menu-item.vue';
const handleRoutes = computed(() => routes[2].children); const store = useStore();
const menuList = computed(() => store.getters['layout/menuList']);
const activeMenu = computed(() => store.state.layout.activeMenu);
const handleClick = () => {}; const handleClick = () => {};
</script> </script>

@ -1,9 +1,9 @@
<template> <template>
<div class="layout-profile"> <div class="layout-profile">
<el-avatar :src="userInfo.avatar" /> <el-avatar :src="userInfo?.avatar" />
<el-dropdown size="middle" @command="handleCommand"> <el-dropdown size="middle" @command="handleCommand">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<span>{{ userInfo.username }}</span> <span>{{ userInfo?.username }}</span>
<XIcon class="el-icon--right" name="ArrowDown" /> <XIcon class="el-icon--right" name="ArrowDown" />
</span> </span>
<template #dropdown> <template #dropdown>

@ -1,710 +1,65 @@
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
// 静态路由 // 全局路由
const routes = [ export const globalRoutes = [
{ {
path: '/login', path: '/login',
name: 'Login', name: 'Login',
component: () => import('@/views/global/login.vue'), component: () => import('@/views/global/login.vue'),
meta: { meta: {
layout: false,
global: true, global: true,
}, },
}, },
{
path: '/404',
name: '404',
component: () => import('@/views/global/404.vue'),
meta: {
global: true,
},
},
];
// 动态模块
const dynamicRoutes = [];
const modules = import.meta.globEager('./modules/*.js');
Object.values(modules).forEach((mod) => {
dynamicRoutes.push(...mod.default);
});
export const routes = [
...globalRoutes,
{ {
path: '/', path: '/',
name: 'App', name: 'App',
redirect: { name: 'Home' }, redirect: { name: 'Home' },
component: () => import('@/layouts/default.vue'), component: () => import('@/layouts/default.vue'),
meta: {
layout: true,
},
children: [ children: [
{ {
path: '/home', path: '/home',
name: 'Home', name: 'Home',
component: () => import('@/views/global/home.vue'), component: () => import('@/views/home/index.vue'),
meta: { meta: {
title: '首页', title: '首页',
icon: 'home-line', icon: 'home-line',
}, },
}, },
{
path: '/user',
name: 'UserManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '用户管理',
icon: 'folder-user-fill',
},
children: [
{
path: 'consult',
name: 'ConsultManagement',
meta: {
title: '咨询管理',
icon: 'kakao-talk-fill',
},
},
{
path: 'course',
name: 'CourseConsultManagement',
meta: {
title: '咨询课程',
icon: 'chat-smile-2-fill',
},
},
{
path: 'account',
name: 'AccountManagement',
meta: {
title: '账号课程',
icon: 'account-box-fill',
},
},
{
path: 'feedback',
name: 'FeedbackManagement',
meta: {
title: '意见反馈',
icon: 'feedback-fill',
},
},
],
},
{
path: '/system',
name: 'SystemManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '系统管理',
icon: 'settings-3-fill',
},
children: [
{
path: 'log',
name: 'LogManagement',
meta: {
title: '日志管理',
icon: 'file-chart-fill',
},
},
{
path: 'app',
name: 'AppManagement',
meta: {
title: 'APP包管理',
icon: 'app-store-fill',
},
},
{
path: 'notify',
name: 'NotifyManagement',
meta: {
title: '通知管理',
icon: 'alarm-warning-fill',
},
},
{
path: 'file',
name: 'FileManagement',
meta: {
title: '文件中台',
icon: 'file-copy-fill',
},
},
],
},
{
path: '/personal',
name: 'Personal',
component: () => import('@/views/global/home.vue'),
meta: {
title: '个人面板',
icon: 'user-2-fill',
},
children: [
{
path: 'answer/:tab?',
name: 'MineAnswer',
meta: {
title: '我的答疑',
icon: 'question-answer-fill',
},
},
{
path: 'file',
name: 'MineFile',
meta: {
title: '我的文件',
icon: 'file-3-line',
},
},
{
path: 'data',
name: 'MineData',
meta: {
title: '数据面板',
icon: 'file-chart-line',
},
},
],
},
{
path: '/operation',
name: 'Operation',
component: () => import('@/views/global/home.vue'),
meta: {
title: '运营管理',
icon: 'briefcase-4-fill',
},
children: [
{
path: 'banner',
name: 'BannerManagement',
meta: {
title: '轮播图',
icon: 'image-fill',
},
},
{
path: 'activity',
name: 'ActivityManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '活动管理',
icon: 'gift-2-fill',
},
children: [
{
path: 'course',
name: 'CourseActivity',
meta: {
title: '课程活动',
icon: 'git-repository-line',
},
},
{
path: 'poster',
name: 'PosterShare',
meta: {
title: '海报分享',
icon: 'dashboard-fill',
},
},
{
path: 'punch',
name: 'PunchActivity',
meta: {
title: '打卡活动',
icon: 'fingerprint-line',
},
},
],
},
{
path: 'distribution',
name: 'DistributionManagement',
meta: {
title: '分销管理',
icon: 'mind-map',
},
},
{
path: 'integral',
name: 'IntegralManagement',
meta: {
title: '积分管理',
icon: 'bubble-chart-fill',
},
},
{
path: 'news',
name: 'GoodNewsManagement',
meta: {
title: '喜报管理',
icon: 'newspaper-fill',
},
},
{
path: 'lottery',
name: 'LotteryActivity',
meta: {
title: '抽奖活动',
icon: 'star-smile-fill',
},
},
{
path: 'promotion',
name: 'promotionActivity',
meta: {
title: '弹窗活动配置',
icon: 'e-bike-2-fill',
},
},
{
path: 'promotion/:id',
name: 'promotionActivityDetail',
meta: {
title: '弹窗活动详情',
icon: 'e-bike-2-fill',
hidden: true,
},
},
{
path: 'landing',
name: 'landingActivity',
meta: {
title: '落地页列表',
icon: 'arrow-left-right-fill',
},
},
{
path: 'landing/:id',
name: 'landingActivityDetail',
meta: {
title: '落地页详情',
icon: 'arrow-left-right-fill',
hidden: true,
},
},
{
path: 'supernatant',
name: 'supernatantActivity',
meta: {
title: '浮窗配置',
icon: 'bring-forward',
},
},
{
path: 'supernatant/:id',
name: 'supernatantActivityDetail',
meta: {
title: '浮窗详情',
icon: 'bring-forward',
hidden: true,
},
},
{
path: 'marketing',
name: 'MarketingActivities',
meta: {
title: '咨询组管理',
icon: 'bar-chart-fill',
},
},
{
path: 'marketing/edit/:id?',
name: 'ActivityEdit',
meta: {
hidden: true,
title: '营销活动-编辑',
icon: 'bar-chart-fill',
},
},
{
path: 'marketing/visitor/:id?',
name: 'MarketingLogs',
meta: {
hidden: true,
title: '营销活动-数据',
icon: 'bar-chart-fill',
},
},
], ],
}, },
...dynamicRoutes,
{ {
path: '/finance', path: '/:pathMatch(.*)*',
name: 'Finance', redirect: '/404',
component: () => import('@/views/global/home.vue'), name: 'NotFound',
meta: {
title: '财务管理',
icon: 'money-cny-circle-fill',
},
children: [
{
path: 'order',
name: 'OrderManagement',
meta: {
title: '订单管理',
icon: 'auction-fill',
},
},
{
path: 'refund',
name: 'RefundManagement',
meta: {
title: '退款管理',
icon: 'refund-2-fill',
},
},
{
path: 'reconciliation',
name: 'FinanceReconciliation',
meta: {
title: '财务对账',
icon: 'money-dollar-box-fill',
},
},
],
},
{
path: '/education',
name: 'Education',
component: () => import('@/views/global/home.vue'),
meta: {
title: '教务教学',
icon: 'book-open-fill',
},
children: [
{
path: 'teacher',
name: 'TeacherManagement',
meta: {
title: '老师管理',
icon: 'user-shared-fill',
},
},
{
path: 'student',
name: 'StudentManagement',
meta: { meta: {
title: '学生管理', global: true,
icon: 'user-received-fill',
},
children: [
{
path: 'list',
name: 'StudentManagementList',
meta: {
title: '学生列表',
icon: 'user-received-line',
},
},
{
path: 'grade',
name: 'GradeManagement',
meta: {
title: '年级管理',
icon: 'account-pin-box-fill',
},
},
{
path: 'class',
name: 'ClassManagement',
meta: {
title: '班级管理',
icon: 'account-pin-circle-fill',
},
},
],
},
{
path: 'admission',
name: 'AdmissionManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '入学',
icon: 'award-fill',
},
children: [
{
path: 'evaluation',
name: 'AdmissionEvaluation',
meta: {
title: '入学评测',
icon: 'file-paper-2-fill',
},
},
{
path: 'way',
name: 'StudyWay',
meta: {
title: '学习路线',
icon: 'send-plane-fill',
},
},
],
},
{
path: 'answer',
name: 'Answer',
component: () => import('@/views/global/home.vue'),
meta: {
title: '答疑',
icon: 'question-answer-fill',
},
children: [
{
path: 'management',
name: 'AnswerManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '答疑管理',
icon: 'question-answer-fill',
},
children: [
{
path: 'list',
name: 'AnswerManagementList',
meta: {
title: '答疑管理列表',
icon: 'question-answer-line',
},
},
{
path: 'invalid',
name: 'InvalidQuestionManagement',
meta: {
title: '无效追问管理',
icon: 'questionnaire-fill',
},
},
{
path: 'lock',
name: 'UserLockManagement',
meta: {
title: '用户锁定管理',
icon: 'lock-fill',
},
},
],
},
{
path: 'assign/:questionId',
name: 'AssignAnswerTeacher',
meta: {
title: '指派老师',
icon: 'account-box-fill',
hidden: true,
activeMenu: '/education/answer/management',
},
},
{
path: 'teacher',
name: 'AnswerTeacher',
component: () => import('@/views/global/home.vue'),
meta: {
title: '答疑老师管理',
icon: 'book-line',
},
children: [
{
path: 'summary',
name: 'AnswerTeacherSummary',
meta: {
title: '答疑老师统计',
icon: 'book-2-line',
},
},
{
path: 'management',
name: 'AnswerTeacherManagement',
meta: {
title: '答疑老师管理',
icon: 'book-3-line',
},
},
{
path: 'bind/:teacher?',
name: 'AnswerTeacherUpdateBind',
meta: {
title: '编辑课程绑定',
icon: 'book-fill',
hidden: true,
activeMenu: '/education/answer/teacher/management',
},
},
{
path: 'course',
name: 'AnswerTeacherBindCourse',
meta: {
title: '绑定课程管理',
icon: 'book-fill',
},
},
],
},
],
},
{
path: 'question',
name: 'QuestionManagement',
meta: {
title: '题库管理',
icon: 'brush-fill',
},
},
{
path: 'note',
name: 'NoteManagement',
meta: {
title: '笔记管理',
icon: 'sticky-note-2-fill',
},
},
{
path: 'research',
name: 'CourseResearch',
component: () => import('@/views/global/home.vue'),
meta: {
title: '课研更新',
icon: 'contacts-book-upload-fill',
},
children: [
{
path: 'plan',
name: 'CourseResearchPlan',
meta: {
title: '课研计划',
icon: 'contacts-book-upload-line',
},
},
{
path: 'log',
name: 'CourseResearchLog',
meta: {
title: '课研更新日志',
icon: 'file-copy-2-line',
},
},
],
},
{
path: 'live',
name: 'LiveManagement',
meta: {
title: '直播管理',
icon: 'live-fill',
},
},
{
path: 'material',
name: 'MaterialManagement',
meta: {
title: '老师资料管理',
icon: 'database-2-fill',
},
},
{
path: 'shift',
name: 'ShiftTable',
meta: {
title: '排班表',
icon: 'calendar-check-line',
},
},
{
path: 'course',
name: 'CourseTable',
meta: {
title: '课程表管理',
icon: 'calendar-todo-fill',
},
},
],
},
{
path: '/course',
name: 'Course',
component: () => import('@/views/global/home.vue'),
meta: {
title: '课程管理',
icon: 'checkbox-multiple-fill',
},
children: [
{
path: 'management',
name: 'CourseManagement',
meta: {
title: '课程列表',
icon: 'book-2-fill',
},
},
{
path: 'category',
name: 'CourseCategory',
meta: {
title: '课程分类',
icon: 'book-read-fill',
},
},
{
path: 'system',
name: 'SystemCourseManagement',
meta: {
title: '体系课',
icon: 'book-3-fill',
},
},
{
path: 'authorize',
name: 'CourseAuthorize',
meta: {
title: '课程授权',
icon: 'book-fill',
},
},
{
path: 'package',
name: 'CoursePackage',
meta: {
title: '课程包',
icon: 'archive-drawer-fill',
},
},
{
path: 'live',
name: 'LiveCourse',
meta: {
title: '直播课',
icon: 'live-fill',
},
},
],
},
{
path: '/audit',
name: 'Audit',
component: () => import('@/views/global/home.vue'),
meta: {
title: '审核管理',
icon: 'checkbox-multiple-fill',
},
children: [
{
path: 'course',
name: 'CourseAudit',
meta: {
title: '课程审核',
icon: 'book-fill',
},
},
{
path: 'answer',
name: 'AnswerCourseAudit',
meta: {
title: '答疑课程审核',
icon: 'book-2-fill',
},
},
{
path: 'admission',
name: 'AdmissionAudit',
meta: {
title: '入学审核',
icon: 'book-3-fill',
},
},
],
}, },
],
}, },
]; ];
// 动态模块
const modules = import.meta.globEager('./modules/*.js');
Object.values(modules).forEach((mod) => {
routes.push(...mod.default);
});
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes, routes,
@ -718,17 +73,40 @@ router.beforeEach(async (to, from, next) => {
if (!from.matched.length) { if (!from.matched.length) {
store.loadCache(); store.loadCache();
} }
// if (!store.state.auth.userInfo) {
// await store.dispatch('auth/getUserInfo');
// }
// if (!store.state.auth.permission.length) {
// await store.dispatch('auth/getPermission');
// }
if (to.matched.length) {
store.commit('layout/setActiveAside', to.matched[0].name);
store.commit('layout/setActiveMenu', to.name);
console.info(`[router] from ${from.name} to ${to.name}`);
if (to.meta.global) { if (to.meta.global) {
next(); next();
} else { } else {
if (!store.state.auth.userInfo) { const deep = (route) => {
await store.dispatch('auth/getUserInfo'); let child = (route.children || []).find((item) => !item.meta?.hidden);
return child?.children?.length ? deep(child?.children) : child?.name;
};
let childName = deep([...to.matched].pop());
if (childName) {
next({ name: childName });
} else {
next();
} }
if (!store.state.auth.permission.length) {
await store.dispatch('auth/getPermission');
} }
next();
} }
}); });
export default router; export default router;
export const reset = (routers) => {
const flat = (arr) => arr.flatMap((item) => [item, item.children || []]);
router.getRoutes().forEach((item) => {
router.removeRoute(item.name);
});
flat(routers).forEach(router.addRoute);
console.info('[router] reset', router.getRoutes());
};

@ -1 +0,0 @@
export default [];

@ -0,0 +1,286 @@
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/global/home.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/global/home.vue'),
component: () => import('@/views/home/index.vue'),
meta: {
title: '答疑',
icon: 'question-answer-fill',
},
children: [
{
path: 'management',
name: 'AnswerManagement',
component: () => import('@/views/global/home.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/global/home.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/global/home.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',
},
},
],
},
];

@ -0,0 +1,49 @@
export default [
{
path: '/system',
name: 'SystemManagement',
component: () => import('@/layouts/default.vue'),
meta: {
title: '系统管理',
icon: 'settings-3-fill',
},
children: [
{
path: 'log',
name: 'LogManagement',
component: () => import('@/views/home/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',
},
},
],
},
];

@ -0,0 +1,49 @@
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',
},
},
],
},
];

@ -16,7 +16,8 @@ const login = () => {
}, 1000); }, 1000);
}); });
}; };
import router from '@/router'; import router, { reset as resetRoutes } from '@/router';
const modules = import.meta.glob('../views/*/*.vue');
const state = () => ({ const state = () => ({
userInfo: null, userInfo: null,
permission: [], permission: [],
@ -24,7 +25,49 @@ const state = () => ({
const getters = {}; const getters = {};
const mutations = { const mutations = {
setUserInfo: (state, data) => (state.userInfo = data), setUserInfo: (state, data) => (state.userInfo = data),
setPermission: (state, data) => (state.permission = data), setPermission: (state, data) => {
const convert = (arr, parent = { path: '/', meta: {} }) => {
return arr.map((item) => {
let route = {
path: item.routeUrl,
name: item.roleCode,
component: item.pageUrl,
meta: {
title: item.title,
icon: item.icon,
hidden: item.type === 2,
activeMenu: item.activeMenu,
},
};
if (!route.path.startsWith('/')) {
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;
if (route.component) {
if (route.component === 'Layout') {
route.component = () => import('@/layouts/default.vue');
} else {
let path = `../views/${route.component}`;
if (!path.endsWith('.vue')) {
path += '.vue';
}
let alias = path.replace(/\.vue$/, '/index.vue');
route.component = modules[path] || modules[alias];
}
}
route.children = convert(item.children || [], route);
route.redirect = route.children.find((item) => !item.meta.hidden)?.menu;
return route;
});
};
state.permission = convert(data);
// resetRoutes(state.permission);
},
}; };
const actions = { const actions = {
sms: async ({ commit }, data) => { sms: async ({ commit }, data) => {

@ -0,0 +1,24 @@
import { routes } from '@/router';
const state = () => ({
activeAside: null,
activeMenu: null,
});
const getters = {
asideList: () => {
const deep = (arr) =>
arr.flatMap((item) => (item.meta?.layout ? deep(item.children || []) : item.meta?.global ? [] : item));
return deep(routes);
},
menuList: (state, getters) => getters.asideList.find((item) => item.name === state.activeAside)?.children || [],
};
const mutations = {
setActiveAside: (state, data) => (state.activeAside = data),
setActiveMenu: (state, data) => (state.activeMenu = data),
};
const actions = {};
export default {
state,
getters,
mutations,
actions,
};

@ -0,0 +1,75 @@
<template>
<div class="container">
<div class="box">
<h1 class="title">404</h1>
</div>
</div>
</template>
<script setup>
const { proxy } = getCurrentInstance();
const store = useStore();
const refsForm = ref(null);
const loading = ref(false);
const form = reactive({
phone: '',
password: '',
verifyCode: '',
});
const lastTime = computed(() => store.state.local.lastSendMessageTime);
const waitTime = ref(60);
const sendStep = ref(60);
const rules = reactive({
phone: [{ required: true, message: '请输入手机号码' }],
password: [{ required: true, message: '请输入登录密码' }],
verifyCode: [{ required: true, message: '请输入验证码' }],
});
waitTime.value = Math.max(0, unref(sendStep) - Math.ceil((new Date().getTime() - unref(lastTime)) / 1000));
setInterval(() => {
waitTime.value = Math.max(0, unref(sendStep) - Math.ceil((new Date().getTime() - unref(lastTime)) / 1000));
}, 1000);
const handleSms = async () => {
if (form.phone) {
loading.value = true;
await store.dispatch('auth/sms', { phone: form.phone, type: 1 });
loading.value = false;
} else {
proxy.$message.warning('请输入手机号码');
}
};
const handleLogin = async () => {
loading.value = true;
try {
await unref(refsForm).validate();
await store.dispatch('auth/login', form);
} catch (e) {
console.info('取消登录', e);
}
loading.value = false;
};
</script>
<style lang="less" scoped>
.container {
width: 100vw;
height: 100vh;
background: #000 url('~/global/login-bgp.png') center center / 100% 100% no-repeat;
display: flex;
justify-content: center;
align-items: center;
color: @color-white;
.box {
width: 420px;
padding: 30px;
background: #1a2229;
border-radius: 10px;
.title {
font-size: 48px;
text-align: center;
}
}
}
</style>

@ -1,7 +1,9 @@
<template> <template>
<div class="container"> <div class="container">
<h1>{{ $route.name }}</h1>
<h1>TOKEN:{{ token }}</h1> <h1>TOKEN:{{ token }}</h1>
<h2>{{ userInfo }}</h2> <h2>{{ userInfo }}</h2>
<h4>{{ permission }}</h4>
<h1> <h1>
<XIcon name="app-store" size="30" /> <XIcon name="app-store" size="30" />
<span>马士兵严选</span> <span>马士兵严选</span>
@ -22,6 +24,7 @@
const store = useStore(); const store = useStore();
const token = computed(() => store.state.local.token); const token = computed(() => store.state.local.token);
const userInfo = computed(() => store.state.auth.userInfo); const userInfo = computed(() => store.state.auth.userInfo);
const permission = computed(() => store.state.auth.permission);
const count = computed(() => store.state.demo.count); const count = computed(() => store.state.demo.count);
const doubleCount = computed(() => store.getters['demo/doubleCount']); const doubleCount = computed(() => store.getters['demo/doubleCount']);
const handleAdd = () => { const handleAdd = () => {
Loading…
Cancel
Save