基于Vue3开发前端页面部分,实现教师、学生两种角色的页面功能展示
文章目录
1. 项目总览
可移步我的主页查看项目总览博客
另外,我清楚我的前端部分写的不是很规范,而且也有一部分代码冗余,甚至还有Vue2和Vue3的混用,但是能正确跑起来!那就万岁!因为团队中没有前端,我的前端是现学的,很多规范还没来得及了解。所以我的第一步肯定是完成,之后再去完善,如果我的代码能对你有些许的启发,那就是好的。由于系统前后端都是我一个人开发的话,对接起来比较方便,没有沟通的代价。有什么不足之处,还望大佬们不吝赐教!
很多前端样式都是靠Element-Plus的,自己写的样式也有但比较少。
2. 前端技术栈
Vue3、VueRouter、Vuex、Element-Plus、Axios等
3. 系统目录结构
目录结构也可以看出,有一些图片我直接和代码文件放在一起了。主要是因为懒,但这可不是好习惯,大家可以自行查看更改!
4. 用户通用功能界面
主要包括:用户注册、用户登录、用户退出、获取用户信息、更新用户信息、上传用户头像等功能
用户注册界面
<template>
<div >
<el-row>
<el-col :span = "24">
<el-text class="w-150px mb-2" truncated>
用户注册
</el-text>
</el-col>
<el-col :span = "24">
<div class="flex items-center justify-center">
<el-form :model="user" :rules="rules" ref="formRef" class="w-[250px]">
<el-form-item prop="username" :span = "24">
<el-text class="w-150px mb-2" truncated>
用户名:
</el-text>
<el-input
v-model="user.username"
style="width: 240px"
placeholder="请输入用户名"
clearable
/>
<br><br>
</el-form-item>
<el-form-item prop="password" class="flex items-center justify-center">
<el-text class="w-150px mb-2" truncated>
密码:
</el-text>
<el-input
v-model="user.password"
style="width: 240px"
type="password"
placeholder="请输入密码"
show-password
/>
<br><br>
</el-form-item>
<el-form-item prop="email">
<el-text class="w-150px mb-2" truncated>
邮箱:
</el-text>
<el-input
v-model="user.email"
style="width: 240px"
type="text"
placeholder="请输入邮箱"
clearable
/>
<br><br>
</el-form-item>
<el-form-item prop="phone">
<el-text class="w-150px mb-2" truncated>
手机:
</el-text>
<el-input
v-model="user.phone"
style="width: 240px"
type="text"
placeholder="请输入手机"
clearable
/>
<br><br>
</el-form-item>
<el-form-item prop="gender">
<el-text class="w-150px mb-2" truncated>
性别:
</el-text>
<el-select
v-model="user.gender"
placeholder="请选择性别"
style="width: 240px"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<br><br>
</el-form-item>
<el-form-item prop="role">
<el-text class="w-150px mb-2" truncated>
身份:
</el-text>
<el-radio-group v-model="user.role">
<el-radio :value=1>学生</el-radio>
<el-radio :value=2>教师</el-radio>
</el-radio-group>
<br><br>
</el-form-item>
<el-form-item>
<el-button round color="#626aef" class="w-[250px]" type="primary" @click="register" :loading="loading">
注 册
</el-button>
</el-form-item>
</el-form>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { ref, reactive} from 'vue'
import api from "../../api"
import { toast } from "../../utils/popup"
import router from "@/router";
export default {
name: "register",
setup(){
const user = reactive({
username: '',
password: '',
email: '',
phone: '',
gender: '',
role: ''
})
const rules = {
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" },
{ min: 6, max: 20, message: "用户名长度必须是6-20个字符", trigger: "blur" },
],
password: [
{
required: true,
message: "密码不能为空",
trigger: "blur",
},
{ min: 6, max: 20, message: "密码长度必须是6-20个字符", trigger: "blur" },
],
email: [
{ required: true, message: "邮箱不能为空", trigger: "blur" },
{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] },
],
phone: [
{ required: true, message: "手机不能为空", trigger: "blur" },
{
pattern: /^1[3-9]\d{9}$/,
message: "请输入正确的手机号码",
trigger: "blur",
},
],
gender: [
{ required: true, message: "性别不能为空", trigger: "blur" },
],
role: [
{ required: true, message: "身份不能为空", trigger: "blur" },
]
};
const formRef = ref(null) // 表单引用
function register() {
console.log("register")
formRef.value.validate((valid) => {
if (valid) {
api.register(user).then(res => {
if (res.data.code === 200) {
toast("注册成功,请继续登录", "success")
setTimeout(() => {
router.push("/")
}, 1000)
}else {
toast(res.data.msg, "error")
}
})
}else {
toast("表单有误", "error")
}
})
}
const value = ref('')
const options = [
{
value: true,
label: '男',
},
{
value: false,
label: '女',
}
]
return{
user,
register,
options,
rules,
formRef
}
}
}
</script>
<style scoped>
</style>
注册界面展示
用户登录界面
<template>
<div id="bg">
<div id="title_bar">
<span class="title_con">青云智汇</span>
<span class="title_con_para">--更懂学生的智教平台</span>
<img id="logo" src="../../assets/myLogo.jpg">
</div>
<div id="hengline"></div>
<div id="main">
<div id="title_blank"><span class="con_title_sp">欢迎登录</span></div>
<el-form :model="form" :rules="rules" ref="formRef" class="w-[300px]">
<el-form-item prop="username" class="form-item-up">
<el-text class="w-150px mb-2" truncated>
用户名:
</el-text>
<el-input
v-model="form.username"
style="width: 240px"
placeholder="请输入用户名"
clearable
@focus="clearValidationMessage('username')"
/>
</el-form-item>
<el-form-item prop="password" class="form-item" >
<el-text class="w-150px mb-2" truncated>
密 码 :
</el-text>
<el-input
v-model="form.password"
style="width: 240px"
type="password"
placeholder="请输入密码"
show-password
@keyup.enter.prevent="login"
/>
</el-form-item>
<el-form-item class="form-item">
<el-button round color="#1343db" class="login-btn" @click="login" :loading="loading">
登 录
</el-button>
</el-form-item>
<el-form-item>
<el-button @click="drawer = true" class="register-btn" :style="{ opacity: dynamicOpacity }" >注 册</el-button>
<el-dialog v-model="drawer" size = '100%' direction='btt'
:before-close="handleClose">
<register></register>
</el-dialog>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { ref, reactive} from 'vue'
import api from "../../api"
import { toast } from "../../utils/popup"
import register from "@/components/user/register"
import { ElMessageBox } from 'element-plus'
import { useRouter } from "vue-router";
import store from '../../store'
export default {
dynamicOpacity: 0.4,
name: "login",
components:{
register
},
setup() {
const router = useRouter()
const form = reactive({
username: "",
password: "",
});
const drawer = ref(false)
const rules = {
username: [
{ required: true, message: "用户名不能为空", trigger: "focus" },
],
password: [
{
required: true,
message: "密码不能为空",
trigger: "submit",
},
{message: "密码不能为空",
trigger: "input",}
],
};
const clearValidationMessage = (field) => {
// 设置该字段的验证消息为空
form[field + 'ErrorMessage'] = ''
}
const formRef = ref(null)
const loading = ref(false)
function login() {
formRef.value.validate((valid) => {
if (valid) {
loading.value = true
api.login(form.username, form.password).then((res) => {
if(res.data.code == 200){
toast("登录成功,正在跳转")
const token = res.data.data.token; // 假设 token 在这个位置
if (token) {
localStorage.setItem("access_token", token); // 设置 token 到 localStorage
}
// 调用 Vuex 中的 login action
store.dispatch('login', res.data.data)
if(res.data.data.role == 1){
setTimeout(() => {
router.push("/");
}, 1000)
}else if(res.data.data.role == 2){
setTimeout(() => {
router.push("/teacherHome");
}, 1000)
}else{
setTimeout(() => {
router.push("/adminHome");
}, 1000)
}
}else {
toast(res.data.msg, "error")
}
}).finally(() => {
loading.value = false
})
} else {
toast("表单有误,请正确填写表单", "error")
}
})
}
const handleClose = (done) => {
ElMessageBox.confirm('确定要取消注册?')
.then(() => {
done()
})
.catch(() => {
})
}
return {
form,
login,
rules,
formRef,
drawer,
handleClose,
clearValidationMessage
}
}
}
</script>
<style scoped>
#main{
text-align: center;
background-color: #fff;
border-radius: 20px;
width: 400px;
height: 300px;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#logo{
margin-left: 70%;
width: 165px;
height: 55px;
}
#app{
margin-top: 0;
}
#bg{
width: 100%;
min-height: 100vh;
background-color: #6495ed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#title_blank{
width: 100%;
height: 60px;
background-color: #1343db;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
display: flex;
align-items: center;
}
#title_bar{
background-color: white;
display: flex; /* 使用 Flexbox 布局 */
justify-content: space-between; /* 在两端对齐,中间自动分配空间 */
align-items: center; /* 垂直居中对齐 */
}
#hengline{
margin-top: 5px;
background-color: white;
height: 3px;
}
.title_con{
margin-left: 30px;
color: black;
font-size: 40px;
font-family: 隶书;
}
.title_con_para{
color: black;
font-size: 25px;
font-family: 隶书;
margin-right: 10px;
}
.con_title_sp{
color: #fff;
display: flex;
align-items: center;
margin-left: 32%;
font-size: 30px;
font-family: 隶书;
}
.form-item {
margin-left: 50px;
margin-right: 50px;
display: flex;
align-items: center;
}
.form-item-up {
margin-top: 40px;
margin-left: 50px;
margin-right: 50px;
display: flex;
align-items: center;
}
.login-btn {
width: 100%;
}
.login-btn:hover {
transform: scale(1.1,1.1);
}
.register-btn {
width: 80%;
margin-left: 50px;
margin-right: 50px;
border-radius: 20px;
}
.register-btn:hover {
transform: scale(1.1,1.1);
}
</style>
登录界面展示
用户个人信息界面
<template>
<el-dialog v-model="drawer" size="50%" direction="btt" :before-close="handleClose">
<div
title="修改个人信息"
:visible.sync="dialogVisible"
width="200px"
@close="handleClose"
>
<el-form
:model="userUpdateInfo"
:rules="rules"
:label-position="labelPosition"
label-width="auto"
ref="userInfoFormRef"
>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="userUpdateInfo.nickname" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="userUpdateInfo.email" />
</el-form-item>
<el-form-item label="手机" prop="phone">
<el-input v-model="userUpdateInfo.phone" />
</el-form-item>
<el-form-item>
<span slot="footer" class="dialog-footer">
<el-button @click="drawer = false">取 消</el-button>
<el-button type="primary" @click="submitForm">确 定</el-button>
</span>
</el-form-item>
</el-form>
</div>
</el-dialog>
<el-dialog v-model="cameraDialog" size="50%" :before-close="handleClose" style="width: 700px">
<div
title="制作趣味头像"
width="150px"
@close="handleClose"
>
<camera @avatar-updated="onAvatarUpdated"></camera>
</div>
</el-dialog>
<el-descriptions
class="margin-top"
:column="2"
size="large"
border
>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon :style="iconStyle">
<user />
</el-icon>
用户名
</div>
</template>
{{ $store.state.user.username }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon :style="iconStyle">
<user />
</el-icon>
昵称
</div>
</template>
{{ $store.state.user.nickname }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon :style="iconStyle">
<Female />
</el-icon>
性别
</div>
</template>
<el-tag size="small">{{ $store.state.user.gender }}</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon :style="iconStyle">
<iphone />
</el-icon>
联系方式
</div>
</template>
{{ $store.state.user.phone }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon :style="iconStyle">
<ChatLineRound />
</el-icon>
电子邮箱
</div>
</template>
{{ $store.state.user.email }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon :style="iconStyle">
<tickets />
</el-icon>
用户身份
</div>
</template>
<el-tag size="small">{{ userRole }}</el-tag>
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">
<el-icon :style="iconStyle">
<Avatar />
</el-icon>
用户头像
</div>
</template>
<uploadAvatar></uploadAvatar>
<el-button @click="cameraDialog = true">制作趣味头像</el-button>
</el-descriptions-item>
</el-descriptions>
<br>
<el-button type="primary" @click="drawer = true">修改个人信息</el-button>
</template>
<script>
import api from '../../api'
import { ref, onMounted, reactive } from 'vue'
import {ElMessageBox} from "element-plus";
import store from "@/store";
import { toast, showModal } from "@/utils/popup";
import courseVideo from "@/components/courseVideo";
import defaultImg from "@/assets/default.png";
import uploadAvatar from "@/components/uploadAvatar"
import {Avatar} from "@element-plus/icons-vue";
import Camera from "@/components/Camera";
export default {
name: "studentHome",
components: {Camera, Avatar, courseVideo,uploadAvatar },
data() {
return {
dialogVisible: true, // 控制对话框的显示与隐藏
labelPosition: 'right',
};
},
setup() {
const drawer = ref(false)
const cameraDialog = ref(false)
const username = store.getters.getUsername
const userUpdateInfo = reactive({
username: username,
email: '',
phone: '',
nickname: '',
})
const rules = {
nickname: [
{ required: true, message: "昵称不能为空", trigger: "blur" },
{ min: 6, max: 20, message: "昵称长度必须是6-20个字符", trigger: "blur" },
],
email: [
{ required: true, message: "邮箱不能为空", trigger: "blur" },
{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] },
],
phone: [
{ required: true, message: "手机不能为空", trigger: "blur" },
{
pattern: /^1[3-9]\d{9}$/,
message: "请输入正确的手机号码",
trigger: "blur",
},
],
};
const userRole = ref();
onMounted(() => {
userRole.value = store.state.user.role == 1 ? '学生' : '教师';
})
const handleClose = (done) => {
ElMessageBox.confirm('确定要取消更改?')
.then(() => {
done()
userInfoFormRef.value.resetFields(); // 清空表单字段
})
.catch(() => {
})
}
const userInfoFormRef = ref(null) // 表单引用
function submitForm() {
// 提交表单的逻辑,这里简单打印表单数据,实际应提交至后端
userInfoFormRef.value.validate(valid => {
if (valid) {
// 表单验证通过,执行提交逻辑
// 这里可以调用API更新用户信息,并处理响应
api.updateUserInfo(userUpdateInfo).then((res) => {
store.dispatch("updateUserInStore", res.data.data)
})
toast('信息修改成功!', 'success');
drawer.value = false; // 提交成功后关闭对话框
} else {
toast("表单有误", "error");
}
});
}
const AvatarUrl = store.state.user.avatar == null ? defaultImg : store.state.user.avatar;
function onAvatarUpdated(updated){
if(updated){
cameraDialog.value = false
}
}
return {
drawer,
handleClose,
userUpdateInfo,
rules,
submitForm,
userInfoFormRef,
AvatarUrl,
userRole,
cameraDialog,
onAvatarUpdated,
}
}
}
</script>
<style scoped>
.my-info {
text-align: center;
font-weight: bold;
margin-bottom: 20px;
}
.info-container {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.info-label {
font-weight: bold;
margin-right: 10px;
}
.el-descriptions {
margin-top: 20px;
}
.cell-item {
display: flex;
align-items: center;
}
.margin-top {
margin-top: 20px;
}
</style>
用户个人信息展示界面
5. 教师功能界面
主要包括:发布新课程、查看开课的选课情况、教师查看自己开设的课程功能
教师查看自己开设课程界面
<template>
<el-table :data="myCourses" stripe style="width: 100%">
<el-table-column label="课程ID" prop="courseId" />
<el-table-column label="课程名称">
<template #default="{ row }">
<router-link :to="{ name: 'courseDetail', params: { courseName: row.courseName }}">
{{ row.courseName }}
</router-link>
</template>
</el-table-column>
<el-table-column label="课程类型" prop="courseType" />
<el-table-column label="先修要求" prop="prerequisites" />
<el-table-column label="课程简介" prop="courseDescription">
<template #default="{ row }">
<div class="description-cell">{{ row.courseDescription }}</div>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button size="small" @click="handlePredict(row.courseName)">课程热度预测</el-button>
<br>
<el-button size="small" @click="handleDownload(row.courseName)">生成教学ppt</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-button type="primary" @click="drawer = true">去开课</el-button>
<div v-if="predictUrl">
<p>课程热度预测图:</p>
<img :src=predictUrl style="height: 300px; width: 400px"/>
</div>
<el-dialog v-model="drawer" size = '100%' direction='btt'
:before-close="handleClose">
<div
title="发布新课程"
width="600px"
@close="handleClose"
>
<el-form
:model="newCourseInfo"
:rules="rules"
:label-position="labelPosition"
label-width="auto"
ref="newCourseFormRef"
>
<el-form-item label="课程ID" prop="courseId">
<el-input v-model="newCourseInfo.courseId" />
</el-form-item>
<el-form-item label="课程名" prop="courseName">
<el-input v-model="newCourseInfo.courseName" />
</el-form-item>
<el-form-item label="课程类型" prop="courseType">
<el-input v-model="newCourseInfo.courseType" />
</el-form-item>
<el-form-item label="先修课程" prop="prerequisites">
<el-input v-model="newCourseInfo.prerequisites" />
</el-form-item>
<el-form-item label="课程描述" prop="courseDescription">
<el-input v-model="newCourseInfo.courseDescription" />
</el-form-item>
<el-form-item>
<span slot="footer" class="dialog-footer">
<el-button @click="drawer = false">取 消</el-button>
<el-button type="primary" @click="teaReleaseCourse">确 定</el-button>
</span>
</el-form-item>
</el-form>
</div>
</el-dialog>
</template>
<script>
import {ref, onMounted, reactive} from 'vue';
import api from '../../api';
import {ElMessageBox} from "element-plus";
import {toast} from "@/utils/popup";
import axios from "axios";
import path from "@/api/path"
export default {
name: "MyCourses",
setup() {
const myCourses = ref([]);
const fetchCourses = () => {
api.teaGetCourses().then(res => {
myCourses.value = res.data.data;
});
};
onMounted(fetchCourses);
const drawer = ref(false)
const handleClose = (done) => {
ElMessageBox.confirm('确定要取消开课?')
.then(() => {
done()
userInfoFormRef.value.resetFields(); // 清空表单字段
})
.catch(() => {
// catch error
})
}
const newCourseInfo = reactive({
courseId: '',
courseName: '',
courseType: '',
prerequisites: '',
courseDescription: '',
})
const rules = {
courseId: [
{required: true, message: "课程ID不能为空", trigger: "blur"},
],
courseName: [
{required: true, message: "课程名不能为空", trigger: "blur"},
{min: 2, max: 20, message: "课程名长度必须是2-20个字符", trigger: "blur"},
],
courseType: [
{required: true, message: "课程类型不能为空", trigger: "blur"},
],
prerequisites: [
{required: true, message: "先修课程描述不能为空", trigger: "blur"},
],
courseDescription: [
{required: true, message: "课程描述不能为空", trigger: "blur"},
],
};
const newCourseFormRef = ref(null);
function teaReleaseCourse() {
newCourseFormRef.value.validate((valid) => {
if (valid) {
drawer.value = false
api.teaReleaseCourse(newCourseInfo).then(res => {
if (res.data.code === 200) {
toast("课程已发布成功,快让学生来选课吧!", 'success')
fetchCourses()
} else {
toast(res.data.msg, 'error')
}
})
} else {
toast("表单有误", "error");
}
})
}
function handleDownload(courseName) {
generatePPT(courseName).then(downloadUrl => {
if (downloadUrl) {
toast("生成ppt完成!", "success")
const link = document.createElement('a');
link.href = downloadUrl;
link.download = courseName + `教学.pptx`; // 假设你想以课程名为文件名
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} else {
toast("下载链接无效!", "error")
}
}).catch(error => {
toast("生成ppt失败!", "error")
});
}
async function generatePPT(courseName) {
toast("生成ppt中,请稍后...", "success")
try {
const response = await axios.post(path.remoteBaseUrl+"/ppt", {
// const response = await axios.post("http://127.0.0.1:5007/ppt", {
pptName: "请根据以下内容生成ppt,要求ppt样式美观,展现方式多样,底色避免高对比度高饱和度的颜色:" + courseName
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
});
return response.data.download_url; // 确保此响应包含正确的下载链接
} catch (error) {
toast("生成ppt失败!", "error")
}
}
const predictUrl = ref(null)
function handlePredict(courseName){
toast("生成预测图中,请稍后...", "warning")
axios.post(path.remoteBaseUrl+"/transformer", {
"will_train": false,
"add_new": { "date": "2015/12/17", "open": Math.floor(Math.random() * (4000 - 2000 + 1)) + 3000
},
"mode": 0,
"courseName": courseName,
"userName": "666"
}, {
headers: {
'Content-Type': 'application/json'
},
}).then(res => {
predictUrl.value = res.data[1].url
toast("生成预测图完成!", "success")
})
}
return {
myCourses,
drawer,
handleClose,
newCourseInfo,
rules,
newCourseFormRef,
teaReleaseCourse,
handleDownload,
handlePredict,
predictUrl,
};
}
};
</script>
<style scoped>
.description-cell {
max-height: 80px;
overflow: auto;
}
</style>
界面展示
查看开课的选课情况
<template>
<el-table :data="myCourses.value" :border="false" stripe style="width: 100%" id="my-table">
<el-table-column type="expand">
<template #default="props">
<div m="4">
<el-table :data="props.row.studentStudyInfoList" :border="true">
<el-table-column label="选课学生姓名" prop="studentName" />
<el-table-column label="学生活跃天数" prop="studyDay" />
<el-table-column label="学生视频观看数量" prop="todayStudyTime" />
<el-table-column label="学生章节完成数" prop="studyTimeSum" />
<el-table-column label="学生预估分数" prop="score" />
<el-table-column label="操作" width="100">
<template #default="innerProps">
<el-button type="text" @click="predictStudyStatue(innerProps.row.studentName, props.row.courseName)">预测学习状态</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column label="开课课程名" prop="courseName" />
<el-table-column label="选修学生人数" prop="studentNum" />
</el-table>
<el-button type="primary" round @click="exportClick ">导出表格</el-button>
<div v-if="predictUrl">
<p>学生学习状态预测图:</p>
<img :src="predictUrl" style="width: 400px; height: 300px"/>
</div>
</template>
<script>
import api from '../../api'
import {ref, onMounted, reactive} from 'vue'
import {mapActions} from 'vuex';
import store from "@/store";
import { toast } from "@/utils/popup";
import FileSaver from 'file-saver'
import * as XLSX from 'xlsx';
import AutoScoring from '@/components/AutoScoring'
import path from "@/api/path"
import axios from "axios";
export default {
name: "chosenCourses",
components: {
AutoScoring
},
methods: {
...mapActions(['logout']),
},
setup() {
onMounted(() => {
teaGetCourseStudyInfo()
})
const myCourses = reactive([{}])
function teaGetCourseStudyInfo() {
api.teaGetCourseStudyInfo().then(res => {
if (res.data.code === 200) {
myCourses.value = res.data.data
} else {
toast(res.data.msg, 'error')
}
})
}
const exportClick = () => {
// 首先,我们需要构建一个新的数据结构,其中包含主表格和子表格的数据
// 合并数据,将子表格数据转换为JSON字符串
// 创建一个新的数据结构,包含主表格和子表格的数据
const mergedData = [];
myCourses.value.forEach((course) => {
course.studentStudyInfoList.forEach((student) => {
const newRow = {
courseName: course.courseName,
teacherName: store.state.user.username,
studentName: student.studentName,
todayStudyTime: student.studyDay,
studyDay: student.todayStudyTime,
studyTimeSum: student.studyTimeSum,
score: student.score,
};
mergedData.push(newRow);
});
});
// 创建一个新的HTML表格
const tableElement = document.createElement('table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
// 创建表头
const headerRow = document.createElement('tr');
['开课课程名', '开课教师名', '选修学生姓名', '学生活跃天数', '学生视频观看数量', '学生章节完成数', '学生预估分数'].forEach((header) => {
const th = document.createElement('th');
th.textContent = header;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
// 创建表格行
mergedData.forEach((row) => {
const tr = document.createElement('tr');
Object.keys(row).forEach((key) => {
const td = document.createElement('td');
td.textContent = row[key];
tr.appendChild(td);
});
tbody.appendChild(tr);
});
tableElement.appendChild(thead);
tableElement.appendChild(tbody);
// 将新的表格转换为工作簿
const wb = XLSX.utils.table_to_book(tableElement);
/* get binary string as output */
const wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: true,
type: 'array',
});
try {
FileSaver.saveAs(new Blob([wbout], {
type: 'application/octet-stream',
}), store.state.user.username + '-课程数据.xlsx'); // 自定义文件名
} catch (e) {
toast('导出失败'+ e, 'error')
}
};
const drawer = ref(false)
const newCourseInfo = reactive({
courseId: '',
courseName: '',
courseType: '',
prerequisites: '',
courseDescription: '',
})
const predictUrl = ref(null)
function predictStudyStatue(userName, courseName){
toast("生成预测图中,请稍后...", "warning")
axios.post(path.remoteBaseUrl+"/transformer", {
"will_train": false,
"add_new": { "date": "2015/12/17", "open": Math.floor(Math.random() * (4000 - 2000 + 1)) + 3000},
"mode": 1,
"courseName": courseName,
"userName": userName
}, {
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
predictUrl.value = response.data[1].url
toast("生成预测图完成!", "success")
})
}
return {
teaGetCourseStudyInfo,
myCourses,
exportClick,
drawer,
newCourseInfo,
predictStudyStatue,
predictUrl,
}
}
}
</script>
<style scoped>
</style>
界面展示
6. 学生功能界面
主要包括:选课、退选、获取自己的选课信息
我的选课界面
<template>
<el-card style="width: 100%">
<el-table :data="myCourses.value" stripe style="width: 100%">
<el-table-column label="课程名称" prop="courseName" />
<el-table-column label="授课老师/单位" prop="teacherName" />
<el-table-column label="活跃天数" prop="studyDay" />
<el-table-column label="视频观看数量" prop="todayStudyTime" />
<el-table-column label="章节完成数" prop="studyTimeSum" />
<el-table-column label="预估学习得分" prop="score">
<template #default="scope">
<el-tag v-if="scope.row.score >= 90" size="small" type="success">
<span>{{ scope.row.score }},很好!继续保持!</span>
</el-tag>
<el-tag v-else-if="scope.row.score < 70" size="small" type="warning">
<span>{{ scope.row.score }},还需要加把劲哦!</span>
</el-tag>
<el-tag v-else size="small">
<span>{{ scope.row.score }},再努力一把会更棒!</span>
</el-tag>
</template>
</el-table-column>
<el-table-column align="right">
<template #default="scope">
<el-button
size="small"
@click="$router.push('/courseVideo/'+scope.row.courseName)"
>
去学习
</el-button>
<el-button
size="small"
type="danger"
@click="stuDeleteCourse(scope.row.courseName)"
>
退选
</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="mt-4" style="width: 100%" @click="$router.push('/courseCenter')">
去选课
</el-button>
</el-card>
</template>
<script>
import api from '../../api'
import {ref, onMounted, reactive} from 'vue'
import {ElMessageBox} from "element-plus";
import { toast, showModal } from "@/utils/popup";
import courseVideo from "@/components/courseVideo";
export default {
name: "studentCourse",
components: { courseVideo },
data() {
return {
dialogVisible: true, // 控制对话框的显示与隐藏
labelPosition: 'right',
};
},
setup() {
onMounted(() => {
getCourseStudyInfo()
})
// 加载课程数据
const userInfo = () => {
api.getUserInfo()
.then((res) => {
});
};
const handleClose = (done) => {
ElMessageBox.confirm('确定要取消更改?')
.then(() => {
done()
userInfoFormRef.value.resetFields(); // 清空表单字段
})
.catch(() => {
toast('出错了', 'error')
})
}
const myCourses = reactive([{}])
function getCourseStudyInfo() {
api.getCourseStudyInfo()
.then((res) => {
myCourses.value = res.data.data
})
}
async function stuDeleteCourse(courseName) {
try {
const shouldDelete = await showModal('确定删除该课程吗?', '提示', 'warning')
if (shouldDelete) {
api.stuDeleteCourse(courseName)
.then((res) => {
getCourseStudyInfo()
})
toast('退选课程成功', 'success')
}
} catch (err) {
}
}
return {
handleClose,
myCourses,
stuDeleteCourse,
}
}
}
</script>
<style scoped>
</style>
界面展示
7. 课程功能界面
主要包括:课程中心分类分页展示、课程详情页
课程中心界面
<template>
<el-card style="width: 100%">
<el-table :data="myCourses.value" stripe style="width: 100%">
<el-table-column label="课程名称" prop="courseName" />
<el-table-column label="授课老师/单位" prop="teacherName" />
<el-table-column label="活跃天数" prop="studyDay" />
<el-table-column label="视频观看数量" prop="todayStudyTime" />
<el-table-column label="章节完成数" prop="studyTimeSum" />
<el-table-column label="预估学习得分" prop="score">
<template #default="scope">
<el-tag v-if="scope.row.score >= 90" size="small" type="success">
<span>{{ scope.row.score }},很好!继续保持!</span>
</el-tag>
<el-tag v-else-if="scope.row.score < 70" size="small" type="warning">
<span>{{ scope.row.score }},还需要加把劲哦!</span>
</el-tag>
<el-tag v-else size="small">
<span>{{ scope.row.score }},再努力一把会更棒!</span>
</el-tag>
</template>
</el-table-column>
<el-table-column align="right">
<template #default="scope">
<el-button
size="small"
@click="$router.push('/courseVideo/'+scope.row.courseName)"
>
去学习
</el-button>
<el-button
size="small"
type="danger"
@click="stuDeleteCourse(scope.row.courseName)"
>
退选
</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="mt-4" style="width: 100%" @click="$router.push('/courseCenter')">
去选课
</el-button>
</el-card>
</template>
<script>
import api from '../../api'
import {ref, onMounted, reactive} from 'vue'
import {ElMessageBox} from "element-plus";
import { toast, showModal } from "@/utils/popup";
import courseVideo from "@/components/courseVideo";
export default {
name: "studentCourse",
components: { courseVideo },
data() {
return {
dialogVisible: true, // 控制对话框的显示与隐藏
labelPosition: 'right',
};
},
setup() {
onMounted(() => {
getCourseStudyInfo()
})
// 加载课程数据
const userInfo = () => {
api.getUserInfo()
.then((res) => {
});
};
const handleClose = (done) => {
ElMessageBox.confirm('确定要取消更改?')
.then(() => {
done()
userInfoFormRef.value.resetFields(); // 清空表单字段
})
.catch(() => {
toast('出错了', 'error')
})
}
const myCourses = reactive([{}])
function getCourseStudyInfo() {
api.getCourseStudyInfo()
.then((res) => {
myCourses.value = res.data.data
})
}
async function stuDeleteCourse(courseName) {
try {
const shouldDelete = await showModal('确定删除该课程吗?', '提示', 'warning')
if (shouldDelete) {
api.stuDeleteCourse(courseName)
.then((res) => {
getCourseStudyInfo()
})
toast('退选课程成功', 'success')
}
} catch (err) {
}
}
return {
handleClose,
myCourses,
stuDeleteCourse,
}
}
}
</script>
<style scoped>
</style>
界面展示
课程详情页面
<template>
<div class="container" style="background-color: aliceblue;">
<!-- 图片需要更换 -->
<img class="mb-5" src="../../../src/assets/course.jpg" alt="">
<div class="containeriner">
<p style="font-weight: bold; font-size: 2.2em;font-family: 隶书;" class="mb-4">{{ courseName }}</p>
<p class="mr-4" style="font-family: 楷体; font-size: 2rex;">课程id:{{ courseId }}</p>
<p class="mr-4" style="font-family: 楷体; font-size: 2rex;">课程类型:{{ courseType }}</p>
<p class="mr-4" style="font-family: 楷体; font-size: 2rex;">授课教师/机构:{{ courseTeacherName }}</p>
</div>
<div class="flex items-center justify-center text-gray-400 mt-3">
<span class="h-[0.1rem] w-50 bg-gray-400"></span>
<span style="font-family: 隶书; font-size: 3rex;">先修要求</span>
<span class="h-[0.1rem] w-50 bg-gray-400"></span>
</div>
<p style="text-align: center;font-family: 楷体; font-size: 2rex;" class="mr-30 ml-30">{{ coursePrerequisites }}</p>
<div class="flex items-center justify-center text-gray-400 mt-3">
<span class="h-[0.1rem] w-55 bg-gray-400"></span>
<span style="font-family: 隶书; font-size: 3rex;">课程简介</span>
<span class="h-[0.1rem] w-55 bg-gray-400"></span>
</div>
<p style="text-align: center;font-family: 楷体; font-size: 2rex; margin-left: 180px; margin-right: 180px" class="mr-30 ml-30 mb-5">{{ courseDescription }}</p>
<div class="contanerbtn" style="margin-bottom: 2%;">
<el-button v-if="$store.state.user.role == 2" round color="#6495ed"
class="w-[250px] text-white font-light hover:(bg-[#1343db] text-white) focus:(ring-8 font-semibold)"
type="primary" @click="handleDownload">生成课程教学PPT
</el-button>
<el-button v-else
round color="#6495ed"
class="w-[250px] text-white font-light hover:(bg-[#1343db] text-white) focus:(ring-8 font-semibold)"
type="primary" @click="selectCourse">加入我的选课
</el-button>
<el-button round color="#6495ed"
class="w-[250px] text-white font-light hover:(bg-[#1343db] text-white) focus:(ring-8 font-semibold)"
type="primary" @click="goBack">返回</el-button>
</div>
<XFBigModel :message="message"></XFBigModel>
<div class="bestsellers-container">
<chart-word-cloud :key="chartUpdateKey" :series="chartOptions"></chart-word-cloud>
</div>
</div>
</template>
<script>
import 'echarts-wordcloud';
import ChartWordCloud from '../../components/ChartWordCloud.vue'
import { useRoute, useRouter } from 'vue-router';
import { computed, onMounted, ref, reactive, nextTick } from "vue";
import api from "../../api"
import {toast} from "../../utils/popup";
import axios from 'axios';
import path from "@/api/path"
import XFBigModel from "@/components/XFBigModel";
export default {
name: "courseDetail",
components: {
ChartWordCloud,
XFBigModel
},
setup() {
const route = useRoute(); // 获取当前路由信息
const router = useRouter();
const courseId = ref();
const courseDescription = ref();
const courseType = ref();
const coursePrerequisites = ref();
const courseTeacherName = ref();
const chartOptions = reactive({
series: [
{
gridSize: 20,
data: [
{
name: '词云生成中',
value: 30,
textStyle: {
color: '#000', // 字体颜色设置为黑色
},
},
{ name: '词云生成中', value: 30 },
{ name: '词云生成中', value: 28 },
{ name: '词云生成中', value: 28 },
{ name: '词云生成中', value: 25 },
{ name: "词云生成中", value: 23 },
{ name: '词云生成中', value: 20 },
{ name: '词云生成中', value: 18 },
{ name: '词云生成中', value: 15 },
{ name: '词云生成中', value: 10 },
],
},
],
})
const message = ref('');
onMounted(() => {
fetchData()
})
async function fetchData() {
try {
const response = await axios.post(
path.remoteBaseUrl + '/process_text',
{text: "我需要你为我介绍这门课的核心内容,要求100个字且不分段:"+courseName.value},
{headers: {'Content-Type': 'application/json'}}
);
message.value = response.data.result + "有疑问就来问我吧!"; // 正确设置message的值
} catch (error) {
// 可能需要处理错误情况,比如设置默认值或提示用户
toast("获取数据失败,请稍后再试", "error")
}
}
onMounted(() => {
getCourseDetail() //获取课程详细信息
axios.post(path.remoteBaseUrl + "/word_cloud", {
text: courseName.value
}, {
headers: {
'Content-Type': 'application/json'
}
}).then(res => {
chartOptions.series[0].data[0].name = res.data.ls_keyword[0];
chartOptions.series[0].data[1].name = res.data.ls_keyword[1];
chartOptions.series[0].data[2].name = res.data.ls_keyword[2];
chartOptions.series[0].data[3].name = res.data.ls_keyword[3];
chartOptions.series[0].data[4].name = res.data.ls_keyword[4];
chartOptions.series[0].data[5].name = res.data.ls_keyword[5];
chartOptions.series[0].data[6].name = res.data.ls_keyword[6];
chartOptions.series[0].data[7].name = res.data.ls_keyword[7];
chartOptions.series[0].data[8].name = res.data.ls_keyword[8];
chartOptions.series[0].data[9].name = res.data.ls_keyword[9];
chartUpdateKey.value = Date.now(); // 或者使用任何能表示数据已更新的变量
})
})
const courseName = computed(() => route.params.courseName); // 使用computed属性获取courseName参数
const chartUpdateKey = ref(0);
function getCourseDetail() {
api.getCourseDetail(courseName.value).then(res => {
courseId.value = res.data.data.courseId;
courseDescription.value = res.data.data.courseDescription;
courseType.value = res.data.data.courseType;
coursePrerequisites.value = res.data.data.prerequisites;
courseTeacherName.value = res.data.data.teacherName;
chartUpdateKey.value = Date.now(); // 或者使用任何能表示数据已更新的变量
})
};
const goBack = () => {
router.back();
};
const _nevents = ref(null);
const _ndays_act = ref(null);
const _nplay_video = ref(null);
const _nchapters = ref(null);
function selectCourse() {
api.selectCourse(courseName.value).then(res => {
if (res.data.data == null) {
toast(res.data.msg, "error")
return
} else {
toast("选课成功", "success")
}
_nevents.value = res.data.data.studyTimeSum;
_ndays_act.value = res.data.data.studyDay;
_nplay_video.value = res.data.data.studyTimeSum;
_nchapters.value = res.data.data.todayStudyTime;
predictScore(_nevents.value, _ndays_act.value, _nplay_video.value, _nchapters.value)
})
}
const myScore = ref(null);
function updateStudentPredictScore(score){
myScore.value = Math.round(score * 100) + 50
if (myScore.value > 100){
myScore.value = 99
}
api.updateStudentPredictScore(courseName.value, myScore.value).then(res => {
})
}
function predictScore(_nevents, _ndays_act, _nplay_video, _nchapters) {
axios.post(path.remoteBaseUrl + "/score", {
nevents: _nevents,
ndays_act: _ndays_act,
nplay_video: _nplay_video,
nchapters: _nchapters
}, {
headers: {
'Content-Type': 'application/json'
},
}).then(res=>{
updateStudentPredictScore(res.data.score)
}).catch((error) => {
toast("获取数据失败,请稍后再试", "error")
});
}
function handleDownload() {
generatePPT().then(downloadUrl => {
if (downloadUrl) {
toast("生成ppt完成!", "success")
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `《${courseName.value}》教学.pptx`; // 假设你想以课程名为文件名
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} else {
toast("下载链接无效!", "error")
}
}).catch(error => {
toast("生成ppt失败!", "error")
});
}
async function generatePPT() {
toast("生成ppt中,请稍后...", "success")
try {
const response = await axios.post(path.remoteBaseUrl + "/ppt", {
pptName: courseName.value
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
});
return response.data.download_url; // 确保此响应包含正确的下载链接
} catch (error) {
toast("生成ppt失败!", "error")
}
}
return {
courseName,
courseId,
courseDescription,
courseType,
coursePrerequisites,
courseTeacherName,
getCourseDetail,
goBack,
selectCourse,
chartOptions,
chartUpdateKey,
generatePPT,
handleDownload,
message
};
}
}
</script>
<style scoped lang="less">
.bestsellers-container {
height: 18.56rem;
width: 100%;
background: white;
#charts-content {
/* 需要设置宽高后才会显示 */
width: 100%;
height: 100%;
}
}
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.container>img {
width: 100%;
height: 5%;
}
.containeriner {
display: flex;
flex-direction: column;
align-items: center;
// margin-bottom: 50px;
}
.titlebar {
display: flex;
flex-direction: horizontal;
align-items: center;
// margin-bottom: 50px;
}
.containerbtn {
display: flex;
flex-direction: horizontal;
align-items: center;
margin-bottom: 50px;
}
</style>
页面展示
8. 其他模块
api封装
import path from "./path"
import axios from "../utils/request";
const api = {
//-----------------------------用户接口-----------------------------------
//用户登录
login(username, password){
return axios.post(path.baseUrl + path.login, null, {
params:{
username: username,
password: password
},
headers: {
"Content-Type":"application/json;charset=utf-8"
},
})
},
register(user){
return axios.post(path.baseUrl + path.register, {
username: user.username,
password: user.password,
email: user.email,
phone: user.phone,
gender: user.gender,
role: user.role
}
)
},
logout(){
return axios.get(path.baseUrl + path.logout, {
params:{
token: localStorage.getItem("access_token")
}
})
},
updateUserInfo(userUpdateInfo){
return axios.post(path.baseUrl + path.updateUserInfo, {
username: userUpdateInfo.username,
nickname: userUpdateInfo.nickname,
email: userUpdateInfo.email,
phone: userUpdateInfo.phone,
}
)
},
//查看用户信息
getUserInfo(){
return axios.get(path.baseUrl + path.userInfo, {
params:{
token: localStorage.getItem("access_token")
}
})
},
//上传用户头像
uploadAvatar(formData){
return axios.post(path.baseUrl + path.uploadAvatar, formData, {
headers: {
"Content-Type":"multipart/form-data"
},
})
},
updateAvatarUrl(avatarUrl){
return axios.post(path.baseUrl + path.updateAvatarUrl, null, {
params:{
avatarUrl: avatarUrl,
token: localStorage.getItem("access_token")
}
})
},
//--------------------------课程相关接口------------------------------------
//获取所有课程
getCourses(pageInfo, courseType){
return axios.post(path.baseUrl + path.getAllCourses,{
pageNum: pageInfo.pageNum,
pageSize: pageInfo.pageSize},
{
params:{
courseType: courseType
}
})
},
//获取所有课程类别
getAllCourseType(){
return axios.get(path.baseUrl + path.getAllCourseType)
},
//根据课程类别获取课程
getCourseByType(type, pageInfo){
return axios.post(path.baseUrl + path.getCourseByType, {
type: type,
pageNum: pageInfo.pageNum,
pageSize: pageInfo.pageSize
})
},
//获取课程详情
getCourseDetail(courseName){
return axios.get(path.baseUrl + path.courseDetail, {
params:{
courseName: courseName
}
})
},
//---------------------------学生相关接口------------------------------------
selectCourse(courseName){
return axios.get(path.baseUrl + path.selectCourse, {
params:{
courseName: courseName,
token: localStorage.getItem("access_token")
}
})
},
getCourseStudyInfo(){
return axios.get(path.baseUrl + path.getCourseStudyInfo, {
params:{
token: localStorage.getItem("access_token")
}
})
},
stuDeleteCourse(courseName){
return axios.get(path.baseUrl + path.stuDeleteCourse, {
params:{
courseName: courseName,
token: localStorage.getItem("access_token")
}
})
},
updateStudentPredictScore(courseName, score){
return axios.get(path.baseUrl + path.updateStudentPredictScore, {
params:{
courseName: courseName,
score: score,
token: localStorage.getItem("access_token")
}
})
},
//---------------------------教师相关接口------------------------------------
teaGetCourses(){
return axios.get(path.baseUrl + path.teaGetCourses, {
params:{
token: localStorage.getItem("access_token")
}
})
},
teaGetCourseStudyInfo(){
return axios.get(path.baseUrl + path.teaGetCourseStudyInfo, {
params:{
token: localStorage.getItem("access_token")
}
})
},
teaReleaseCourse(newCourseInfo){
return axios.post(path.baseUrl + path.teaReleaseCourse, {
courseId: newCourseInfo.courseId,
courseName: newCourseInfo.courseName,
courseType: newCourseInfo.courseType,
prerequisites: newCourseInfo.prerequisites,
courseDescription: newCourseInfo.courseDescription,
}, {
params:{
token: localStorage.getItem("access_token")
}
})
}
}
export default api
router设置
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'index',
component: () => import(/* webpackChunkName: "about" */ '../views/index'),
children: [
{
path: '/courseCenter',
name: 'courseCenter',
component: () => import(/* webpackChunkName: "about" */ '../views/course/courseCenter'),
meta: {
// title: '选课中心'
}
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../components/About'),
meta: {
// title: '关于我们'
}
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "about" */ '../components/user/login'),
meta: {
title: '用户登录'
}
},
{
path: '/studentInfo',
name: 'studentInfo',
meta: {
requireAuth: true, // 添加该字段,表示进入这个路由是需要登录的
// title: '个人信息'
},
component: () => import(/* webpackChunkName: "about" */ '../components/user/userInfo'),
},
{
path: '/studentCourse',
name: 'studentCourse',
meta: {
requireAuth: true, // 添加该字段,表示进入这个路由是需要登录的
// title: '我的课程'
},
component: () => import(/* webpackChunkName: "about" */ '../views/student/studentCourse')
}
],
meta: {
// title: '龙兴启智智教平台'
}
},
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "about" */ '../components/user/login'),
meta: {
title: '用户登录'
}
},
{
path: '/teacherHome',
name: 'teacherHome',
meta: {
requireAuth: true, // 添加该字段,表示进入这个路由是需要登录的
// title: '龙兴启智智教平台'
},
children: [
{
path: '/teacherInfo',
name: 'teacherInfo',
meta: {
requireAuth: true, // 添加该字段,表示进入这个路由是需要登录的
// title: '个人信息'
},
component: () => import(/* webpackChunkName: "about" */ '../components/user/userInfo')
},
{
path: '/chosenCourses',
name: 'chosenCourses',
component: () => import(/* webpackChunkName: "about" */ '../views/teacher/chosenCourses'),
meta: {
// title: '学生选课管理'
}
},
{
path: '/teacherCourse',
name: 'teacherCourse',
component: () => import(/* webpackChunkName: "about" */ '../views/teacher/myCourses'),
meta: {
// title: '开课课程管理'
}
},
{
path: '/autoScoring',
name: 'autoScoring',
component: () => import(/* webpackChunkName: "about" */ '../components/AutoScoring2'),
meta: {
// title: '自动评分'
}
}
],
component: () => import(/* webpackChunkName: "about" */ '@/views/teacher/teacherHome')
},
{
path: '/adminHome',
name: 'adminHome',
meta: {
requireAuth: true, // 添加该字段,表示进入这个路由是需要登录的
},
component: () => import(/* webpackChunkName: "about" */ '@/views/admin/adminHome')
},
{
path: '/courseDetail/:courseName',
name: 'courseDetail',
meta: {
requireAuth: true, // 添加该字段,表示进入这个路由是需要登录的
// title: '课程详情'
},
component: () => import(/* webpackChunkName: "about" */ '@/views/course/courseDetail'),
props: true, // 添加这一行,以便将路由参数传递给组件
},
{
path: '/courseVideo/:courseName',
name: 'courseVideo',
component: () => import(/* webpackChunkName: "about" */ '../components/courseVideo'),
meta: {
// title: '课程视频'
}
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
router.beforeEach(async(to) => {
if (to.meta.title) {
document.title = to.meta.title
} else {
document.title = '青云智汇智教平台' //此处写默认的title
}
})
export default router