This commit is contained in:
zdg 2024-07-26 12:52:22 +08:00
commit beca8c21b6
11 changed files with 1553 additions and 2 deletions

View File

@ -22,6 +22,7 @@
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@element-plus/icons-vue": "^2.3.1",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"@vueuse/core": "^10.11.0",
"crypto-js": "^4.2.0",
"electron-dl-manager": "^3.0.0",
@ -37,8 +38,9 @@
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"spark-md5": "^3.0.2",
"vue-cropper": "^1.0.3",
"vue-router": "^4.4.0"
"vue-cropper": "^1.1.4",
"vue-router": "^4.4.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2",

View File

@ -0,0 +1,132 @@
// 查询evaluation列表
import request from '@/utils/request'
// 查询班级列表
export function listClassmain(query) {
return request({
url: '/education/classmain/list',
method: 'get',
params: query
})
}
// 查询学生列表
export function listClassuser(query) {
return request({
url: '/education/classuser/list',
method: 'get',
params: query
})
}
// 新增班级
export function addClassmain(data) {
return request({
url: '/education/classmain',
method: 'post',
data: data
})
}
// 查询所有学科的列表
export function listEvaluation(query) {
return request({
url: '/education/evaluation/list',
method: 'get',
params: query
})
}
// 新增小组
export function addClassgroup(data) {
return request({
url: '/education/classgroup',
method: 'post',
data: data
})
}
//班级详情
export function getClassmain(id) {
return request({
url: '/education/classmain/' + id,
method: 'get'
})
}
// 获取小组列表
export function listClassgroup(query) {
return request({
url: '/education/classgroup/list',
method: 'get',
params: query
})
}
//删除小组
export function delClassgroup(id) {
return request({
url: '/education/classgroup/' + id,
method: 'delete'
})
}
//查询小组信息
export function getClassgroup(id) {
return request({
url: '/education/classgroup/' + id,
method: 'get'
})
}
//修改小组信息
export function updateClassgroup(data) {
return request({
url: '/education/classgroup',
method: 'put',
data: data
})
}
//新增学生
export function addStudentmain(data) {
return request({
url: '/education/studentmain',
method: 'post',
data: data
})
}
//修改学生信息
export function updateStudentmain(data) {
return request({
url: '/education/studentmain',
method: 'put',
data: data
})
}
//获取学生信息
export function getStudentmain(id) {
return request({
url: '/education/studentmain/' + id,
method: 'get'
})
}
//删除学生
export function leaveClass(data) {
return request({
url: '/education/classuser/leaveClass',
method: 'post',
data: data
})
}
//删除学生所有数据
export function removeStudentDataAll(id) {
return request({
url: '/education/studentmain/removeStudent/' + id,
method: 'post'
})
}
//删除教室
export function delClassroom(id) {
return request({
url: '/education/classroom/' + id,
method: 'delete'
})
}
//导入学生
export function addStudentmainByNameArray(data) {
return request({
url: '/education/studentmain/addByNameArray',
method: 'post',
data: data
})
}

View File

@ -0,0 +1,75 @@
<template>
<div class="update-avatar">
<img :src="image" ref="imgRef" />
<div class="toolbar">
<span @click="handleClose">取消</span>
<!-- 实时预览 -->
<div class="view"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import 'cropperjs/dist/cropper.css';
import Cropper from 'cropperjs';
const props = defineProps({
image: {
type: String,
default: ''
}
})
const imgRef = ref(null);
const cropper = ref(null);
const handleClose = () => {
//
};
const cropperInit = () => {
const image = imgRef.value;
cropper.value = new Cropper(image, {
aspectRatio: 1,
dragMode: 'move',
background: false,
preview: '.view'
});
};
const onConfirm = () => {
cropper.value.getCroppedCanvas().toBlob(blob => {
//
console.log('blob', blob);
});
};
onMounted(() => {
cropperInit();
});
</script>
<style scoped>
.update-avatar {
width: 170px;
height: 100px;
border: 1px blue solid;
}
.update-avatar img {
display: block;
width: 100%;
height: 100%;
}
.update-avatar .view {
width: 100px;
height: 100px;
border: 1px black solid;
}
.view {
width: 100px;
height: 100px;
overflow: hidden; /*这个超出设置为隐藏很重要,否则就会整个显示出来了*/
}
</style>

View File

@ -0,0 +1,23 @@
<template>
<el-select v-model="modelValue" v-bind="$attrs" :placeholder="placeholder">
<slot></slot>
</el-select>
</template>
<script setup>
import { ref, watch, defineProps, onMounted, defineEmits } from 'vue';
const placeholder = ref('')
const props = defineProps({
statement:String,
});
onMounted(() => {
placeholder.value = props.statement || "请选择"
})
const emit = defineEmits(['update:modelValue']);
const modelValue = ref(props.modelValue);
watch(modelValue, (value) => {
emit('update:modelValue', value);
});
</script>

View File

@ -25,6 +25,7 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="changePage('/profile')">个人中心</el-dropdown-item>
<el-dropdown-item @click="changePage('/class')">班级中心</el-dropdown-item>
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>

View File

@ -0,0 +1,22 @@
/*
author: yangws
time: 2024-20-24 17:20:50
function: 用于删除班级更新主页面
*/
import { defineStore } from "pinia"
const delClassDemo = defineStore(
'del',
{
state: () => ({
idDelete: false
}),
actions: {
//删除提醒
isDelClass(){
this.idDelete = true
}
}
})
export default delClassDemo

View File

@ -0,0 +1,53 @@
<template>
<el-menu
style="border:none;overflow: auto;flex: 1"
class="el-menu-vertical-demo"
:default-active="activeIndex"
@select="handleSelect"
>
<template v-for="(item,index) in classList" :key="index">
<el-sub-menu :index="`${index}`">
<template #title>
<span style="overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">{{item.caption}}</span>
</template>
<el-menu-item-group>
<el-menu-item v-for="(items,routerIndex) in menuItems" :key="`${routerIndex}`" :index="`${items.index}-${item.id}`">{{items.title}}</el-menu-item>
</el-menu-item-group>
</el-sub-menu>
</template>
</el-menu>
</template>
<script setup>
import {ref,defineProps,defineEmits} from "vue";
import { useRouter } from 'vue-router'
const activeIndex = ref('0')
const router = useRouter()
const props = defineProps({
classList:{
type:Array,
},
menuItems:{
type:Array,
}
})
const emits = defineEmits(['handleSelect'])
//
const handleSelect = (itemDom,pathKey) => {
const parts = pathKey[1].split("-")
const result = parts.slice(0, 2).join("-")
const index = props.menuItems.findIndex(item=>item.index===result)
const id = props.classList[pathKey[0]].id
// router.push({
// path: `${props.menuItems[index].path}/${id}`,
// query: { id }
// })
emits('handleSelect',{index,id})
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,417 @@
<template>
<el-card style="width: 100%;height: 100%">
<template #header>
<div class="card-header" style="text-align: left">
<el-button type="primary" @click="addGroup">新建分组</el-button>
</div>
</template>
<template v-if="groupList.length > 0">
<div style="font-size: 16px;font-weight: bold;color: #000;text-align: left;margin-bottom: 5px">可用分组</div>
<div class="groupList">
<template v-for="(item,index) in groupList" :key="index">
<el-card style="width: 20%;
margin-right: 10px;
margin-bottom: 10px;
cursor: pointer;
position: relative;"
v-if="item.parentid === 0"
@mouseenter="cardEnter(item,index)"
@mouseleave="cardLeave(item,index)"
>
<div class="card-content" @click="showGroupDialog(item)">
<div style="text-align: left;margin-bottom:10px">
<el-text class="mx-1">小组名称:</el-text>
<el-text class="mx-1" type="primary" size="large">
{{item.groupname}}
</el-text>
</div>
<el-row :gutter="4">
<el-col>
<el-text class="mx-1" type="warning">{{item.studentcount}}名学生</el-text>
</el-col>
</el-row>
</div>
<el-popconfirm
confirm-button-text="是"
title="确定删除该小组吗?"
@confirm="delGroup(item,index)"
>
<template #reference>
<div class="card-row" v-show="item.isShow">
删除
</div>
</template>
</el-popconfirm>
</el-card>
</template>
</div>
</template>
<template v-else>
<el-empty description="暂无小组" style="margin: 0 auto"/>
</template>
</el-card>
<!-- 新建小组-->
<el-dialog v-model="groupVisible" title="新建小组" width="40%">
<el-form ref="myForm" label-width="80px" :model="groupForm" :rules="rules">
<el-form-item label="小组名称" style="width: 80%" prop="groupname">
<el-input v-model="groupForm.groupname" placeholder="请输入小组名称"></el-input>
</el-form-item>
<el-form-item label="排序" style="width: 80%" prop="orderidx">
<el-input-number v-model="groupForm.orderidx" placeholder="排序"></el-input-number>
</el-form-item>
<el-form-item label="对应学科">
{{ userStore.edusubject }}
</el-form-item>
<el-form-item>
请首先创建小组完成后再点击进入添加学生
</el-form-item>
</el-form>
<template #footer>
<el-button @click="groupVisible = false"> </el-button>
<el-button type="primary" @click="btnGroupSave"> </el-button>
</template>
</el-dialog>
<!-- 添加学生-->
<el-dialog v-model="dialogVisible" width="60%">
<el-form>
<el-form-item>
<!-- 选择学生-->
<div class="allItems">
<el-row :gutter="4" style="width: 100%">
<el-col :span="4">已选学生:</el-col>
<el-col :span="20" class="studentContent">
<div class="aItem" @mouseenter="showGroup(stuItem,stuIndex)" @mouseleave="hideGroup(stuItem,stuIndex)" v-for="(stuItem,stuIndex) in selectStudents" :key="stuIndex">
<el-tag closable size="large" :class="stuItem.choose?'el-row-group':''" style="width: 100%" plain type="primary" @close="selectStudent(stuItem,stuIndex)">{{stuItem.studentname}}</el-tag>
<el-button v-show="stuItem.showGroup" class="itemGroup" plain link type="primary" @click="chooseGroup(stuItem,stuIndex)">{{!stuItem.choose?'设置组长':''}}</el-button>
</div>
</el-col>
</el-row>
<el-divider />
<el-row :gutter="4" style="width: 100%">
<el-col :span="4">未选学生:</el-col>
<el-col :span="20" class="studentContent">
<div class="aItem" v-for="(stuItem,stuIndex) in avaliableStudents" :key="stuIndex">
<el-button style="width: 100%" plain @click="chooseItem(stuItem,stuIndex)">{{stuItem.name || stuItem.studentname}}</el-button>
</div>
</el-col>
</el-row>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="btnSave"> </el-button>
</template>
</el-dialog>
</template>
<script setup>
import {listClassmain, listClassuser, addClassgroup, listClassgroup, delClassgroup,getClassgroup,updateClassgroup} from '@/api/classManage/index'
import {ref, onMounted, reactive, defineProps, watch} from 'vue'
import useUserStore from '@/store/modules/user'
import {useRoute} from 'vue-router'
import {ElMessage} from "element-plus";
const userStore = useUserStore()
const route = useRoute()
const props = defineProps({
classId: {type: Number}
})
//
const groupForm = reactive({
groupname: '',
orderidx: 0,
classid: Number(props.classId),
edituserid: userStore.id,
edusubject: userStore.edusubject,
userid : 0,
studentid : 0,
parentid:0,
status:''
})
//
const avaliableStudents = ref([])
const selectStudents = ref([])
const rules = reactive({
groupname: [
{ required: true, message: '请输入小组名称', trigger: 'blur' },
{ min: 1, max: 10, message: '小组名称必须是 1-10 位 的字符', trigger: 'blur' }
],
})
const myForm = ref(null)
//
const routeClass = ref(0)
//
const dialogVisible = ref(false)
const groupVisible = ref(false)
//
const classList = ref([])
//
const groupList = ref([])
//
const groupFormInfo = ref({})
//parentId
const parentId = ref(0)
//
const groupLeader = reactive({
id: 0,
name: '',
})
//
const getStudentInfo = (id) => {
avaliableStudents.value = []
listClassuser({ classid:id, pageSize: 100 }).then(response => {
response.rows.forEach(item => {
item.showGroup = false
item.choose = false
if(item.inrole == 'student'){
const index = groupList.value.findIndex(items => items.studentid == item.studentid)
if(index === -1){
avaliableStudents.value.push(item)
}
}
})
});
}
//
const getClassInfo = () => {
listClassmain({ entpid: userStore.deptId, pageSize: 500, status: 'open' }).then(response => {
classList.value = response.rows.map(item => {
return {
...item,
}
})
routeClass.value = classList.value.findIndex(item => item.id == props.classId)
});
}
//
const chooseItem = (item,stuIndex) => {
let formdata = {}
formdata.classid = props.classId;
formdata.edituserid = userStore.id;
formdata.userid = item.userid;
formdata.parentid = parentId.value;
formdata.studentid = item.studentid;
addClassgroup(formdata).then(response => {
if(response.code === 200)
//
getGroupListInfo()
//
getSelectStudentInfo(parentId.value)
})
avaliableStudents.value.splice(stuIndex,1)
}
//
const selectStudent = (item) => {
delClassgroup(item.id).then(() => {
getSelectStudentInfo(parentId.value)
getGroupListInfo()
})
avaliableStudents.value.push(item)
}
//
const chooseGroup = (item) => {
let choose = item.choose
selectStudents.value.forEach((items) => {
items.showGroup = false
items.choose = false
})
const index = selectStudents.value.findIndex(items => items.studentid == item.studentid)
selectStudents.value[index].choose = !choose
item.showGroup = true
updateClassgroup({ id: groupFormInfo.value.id, studentid: item.studentid }).then(() => {
getClassgroup(groupFormInfo.value.id).then(res => {
groupLeader.id = res.data.studentid;
groupLeader.name = res.data.studentname;
})
});
}
/*
author: yangws
time: 2024-20-23 11:20:04
function:鼠标的移入移出
*/
const showGroup = (item) => {
item.showGroup = true
}
const hideGroup = (item) => {
if(item.choose) return
item.showGroup = false
}
const showGroupDialog = (item) => {
parentId.value = item.id
//
getClassgroup(item.id).then((response) => {
groupFormInfo.value = Object.assign({},response.data)
//
getStudentInfo(props.classId)
//
getSelectStudentInfo(parentId.value)
//
getGroupListInfo()
dialogVisible.value = true
})
}
const btnSave = () => {
updateClassgroup({id:groupFormInfo.value.id,groupname:groupFormInfo.value.groupname,orderidx:groupFormInfo.value.orderidx}).then((response) => {
if (response.code === 200) {
ElMessage({
message: '修改成功',
type: 'success',
})
dialogVisible.value = false
getGroupListInfo()
}
})
}
//
const addGroup = () => {
groupVisible.value = true
}
const btnGroupSave = () => {
myForm.value.validate((valid) => {
if (valid) {
addClassgroup(groupForm).then((response) => {
if (response.code === 200) {
ElMessage({
message: '新增成功',
type: 'success',
})
getGroupListInfo()
}
})
groupVisible.value = false
} else {
return false;
}
});
}
//
const getGroupListInfo = () => {
//
listClassgroup({ classid: props.classId, orderby: 'orderidx', pageSize: 100 }).then(response => {
groupList.value = response.rows.map(item => {
return {
...item,
isShow : false
}
})
});
}
//
const getSelectStudentInfo = (id) => {
listClassgroup({ parentid: id, orderby: 'orderidx', pageSize: 100 }).then(response => {
selectStudents.value = response.rows.map(item => {
item.showGroup=false
item.choose=false
return {
...item
}
})
//
changeGroupLeader()
})
}
//
const cardEnter = (item) => {
item.isShow = true
}
const cardLeave = (item) => {
item.isShow = false
}
//
const delGroup = (item) => {
delClassgroup(item.id).then((response) => {
if (response.code === 200) {
ElMessage({
message: '删除成功',
type: 'success',
})
getGroupListInfo()
}
})
}
//
const changeGroupLeader = () => {
const index = selectStudents.value.findIndex(item => groupFormInfo.value.studentid == item.studentid)
selectStudents.value.forEach(item => {
item.choose = false
})
if(index !== -1)
selectStudents.value[index].choose = true
}
onMounted(() => {
getClassInfo()
getStudentInfo(props.classId)
getGroupListInfo()
})
watch(()=> props.classId,()=> {
getClassInfo()
getStudentInfo(props.classId)
getGroupListInfo()
})
</script>
<style scoped>
.allItems{
display: flex;
justify-content: start;
flex-wrap: wrap;
height: auto;
width: 100%;
}
.allItems .aItem{
margin-bottom: 20px;
margin-right: 5px;
position: relative;
width: 80px;
}
.allItems .aItem .itemGroup{
position: absolute;
top: 32px;
left: 10px;
}
.el-row-group::after {
content: "组";
font-size: 12px;
width: auto;
height: auto;
color: rgb(121,187,255);
transform: rotate(45deg);
position: absolute;
right: 0;
top: 0;
font-weight: bold;
letter-spacing:2px;
cursor: pointer;
}
.groupList{
display: flex;
flex-wrap: wrap;
}
.card-row {
font-size: 12px;
width: auto;
height: auto;
color: rgb(121,187,255);
position: absolute;
right: 0;
top: 0;
font-weight: bold;
letter-spacing:2px;
cursor: pointer;
}
.studentContent{
display: flex;
justify-content: start;
flex-wrap: wrap;
max-height: 150px;
overflow-y: auto;
}
.card-content{
display: flex;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,80 @@
<template>
<el-card style="width: 100%">
<template #header>
<div style="text-align: left">
<el-button type="danger" @click="deleteClassRoom">删除班级</el-button>
</div>
</template>
<el-descriptions :column="1">
<el-descriptions-item label="班级名称">{{ classInfo.className }}</el-descriptions-item>
<el-descriptions-item label="教师">
<template v-if="classInfo.teacher.length > 0">
<el-tag type="primary" v-for="(item, index) in classInfo.teacher" :key="index">{{item.name}}</el-tag>
</template>
<template v-else>暂无</template>
</el-descriptions-item>
<el-descriptions-item label="学生人数">{{ classInfo.student.length }}</el-descriptions-item>
</el-descriptions>
</el-card>
</template>
<script setup>
import {ElMessage, ElMessageBox} from "element-plus";
import { getClassmain,listClassuser,leaveClass} from '@/api/classManage/index'
import useUserStore from '@/store/modules/user'
import {reactive,onMounted,defineProps,nextTick,watch} from 'vue'
import delClassDemo from '@/store/modules/delClass'
const props = defineProps({
classId: {
type: Number,
}
})
const classInfo = reactive({
className: '',
teacher: [],
student: []
})
const isDelClass = delClassDemo()
const userStore = useUserStore()
//
const deleteClassRoom = () => {
ElMessageBox.alert('确认删除该班级?', {
confirmButtonText: '确认',
type: 'warning'
}).then(() => {
leaveClass({ classid: props.classId, userid: userStore.id, inrole: 'teacher' }).then(() => {
ElMessage({
message: '删除成功',
type: 'success',
})
//
isDelClass.isDelClass()
}).catch((error) => {
console.log(error)
});
})
}
const getClassInfo = () => {
if(props.classId){
getClassmain(props.classId).then(response => {
classInfo.className = response.data.caption
listClassuser({classid:props.classId,pageSize:100}).then(res => {
classInfo.teacher = res.rows.filter(item => item.inrole === 'teacher')
classInfo.student = res.rows.filter(item => item.inrole === 'student')
})
})
}
}
onMounted(() => {
nextTick(() => {
getClassInfo()
})
})
watch(()=> props.classId,()=> {
getClassInfo()
})
</script>
<style scoped>
</style>

View File

@ -0,0 +1,292 @@
<template>
<div class="common-layout">
<el-container>
<el-aside style="width: 200px;margin-top: 20px">
<el-card style="width: 200px" class="el-card-demo" :style="{'min-height': (viewportHeight - 120) + 'px'}">
<div :style="{'max-height': (viewportHeight - 120) + 'px','overflow-y': 'auto'}">
<Aside :menuItems="menuItems" :classList="classList" @handleSelect="handleSelect"></Aside>
</div>
<template #footer>
<div>
<el-button @click="addClass" type="primary" :icon="Plus" >新增班级</el-button>
</div>
</template>
</el-card>
</el-aside>
<el-main :style="{'min-height': (viewportHeight - 160) + 'px'}">
<!-- <router-view :style="{'height': (viewportHeight - 120) + 'px','overflow-y': 'auto'}" :key="route.path"></router-view>-->
<!-- 班级概况-->
<ClassInfo v-if="currentIndex==0" :classId="classId"></ClassInfo>
<!-- 学生列表-->
<StudentList v-else-if="currentIndex==1" :classId="classId"></StudentList>
<!-- 分组情况-->
<BasicGroup v-else-if="currentIndex==2" :classId="classId"></BasicGroup>
</el-main>
</el-container>
</div>
<el-dialog v-model="dialogVisible" title="新增班级" width="50%">
<el-form
style="width: 100%"
label-width="auto"
:model="classForm"
:rules="rules"
ref="myForm"
>
<el-form-item label="班级名称" style="margin-right: 10px;width: 50%" prop="caption">
<el-input v-model="classForm.caption" placeholder="请输入班级名称"></el-input>
</el-form-item>
<el-form-item label="任选学科" style="margin-right: 10px;">
<el-radio-group v-model="classForm.edusubject" class="ml-4">
<template v-for="(item, index) in courseList" :key="index">
<el-radio v-if="item.edustage == userStore.edustage" :value="item.itemtitle">{{ item.itemtitle }}</el-radio>
</template>
</el-radio-group>
</el-form-item>
<el-form-item label="年级" style="margin-right: 10px;" prop="agekey">
<el-radio-group v-model="classForm.edudegree">
<template v-for="(item,index) in gradeList" :key="index">
<el-radio v-if="item.edustage == userStore.edustage" :value="item.value">{{ item.label }}</el-radio>
</template>
</el-radio-group>
</el-form-item>
<el-form-item label="老师" prop="teacherid">
{{ userStore.nickName }}
</el-form-item>
<el-form-item label="简要说明" style="margin-right: 10px;" prop="classdesc">
<el-input v-model="classForm.classdesc" placeholder="请输入简要说明"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="btnSave"> </el-button>
</template>
</el-dialog>
</template>
<script setup>
import {ref, onMounted, reactive,watch,nextTick} from 'vue'
import {listClassmain, addClassmain, listEvaluation} from '@/api/classManage/index'
import { Plus } from '@element-plus/icons-vue'
import useUserStore from '@/store/modules/user'
import {ElMessage} from "element-plus";
import delClassDemo from '@/store/modules/delClass'
import StudentList from './studentList.vue'
import BasicGroup from './basicGroup.vue'
import ClassInfo from './classInfo.vue'
import Aside from './aside.vue'
const userStore = useUserStore()
const isDel = delClassDemo()
//
const classList = ref([])
const menuItems = [
{
index: '1-1',
title: '班级概况',
path: '/classInfo'
},
{
index: '1-2',
title: '学生名单',
path: '/studentList'
},
{
index: '1-3',
title: '基本分组',
path: '/basicGroup'
},
]
//
const classForm = reactive({
caption: '',
classdesc: '',
teacherid: '',
agekey:'',
edusubject:'',
edudegree:''
})
const viewportHeight = ref(0)
//
const courseList = ref([])
//id
const classId = ref('')
//
const currentIndex = ref(0)
//
const gradeList = ref([
{ label: '一年级', value: '1年级', checked: false, edustage: '小学', agekey: 1 },
{ label: '二年级', value: '2年级', checked: false, edustage: '小学', agekey: 2 },
{ label: '三年级', value: '3年级', checked: false, edustage: '小学', agekey: 3 },
{ label: '四年级', value: '4年级', checked: false, edustage: '小学', agekey: 4 },
{ label: '五年级', value: '5年级', checked: false, edustage: '小学', agekey: 5},
{ label: '六年级', value: '6年级', checked: false, edustage: '小学', agekey: 6 },
{ label: '初一', value: '初一', checked: false, edustage: '初中', agekey: 7 },
{ label: '初二', value: '初二', checked: false, edustage: '初中', agekey: 8 },
{ label: '初三', value: '初三', checked: false, edustage: '初中', agekey: 9 },
{ label: '高一', value: '高一', checked: false, edustage: '高中', agekey: 10 },
{ label: '高二', value: '高二', checked: false, edustage: '高中', agekey: 11 },
{ label: '高三', value: '高三', checked: false, edustage: '高中', agekey: 12 },
])
//
const dialogVisible = ref(false)
//
const myForm = ref(null)
const rules = reactive({
caption: [
{ required: true, message: '请输入班级名称', trigger: 'blur' },
{ min: 1, max: 10, message: '班级名称必须是 1-10 位 的字符', trigger: 'blur' }
],
})
//
const getClassInfo = () => {
classList.value = []
listClassmain({ entpid: userStore.deptId, pageSize: 500, status: 'open' }).then(response => {
response.rows.forEach(item => {
if(item.classteacherids && Number(item.classteacherids) === userStore?.id){
classList.value.push(item)
}
})
if(classList.value.length > 0){
classId.value = classList.value[0].id
currentIndex.value = 0
}
});
}
//
const getCourseList = () => {
//
listEvaluation({ itemkey: "subject", pageSize: 500 }).then((res) => {
courseList.value = [...res.rows];
});
}
//
const addClass = () => {
dialogVisible.value = true
getCourseList()
}
const btnSave = () => {
myForm.value.validate((valid) => {
if (valid) {
//
listClassmain({ entpid: userStore.deptId, status: 'open', pageSize: 500 }).then(response => {
const data = [...response.rows]
const existList = [];
data.forEach(item => {
if (parseInt(textSimilar(item.caption, classForm.caption, 2)) > 80) {
existList.push(item);
}
})
if (existList.length == 0) {
const age = classForm.edudegree;
const index = gradeList.value.findIndex(item => item.label === age);
classForm.agekey = gradeList.value[index].agekey
classForm.edudegree = `${gradeList.value[index].agekey}年级`
classForm.entpid = userStore.deptId;
classForm.status = 'open';
classForm.teachername = userStore.nickName;
classForm.teacherid = userStore.id;
classForm.teacherSubject = classForm.edusubject;
addClassmain(classForm).then(response => {
if (response.code === 200) {
dialogVisible.value = false
ElMessage({
message: '新增成功',
type: 'success',
})
getClassInfo()
}
});
}else{
ElMessage({
message: '班级名称重复',
type: 'warning',
})
}
})
}
})
}
//
const textSimilar = (s, t, f) => {
if (!s || !t) {
return 0
}
if (s === t) {
return 100;
}
var l = s.length > t.length ? s.length : t.length
var n = s.length
var m = t.length
var d = []
f = f || 2
var min = function (a, b, c) {
return a < b ? (a < c ? a : c) : (b < c ? b : c)
}
var i, j, si, tj, cost
if (n === 0) return m
if (m === 0) return n
for (i = 0; i <= n; i++) {
d[i] = []
d[i][0] = i
}
for (j = 0; j <= m; j++) {
d[0][j] = j
}
for (i = 1; i <= n; i++) {
si = s.charAt(i - 1)
for (j = 1; j <= m; j++) {
tj = t.charAt(j - 1)
if (si === tj) {
cost = 0
} else {
cost = 1
}
d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost)
}
}
let res = (1 - d[n][m] / l) * 100
return res.toFixed(f)
}
//
const getViewportHeight = () => {
return Math.max(
document.documentElement.clientHeight,
window.innerHeight || 0
);
}
const handleSelect = (obj) => {
classId.value = obj.id
currentIndex.value = obj.index
}
onMounted(() => {
getClassInfo()
nextTick(() => {
viewportHeight.value = getViewportHeight()
})
window.addEventListener('resize', () => {
viewportHeight.value = getViewportHeight()
})
})
watch(() => isDel.idDelete, () => {
//
getClassInfo()
isDel.idDelete = false
})
</script>
<style scoped>
.el-card-demo{
width: 200px;
overflow-y: auto;
display: flex;
justify-content: space-between;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,454 @@
<template>
<div>
<el-card style="width: 100%;height: 100%">
<template #header>
<div style="text-align: left;display: flex;justify-content: space-between">
<div>
<el-button type="primary" @click="addStudent(0)">新增学生</el-button>
<el-button type="primary" @click="importStudent()">导入学生</el-button>
</div>
<el-text class="mx-1">点击学生头像查看学生信息</el-text>
</div>
</template>
<div>
<div class="studentContent">
<template v-if="studentList.length > 0">
<div v-for="(item,index) in studentList" :key="index" style="width: 10%" @click="addStudent(item.studentid)">
<div>
<el-avatar
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
/>
</div>
<div>{{ item.name }}</div>
</div>
</template>
<template v-else>
<el-empty description="暂无学生" style="margin: 0 auto"/>
</template>
</div>
</div>
</el-card>
<!-- 新增学生-->
<el-dialog v-model="studentVisible" title="学生信息" width="60%" append-to-body>
<el-form label-width="80px" ref="myForm" :model="studentForm" :rules="rules">
<el-form-item label="学生姓名" prop="name">
<el-input v-model="studentForm.name" placeholder="请输入学生姓名" style="width: 50%"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="studentForm.gender">
<el-radio value="">空缺</el-radio>
<el-radio value="男">男生</el-radio>
<el-radio value="女">女生</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker v-model="studentForm.birthday" placeholder="请选择出生日期" style="width: 50%"/>
</el-form-item>
<el-form-item label="住址" prop="address">
<el-input v-model="studentForm.address" placeholder="请输入住址" style="width: 50%"/>
</el-form-item>
<el-form-item label="联系人" prop="parentmobile">
<el-input v-model="studentForm.parentname" placeholder="请输入联系人" style="width: 50%"/>
</el-form-item>
<el-form-item label="电话" prop="parentmobile">
<el-input v-model="studentForm.parentmobile" placeholder="请输入电话" style="width: 50%"/>
</el-form-item>
<div>
<el-row :gutter="4">
<el-col :span="12">
<el-form-item label-width="100px" label="平台登录账号">
系统自动创建
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label-width="100px" label="平台登录密码">
系统自动创建默认为123123
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<el-button type="warning" v-show="studentForm.id > 0" @click="delStudent(1)">移出班级</el-button>
<el-button type="danger" v-show="studentForm.id > 0 && studentForm.editoruserid == userStore.id" @click="delStudent(2)">删除学生</el-button>
<el-button @click="studentVisible = false"> </el-button>
<el-button type="primary" @click="btnStudentSave"> </el-button>
</template>
</el-dialog>
<!-- 学生导入-->
<el-dialog title="学生导入" v-model="importVisiable" :width="600" append-to-body>
<el-form>
<el-form-item label="第一步">
<el-link type="primary" :underline="true" style="font-size: 1.2em"
@click="downloadTemplate(tableInfo)">下载学生名单的Excel模板</el-link>
</el-form-item>
<el-form-item label="第二步">
<div>打开下载到本地的Excel填写学生的姓名等数据</div>
</el-form-item>
<el-form-item label="第三步">
<div>
<el-upload :on-change="handleStudentFileUploadChange"
ref="uploadRef" :limit="1" accept=".xlsx, .xls" :auto-upload="false" drag>
<div class="el-upload__text">将Excel文件拖到此处<em>点击上传</em></div>
</el-upload>
</div>
</el-form-item>
<el-form-item label="导入学生" v-if="tableInfo.studentList.length > 0">
<template v-for="(item, index) in tableInfo.studentList" :key="index">
<el-tag size="large" style="margin: 5px" closable @close="handleRemoveImportStudent(index)">{{ item.name }}</el-tag>
</template>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="importVisiable = false"> </el-button>
<el-button type="primary" @click="HandleImportStudentData()">导入</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {addStudentmain, listClassuser,updateStudentmain,getClassmain,getStudentmain,leaveClass,removeStudentDataAll,addStudentmainByNameArray} from '@/api/classManage/index'
import {ref, onMounted, reactive, nextTick, defineProps, watch} from 'vue'
import {ElMessage,ElMessageBox} from "element-plus";
import useUserStore from '@/store/modules/user'
import * as XLSX from 'xlsx'
const studentVisible = ref(false)
const importVisiable = ref(false)
const userStore = useUserStore()
const props = defineProps({
classId: {type: Number}
})
//
const studentList = ref([])
let studentForm = reactive({
name: '',
gender: '',
birthday: '',
address: '',
parentname: '',
parentmobile: '',
isAutoCreateTimAccount: false,
})
//
const tableInfo = reactive({
//excel
columns: [
{ title: '姓名', key: 'name' },
{ title: '性别', key: 'gender' },
{ title: '手机号', key: 'phone' },
{ title: '父母手机号1', key: 'parentmobile' },
{ title: '父母手机号2', key: 'parentmobile2' },
{ title: '年级', key: 'classname' },
{ title: '地址', key: 'address' },
{ title: '生日', key: 'birthday' },
{ title: '学校名称', key: 'schoolname' },
{ title: '描述', key: 'status' },
],
studentList: [],
})
//
const classInfo = reactive({})
//
const myForm = ref(null)
const rules = reactive({
name: [
{ required: true, message: '请输入学生名称', trigger: 'blur' },
{ min: 2, max: 5, message: '学生必须是 2-5 位 的字符', trigger: 'blur' }
],
birthday: [
{ required: true, message: '请选择出生日期', trigger: 'blur' }
]
})
//
const getClassStudentInfo = () => {
studentList.value = []
listClassuser({ classid:props.classId, pageSize: 100 }).then(response => {
response.rows.forEach(item => {
if(item.inrole == 'student'){
studentList.value.push(item)
}
})
});
}
//
const getClassInfo = () => {
getClassmain(props.classId).then(response => {
classInfo.value = Object.assign({},response.data )
})
}
const btnStudentSave = () => {
myForm.value.validate((valid) => {
if (valid) {
//
if(studentForm.id > 0){
updateStudentmain(studentForm).then(() => {
ElMessage({
message: '修改成功',
type: 'success',
})
})
}else{
studentForm.entpid = classInfo.value.entpid;
studentForm.classid = classInfo.value.id;
studentForm.classname = classInfo.value.caption;
studentForm.schoolname = classInfo.value.entpname;
studentForm.status = '';
studentForm.editoruserid = userStore.id;
addStudentmain(studentForm).then((response) => {
if (response.code === 200) {
ElMessage({
message: '新增成功',
type: 'success',
})
}
})
}
getClassStudentInfo()
studentVisible.value = false
} else {
return false;
}
});
}
//
const addStudent = (id) => {
if(id === 0){
studentForm.id = 0
studentForm.entpid = 0
studentForm.name = ''
studentVisible.value = true
}else{
getStudentmain(id).then((response) => {
nextTick(() => {
studentForm.address = response.data.address
studentForm.name = response.data.name
studentForm.birthday = response.data.birthday
studentForm.gender = response.data.gender
studentForm.parentname = response.data.parentname
studentForm.parentmobile = response.data.parentmobile
studentForm.id = response.data.id
studentVisible.value = true
})
})
}
}
//
const delStudent = (index) => {
//index 1 2
if(index === 1){
ElMessageBox.alert('是否将该学生移出班级', {
confirmButtonText: '确认',
type: 'warning',
}).then(() => {
leaveClass({ classid: props.classId, studentid: studentForm.id, inrole: 'student' }).then((response) => {
ElMessage({
type: 'info',
message: '移出成功',
})
getClassStudentInfo()
studentVisible.value = false
})
})
}
else{
ElMessageBox.alert('确认删除该学生并删除该学生所有数据', {
confirmButtonText: '确认',
type: 'warning',
}).then(() => {
leaveClass({classid: props.classId, studentid: studentForm.id, inrole: 'student'}).then((response) => {
if (response.code === 200) {
ElMessage({
type: 'info',
message: '删除成功',
})
}
removeStudentDataAll(studentForm.id).then((res) => {
if (res.code === 200) {
getClassStudentInfo()
studentVisible.value = false
}
})
})
})
}
}
//
const importStudent = () => {
importVisiable.value = true
}
//
const downloadTemplate = (tableInfo) => {
let expData = [];
tableInfo.importColumns = appendTableImportColumns(tableInfo.columns);
var header = [];
tableInfo.importColumns.map((item, index) => {
header[index] = item.title;
});
//1. 簿
let workbook = XLSX.utils.book_new();
//2.2 json
let sheet1 = XLSX.utils.json_to_sheet(expData, { header: header });
//3.簿
XLSX.utils.book_append_sheet(workbook, sheet1, '导入模板'); //簿
XLSX.writeFile(workbook, '学生名单导入模板.xlsx'); //
}
//
const appendTableImportColumns = (columns) => {
return columns.filter((item) => {
if (item.hasOwnProperty("title")) {
if (item.hasOwnProperty("key")) {
return true;
} else {
if (item.hasOwnProperty("slot") && item.slot.split(".").length > 1) {
item.title = item.slot.split(".")[1];
return true;
} else {
return false;
}
}
}
});
}
//
const handleStudentFileUploadChange = (file) => {
let fileTemp = file.raw;
if (fileTemp) {
if ((fileTemp.type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|| (fileTemp.type == 'application/vnd.ms-excel')) {
handleStudentExcel(fileTemp);
} else {
ElMessage({
type: 'warning',
message: '文件格式错误,请删除后重新上传!'
})
}
} else {
ElMessage({
type: 'warning',
message: '请上文件!'
})
}
}
const handleStudentExcel = (fileTemp) => {
const file = fileTemp;
var rABS = false; //
var f = file;
var reader = new FileReader();
FileReader.prototype.readAsBinaryString = function (f) {
var binary = "";
var rABS = false; //
var wb; //
var outdata;
var reader = new FileReader();
reader.onload = function () {
var bytes = new Uint8Array(reader.result);
var length = bytes.byteLength;
for (var i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
if (rABS) {
wb = XLSX.read(btoa(fixdata(binary)), {
//
type: "base64"
});
} else {
wb = XLSX.read(binary, {
type: "binary"
});
}
outdata = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); //outdata西
const tempColumnMapping = {};
for (const column of tableInfo.columns) {
tempColumnMapping[column.title] = column.key;
}
const mappedOutdata = outdata.map((item) => {
return Object.keys(item).reduce((mappedItem, key) => {
const targetKey = tempColumnMapping[key] || key;
mappedItem[targetKey] = item[key];
return mappedItem;
}, {});
});
tableInfo.studentList = [...mappedOutdata];
//
let arr = [];
outdata.map(v => {
let obj = {}
obj.code = v['code']
obj.name = v['name']
obj.pro = v['province']
obj.cit = v['city']
obj.dis = v['district']
arr.push(obj)
});
// _this.da = arr;
// _this.dalen = arr.length;
return arr;
};
reader.readAsArrayBuffer(f);
};
if (rABS) {
reader.readAsArrayBuffer(f);
} else {
reader.readAsBinaryString(f);
}
}
//
const HandleImportStudentData = () => {
if(tableInfo.studentList.length == 0){
ElMessage({
type: 'warning',
message: '请选择导入的文件!'
})
return false
}
var names = [];
tableInfo.studentList.forEach(item => {
names.push(item.name);
})
var formdata = {};
formdata.namearray = names.join(",");
formdata.entpid = classInfo.value.entpid;
formdata.classid = classInfo.value.id;
formdata.classname = classInfo.value.caption;
formdata.schoolname = classInfo.value.entpname;
formdata.status = '';
formdata.editoruserid = userStore.id;
addStudentmainByNameArray(formdata).then(res => {
if(res.code == 200){
ElMessage({
type: 'success',
message: '导入成功',
})
getClassStudentInfo()
importVisiable.value = false
}
})
}
const handleRemoveImportStudent = (index) => {
tableInfo.studentList.splice(index, 1);
}
onMounted(() => {
studentList.value = []
getClassStudentInfo()
getClassInfo()
})
watch(()=> props.classId,()=> {
getClassStudentInfo()
})
</script>
<style scoped>
.studentContent{
display: flex;
justify-content: start;
flex-wrap: wrap;
}
</style>