You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/layout/components/AppMain.vue

474 lines
13 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
* @Author: qinzhenpen qzp1807@126.com
* @Date: 2024-07-15 15:44:46
* @LastEditors: qinzhenpen qzp1807@126.com
* @LastEditTime: 2025-05-21 18:07:48
* @FilePath: \vue3\src\layout\components\AppMain.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<section :class="mainClass">
<div v-if="isDemo" class="student-demo-banner">演示模式:{{ demoClassName }}<button @click="exitDemo">退出演示</button></div>
<router-view v-slot="{ Component, route }">
<keep-alive :include="tagsViewStore.cachedViews">
<component v-if="!route.meta.link" :is="Component" :key="route.path" />
</keep-alive>
</router-view>
<DynamicTrainingStepNavigator v-if="Number(role) === 4 && route.meta?.pageKey" :task-key="route.meta.pageKey" />
<iframe-toggle />
</section>
</template>
<script setup>
import * as indexApi from "@/api/index";
const { proxy } = getCurrentInstance();
import { dynamicRoutes } from "@/router";
import iframeToggle from "./IframeToggle/index";
import DynamicTrainingStepNavigator from "@/views/components/DynamicTrainingStepNavigator.vue";
import useTagsViewStore from "@/store/modules/tagsView";
import { getUserInfo } from "@/utils/auth";
import useUserStore from "@/store/modules/user";
import { clearStudentDemoSession, getStudentDemoSession, isStudentDemo } from "@/utils/studentDemo";
const userStore = useUserStore();
const tagsViewStore = useTagsViewStore();
const route = useRoute();
const router = useRouter();
const role = ref(JSON.parse(getUserInfo()).roleId);
const isDemo = computed(() => isStudentDemo());
const demoClassName = computed(() => getStudentDemoSession()?.userInfo?.className || "教学班");
function exitDemo() {
clearStudentDemoSession();
window.close();
// 仅在浏览器不允许关闭窗口时兜底,避免停留在演示会话中转页。
window.setTimeout(() => {
if (!window.closed) window.location.replace("/index");
}, 100);
}
const mainClass = computed(() => {
if (Number(role.value) === 4) return "app-main";
if (Number(role.value) === 3) return "teacher-main";
return "app-main2";
});
const module = ["市场需求挖掘", "产品规划", "产品投放测试", "供应渠道管理", "产品评估与考核"];
// 全局监听路由进入和离开
onBeforeRouteUpdate((to, from) => {
tagsViewStore.addVisitedView(to);
tagsViewStore.addCachedView(to);
});
function findParentByTitle(routes, targetTitle) {
for (const route of routes) {
if (route.children && route.children.length) {
for (const child of route.children) {
// 如果 child 还有 children则递归查找
if (child.children && child.children.length) {
const found = findParentByTitle([child], targetTitle);
if (found) return found;
} else if (child.meta && child.meta.title === targetTitle) {
// 找到了目标 title返回它的父级 route
return route;
}
}
}
}
return null;
}
function useTimeRecorder() {
const startTime = ref(null);
const start = () => {
startTime.value = new Date();
console.log("计时开始");
};
const end = () => {
if (!startTime.value) {
console.warn("请先调用 start() 方法!");
return null;
}
const durationMs = new Date() - startTime.value; // 毫秒差值
const durationMinutes = (durationMs / (1000 * 60)).toFixed(2); // 转为分钟保留2位小数
console.log(`计时结束,耗时: ${durationMinutes} 秒`);
return parseFloat(durationMinutes); // 返回字符串(如 "3.14"
};
const reset = () => {
startTime.value = null;
console.log("计时器已重置");
};
return { start, end, reset };
}
const { start, end, reset } = useTimeRecorder();
const getCurrentTeachingClassId = async () => {
if (isDemo.value) return getStudentDemoSession()?.userInfo?.demoTeachingClassId || "";
const cachedClassId = proxy.$cache.session.get("currentTeachingClassId");
if (cachedClassId) return cachedClassId;
const res = await indexApi.getCurrentTeachingClass({ userId: userStore.userInfo.userId });
const classId = res?.data?.schoolClassId || "";
if (classId) {
proxy.$cache.session.set("currentTeachingClassId", classId);
}
return classId;
};
const updateVisitCountInBackground = (projectName) => {
getCurrentTeachingClassId()
.then((currentClassId) => {
if (!currentClassId) return;
return indexApi.updateVisitCount({
classId: currentClassId,
projectName,
schoolId: userStore.userInfo.schoolId,
userId: userStore.userInfo.userId,
});
})
.catch((error) => {
console.warn("更新模块访问次数失败", error);
});
};
router.beforeEach((to, from, next) => {
if (isDemo.value) { next(); return; }
const disabledTitles = proxy.$cache.session.getJSON("disabledTitles");
const routess = Array.isArray(disabledTitles) ? disabledTitles : [];
// 获取当前和上一个模块
const toModule = findParentByTitle(dynamicRoutes, to.meta.title)?.meta?.title;
const fromModule = findParentByTitle(dynamicRoutes, from.meta.title)?.meta?.title;
// 离开模块时结束计时
if (module.includes(fromModule)) {
const timeData = end();
if (timeData) {
// 发送学习时间到后端
indexApi
.updateStudyTime({
time: Number(timeData),
projectName: fromModule,
userId: userStore.userInfo.userId,
})
.then((res) => {});
}
}
// 进入新模块时开始计时
if (module.includes(toModule)) {
// 判断是否有禁用的模块
if (routess.includes(to.meta.title)) {
proxy.$modal.msgError("该模块示暂未开放");
return;
}
start(toModule);
if (module.includes(fromModule)) {
// 访问统计不影响页面跳转,避免等待当前教学班接口。
updateVisitCountInBackground(fromModule);
}
}
next();
});
</script>
<style lang="scss" scoped>
.app-main {
/* 50= navbar 50 */
min-height: calc(100vh - 50px);
height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: auto;
background: url("@/assets/images/背景.webp") no-repeat;
background-size: 100% 100%;
}
.student-demo-banner{position:sticky;top:0;z-index:30;display:flex;justify-content:space-between;padding:8px 18px;color:#fff;background:#b45309}.student-demo-banner button{border:0;border-radius:4px;padding:3px 10px;cursor:pointer}
.app-main2 {
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
background: url("@/assets/images/背景f6.png") no-repeat;
background-size: 100% 100%;
}
.teacher-main {
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: auto;
padding: 22px 24px 28px;
background:
radial-gradient(circle at 12% 18%, rgba(14, 74, 112, 0.5), transparent 34%),
radial-gradient(circle at 78% 6%, rgba(25, 92, 170, 0.34), transparent 28%),
linear-gradient(135deg, #03070e 0%, #061827 48%, #02060d 100%);
}
.fixed-header + .app-main {
padding-top: 50px;
}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 50px);
}
.fixed-header + .app-main {
padding-top: 84px;
}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 6px;
}
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
border-radius: 3px;
}
.teacher-main {
color: #e8f5ff;
.app-container {
min-height: calc(100vh - 96px);
padding: 18px 20px 22px;
border: 1px solid rgba(39, 172, 238, 0.28);
border-radius: 14px;
background: linear-gradient(135deg, rgba(6, 23, 36, 0.84), rgba(4, 12, 23, 0.78));
box-shadow: 0 18px 48px rgba(0, 5, 16, 0.34), inset 0 1px 0 rgba(153, 220, 255, 0.08);
backdrop-filter: blur(12px);
}
.school,
.teacher,
.task,
.score,
.class-page,
.teachers {
height: auto !important;
min-height: calc(100vh - 96px);
background: transparent !important;
}
.df,
.online,
.query-bar,
.table-actions,
.demo-form-inline {
color: #cfeaff;
}
.df,
.query-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
padding: 14px 16px;
border: 1px solid rgba(44, 169, 226, 0.28) !important;
border-radius: 10px;
background: rgba(2, 18, 31, 0.72) !important;
}
.online {
display: inline-flex;
align-items: center;
gap: 8px;
}
.online span,
.el-form-item__label {
color: #cfeaff;
font-weight: 600;
}
.mt10,
.table-actions {
margin-top: 14px;
}
.el-input__wrapper,
.el-select__wrapper,
.el-textarea__inner {
background: rgba(3, 27, 43, 0.88);
border: 1px solid rgba(61, 171, 220, 0.42);
box-shadow: none;
}
.el-input__inner,
.el-select__placeholder,
.el-select__selected-item,
.el-textarea__inner {
color: #eaf7ff;
}
.el-input__inner::placeholder {
color: rgba(189, 221, 244, 0.58);
}
.el-button--primary {
border-color: rgba(48, 185, 255, 0.85);
background: linear-gradient(135deg, #078dce, #1557d6);
box-shadow: 0 8px 18px rgba(0, 110, 220, 0.24);
}
.el-button--warning {
border-color: rgba(245, 166, 35, 0.75);
background: linear-gradient(135deg, #d99620, #b76418);
}
.el-button.is-text {
color: #80d7ff;
}
.el-button--danger.is-text {
color: #ff8d8d;
}
.el-table {
--el-table-border-color: rgba(54, 159, 212, 0.2);
--el-table-header-bg-color: rgba(9, 48, 73, 0.96);
--el-table-tr-bg-color: rgba(2, 16, 27, 0.78);
--el-fill-color-lighter: rgba(2, 16, 27, 0.72);
--el-table-row-hover-bg-color: rgba(0, 111, 177, 0.18);
margin-top: 16px !important;
color: #dff4ff;
border-radius: 10px;
overflow: hidden;
background: transparent;
}
.el-table th.el-table__cell {
color: #dff6ff !important;
font-weight: 700;
background: linear-gradient(180deg, rgba(13, 70, 103, 0.98), rgba(8, 43, 67, 0.98)) !important;
}
.el-table tr,
.el-table td.el-table__cell {
background: rgba(2, 16, 27, 0.72);
}
.el-table__empty-text {
color: #9abed8;
}
.el-card {
color: #e9f7ff;
border: 1px solid rgba(54, 159, 212, 0.28);
background: rgba(4, 20, 33, 0.78);
}
.el-card__header {
border-bottom-color: rgba(54, 159, 212, 0.22);
}
.el-pagination {
margin-top: 16px;
justify-content: flex-end;
color: #cdeaff;
}
.el-tabs__item {
color: #a9cbe1;
}
.el-tabs__item.is-active {
color: #7fe0ff;
}
.el-tabs__active-bar {
background-color: #21c8ff;
}
}
</style>
<style lang="scss">
.teacher-dialog {
--el-dialog-bg-color: #071522;
--el-text-color-primary: #eef7ff;
--el-text-color-regular: #d5ecff;
}
.teacher-dialog.el-dialog,
.teacher-dialog .el-dialog {
border: 1px solid rgba(52, 177, 238, 0.42);
border-radius: 12px;
background: linear-gradient(135deg, rgba(6, 24, 38, 0.98), rgba(4, 13, 24, 0.98));
box-shadow: 0 20px 60px rgba(0, 4, 14, 0.55), inset 0 1px 0 rgba(169, 226, 255, 0.08);
}
.teacher-dialog .el-dialog__header {
padding: 18px 22px 12px;
border-bottom: 1px solid rgba(52, 177, 238, 0.18);
background: transparent;
}
.teacher-dialog .el-dialog__title {
color: #eef9ff;
font-weight: 700;
}
.teacher-dialog .el-dialog__headerbtn .el-dialog__close {
color: #bdeaff;
}
.teacher-dialog .el-dialog__headerbtn:hover .el-dialog__close {
color: #ffffff;
}
.teacher-dialog .el-dialog__body {
color: #d9efff;
background: transparent;
}
.teacher-dialog .el-dialog__footer {
background: transparent;
}
.teacher-dialog .el-form-item__label {
color: #cfeaff;
font-weight: 600;
}
.teacher-dialog .el-input__wrapper,
.teacher-dialog .el-select__wrapper,
.teacher-dialog .el-textarea__inner {
background: rgba(3, 27, 43, 0.92);
border: 1px solid rgba(61, 171, 220, 0.42);
box-shadow: none;
}
.teacher-dialog .el-input__inner,
.teacher-dialog .el-select__placeholder,
.teacher-dialog .el-select__selected-item,
.teacher-dialog .el-textarea__inner {
color: #eaf7ff;
}
.teacher-dialog .el-input__inner::placeholder {
color: rgba(189, 221, 244, 0.58);
}
.teacher-dialog .el-input.is-disabled .el-input__wrapper,
.teacher-dialog .el-select.is-disabled .el-select__wrapper {
background: rgba(3, 20, 33, 0.72);
border-color: rgba(61, 171, 220, 0.22);
}
.teacher-dialog .el-input.is-disabled .el-input__inner,
.teacher-dialog .el-select.is-disabled .el-select__selected-item {
color: rgba(220, 242, 255, 0.62);
}
.teacher-dialog .el-button:not(.el-button--primary) {
color: #dff5ff;
background: rgba(4, 24, 38, 0.92);
border-color: rgba(88, 178, 224, 0.45);
}
.teacher-dialog .el-button:not(.el-button--primary):hover,
.teacher-dialog .el-button:not(.el-button--primary):focus {
color: #ffffff;
background: rgba(10, 48, 72, 0.96);
border-color: rgba(103, 213, 255, 0.72);
}
</style>