<template> <div> <el-form :disabled="isDetail"> <el-card shadow="hover" class="m-b-20"> <div class="flex-between"> <el-page-header @back="goBack" :content="isDetail ? '查看' : (form.id ? '更新' : '创建') + '教学实验'"></el-page-header> <div> <el-button type="primary" @click="save(0)" v-show="!isDetail">{{ form.id ? "更新" : "创建" }}</el-button> </div> </div> </el-card> <el-card shadow="hover" class="mgr20 m-b-20"> <div> <p class="m-b-20">考核名称</p> <el-input placeholder="请输入考核名称" v-model.trim="form.experimentalName" clearable maxlength="15" class="inline-input"></el-input> </div> </el-card> <el-card shadow="hover" class="m-b-20"> <div> <p class="m-b-20">发布方式</p> <el-radio-group v-model="form.type"> <el-radio :label="1">手动发布</el-radio> <el-radio :label="2">定时发布</el-radio> </el-radio-group> </div> </el-card> <!-- 根据发布方式判断时间的显示 --> <el-card shadow="hover" class="m-b-20"> <div> <p class="m-b-20">实验时间</p> <!-- 手动发布显示 --> <div class="date-inputs" v-if="form.type==1"> 实验时长: <el-input type="number" min="0" v-model.trim="duration.day" placeholder></el-input> 天 <el-input type="number" min="0" v-model.trim="duration.hour" placeholder></el-input> 小时 <el-input type="number" min="0" v-model.trim="duration.minute" placeholder></el-input> 分 </div> <!-- 定时发布显示 --> <div v-if="form.type==2" class="addAssess"> <span class="mgr10">开始时间:</span> <el-date-picker size="small" v-model="date" type="datetimerange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :picker-options="pickerOptions"></el-date-picker> </div> </div> </el-card> <el-card shadow="hover" class="mgr20 m-b-20"> <div> <p class="m-b-20">课程</p> <div class="inline-input"> <el-select v-model="form.mallId" @change="initData"> <el-option v-for="item in curriculumList" :key="item.mallId" :label="item.curriculumName" :value="item.mallId"> </el-option> </el-select> <!-- <el-select v-model="form.curriculumId" @change="initData"> <el-option v-for="item in systemList" :key="item.id" :label="item.label" :value="item.id"> </el-option> </el-select> --> </div> </div> </el-card> <!-- 实训项目模块 --> <el-card shadow="hover" class="m-b-20"> <div class="flex-between m-b-20"> <span>实训项目</span> <div style="display: inline-flex;"> <div> <el-input placeholder="请输入项目名称" prefix-icon="el-icon-search" v-model.trim="keyword" clearable></el-input> </div> <el-button style="margin-left: 5px" type="primary" round @click="toProject">自定义实验项目</el-button> </div> </div> <!-- 实训项目表格 --> <el-table :data="projectData" class="table" stripe header-align="center"> <!-- 单选实训项目ID --> <el-table-column width="60" label="选择" align="center"> <template slot-scope="scope"> <el-radio v-model="form.projectId" :label="scope.row.projectId"> </el-radio> </template> </el-table-column> <el-table-column prop="projectName" label="项目名称" align="center"></el-table-column> <el-table-column prop="auth" label="项目权限" align="center"> <template slot-scope="scope"> {{ permissionsKeys[scope.row.permissions] }} </template> </el-table-column> <!-- <el-table-column prop="createUser" label="创建人" align="center"></el-table-column> --> <el-table-column prop="founder" label="创建人" align="center"> <template slot-scope="scope"> {{ scope.row.createUser }} </template> </el-table-column> <el-table-column prop="createTime" label="创建时间" align="center"></el-table-column> <el-table-column label="操作" align="center"> <template slot-scope="scope"> <el-button type="text" @click="showProject(scope.row)">查看</el-button> </template> </el-table-column> </el-table> <div class="pagination"> <el-pagination background :page-size="pageSize" @current-change="handleCurrentChange" layout="total,prev, pager, next" :total="total"></el-pagination> </div> </el-card> <el-card shadow="hover" class="mgr20 m-b-20"> <div> <p class="m-b-20">考核发布</p> <el-radio-group v-model="form.isSpecify"> <el-radio :label="1">指定范围</el-radio> <el-radio :label="0">无指定范围</el-radio> </el-radio-group> </div> <div v-show="form.isSpecify == 1" style="padding-top: 24px;"> <p class="m-b-20">班级名称</p> <el-input placeholder="请输入班级名称" v-model.trim="filterClassName" class="inline-input m-b-20" clearable> <el-button slot="append" icon="el-icon-search"></el-button> </el-input> <div v-show="tagList.length" class="m-b-20"> <el-tag v-for="(tag, index) in tagList" :key="index" closable @close="handleCloseTag(tag)" style="margin-right: 10px"> {{ tag.organizationName }} </el-tag> </div> <div class="tree-con"> <student-tree ref="tree" node-key="id" show-checkbox highlight-current default-expand-all lazy :load="loadTree" :default-checked-keys="defaultCheckedKeys" :props="{children: 'children', label: 'organizationName', isLeaf: 'leaf'}" :filter-node-method="filterNode" @check="handleCheck"> </student-tree> </div> </div> </el-card> <!-- 邀请码 --> <el-card v-if="form.isSpecify == 0" shadow="hover" class="m-b-20"> <div style="margin-bottom: 10px"> <p class="m-b-20">设置邀请码</p> <el-radio-group v-model="form.isEnableCode"> <el-radio :label="1">是</el-radio> <el-radio :label="0">否</el-radio> </el-radio-group> </div> <div v-if="form.isEnableCode == 1"> <el-input style="display: inline-block;width: auto;margin-right: 10px" type="number" v-model.trim="form.invitationCode" maxlength="6" placeholder="请设置6个数字"></el-input> <el-button type="text" @click="createInv">随机</el-button> </div> </el-card> </el-form> </div> </template> <script> import util from "@/libs/util"; import { mapState, mapActions } from "vuex"; import StudentTree from "@/components/student-tree/src/tree"; export default { components: { StudentTree }, data () { return { founderKeys: { 0: "系统", 1: "老师" }, cidList: [], permissionsKeys: { 0: "练习", 1: "考核", 2: "竞赛" }, isDetail: Boolean(this.$route.query.show), form: { id: this.$route.query.id ? this.$route.query.id : "", experimentalName: "", type: 1, // 发布类型(1、手动发布 2、定时发布) experimentDuration: "0d0h0m", curriculumId: "", mallId: '', projectId: "", isSpecify: 1, // 考核发布(1、指定范围 0、无指定范围) isEnableCode: 0, //是否设置邀请码 invitationCode: "", status: 0, // 状态(0、待开始 1、进行中 2、已结束) classId: "", stuInfo: [] }, date: "", // 实验时间 duration: { day: "", hour: "", minute: "" }, // 实验时长 startTime: "0000-00-00 00:00:00", //开始时间 stopTime: "0000-00-00 00:00:00", //结束时间 expNameRepeat: false, // 考核名称是否重复 curriculumList: [], // 课程列表 filterClassName: "", // 班级名称搜索 tagList: [], // 班级名称标签 defaultCheckedKeys: [], // 默认选中 allCheckedNodes: [], // 当前选中和半选节点 keyword: "", // 项目名称搜索 searchTimer: null, surplusTime: "", projectDataAll: [], projectData: [], pickerOptions: { disabledDate: time => { return time.getTime() < new Date().getTime() - 86400000; } }, page: 1, pageSize: 5, total: 0, isToProject: false, systemList: [], submiting: false, // 新增编辑防抖标识 updateTime: 0 }; }, computed: { ...mapState("project", [ "assFields" ]) }, // 离开的时候判断是否有保存更改的信息,没有则拦截并提示 beforeRouteLeave (to, from, next) { if (!this.isToProject && this.updateTime) { this.$confirm(`您所更改的内容未更新,是否更新?`, '提示', { type: 'warning', closeOnClickModal: false }).then(() => { this.save(next) }).catch(() => { next() }) } else { next() } }, beforeDestroy () { if (!this.isToProject) this.setAss({}); }, mounted () { this.date = [util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(new Date().getTime() + 300000)), util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(new Date().getTime() + 300000))]; this.form.id && this.getData(); this.recoveryData(); this.getschoolCourse(); }, watch: { // 监听信息是否有更改,有的话页面离开的时候要询问是否要保存 form: { handler (val) { this.updateTime++ }, deep: true }, date: function (val) { if (val[0] != "0000-00-00 00:00:00") { this.startTime = util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(val[0])); this.stopTime = util.formatDate("yyyy-MM-dd hh:mm:ss", new Date(val[1])); this.updateTime++ } }, duration: { handler (n, o) { this.form.experimentDuration = `${n.day ? n.day : 0}d${n.hour ? n.hour : 0}h${n.minute ? n.minute : 0}m`; this.updateTime++ }, deep: true }, keyword: function (val) { clearTimeout(this.searchTimer); this.searchTimer = setTimeout(() => { this.initData(); }, 500); }, filterClassName (val) { this.$refs.tree.filter(val); } }, methods: { ...mapActions("project", [ "setAss" ]), handleCloseTag (tag) { // 关闭班级标签 this.allCheckedNodes = this.$refs.tree.getCheckedNodes().concat(this.$refs.tree.getHalfCheckedNodes()); let tagIndex = this.tagList.findIndex(i => i.id === tag.id); this.tagList.splice(tagIndex, 1); // 设置选中 let setKeys = []; this.allCheckedNodes.forEach(i => { if (i.level === 4 && i.parentId !== tag.id) setKeys.push(i.nodeKey); }); this.$refs.tree.setCheckedKeys(setKeys); this.allCheckedNodes = this.allCheckedNodes.filter(i => (i.level === 3 && i.id !== tag.id) || (i.level === 4 && i.parentId !== tag.id)); }, handleCheck (data, checked) { // 当复选框被点击的时候触发 // 全选和半选状态下的班级都显示在标签 let checkedClass = checked.checkedNodes.filter(i => i.level === 3); let halfCheckedClass = checked.halfCheckedNodes.filter(i => i.level === 3); this.tagList = [...checkedClass, ...halfCheckedClass].map(i => { return { id: i.id, organizationName: i.organizationName }; }); // 缓存全选和半选状态下的所有节点 this.allCheckedNodes = [...checked.checkedNodes, ...checked.halfCheckedNodes]; }, filterNode (value, data) { // 对树节点进行过滤 if (!value) return true; return data.organizationName.indexOf(value) !== -1; }, loadTree (node, resolve) { // 懒加载所在班级树结构 let level = 1; let parentId = ""; if (node.level === 0) { this.treeNode = node; this.treeResolve = resolve; this.getTreeData(resolve, level, parentId); } else if (node.level > 3) { return resolve([]); } else { if (node.data && node.data.level && node.data.id) { this.getTreeData(resolve, node.data.level + 1, node.data.id); } } }, async getTreeData (resolve, level, parentId) { // 获取组织架构树数据 let { status, treeList } = await this.$post(`${this.api.stuOrganizationTree}?level=${level}&parentId=${parentId}`); if (status === 200 && treeList.length) { let result = []; treeList.forEach(i => { if (i.level === 4) { i.nodeKey = `${i.parentId}-${i.id}`; i.leaf = true; } else { i.nodeKey = `${i.id}-${new Date().getTime()}`; i.leaf = false; } result.push(i); }); this.$nextTick(() => { // 编辑时,设置默认勾选 if (this.form.classInfo && this.form.classInfo.length) { let keys = this.form.classInfo.map(i => { return i.id; }); this.defaultCheckedKeys = keys; } // 获取树结构选中和半选中状态下的数据 let nodes = this.$refs.tree.getCheckedNodes().concat(this.$refs.tree.getHalfCheckedNodes()); this.allCheckedNodes = nodes; this.tagList = nodes.filter(i => i.level === 3); }); return resolve(result); } else { return resolve([]); } }, getschoolCourse () { // 获取课程 this.$get(this.api.getSchoolEffectiveCourse).then(({ data }) => { this.curriculumList = data; if (this.curriculumList.length) { if (!this.form.mallId) this.form.mallId = data[0].mallId this.getProjectData() } this.$nextTick(() => { this.updateTime = 0 }) }).catch(err => { }); }, getProjectData () { const curItem = this.curriculumList.find(e => e.mallId === this.form.mallId) console.log("🚀 ~ file: index.vue:471 ~ getProjectData ~ curItem:", curItem) let data = { pageNum: this.page, pageSize: this.pageSize, cid: curItem.cid, projectName: this.keyword, systemId: curItem ? curItem.systemId : 1, permissions: 1, mallId: this.form.mallId } this.$post(this.api.projectListByCourseId, data).then(({ data }) => { this.projectData = data.records this.total = data.total }).catch(err => { }) }, handlePage () { let result = this.projectDataAll.slice((this.page - 1) * this.pageSize, this.page * this.pageSize); this.projectData = result; }, initData () { this.page = 1; this.getProjectData(); }, save (cb) { // 提交 if (this.submiting) return false if (!this.form.experimentalName) return util.warningMsg("请填写考核名称"); if (this.expNameRepeat) return util.warningMsg("考核名称重复,请重新输入"); if (this.form.type !== 1) { if (new Date().getTime() > new Date(this.startTime).getTime()) return util.warningMsg("开始时间不能早于当前时间"); let timestamp = new Date(new Date(this.stopTime).getTime() - new Date(this.startTime).getTime()); let minute = 1000 * 60; let hour = minute * 60; let day = hour * 24; this.form.experimentDuration = `${Math.floor(timestamp / day)}d${Math.floor(timestamp % day / hour)}h${Math.floor(timestamp % day % hour / minute)}m`; } if (this.form.type == 1 && this.form.experimentDuration == "0d0h0m") return util.warningMsg("请填写实验时长"); if (this.form.type == 2 && this.startTime == "0000-00-00 00:00:00") return util.warningMsg("请填写实验时间"); if (this.form.type == 1) { const { day, hour, minute } = this.duration if (String(day).includes('.')) return util.warningMsg('实验天数请填写整数') if (day < 0) return util.warningMsg('实验天数请勿填写负数') if (String(hour).includes('.')) return util.warningMsg('实验小时请填写整数') if (hour < 0) return util.warningMsg('实验小时请勿填写负数') if (String(minute).includes('.')) return util.warningMsg('实验分钟请填写整数') if (minute < 0) return util.warningMsg('实验分钟请勿填写负数') } if (!this.form.projectId) return util.warningMsg("请选择实训项目"); if (this.form.isSpecify == 0 && this.form.isEnableCode == 1) { if (!this.form.invitationCode) return util.warningMsg("请设置邀请码"); if (!this.form.invitationCode || String(this.form.invitationCode).length < 6 || isNaN(this.form.invitationCode)) return util.warningMsg("请输入6位纯数字邀请码"); } this.form.startTime = this.form.type == 2 ? this.startTime : '' this.form.stopTime = this.form.type == 2 ? this.stopTime : '' let classId = []; let stuInfo = []; this.allCheckedNodes.forEach(i => { if (i.level === 3) { classId.push(i.id); } else if (i.level === 4) { stuInfo.push({ classId: i.parentId, stuAccountId: i.id }); } }); if (this.isSpecify == 1 && !stuInfo.length) { util.warningMsg("请选择学生"); return; } else { this.form.classId = classId.toString(); this.form.stuInfo = stuInfo; } const curItem = this.curriculumList.find(e => e.mallId === this.form.mallId) this.form.curriculumId = curItem.cid this.submiting = true if (this.form.id) { this.$post(this.api.modifyAssessment, this.form).then(async res => { this.updateTime = 0 util.successMsg("修改成功"); cb ? cb() : this.$router.back() }).catch(err => { this.submiting = false }); } else { this.$post(this.api.saveAssessment, this.form).then(res => { this.updateTime = 0 util.successMsg("创建成功"); cb ? cb() : this.$router.back() }).catch(err => { this.submiting = false }); } }, getData () { // 获取详情 this.$get(`${this.api.getDetailById}?id=${this.form.id}`).then(({ data }) => { this.form = data; this.startTime = data.startTime this.stopTime = data.stopTime this.formatDuration(); this.getschoolCourse(); }).catch(err => { }); }, formatDuration () { // 格式化实验时长 let duration = this.form.experimentDuration.replace(/\D+/g, ",").split(","); this.duration = { day: duration[0], hour: duration[1], minute: duration[2] }; this.date = [this.startTime, this.stopTime]; this.$nextTick(() => { this.updateTime = 0 }) }, recoveryData () { // 恢复数据 if (JSON.stringify(this.assFields) != "{}") { let info = this.assFields; this.form = info.form; this.duration = info.duration; // 实验时长 this.startTime = info.startTime; //开始时间 this.stopTime = info.startTime; //结束时间 this.expNameRepeat = info.expNameRepeat; // 考核名称是否重复 this.allCheckedNodes = info.allCheckedNodes; // 选中的树节点 this.formatDuration(); } }, handleCacheData () { // 缓存数据,用于从项目管理页面返回时,数据回显 this.allCheckedNodes.forEach(i => { if (i.level === 4) { this.form.stuInfo.push({ classId: i.parentId, stuAccountId: i.id }); } }); let data = { form: this.form, date: this.date, // 实验时间 duration: this.duration, // 实验时长 startTime: this.startTime, //开始时间 stopTime: this.startTime, //结束时间 expNameRepeat: this.expNameRepeat, // 考核名称是否重复 allCheckedNodes: this.allCheckedNodes // 选中的树节点 }; this.setAss(data); this.isToProject = true; }, toProject () { this.handleCacheData(); this.$router.push("/project/list?show=1"); }, showProject (row) { this.handleCacheData(); this.$router.push(`/project/add?projectId=${row.projectId}&show=1`); }, createInv () { let result = ""; for (let i = 0; i < 6; i++) { result += Math.floor(Math.random() * 10); } this.form.invitationCode = result; }, handleCurrentChange (val) { this.page = val; this.getProjectData(); }, // 返回上一页 backPage () { this.$router.back() }, goBack () { // 更改了信息才需要提示 if (this.updateTime) { this.$confirm(`编辑的内容未保存,是否保存?`, '提示', { type: 'warning', closeOnClickModal: false }).then(() => { this.save() }).catch(() => { this.updateTime = 0 this.backPage() }) } else { this.updateTime = 0 this.backPage() } } } }; </script> <style lang="scss" scoped> .inline-input { width: 500px; } .date-inputs { .el-input { width: 100px; } } .tree-con { height: 400px; max-height: 400px; width: 100%; border: 1px solid #dcdfe6; border-radius: 4px; padding: 10px 10px 10px 40px; overflow: auto; /deep/ .el-tree-node__content { height: 30px; .el-tree-node__expand-icon { padding: 2px; color: #fff; background-color: #9278ff; border-radius: 30px; margin: 0 10px; } .el-tree-node__expand-icon.is-leaf { background-color: transparent; } .el-icon-caret-right:before { font-size: 14px; } .el-checkbox__inner { width: 18px; height: 18px; border: 1px solid #9278ff; border-radius: 20px; } .el-checkbox__inner::after { position: absolute; top: 3px; left: 6px; } .el-checkbox__input.is-indeterminate .el-checkbox__inner::before { position: absolute; top: 7px; } } } </style>