前言
项目地址
本项目是为开发一套容器化的开发、运行、测试环境,用以支持Web开发、程序设计等课程的实验教学。
任务
添加上传用户信息、项目管理以及组织管理模块。
通过请求和响应拦截器验证token
instance.interceptors.request.use(
function (config) {
if(config.headers!.access_token==''){
config.headers!.access_token=store.getToken;
}
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
if (response.data.msg == undefined) {
store.userLogout();
router.push({ path: '/' })
}
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
);
个人中心页面
提交用户个人信息
<template>
<el-page-header :icon="ArrowLeft" content="修改个人信息" @back="goback()" />
<el-main>
<el-descriptions :column="1" border>
<el-descriptions-item label="姓名" label-align="center" align="center" label-class-name="my-label"
class-name="my-content" width="30px">
<el-input type="text" v-model="selfInfo.realName" />
</el-descriptions-item>
<el-descriptions-item label="年级" label-align="center" align="center">
<el-input v-model="selfInfo.grade" oninput="value=value.replace(/[^\d]/g,'')" />
</el-descriptions-item>
<el-descriptions-item label="班级" label-align="center" align="center">
<el-input type="text" v-model="selfInfo.banji" />
</el-descriptions-item>
<el-descriptions-item label="学号" label-align="center" align="center">
<el-input type="text" v-model="selfInfo.studentId" />
</el-descriptions-item>
<el-descriptions-item label="头像" label-align="center" align="center">
<el-upload class="avatar-uploader" action="#" :show-file-list="false" :before-upload="beforeAvatarUpload"
:http-request="submitImage">
<img v-if="selfInfo.imageUrl" :src="selfInfo.imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
<template #tip>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2MB</div>
</template>
</el-upload>
</el-descriptions-item>
<el-descriptions-item label="个性签名" label-align="center" align="center">
<el-input type="text" v-model="selfInfo.signature" />
</el-descriptions-item>
<!-- <el-descriptions-item label="组织" label-align="center" align="center"
><el-row>
<el-col :span="24">
<el-button @click="deletelesson()">退出</el-button>
<el-button @click="addlesson()">加入</el-button
>
</el-col>
<el-col :span="24">
<el-checkbox
v-model="checkAll"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
>Check all</el-checkbox>
<el-checkbox-group
v-model="checkedCities"
@change="handleCheckedCitiesChange"
>
<el-checkbox v-for="city in cities" :key="city" :label="city">{{
city
}}</el-checkbox>
</el-checkbox-group>
</el-col>
</el-row>
</el-descriptions-item
> -->
</el-descriptions>
</el-main>
<el-footer>
<el-row :gutter="20">
<el-col :span="12" />
<el-col :span="8">
<el-button @click="submit()">保存</el-button>
</el-col>
<el-col :span="6" />
</el-row>
</el-footer>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { Action } from 'element-plus'
import { Plus, ArrowLeft } from '@element-plus/icons-vue'
import { useRouter } from "vue-router"
import type { UploadProps } from 'element-plus'
import { request } from '@/network/request'
import { useLoginStore } from '@/stores/store'
const router = useRouter();
const store = useLoginStore();
const selfInfo = ref({
realName: '',
grade: '',
banji: '',
studentId: '',
imageUrl: '',
signature: ''
})
request('/weblab/user/getUserWithInfo', undefined, store.getToken)
.then(res => {
console.log(res.data);
if (res.data.msg == 'success') {
const data = res.data.pkg;
selfInfo.value.realName = data.realName;
selfInfo.value.grade = data.grade;
selfInfo.value.banji = data.banji;
selfInfo.value.studentId = data.studentId;
selfInfo.value.imageUrl = data.headImg;
selfInfo.value.signature = data.signature;
}
})
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
ElMessage.error('头像必须为JPG/PNG格式!')
return false;
} else if (rawFile.size / 1024 / 1024 > 2) {
ElMessage.error('头像大小不能超过2MB!');
return false
}
return true
}
const submit = () => {
let param = new FormData();
// if (selfInfo.value.realName != '')
param.append('realName', selfInfo.value.realName);
// if (selfInfo.value.grade != '')
param.append('grade', selfInfo.value.grade);
// if (selfInfo.value.banji != '')
param.append('banji', selfInfo.value.banji);
// if (selfInfo.value.studentId != '')
param.append('studentId', selfInfo.value.studentId);
// if (selfInfo.value.imageUrl != '')
param.append('headImg', selfInfo.value.imageUrl);
// if (selfInfo.value.signature != '')
param.append('signature', selfInfo.value.signature);
request('/weblab/user/setUserInfo', param)
.then(res => {
if (res.status == 200 && res.data.msg == 'success') {
ElMessage({
showClose: true,
message: '保存成功',
type: 'success',
grouping: true
})
}
})
.catch(error => {
console.log(error);
})
}
const submitImage = (file: any) => {
let param = new FormData();
param.append('image', file.file);
request('/weblab/user/uploadHeadImg', param, store.getToken)
.then(res => {
if (res.data.msg == 'success') {
selfInfo.value.imageUrl = res.data.pkg;
}
})
.catch(error => {
console.log(error);
})
}
// const checkAll = ref(true)
// const isIndeterminate = ref(true)
// const checkedCities = ref(['lesson1', 'lesson2', 'lesson3', 'lesson4'])
// const cities = ['lesson1', 'lesson2', 'lesson3', 'lesson4']
// const addlesson = () => {
// ElMessageBox.prompt('输入课程班级邀请码', '添加课程班级', {
// confirmButtonText: 'OK',
// cancelButtonText: 'Cancel',
// })
// }
// const deletelesson = (currIdx: number) => {
// ElMessageBox.confirm('将退出课程班级,继续?', 'Warning',
// {
// confirmButtonText: 'OK',
// cancelButtonText: 'Cancel',
// type: 'warning',
// })
// // .then(() => {
// // curriculumData.value.splice(currIdx, 1);
// // })
// // .catch(() => {
// //
// // })
// }
// const handleCheckAllChange = (val: boolean) => {
// checkedCities.value = val ? cities : []
// isIndeterminate.value = false
// }
// const handleCheckedCitiesChange = (value: string[]) => {
// const checkedCount = value.length
// checkAll.value = checkedCount === cities.length
// isIndeterminate.value = checkedCount > 0 && checkedCount < cities.length
// }
function goback() {
router.push({ path: '/coding' });
}
</script>
<style scoped>
.my-label {
background: var(--el-color-success-light-9);
}
.my-content {
background: var(--el-color-danger-light-9);
}
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>
组织模块
<template>
<el-main v-show="activeIndex == '5'">
<el-header>
<el-row :gutter="20">
<el-col :span="6">
<el-input v-model="invitationCode" placeholder="组织邀请码" />
</el-col>
<el-col :span="6">
<el-button text @click="addOrganization()">添加组织</el-button>
</el-col>
<el-col :span="6" />
<el-col :span="6" />
</el-row>
</el-header>
<el-main>
<el-table :data="organizationData" border style="width: 100%">
<el-table-column prop="lesson" label="课程" width="510px" />
<el-table-column prop="class" label="班级" width="510px" />
<el-table-column width="110px">
<template #default="scope">
<el-button text size="small" @click="deleteOrganization(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
</el-main>
</template>
<script lang="ts" setup>
const invitationCode = ref('');
const organizationData = ref([
{
lesson: '项目',
class: '软一',
},
{
lesson: '实训',
class: '软一',
},
])
const addOrganization = () => {
}
const deleteOrganization = (val: any) => {
ElMessageBox.confirm('将删除该课程,继续?', 'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
const targetIdx = organizationData.value.findIndex((d) => d == val);
organizationData.value.splice(targetIdx, 1);
})
}
</script>
项目中心
<template>
<el-main v-show="activeIndex == '2'">
<el-tabs v-model="activeProjectTabName" class="demo-tabs" @tab-click="handleProjectTabClick">
<el-tab-pane label="课程任务" name="1">
<el-main>
<el-table :data="homeworkTableData" border style="width: 100%">
<el-table-column prop="lesson" label="课程" width="180" />
<el-table-column prop="date" label="截止日期" sortable width="180">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon>
<timer />
</el-icon>
<span style="margin-left: 10px">{{ scope.row.date }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="mission" label="任务简介" />
<el-table-column fixed="right" width="120">
<template #default="scope">
<el-button text size="small" @click="checkDetail(scope.row.missionDetail)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
</el-tab-pane>
<el-tab-pane label="我的项目" name="2">
<div v-show="!showDetail">
<el-header>
<el-button @click="addProject()">
添加<el-icon class="el-icon--right">
<Plus />
</el-icon>
</el-button>
<el-button @click="deleteProject()">
删除<el-icon class="el-icon--right">
<Delete />
</el-icon>
</el-button>
<el-button @click="downloadProject()">
下载<el-icon class="el-icon--right">
<Bottom />
</el-icon>
</el-button>
</el-header>
<el-main>
<el-dialog v-model="createProjectFormVisible" title="添加项目" width="30%">
<el-form :model="createProjectForm">
<el-form-item label="项目名称" label-width="100px">
<el-input v-model="createProjectForm.name" autocomplete="off" />
</el-form-item>
<el-form-item label="项目简介" label-width="100px">
<el-input type="textarea" v-model="createProjectForm.description" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="createProjectFormVisible = false">取消</el-button>
<el-button type="primary" @click="confirmAddProject">确认</el-button>
</span>
</template>
</el-dialog>
<el-table ref="projectTableRef" :data="projectData" border style="width: 100%"
@selection-change="handleProjectSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="projectName" label="项目名字" width="180">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon>
<folder />
</el-icon>
<span style="margin-left: 10px">{{ scope.row.projectName }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="date" label="上次更新" width="180" />
<el-table-column prop="introduction" label="项目简介" />
<el-table-column fixed="right" width="220">
<template #default="scope">
<el-button text size="small" @click="showProjectDetail(scope.row)">项目详情</el-button>
<el-button text size="small" @click="editProject(scope.raw)">编辑项目</el-button>
</template>
</el-table-column>
</el-table>
</el-main>
</div>
<div v-show="showDetail">
<el-main>
<el-page-header content="项目详情" @back="closeProjectDetail" />
<el-container>
<el-main>
<el-table :data="selectedProjectDetail?.files" style="width: 100%">
<el-table-column prop="name" label="文件夹名字" width="180">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon>
<folder />
</el-icon>
<el-button type="text" style="margin-left: 10px">{{ scope.row.name }}</el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="date" label="上次更新" width="180" />
<el-table-column prop="introduction" label="简介" />
<el-table-column fixed="right" width="120">
<template #default="scope">
<el-button text size="small" @click="deleteProjecFolder(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-main>
<el-divider />
<h6>项目输出日志</h6>
<div class="demo-radius">
<el-text class="example-demonstration">{{selectedProjectDetail?.outputlog}}</el-text>
</div>
</el-main>
</el-main>
<el-aside width="300px">
<el-row>
<el-col :span="2">
<el-divider direction="vertical" style="height: 800px" />
</el-col>
<el-col :span="22">
<el-form label-width="120px">
<el-form-item size="large" label="项目介绍">
</el-form-item>
<el-text class="example-demonstration">{{selectedProjectDetail?.introduction}}</el-text>
</el-form>
</el-col>
</el-row>
</el-aside>
</el-container>
</el-main>
</div>
</el-tab-pane>
</el-tabs>
</el-main>
</template>
<script lang="ts" setup>
//项目中心
const showDetail = ref(false);
const activeProjectTabName = ref('1');
const createProjectFormVisible = ref(false);
const createProjectForm = reactive({
name: '',
description: ''
})
const projectTableRef = ref<InstanceType<typeof ElTable>>()
const selectedProject = ref<projectDataIF[]>();
const selectedProjectDetail = ref<projectDetailIF>();
//任务数据
interface hwDataIF {
lesson: string,
mission: string,
date: string,
missionDetail: string
}
const homeworkTableData = ref<hwDataIF[]>([
{
lesson: '项目实训',
mission: '任务简介',
date: '2022-05-15',
missionDetail: '用vue3+springboot完成一个简单的前后端分离项目'
},
{
lesson: '项目实训',
mission: '任务简介',
date: '2022-06-15',
missionDetail: '用vue3+springboot完成一个简单的前后端分离项目'
},
])
//项目数据
//项目详情内容
interface projectFilesIF {
name: string,
date: string,
introduction: string
}
interface projectDetailIF {
outputlog: string,
introduction: string,
files: projectFilesIF[]
}
interface projectDataIF {
projectName: string,
introduction: string,
date: string,
detail: projectDetailIF
}
const projectData = ref<projectDataIF[]>([
{
projectName: '项目实训',
introduction: '项目实训简介',
date: '2022-05-15',
detail: {
outputlog: '输出日志',
introduction: '项目介绍',
files: [
{
name: 'src',
introduction: 'src',
date: '2022-05-15',
},
{
name: 'views',
introduction: 'views',
date: '2022-05-15',
},
]
}
},
])
//查看作业详情
const checkDetail = (detail: string) => {
ElMessageBox.alert(detail, '任务详情', {
confirmButtonText: 'OK',
})
}
//编辑项目
const editProject = (data: projectDataIF) => {
activeIndex.value = '4'
}
//查看项目详情
const showProjectDetail = (data: projectDataIF) => {
const idx = projectData.value.findIndex(item => item == data);
selectedProjectDetail.value = projectData.value[idx].detail;
showDetail.value = true;
}
const closeProjectDetail = () => {
showDetail.value = false;
}
//删除项目内容
const deleteProjecFolder = (index: number) => {
ElMessageBox.confirm('将删除该文件,继续?', 'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
selectedProjectDetail.value?.files.splice(index, 1);
})
}
//添加项目
const addProject = () => {
createProjectFormVisible.value = true;
}
const confirmAddProject = () => {
createProjectFormVisible.value = false;
projectData.value.push({
projectName: createProjectForm.name,
introduction: createProjectForm.description,
date: getFormDate(),
detail: {
outputlog: '',
introduction: '',
files: []
}
})
}
//删除项目
const deleteProject = () => {
ElMessageBox.confirm('将删除选中的项目,继续?', 'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
projectData.value = projectData.value.filter((x) => !selectedProject.value!.some((item) => x.projectName === item.projectName));
projectTableRef.value!.clearSelection();
})
}
//下载项目
const downloadProject = () => {
projectTableRef.value!.clearSelection();
}
const handleProjectTabClick = (pane: TabsPaneContext, ev: Event) => {
// console.log(pane);
}
const handleProjectSelectionChange = (val: projectDataIF[]) => {
selectedProject.value = val;
}
</script>