山东大学软件学院项目实训weblab-12_山东大学软件项目管理实践-程序员宅基地

技术标签: weblab  前端  vue.js  javascript  

前言

项目地址
本项目是为开发一套容器化的开发、运行、测试环境,用以支持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>
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xjunjin/article/details/124826288

智能推荐

在Google使用Borg进行大规模集群的管理 7-8-程序员宅基地

文章浏览阅读606次。为什么80%的码农都做不了架构师?>>> ..._google trace batch job

python加密字符串小写字母循环后错两位_python学习:实现将字符串进行加密-程序员宅基地

文章浏览阅读2.6k次,点赞3次,收藏3次。'''题目描述1、对输入的字符串进行加解密,并输出。2加密方法为:当内容是英文字母时则用该英文字母的后一个字母替换,同时字母变换大小写,如字母a时则替换为B;字母Z时则替换为a;当内容是数字时则把该数字加1,如0替换1,1替换2,9替换0;其他字符不做变化。s'''#-*-coding:utf-8-*-importre#判断是否是字母defisLetter(letter):iflen..._编写函数fun2实现字符串加密,加密规则为:如果是字母,将其进行大小写转换;如果

【Java容器源码】集合应用总结:迭代器&批量操作&线程安全问题_迭代器是否可以保证容器删除和修改安全操作-程序员宅基地

文章浏览阅读4.4k次,点赞6次,收藏8次。下面列出了所有集合的类图:每个接口做的事情非常明确,比如 Serializable,只负责序列化,Cloneable 只负责拷贝,Map 只负责定义 Map 的接口,整个图看起来虽然接口众多,但职责都很清晰;复杂功能通过接口的继承来实现,比如 ArrayList 通过实现了 Serializable、Cloneable、RandomAccess、AbstractList、List 等接口,从而拥有了序列化、拷贝、对数组各种操作定义等各种功能;上述类图只能看见继承的关系,组合的关系还看不出来,比如说_迭代器是否可以保证容器删除和修改安全操作

养老金融:编织中国老龄社会的金色安全网

在科技金融、绿色金融、普惠金融、养老金融、数字金融这“五篇大文章”中,养老金融以其独特的社会价值和深远影响,占据着不可或缺的地位。通过政策引导与市场机制的双重驱动,激发金融机构创新养老服务产品,如推出更多针对不同年龄层、风险偏好的个性化养老金融产品,不仅能提高金融服务的可获得性,还能增强民众对养老规划的主动参与度,从而逐步建立起适应中国国情、满足人民期待的养老金融服务体系。在人口老龄化的全球趋势下,中国养老金融的发展不仅仅是经济议题,更关乎社会的稳定与进步。养老金融:民生之需,国计之重。

iOS 创建开源库时如何使用图片和xib资源

在需要使用图片的地方使用下面的代码,注意xib可以直接设置图片。将相应的图片资源文件放到bundle文件中。

R语言学习笔记9_多元统计分析介绍_r语言多元统计分析-程序员宅基地

文章浏览阅读3.6k次,点赞4次,收藏66次。目录九、多元统计分析介绍九、多元统计分析介绍_r语言多元统计分析

随便推点

基于psk和dpsk的matlab仿真,MATLAB课程设计-基于PSK和DPSK的matlab仿真-程序员宅基地

文章浏览阅读623次。MATLAB课程设计-基于PSK和DPSK的matlab仿真 (41页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.90 积分武汉理工大学MATLAB课程设计.目录摘要 1Abstract 21.设计目的与要求 32.方案的选择 42.1调制部分 42.2解调部分 43.单元电路原理和设计 63.1PCM编码原理及设计 63.1.1PCM编码原理 ..._通信原理课程设计(基于matlab的psk,dpsk仿真)(五篇模版)

腾讯微搭小程序获取微信用户信息_微搭 用微信号登录-程序员宅基地

文章浏览阅读3.5k次,点赞6次,收藏28次。腾讯微搭小程序获取微信用户信息无论你对低代码开发的爱与恨, 微信生态的强大毋庸置疑. 因此熟悉微搭技术还是很有必要的! 在大多数应用中, 都需要获取和跟踪用户信息. 本文就微搭中如何获取和存储用户信息进行详细演示, 因为用户信息的获取和存储是应用的基础.一. 微搭每个微搭平台都宣称使用微搭平台可以简单拖拽即可生成一个应用, 这种说法我认为是"夸大其词". 其实微搭优点大致来说, 前端定义了很多组件, 为开发人员封装组件节省了大量的时间,这是其一; 其二对后端开发来说, 省去了服务器的部署(并没有省去后_微搭 用微信号登录

sql中索引的使用分析

sql中索引的使用分析

termux安装metasploit()-程序员宅基地

文章浏览阅读8.9k次,点赞16次,收藏108次。因为呢,termux作者,不希望让termux变成脚本小子的黑客工具,于是把msf , sqlmap等包删了。至于如何安装metasploit呢。apt update -y && apt upgrade -y #更新升级更新升级之后要安装一个叫 git 的安装包apt install git -y然后我们就开始//这里的话建议把手机放到路由器旁边,保持网络的优良。或者科学上网。//git clone https://github.com/gushmazuko/metaspl_termux安装metasploit

armbian docker Chrome_一起学docker06-docker网络-程序员宅基地

文章浏览阅读141次。一、Docker支持4种网络模式Bridge(默认)--network默认网络,Docker启动后创建一个docker0网桥,默认创建的容器也是添加到这个网桥中;IP地址段是172.17.0.1/16 独立名称空间 docker0桥,虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。host容器不会获得一个独立的network namespace,而是与宿主..._armbian 172.17.0.1

Ansible-Tower安装破解

Ansible-Tower安装破解。

推荐文章

热门文章

相关标签