|
|
|
|
<!--
|
|
|
|
|
* @Author: qinzhenpen qzp1807@126.com
|
|
|
|
|
* @Date: 2024-08-09 17:36:23
|
|
|
|
|
* @LastEditors: qinzhenpen qzp1807@126.com
|
|
|
|
|
* @LastEditTime: 2025-05-21 17:19:46
|
|
|
|
|
* @FilePath: \vue3\src\layout\components\Sidebar\index.vue
|
|
|
|
|
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|
|
|
|
-->
|
|
|
|
|
<template>
|
|
|
|
|
<div :class="[{ 'has-logo': showLogo }, { 'student-sidebar-shell': isCourseStyleSidebar }]" :style="sidebarStyle">
|
|
|
|
|
<!-- <logo v-if="showLogo" :collapse="isCollapse" /> -->
|
|
|
|
|
<template v-if="isCourseStyleSidebar">
|
|
|
|
|
<el-scrollbar class="theme-dark student-sidebar-scroll" wrap-class="scrollbar-wrapper">
|
|
|
|
|
<nav class="student-course-nav">
|
|
|
|
|
<template v-for="section in currentCourseNav" :key="section.path || section.title">
|
|
|
|
|
<button v-if="section.path" type="button" :class="['student-course-heading', { active: isStudentNavActive(section) }]" @click="goStudentNav(section.path)">
|
|
|
|
|
<span class="student-course-icon student-course-icon--heading">
|
|
|
|
|
<component :is="getStudentNavIcon(section)" />
|
|
|
|
|
</span>
|
|
|
|
|
<span class="student-course-text">{{ getCourseNavTitle(section) }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
<div v-else class="student-course-heading">
|
|
|
|
|
<span class="student-course-icon student-course-icon--heading">
|
|
|
|
|
<component :is="getStudentNavIcon(section)" />
|
|
|
|
|
</span>
|
|
|
|
|
<span class="student-course-text">{{ getCourseNavTitle(section) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="section.children?.length" class="student-course-list">
|
|
|
|
|
<template v-for="child in section.children" :key="child.path || child.title">
|
|
|
|
|
<button v-if="child.path" type="button" :class="['student-course-link', { active: isStudentNavActive(child) }]" @click="goStudentNav(child.path)">
|
|
|
|
|
<span class="student-course-icon">
|
|
|
|
|
<component :is="getStudentNavIcon(child)" />
|
|
|
|
|
</span>
|
|
|
|
|
<span class="student-course-text">{{ getCourseNavTitle(child) }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
<div v-else class="student-course-link disabled">
|
|
|
|
|
<span class="student-course-icon">
|
|
|
|
|
<component :is="getStudentNavIcon(child)" />
|
|
|
|
|
</span>
|
|
|
|
|
<span class="student-course-text">{{ getCourseNavTitle(child) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<button v-if="isStudentRole" type="button" :class="['student-report-button', { active: route.path === '/report/index' }]" @click="goStudentNav('/report/index')">
|
|
|
|
|
<span class="student-course-icon">
|
|
|
|
|
<component :is="ElementPlusIconsVue.DocumentChecked" />
|
|
|
|
|
</span>
|
|
|
|
|
<span>生成实训报告</span>
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
</template>
|
|
|
|
|
<el-scrollbar v-else :class="sideTheme" wrap-class="scrollbar-wrapper">
|
|
|
|
|
<el-menu :key="sidebarMenuKey" :default-openeds="['/demand/demand', '/product/product', '/test/test', '/channel/channel', '/assessment/assessment']" :default-active="activeMenu" :collapse="isCollapse" :background-color="sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground" :text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor" :unique-opened="false" :active-text-color="theme" :collapse-transition="false" mode="vertical">
|
|
|
|
|
<sidebar-item v-for="(route, index) in visibleSidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
|
|
|
|
|
</el-menu>
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<script setup>
|
|
|
|
|
import Logo from "./Logo";
|
|
|
|
|
import SidebarItem from "./SidebarItem";
|
|
|
|
|
import variables from "@/assets/styles/variables.module.scss";
|
|
|
|
|
import useAppStore from "@/store/modules/app";
|
|
|
|
|
import * as indexApi from "@/api/teacher";
|
|
|
|
|
import useSettingsStore from "@/store/modules/settings";
|
|
|
|
|
import usePermissionStore from "@/store/modules/permission";
|
|
|
|
|
import { constantRoutes, dynamicRoutes, teacherRoutes, platformAdminRoutes, schoolAdminRoutes } from "@/router/index.js";
|
|
|
|
|
import useUserStore from "@/store/modules/user";
|
|
|
|
|
import { listTrainingTasks } from "@/api/trainingTask";
|
|
|
|
|
import { buildStudentTaskSections } from "@/utils/studentTrainingNavigation";
|
|
|
|
|
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const appStore = useAppStore();
|
|
|
|
|
const settingsStore = useSettingsStore();
|
|
|
|
|
const permissionStore = usePermissionStore();
|
|
|
|
|
const sidebarRouters = ref([...constantRoutes]);
|
|
|
|
|
const visibleSidebarRouters = computed(() => filterRoutesByOrganizationMode(sidebarRouters.value));
|
|
|
|
|
const sidebarMenuKey = computed(() => visibleSidebarRouters.value.map((route) => route.path).join("|"));
|
|
|
|
|
const routerStatus = {
|
|
|
|
|
行业需求分析: [
|
|
|
|
|
{
|
|
|
|
|
id: "9f413263-e6ff-4ee5-984a-7a231cad6818",
|
|
|
|
|
module: "行业需求分析-蛋糕指数",
|
|
|
|
|
sort: 1,
|
|
|
|
|
status: 0,
|
|
|
|
|
parentDisabled: true,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
竞争环境分析: [
|
|
|
|
|
{
|
|
|
|
|
id: "ae274ec6-971a-4a33-aa63-2e449f146487",
|
|
|
|
|
module: "竞争环境分析-赫芬达尔指数",
|
|
|
|
|
sort: 2,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
消费人群分析: [
|
|
|
|
|
{
|
|
|
|
|
id: "b91026aa-275c-4779-8602-61051a931d3a",
|
|
|
|
|
module: "消费人群分析-波士顿矩阵分析",
|
|
|
|
|
sort: 3,
|
|
|
|
|
status: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "191026aa-275c-4779-8602-61051a931d3a",
|
|
|
|
|
classId: "1",
|
|
|
|
|
schoolId: "1",
|
|
|
|
|
module: "消费人群分析-RFM分析",
|
|
|
|
|
status: 0,
|
|
|
|
|
sort: 3,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
用户数据分析: [
|
|
|
|
|
{
|
|
|
|
|
id: "4b4a685a-b9a3-4edc-9a68-a670d8c84c94",
|
|
|
|
|
module: "用户数据分析-描述性统计",
|
|
|
|
|
sort: 4,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "1c6099e7-fc8a-4485-b828-8d1aef8e34b4",
|
|
|
|
|
module: "用户数据分析-聚类分析",
|
|
|
|
|
sort: 5,
|
|
|
|
|
status: 1,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
数据化选品: [
|
|
|
|
|
{
|
|
|
|
|
id: "fc9f19cb-8f77-4810-a538-eec97f840c0a",
|
|
|
|
|
module: "数据化选品-线性预测法-销售预测",
|
|
|
|
|
sort: 6,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
产品定价: [
|
|
|
|
|
{
|
|
|
|
|
id: "b95af20c-6242-42cd-a7c2-ddec7b9a0957",
|
|
|
|
|
module: "产品定价-PSM模型",
|
|
|
|
|
sort: 7,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
产品差异化定位: [
|
|
|
|
|
{
|
|
|
|
|
id: "4ca53e70-c168-455a-8dbd-f7d6475c5903",
|
|
|
|
|
module: "产品差异化定位-SPAN模型",
|
|
|
|
|
sort: 8,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
产品结构规划: [
|
|
|
|
|
{
|
|
|
|
|
id: "31f289b7-04a7-4b8b-b5b4-6333f7c3d76e",
|
|
|
|
|
module: "产品结构规划",
|
|
|
|
|
sort: 9,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
发布渠道评估: [
|
|
|
|
|
{
|
|
|
|
|
id: "b58ef210-624b-43f2-bbac-f788c8d96cc8",
|
|
|
|
|
module: "发布渠道评估-指标评估法-熵值法",
|
|
|
|
|
sort: 10,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
产品推广测试: [
|
|
|
|
|
{
|
|
|
|
|
id: "de9206fe-1e95-454f-912c-54dbd0a383cc",
|
|
|
|
|
module: "产品推广测试-A/B测试",
|
|
|
|
|
sort: 11,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
渠道表现分析: [
|
|
|
|
|
{
|
|
|
|
|
id: "2063efde-f8ba-4f69-be9b-1d87586044f6",
|
|
|
|
|
module: "渠道表现分析-转化分析-漏斗模型",
|
|
|
|
|
sort: 12,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
产品营销定位: [
|
|
|
|
|
{
|
|
|
|
|
id: "e99dee9f-a7a9-4e8c-a77b-8db6dfbc64d7",
|
|
|
|
|
module: "产品营销定位-四象限法则",
|
|
|
|
|
sort: 13,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
采购需求预测: [
|
|
|
|
|
{
|
|
|
|
|
id: "c8835d65-db31-4952-9d8d-5d07bf4a9c90",
|
|
|
|
|
module: "采购需求预测-季节指数预测法",
|
|
|
|
|
sort: 14,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
采购产品定位: [
|
|
|
|
|
{
|
|
|
|
|
id: "acd195cf-5e43-4c5e-8ec9-e5ad85612063",
|
|
|
|
|
module: "采购产品定位-卡拉杰克模型",
|
|
|
|
|
sort: 15,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
供应商评估与选择: [
|
|
|
|
|
{
|
|
|
|
|
id: "206e5b9a-95a0-41d7-b2f2-19f5cf10331a",
|
|
|
|
|
module: "供应商评估与选择-线性权重法",
|
|
|
|
|
sort: 16,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
产品表现分析: [
|
|
|
|
|
{
|
|
|
|
|
id: "0aca8b42-c665-4759-b1b4-b35a0daf2e80",
|
|
|
|
|
module: "产品表现分析-功效系数法",
|
|
|
|
|
sort: 17,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
产品问题诊断: [
|
|
|
|
|
{
|
|
|
|
|
id: "55a0ffd4-9863-40d8-bc4c-7830c731eaa7",
|
|
|
|
|
module: "产品问题诊断-五维四率法",
|
|
|
|
|
sort: 18,
|
|
|
|
|
status: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "205f28ce-31d8-449a-8053-7d6f85e98a68",
|
|
|
|
|
module: "产品问题诊断-KANO模型",
|
|
|
|
|
sort: 19,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "2768d846-dfe8-42c1-b31f-f4deef85f34d",
|
|
|
|
|
module: "产品问题诊断-市场机会矩阵",
|
|
|
|
|
sort: 20,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "d0fc3cb4-2cde-401f-b243-33bdb5ef1094",
|
|
|
|
|
module: "产品问题诊断-流量数据分析法",
|
|
|
|
|
sort: 21,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "25896e1a-b099-4778-bd87-3b92aa8845da",
|
|
|
|
|
module: "产品问题诊断-用户评论分析",
|
|
|
|
|
sort: 22,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
品类结构优化: [
|
|
|
|
|
{
|
|
|
|
|
id: "6bbeebf1-662a-46d6-aa4a-e5e8c4c3d5f1",
|
|
|
|
|
module: "品类结构优化-帕累托/ABC分析",
|
|
|
|
|
sort: 23,
|
|
|
|
|
status: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "90212d7c-d8b5-4455-bc7a-693ded4b3f8f",
|
|
|
|
|
module: "品类结构优化-购物篮分析",
|
|
|
|
|
sort: 24,
|
|
|
|
|
status: 0,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
const showLogo = computed(() => settingsStore.sidebarLogo);
|
|
|
|
|
const sideTheme = computed(() => settingsStore.sideTheme);
|
|
|
|
|
const theme = computed(() => settingsStore.theme);
|
|
|
|
|
const isCollapse = computed(() => !appStore.sidebar.opened);
|
|
|
|
|
const isTeacherRole = computed(() => Number(userStore.userInfo.roleId) === 3);
|
|
|
|
|
const isStudentRole = computed(() => ![1, 2, 3].includes(Number(userStore.userInfo.roleId)));
|
|
|
|
|
const isCourseStyleSidebar = computed(() => isStudentRole.value || isTeacherRole.value);
|
|
|
|
|
const sidebarStyle = computed(() => {
|
|
|
|
|
if (isCourseStyleSidebar.value) return {};
|
|
|
|
|
return {
|
|
|
|
|
backgroundColor: sideTheme.value === "theme-dark" ? variables.menuBackground : variables.menuLightBackground,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
const studentCourseNav = [
|
|
|
|
|
{ title: "AI智能助手", path: "/assistant/index", activePrefix: "/assistant" },
|
|
|
|
|
{
|
|
|
|
|
title: "互联网产品开发基础",
|
|
|
|
|
children: [
|
|
|
|
|
{ title: "新产品调查与分析", path: "/foundation/new-product-survey" },
|
|
|
|
|
{ title: "产品开发关键因素", path: "/foundation/product-development-factors" },
|
|
|
|
|
{ title: "产品开发主要流程", path: "/foundation/product-development-process" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: "市场洞察与需求分析",
|
|
|
|
|
children: [
|
|
|
|
|
{ title: "行业需求分析", path: "/demand/index" },
|
|
|
|
|
{ title: "竞争环境分析", path: "/demand/environment" },
|
|
|
|
|
{ title: "市场机会识别与选品", path: "/product/index" },
|
|
|
|
|
{ title: "商业可行性论证", path: "/demand/business-feasibility" },
|
|
|
|
|
{ title: "目标用户画像", path: "/demand/people" },
|
|
|
|
|
{ title: "消费者需求与行为", path: "/demand/consumer-behavior" },
|
|
|
|
|
{ title: "消费场景解构", path: "/demand/consumer-scenario" },
|
|
|
|
|
{ title: "需求转化为功能", path: "/demand/feature-conversion" },
|
|
|
|
|
{ title: "产品价值主张及定位", path: "/product/differentiation" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: "产品规划与设计",
|
|
|
|
|
children: [
|
|
|
|
|
{ title: "产品架构设计", path: "/product/structure" },
|
|
|
|
|
{ title: "售后服务流程设计", path: "/product/after-sales-service" },
|
|
|
|
|
{ title: "产品需求说明书", path: "/product/requirements-doc" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: "产品开发与测试验证",
|
|
|
|
|
children: [
|
|
|
|
|
{ title: "智能硬件成本估算", path: "/product/hardware-cost" },
|
|
|
|
|
{ title: "供应渠道评估", path: "/channel/supplier" },
|
|
|
|
|
{ title: "采购需求预测", path: "/channel/index" },
|
|
|
|
|
{ title: "产品定价", path: "/product/price" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: "产品上线与运营推广",
|
|
|
|
|
children: [
|
|
|
|
|
{ title: "品类结构优化", path: "/assessment/optimization" },
|
|
|
|
|
{ title: "产品发布渠道评估", path: "/test/index" },
|
|
|
|
|
{ title: "投放效果评估与优化", path: "/test/promotion" },
|
|
|
|
|
{ title: "产品问题诊断与迭代", path: "/assessment/diagnosis" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: "综合实训",
|
|
|
|
|
children: [
|
|
|
|
|
{ title: "综合案例实训", path: "/comprehensive/index" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
const teacherManageNav = [
|
|
|
|
|
{
|
|
|
|
|
title: "教学管理",
|
|
|
|
|
children: [
|
|
|
|
|
{ title: "院系管理", path: "/school/index", requiresFacultyManagement: true },
|
|
|
|
|
{ title: "专业管理", path: "/major/index", requiresMajorManagement: true },
|
|
|
|
|
{ title: "班级管理", path: "/class/index" },
|
|
|
|
|
{ title: "教师管理", path: "/teacher/index" },
|
|
|
|
|
{ title: "学生管理", path: "/student/index" },
|
|
|
|
|
{ title: "实训任务管理", path: "/training-task/index" },
|
|
|
|
|
{ title: "任务分配", path: "/task/index" },
|
|
|
|
|
{ title: "成绩中心", path: "/score/index" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
const trainingTaskKeyByPath = {
|
|
|
|
|
"/comprehensive/index": "comprehensive-case-training",
|
|
|
|
|
"/foundation/new-product-survey": "new-product-survey",
|
|
|
|
|
"/foundation/product-development-factors": "product-development-factors",
|
|
|
|
|
"/foundation/product-development-process": "product-development-process",
|
|
|
|
|
"/demand/index": "industry-demand-analysis",
|
|
|
|
|
"/demand/environment": "competitive-environment-analysis",
|
|
|
|
|
"/product/index": "market-opportunity-selection",
|
|
|
|
|
"/demand/business-feasibility": "business-feasibility",
|
|
|
|
|
"/demand/people": "target-user-profile",
|
|
|
|
|
"/demand/consumer-behavior": "consumer-behavior",
|
|
|
|
|
"/demand/consumer-scenario": "consumer-scenario",
|
|
|
|
|
"/demand/feature-conversion": "feature-conversion",
|
|
|
|
|
"/product/differentiation": "product-value-positioning",
|
|
|
|
|
"/product/structure": "product-structure",
|
|
|
|
|
"/product/after-sales-service": "after-sales-service",
|
|
|
|
|
"/product/requirements-doc": "requirements-doc",
|
|
|
|
|
"/product/hardware-cost": "hardware-cost",
|
|
|
|
|
"/channel/supplier": "supplier-evaluation",
|
|
|
|
|
"/channel/index": "purchase-demand-forecast",
|
|
|
|
|
"/product/price": "product-pricing",
|
|
|
|
|
"/assessment/optimization": "category-optimization",
|
|
|
|
|
"/test/index": "release-channel-evaluation",
|
|
|
|
|
"/test/promotion": "promotion-effect-optimization",
|
|
|
|
|
"/assessment/diagnosis": "product-diagnosis-iteration",
|
|
|
|
|
};
|
|
|
|
|
const studentCourseBaseNav = [
|
|
|
|
|
{ title: "AI智能助手", path: "/assistant/index", activePrefix: "/assistant" },
|
|
|
|
|
];
|
|
|
|
|
const studentTaskSections = ref([]);
|
|
|
|
|
const currentCourseNav = computed(() => (isTeacherRole.value
|
|
|
|
|
? filterTeacherManageNavigation(teacherManageNav)
|
|
|
|
|
: [...studentCourseBaseNav, ...studentTaskSections.value]));
|
|
|
|
|
const studentIconByPath = {
|
|
|
|
|
"/assistant/index": "MagicStick",
|
|
|
|
|
"/comprehensive/index": "School",
|
|
|
|
|
"/foundation/new-product-survey": "Search",
|
|
|
|
|
"/foundation/product-development-factors": "SetUp",
|
|
|
|
|
"/foundation/product-development-process": "Guide",
|
|
|
|
|
"/demand/index": "DataAnalysis",
|
|
|
|
|
"/demand/environment": "TrendCharts",
|
|
|
|
|
"/product/index": "Opportunity",
|
|
|
|
|
"/demand/business-feasibility": "Histogram",
|
|
|
|
|
"/demand/people": "UserFilled",
|
|
|
|
|
"/demand/consumer-behavior": "ShoppingCart",
|
|
|
|
|
"/demand/consumer-scenario": "MapLocation",
|
|
|
|
|
"/demand/feature-conversion": "Connection",
|
|
|
|
|
"/product/differentiation": "PriceTag",
|
|
|
|
|
"/product/structure": "Box",
|
|
|
|
|
"/product/after-sales-service": "Service",
|
|
|
|
|
"/product/requirements-doc": "Document",
|
|
|
|
|
"/product/hardware-cost": "Cpu",
|
|
|
|
|
"/channel/supplier": "Ship",
|
|
|
|
|
"/channel/index": "ShoppingBag",
|
|
|
|
|
"/product/price": "Money",
|
|
|
|
|
"/assessment/optimization": "CollectionTag",
|
|
|
|
|
"/test/index": "Promotion",
|
|
|
|
|
"/test/promotion": "DataLine",
|
|
|
|
|
"/assessment/diagnosis": "Tools",
|
|
|
|
|
"/report/index": "DocumentChecked",
|
|
|
|
|
"/school/index": "OfficeBuilding",
|
|
|
|
|
"/major/index": "Collection",
|
|
|
|
|
"/class/index": "Notebook",
|
|
|
|
|
"/teacher/index": "UserFilled",
|
|
|
|
|
"/student/index": "Avatar",
|
|
|
|
|
"/training-task/index": "DocumentChecked",
|
|
|
|
|
"/task/index": "Tickets",
|
|
|
|
|
"/score/index": "DataBoard",
|
|
|
|
|
};
|
|
|
|
|
const studentIconBySectionStart = [
|
|
|
|
|
{ path: "/training/", icon: "Document" },
|
|
|
|
|
{ path: "/foundation/", icon: "Collection" },
|
|
|
|
|
{ path: "/demand/", icon: "Search" },
|
|
|
|
|
{ path: "/product/", icon: "Goods" },
|
|
|
|
|
{ path: "/channel/", icon: "Connection" },
|
|
|
|
|
{ path: "/assessment/", icon: "DataBoard" },
|
|
|
|
|
{ path: "/test/", icon: "Finished" },
|
|
|
|
|
{ path: "/school/", icon: "OfficeBuilding" },
|
|
|
|
|
{ path: "/major/", icon: "Collection" },
|
|
|
|
|
{ path: "/class/", icon: "Notebook" },
|
|
|
|
|
{ path: "/teacher/", icon: "UserFilled" },
|
|
|
|
|
{ path: "/student/", icon: "Avatar" },
|
|
|
|
|
{ path: "/training-task/", icon: "DocumentChecked" },
|
|
|
|
|
{ path: "/task/", icon: "Tickets" },
|
|
|
|
|
{ path: "/score/", icon: "DataBoard" },
|
|
|
|
|
];
|
|
|
|
|
function getStudentNavIcon(item) {
|
|
|
|
|
const path = item.path || item.children?.[0]?.path || "";
|
|
|
|
|
const exactIcon = studentIconByPath[path];
|
|
|
|
|
const sectionIcon = studentIconBySectionStart.find((section) => path.startsWith(section.path))?.icon;
|
|
|
|
|
return ElementPlusIconsVue[exactIcon || sectionIcon] || ElementPlusIconsVue.Menu;
|
|
|
|
|
}
|
|
|
|
|
const activeMenu = computed(() => {
|
|
|
|
|
const { meta, path } = route;
|
|
|
|
|
// if set path, the sidebar will highlight the path you set
|
|
|
|
|
if (meta.activeMenu) {
|
|
|
|
|
return meta.activeMenu;
|
|
|
|
|
}
|
|
|
|
|
return path;
|
|
|
|
|
});
|
|
|
|
|
function isStudentNavActive(item) {
|
|
|
|
|
if (item.activePrefix) {
|
|
|
|
|
return route.path.startsWith(item.activePrefix);
|
|
|
|
|
}
|
|
|
|
|
return route.path === item.path;
|
|
|
|
|
}
|
|
|
|
|
function goStudentNav(path) {
|
|
|
|
|
if (!path || route.path === path) return;
|
|
|
|
|
router.push(path);
|
|
|
|
|
}
|
|
|
|
|
function getCourseNavTitle(item) {
|
|
|
|
|
return item?.title || "";
|
|
|
|
|
}
|
|
|
|
|
async function loadStudentTaskNavigation() {
|
|
|
|
|
if (!isStudentRole.value) {
|
|
|
|
|
studentTaskSections.value = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const res = await listTrainingTasks({ enabledOnly: true });
|
|
|
|
|
studentTaskSections.value = buildStudentTaskSections(res.data || []);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
studentTaskSections.value = [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function groupAndSortModules(modules) {
|
|
|
|
|
const grouped = modules.reduce((acc, item) => {
|
|
|
|
|
const prefix = item.module.split("-")[0].trim();
|
|
|
|
|
|
|
|
|
|
if (!acc[prefix]) {
|
|
|
|
|
acc[prefix] = [];
|
|
|
|
|
}
|
|
|
|
|
acc[prefix].push({
|
|
|
|
|
id: item.id,
|
|
|
|
|
module: item.module,
|
|
|
|
|
sort: item.sort,
|
|
|
|
|
status: item.disabledStatus,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
// 对每个分组按sort排序
|
|
|
|
|
Object.keys(grouped).forEach((key) => {
|
|
|
|
|
grouped[key].sort((a, b) => a.sort - b.sort);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return grouped;
|
|
|
|
|
}
|
|
|
|
|
function processModuleStatus(groupedData) {
|
|
|
|
|
const result = {
|
|
|
|
|
data: JSON.parse(JSON.stringify(groupedData)), // 原始数据副本
|
|
|
|
|
disabledParents: [], // 被禁用的父模块列表(所有子模块都禁用)
|
|
|
|
|
activeParents: [], // 活跃的父模块列表
|
|
|
|
|
disabledChildren: [], // 被禁用的子模块(仅当父模块有多个子模块时)
|
|
|
|
|
parentStatus: {}, // 每个父模块的详细状态
|
|
|
|
|
};
|
|
|
|
|
// 分析每个父模块状态
|
|
|
|
|
for (const [parentModule, children] of Object.entries(result.data)) {
|
|
|
|
|
const disabledCount = children.filter((child) => child.status === 1).length;
|
|
|
|
|
const totalCount = children.length;
|
|
|
|
|
result.parentStatus[parentModule] = {
|
|
|
|
|
disabledCount,
|
|
|
|
|
totalCount,
|
|
|
|
|
isDisabled: disabledCount === totalCount && totalCount > 0,
|
|
|
|
|
};
|
|
|
|
|
// 只有当父模块有多个子模块时,才收集被禁用的子模块
|
|
|
|
|
if (totalCount > 1) {
|
|
|
|
|
children.forEach((child) => {
|
|
|
|
|
if (child.status === 1) {
|
|
|
|
|
result.disabledChildren.push({
|
|
|
|
|
parentModule,
|
|
|
|
|
...child,
|
|
|
|
|
module: child.module.split("-")[1].trim(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (result.parentStatus[parentModule].isDisabled) {
|
|
|
|
|
result.disabledParents.push(parentModule);
|
|
|
|
|
children.forEach((child) => {
|
|
|
|
|
child.parentDisabled = true; // 标记父模块禁用状态
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
result.activeParents.push(parentModule);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 过滤掉 children 中 meta.title 在排除列表里的路由
|
|
|
|
|
* @param {Array} routes 原始路由数组
|
|
|
|
|
* @param {Array} excludeTitles 要排除的 meta.title 列表
|
|
|
|
|
* @returns {Array} 新的路由数组
|
|
|
|
|
*/
|
|
|
|
|
function excludeRoutesByMetaTitle(routes, excludeTitles) {
|
|
|
|
|
return routes.reduce((result, route) => {
|
|
|
|
|
if (Array.isArray(route.children)) {
|
|
|
|
|
const filteredChildren = route.children.filter((child) => !excludeTitles.includes(child.meta?.title));
|
|
|
|
|
if (filteredChildren.length > 0) {
|
|
|
|
|
result.push({ ...route, children: filteredChildren });
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
result.push(route); // 没有 children 的保留原样
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}, []);
|
|
|
|
|
}
|
|
|
|
|
const getTaskList = async () => {
|
|
|
|
|
if (userStore.userInfo.roleId == 1) {
|
|
|
|
|
sidebarRouters.value = [...constantRoutes, ...platformAdminRoutes];
|
|
|
|
|
} else if (userStore.userInfo.roleId == 2) {
|
|
|
|
|
await userStore.ensureSchoolProductConfig().catch(() => null);
|
|
|
|
|
sidebarRouters.value = [...constantRoutes, ...filterRoutesByOrganizationMode(schoolAdminRoutes)];
|
|
|
|
|
} else if (userStore.userInfo.roleId != 3) {
|
|
|
|
|
await loadStudentTaskNavigation();
|
|
|
|
|
sidebarRouters.value = [...constantRoutes, ...dynamicRoutes];
|
|
|
|
|
} else {
|
|
|
|
|
await userStore.ensureSchoolProductConfig().catch(() => null);
|
|
|
|
|
sidebarRouters.value = [...constantRoutes, ...filterRoutesByOrganizationMode(teacherRoutes)];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function isSchoolNavigationVisible(item) {
|
|
|
|
|
const config = userStore.schoolProductConfig;
|
|
|
|
|
const facultyEnabled = config?.facultyManagementEnabled === true;
|
|
|
|
|
const majorEnabled = config?.majorManagementEnabled === true;
|
|
|
|
|
const adminClassEnabled = config?.adminClassManagementEnabled === true;
|
|
|
|
|
const capability = item.meta || item;
|
|
|
|
|
if (capability.requiresManagerTeacher && userStore.userInfo.teacherAdmin !== true) return false;
|
|
|
|
|
if (capability.requiresFacultyManagement && !facultyEnabled) return false;
|
|
|
|
|
if (capability.requiresMajorManagement && !majorEnabled) return false;
|
|
|
|
|
if (capability.requiresAdminClass && !adminClassEnabled) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function filterRoutesByOrganizationMode(routes) {
|
|
|
|
|
return routes.filter(isSchoolNavigationVisible);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function filterTeacherManageNavigation(sections) {
|
|
|
|
|
return sections
|
|
|
|
|
.map((section) => {
|
|
|
|
|
if (!section.children) return section;
|
|
|
|
|
return {
|
|
|
|
|
...section,
|
|
|
|
|
children: section.children.filter(isSchoolNavigationVisible),
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter((section) => (section.children ? section.children.length > 0 : isSchoolNavigationVisible(section)));
|
|
|
|
|
}
|
|
|
|
|
watch(
|
|
|
|
|
() => userStore.schoolProductConfig?.organizationMode,
|
|
|
|
|
(organizationMode, previousOrganizationMode) => {
|
|
|
|
|
if (organizationMode === previousOrganizationMode) return;
|
|
|
|
|
if ([2, 3].includes(Number(userStore.userInfo.roleId))) {
|
|
|
|
|
getTaskList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
getTaskList();
|
|
|
|
|
});
|
|
|
|
|
</script>
|