lansir 1 gadu atpakaļ
vecāks
revīzija
0f511a2a7a

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
common/xlsx.core.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 923 - 0
package-lock.json


+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "@vant/weapp": "^1.11.1",
     "echarts": "^5.4.3",
     "element-ui": "^2.15.7",
+    "exceljs": "^4.4.0",
     "js-base64": "^3.6.0",
     "jweixin-module": "^1.6.0",
     "moment": "^2.30.1",

+ 195 - 0
pages/studentRanked/components/students-change-excel.vue

@@ -0,0 +1,195 @@
+<template>
+    <el-popover placement="top-start" title="表格批量替换" width="280" trigger="hover">
+        <view>
+            下载当前座位表格模板,更改后点击表格导入批量替换座位
+            <view class="txt-red">请确保表格格式完整,切勿存在缺漏或重复</view>
+            <view class="ui-flex-row ui-flex-center ui-mt20">
+                <el-button type="" plain class="ui-mr20" @click="makeExcelFile">下载模板</el-button>
+
+                <el-upload class="upload-demo" action="#" :before-upload="()=>{return false}" :auto-upload="false"
+                    :on-change="onStuChangeByExcel" :file-list="fileList">
+                    <!--  @click="onStuChangeByExcel" -->
+                    <el-button type="primary" plain>上传导入</el-button>
+                </el-upload>
+            </view>
+        </view>
+        <text slot="reference" class="txt-link">表格导入</text>
+    </el-popover>
+</template>
+
+<script>
+    import xlsx from '@/common/xlsx.core.min.js'
+    export default {
+        data() {
+            return {
+                fileList: [] //excel上传的数据
+            }
+        },
+        props: {
+            stuList: Array
+        },
+        methods: {
+            makeExcelFile() { //下载表格模板
+                let title = ['1组', '2组', '3组', '4组', '5组', '6组', '7组', '8组'];
+                let list = [];
+                this.stuList.forEach((item, index) => {
+                    item.nodes.forEach((el, ii) => {
+                        if (el.id) {
+                            if (!list[ii]) {
+                                list[ii] = [];
+                            }
+                            list[ii][index] = el.title;
+                        }
+                    })
+                })
+                list = [title].concat(list);
+                console.log(list);
+                // return;
+                var sheet = xlsx.utils.aoa_to_sheet(list);
+                this.openDownloadDialog(this.sheet2blob(sheet), '导出座位表.xlsx');
+            },
+            // 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
+            sheet2blob(sheet, sheetName) {
+                sheetName = sheetName || 'sheet1';
+                var workbook = {
+                    SheetNames: [sheetName],
+                    Sheets: {}
+                };
+                workbook.Sheets[sheetName] = sheet;
+                // 生成excel的配置项
+                var wopts = {
+                    bookType: 'xlsx', // 要生成的文件类型
+                    bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
+                    type: 'binary'
+                };
+                var wbout = xlsx.write(workbook, wopts);
+                var blob = new Blob([s2ab(wbout)], {
+                    type: "application/octet-stream"
+                });
+                // 字符串转ArrayBuffer
+                function s2ab(s) {
+                    var buf = new ArrayBuffer(s.length);
+                    var view = new Uint8Array(buf);
+                    for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
+                    return buf;
+                }
+                return blob;
+            },
+            openDownloadDialog(url, saveName) {
+                if (typeof url == 'object' && url instanceof Blob) {
+                    url = URL.createObjectURL(url); // 创建blob地址
+                }
+                var aLink = document.createElement('a');
+                aLink.href = url;
+                aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
+                var event;
+                if (window.MouseEvent) event = new MouseEvent('click');
+                else {
+                    event = document.createEvent('MouseEvents');
+                    event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0,
+                        null);
+                }
+                aLink.dispatchEvent(event);
+            },
+            stuLength(){
+                let count = 0;
+                this.stuList.forEach(item => {
+                    item.nodes.forEach(el => {
+                        if(el.id){
+                            count ++;
+                        }
+                    })
+                })
+                return count;
+            },
+            onStuChangeByExcel(e) {
+                let fileRaw = e.raw;
+                this.fileList = [];
+                var reader = new FileReader();
+                reader.onload = (e) => {
+                    var data = e.target.result;
+                    var workbook = xlsx.read(data, {
+                        type: 'binary'
+                    });
+                    console.log(workbook)
+
+                    var sheetNames = workbook.SheetNames; // 工作表名称集合
+                    var worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet
+                    var csv = xlsx.utils.sheet_to_csv(worksheet);
+                    console.log(csv);
+                    let json = csv.split("\n").map(item => {
+                        return item.split(',');
+                    })
+                    
+                    let header = json[0];
+                    json.splice(0,1);
+                    console.log(json);
+                    // console.log(json, xlsx.utils.sheet_to_json(worksheet));
+                    // return;
+                    let list = [];
+                    let maxLen = json.length;
+                    let insertList = []; //插入判断用于判断是否有重复的名字
+                    let error = null;
+                    json.forEach((item, index) => {
+                        item = Object.values(item);
+                        item.forEach((name, ii) => {
+                            if (name) {
+                                if(insertList.includes(name)){
+                                    error = name + '学生异常';
+                                }
+                                insertList.push(name);
+                                
+                                if (!list[ii]) {
+                                    list[ii] = {
+                                        id: ii + 1,
+                                        title: header[ii],
+                                        nodes: []
+                                    };
+                                }
+                                list[ii].nodes[index] = name;
+                            }
+                        })
+                    });
+                    if (error) {
+                        this.$alert(error, '表格异常', {
+                            confirmButtonText: '确定',
+                        });
+                        return;
+                    }
+                    if(insertList.length != this.stuLength()){
+                        this.$alert('表格中人数异常,请检查', '表格异常', {
+                            confirmButtonText: '确定',
+                        });
+                        return;
+                    }
+                    
+                    list.forEach((item, index) => {
+                        item.nodes.forEach((name, ii) => {
+                            list[index].nodes[ii] = this.findStuItem(name);
+                        })
+                    });
+
+                    list = Object.values(list);
+                    console.log(list)
+                    this.$emit('change', uni.$u.deepClone(list))
+                };
+                reader.readAsBinaryString(fileRaw);
+            },
+            findStuItem(name) {
+                let findOne = null;
+                this.stuList.some(item => {
+                    return item.nodes.some(el => {
+                        if (el.title == name) {
+                            findOne = el;
+                        }
+                        return !!findOne;
+                    })
+                })
+                return findOne;
+            },
+        }
+    }
+</script>
+
+<style>
+</style>

+ 74 - 56
pages/studentRanked/studentRanked.vue

@@ -11,12 +11,14 @@
                 </view>
             </view>
 
-            <view class="ui-p ui-flex ui-flex-1 ui-flex-align-center" style="justify-content: end;width: 450px;" v-if="multiSelect">
+            <view class="ui-p ui-flex ui-flex-1 ui-flex-align-center" style="justify-content: end;width: 450px;"
+                v-if="multiSelect">
                 <view class="ui-mr30">
                     <text class="f32" style="#9ccbff">请点击选择学生批量点评</text>
                 </view>
                 <view class="ui-flex" style="width: 250px;">
-                    <el-button @click="setMulti(false)" plain text="取消选择" style="background-color: #0d4980;" class="ui-mr10">取消选择</el-button>
+                    <el-button @click="setMulti(false)" plain text="取消选择" style="background-color: #0d4980;"
+                        class="ui-mr10">取消选择</el-button>
                     <el-badge :value="activeList.length">
                         <el-button type="primary" @click="commentMulti()">
                             批量点评
@@ -30,8 +32,9 @@
             <view class="platform-text txt-white">讲台</view>
             <view class="ranked-radio"></view>
         </view> -->
-        <Draggable class="ui-flex-row ui-flex-space-around" :disabled="!seatMove" forceFallback style="padding:20rpx 0 200rpx 0;" group="ii"
-            @end="onMoveColumnEnd" @choose="activeList = []" v-model="list" :delay="500" animation="300">
+        <Draggable class="ui-flex-row ui-flex-space-around" :disabled="!seatMove" forceFallback
+            style="padding:20rpx 0 200rpx 0;" group="ii" @end="onMoveColumnEnd" @choose="activeList = []" v-model="list"
+            :delay="500" animation="300">
             <view v-for="(column, index) in list" :key="index" class="ui-flex-column" :class="{
                         'ui-mrr' : !!(index%2),
                         'ui-mll' : !(index%2)
@@ -40,23 +43,23 @@
                         'ui-mr10' : !!index%1,
                         'ui-ml10' : !!index%2
                     }"> -->
-                    <view @click="toClickStu(stuItem)" v-for="stuItem in column.nodes" :key="stuItem.id"
-                        v-if="stuItem.id" class="ui-flex-column ranked-item-tr ui-flex-align-center"
-                        :class="{active : activeList.includes(stuItem.id)}">
-                        <image :src="getLevelIcon(stuItem)" style="width: 130rpx;height: 48rpx;">
-                        </image>
-                        <image style="width: 100rpx;height: 100rpx;margin-top: -20rpx;"
-                            :src="stuItem.student_cartoon_photo" mode=""></image>
-                        <view class="stu-tag text-center ui-flex-row ui-mt10">
-                            <view class="stu-tag-add ui-flex-1">{{stuItem.plus_score_total}}</view>
-                            <view class="stu-tag-sub ui-flex-1">{{stuItem.minus_score_total}}</view>
-                        </view>
-                        <view class="txt-white f28 ui-pt10">
-                            {{stuItem.title}}
-                        </view>
-                        <u-badge absolute style="left: 10px;top: 10px;"
-                            :value="activeList.indexOf(stuItem.id) + 1"></u-badge>
+                <view @click="toClickStu(stuItem)" v-for="stuItem in column.nodes" :key="stuItem.key"
+                    class="ui-flex-column ranked-item-tr ui-flex-align-center" v-if="stuItem.id || seatMove"
+                    :class="{active : activeList.includes(stuItem.id)}">
+                    <image :src="getLevelIcon(stuItem)" style="width: 130rpx;height: 48rpx;">
+                    </image>
+                    <image style="width: 100rpx;height: 100rpx;margin-top: -20rpx;" :src="stuItem.student_cartoon_photo"
+                        mode=""></image>
+                    <view class="stu-tag text-center ui-flex-row ui-mt10">
+                        <view class="stu-tag-add ui-flex-1">{{stuItem.plus_score_total}}</view>
+                        <view class="stu-tag-sub ui-flex-1">{{stuItem.minus_score_total}}</view>
                     </view>
+                    <view class="txt-white f28 ui-pt10">
+                        {{stuItem.title}}
+                    </view>
+                    <u-badge absolute style="left: 10px;top: 10px;"
+                        :value="activeList.indexOf(stuItem.id) + 1"></u-badge>
+                </view>
                 <!-- </view> -->
             </view>
         </Draggable>
@@ -76,10 +79,12 @@
 
         <!-- 排行榜 -->
         <rankList ref="rankList" :chooseClassId="chooseClassId"></rankList>
-        
-        <view v-if="seatMove" class="ui-pull-bottom ui-p" style="background-color: #0000006b;backdrop-filter: blur(4px);border-top: 1px solid #616161;">
+
+        <view v-if="seatMove" class="ui-pull-bottom ui-p"
+            style="background-color: #0000006b;backdrop-filter: blur(4px);border-top: 1px solid #616161;">
             <view class="ui-flex-row ui-flex-center txt-white ui-mt10 ui-mb30">
-                长按拖动调整整列,单选2位学生互相交换
+                长按拖动调整整列,单选2位学生互相交换,
+                <stuChangeExcel :stuList="list" @change="list = $event"></stuChangeExcel>
             </view>
             <view class="ui-flex-row ui-flex-center">
                 <el-button round type="primary" @click="updateStuChange">保存座位</el-button>
@@ -90,7 +95,7 @@
         <view v-else class="ui-pull-bottom ui-flex-row ui-p"
             style="background-color: #0000006b;backdrop-filter: blur(4px);border-top: 1px solid #616161;">
             <view class="ui-flex-1 ui-flex-row">
-                
+
                 <el-dropdown trigger="click" @command="doChooseClass">
                     <view-btn-item title="选择班级" src="../../static/jyicon/shubao.png"></view-btn-item>
                     <el-dropdown-menu slot="dropdown">
@@ -134,9 +139,12 @@
     import timerClock from './components/timer-clock.vue'
     import rankList from './components/rank-list.vue'
     import viewBtnItem from './components/view-button-item.vue'
+    import stuChangeExcel from './components/students-change-excel.vue'
     import {
         getStuSchoolIdByStuClassId
     } from "@/common/api/stu.js"
+    // const ExcelJS = require('exceljs');
+
     export default {
         components: {
             markScore,
@@ -146,6 +154,7 @@
             rankList,
             viewBtnItem,
             Draggable,
+            stuChangeExcel,
         },
         data() {
             return {
@@ -172,24 +181,24 @@
                 })
                 return list;
             },
-            teacher(){
+            teacher() {
                 return this.$store.state.teacherInfo || {}
             }
         },
-        onLoad(){
+        onLoad() {
             this.getClassroomList();
-            
+
             let teacher_id = this.teacher.id;
             this.$api.sendRequest({
                 url: `/mobile/teacher/draw`,
                 method: "get",
                 data: {
                     teacher_id: teacher_id,
-                    year : 2024
-                    
+                    year: 2024
+
                 },
                 success: res => {
-                    
+
                 }
             })
         },
@@ -199,9 +208,9 @@
             },
             getLevelIcon(item) {
                 let url = '//zhxy.obs.cn-hz1.ctyun.cn:443/static/';
-                if(item.score_total == 0){
+                if (item.score_total == 0) {
                     return url + 'tag_level/0.png'
-                } 
+                }
                 let val = ~~((item.score_total) / 10);
                 val = (val <= 0 ? 1 : val);
                 val = (val >= 16 ? 16 : val);
@@ -248,10 +257,10 @@
                     data: {
                         teacher_id: teacher_id
                     },
-                    fali : res => {
+                    fali: res => {
                         console.log(res);
                     },
-                    success: res => {                        
+                    success: res => {
                         this.classesList = res.data;
                         if (res.data.length) {
                             let value = uni.getStorageSync('lastClasses')
@@ -284,44 +293,45 @@
                 this.$refs.markScore.open(title);
             },
             setSeatMove(value) {
-                if(value){
+                if (value) {
                     this.setMulti(false);
                     this.listCopy = uni.$u.deepClone(this.list);
-                }else{
-                    this.list = uni.$u.deepClone(this.listCopy); 
+                } else {
+                    this.list = uni.$u.deepClone(this.listCopy);
                 }
                 this.seatMove = value;
                 this.activeList = [];
             },
             // 两个学生交换位置
-            onMoveStuChange(id1, id2){
+            onMoveStuChange(id1, id2) {
                 this.list.forEach(item => {
                     item.nodes.forEach((el, index) => {
-                        if(el == this.onMoveStuChange._move1){
+                        if (el == this.onMoveStuChange._move1) {
                             // console.log(1,el);
                             this.$set(item.nodes, index, uni.$u.deepClone(this.onMoveStuChange._move2));
                         }
-                        if(el == this.onMoveStuChange._move2){
+                        if (el == this.onMoveStuChange._move2) {
                             this.$set(item.nodes, index, uni.$u.deepClone(this.onMoveStuChange._move1));
                         }
                     })
                 })
                 this.activeList = [];
             },
-            onMoveColumnEnd(list){  //自动处理,无需改变
+            
+            onMoveColumnEnd(list) { //自动处理,无需改变
                 // console.log(list);
             },
             // 更新座位
-            updateStuChange(){
+            updateStuChange() {
                 let seat_list = this.list.map(item => {
                     return {
-                        id : item.id,
-                        title : item.title,
-                        nodes : item.nodes.map(el => {
+                        id: item.id,
+                        title: item.title,
+                        nodes: item.nodes.map(el => {
                             return {
-                                id : el.id,
-                                key : el.key,
-                                title : el.title
+                                id: el.id,
+                                key: el.key,
+                                title: el.title
                             }
                         })
                     }
@@ -331,8 +341,8 @@
                     url: `/mobile/saveSeat`,
                     method: "PUT",
                     data: {
-                        classroom_id : this.chooseClassId,
-                        seat_list : seat_list
+                        classroom_id: this.chooseClassId,
+                        seat_list: seat_list
                     },
                     success: res => {
                         uni.showToast({
@@ -345,6 +355,11 @@
             },
             // 点击学生
             toClickStu(item) {
+                if(!item.id){
+                    if(!this.seatMove){
+                        return;
+                    }
+                }
                 if (this.seatMove) { //单个换位置
                     if (this.activeList.length == 0) {
                         this.activeList.push(item.id);
@@ -550,19 +565,22 @@
         border: 1px solid #fff;
         border-left: 0;
     }
-    
-    .ui-mrr{
+
+    .ui-mrr {
         margin-right: 50rpx;
     }
-    .ui-mll{
+
+    .ui-mll {
         margin-left: 50rpx;
     }
-    .sortable-chosen{
+
+    .sortable-chosen {
         background-color: #fbfbfb66;
         border-radius: 5px;
-        
+
     }
-    .sortable-ghost{
+
+    .sortable-ghost {
         // margin-left : 0 !important;
         // margin-right : 0 !important;
         background-color: #fbfbfb00;

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels