shilipojs 3 лет назад
Родитель
Сommit
817c63fa38

+ 4 - 1
.gitignore

@@ -1 +1,4 @@
-/yarn.lock
+yarn.lock
+node_modules/
+package-lock.json
+.idea/

+ 28 - 0
mock/modules/elective-dispatch.js

@@ -0,0 +1,28 @@
+const Mock = require('mockjs')
+
+module.exports = [
+  {
+    url: '/mock/front/dispatch/getClass',
+    type:'get',
+    response: config => {
+      return {
+        code: 200,
+        msg: 'success',
+        data:
+          [{
+            classId: 1,
+            className:202,
+          },{
+            classId: 2,
+            className:203,
+          },
+            {
+              classId: 3,
+              className:204,
+            }
+          ]
+
+      }
+    }
+  },
+]

+ 81 - 0
src/api/webApi/elective/dispatch.js

@@ -0,0 +1,81 @@
+import request from '@/utils/request'
+
+// 查询 ROUND
+export function getRound(params) {
+  return request({
+    url: '/front/elective/classes/round',
+    method: 'get',
+    params
+  })
+}
+
+// 查询分班配置
+export function getSettings(params) {
+  return request({
+    url: '/front/elective/classes/settings',
+    method: 'get',
+    params
+  })
+}
+
+// 保存分班配置
+export function saveSettings(query,data) {
+  return request({
+    url: '/front/elective/classes/settings',
+    method: 'post',
+    params: query,
+    data: data
+  })
+}
+
+// 应用分班配置
+export function applySettings(params) {
+  return request({
+    url: '/front/elective/classes/settings/apply',
+    method: 'post',
+    params
+  })
+}
+
+// 分派名单查询
+export function classesResult(params) {
+  return request({
+    url: '/front/elective/classes/result',
+    method: 'get',
+    params
+  })
+}
+
+// 分派转移
+export function resultDispatch(data) {
+  return request({
+    url: '/front/elective/classes/result/dispatch',
+    method: 'post',
+    data
+  })
+}
+
+// 锁定分班
+export function lockDispatch(params) {
+  return request({
+    url: '/front/elective/classes',
+    method: 'post',
+    params
+  })
+}
+
+export function getClass(params) {
+  return request({
+    url: '/front/elective/classes/list',
+    method: 'get',
+    params
+  })
+}
+
+export function getDispatchResult(params) {
+  return request({
+    url: '/mock/front/report/getDispatchResult',
+    method: 'get',
+    params
+  })
+}

+ 10 - 0
src/components/Cache/modules/mx-classTree-translate-mixin.js

@@ -1,4 +1,5 @@
 import cacheMixin from '@/components/Cache/mx-cache-mixin.js'
+
 export default {
   mixins: [cacheMixin],
   data() {
@@ -43,5 +44,14 @@ export default {
       })
       return nodes.join('/')
     },
+    getClassName(classId) {
+      if (!classId) return ''
+      const nodes = []
+      this.classTree.forEach(grade => {
+        const match = grade.classList.find(c => c.classId == classId)
+        if (match) nodes.push(match.className)
+      })
+      return nodes.join('/')
+    }
   }
 }

+ 28 - 0
src/components/MxCondition/condition-object/condition-dispatch-class.js

@@ -0,0 +1,28 @@
+import conditionObjectBase from '../condition-object-base.js'
+import { getClass } from '@/api/webApi/elective/dispatch.js'
+export default {
+  ...conditionObjectBase,
+  dependentKeys: ['localGroupId'],
+  key: 'dispatchClassId',
+  title: '班级',
+  isDependencyReady(params) {
+    return params.localGroupId
+  },
+  getList: function(param, $vue) {
+    return new Promise((resolve, reject) => {
+      getClass({
+        groupId: param.localGroupId,
+        roundId: $vue.model.dispatchRoundId
+      }).then(res => {
+        resolve(res.data)
+      })
+      .catch(e => reject(e))
+    })
+  },
+  getCode: function(item) {
+    return item.id
+  },
+  getLabel: function(item) {
+    return item.name
+  }
+}

+ 15 - 0
src/components/MxCondition/condition-object/condition-dispatch-gender.js

@@ -0,0 +1,15 @@
+import conditionObjectBase from '../condition-object-base.js'
+import MxConfig from '@/common/mx-config'
+export default {
+  ...conditionObjectBase,
+  key: 'dispatchGender',
+  title: '性别',
+  getList: function(param, $vue) {
+    return Promise.resolve(MxConfig.form.sexOptions.map(item => {
+      return {
+        id: item.value,
+        name: item.text
+      }
+    }))
+  },
+}

+ 1 - 1
src/components/MxTable/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-table ref="table" :border="border" :data="rows" :span-method="mergeRowsColumns"
+  <el-table ref="table" :border="border"  :data="rows" :span-method="mergeRowsColumns"
             @selection-change="$emit('selection-change', $event)" tooltip-effect="dark">
     <template v-for="(prop, key) in propDefines">
       <template v-if="prop.hidden"></template>

+ 86 - 0
src/components/mx-select/mx-select.vue

@@ -0,0 +1,86 @@
+<template>
+  <el-col :span="span">
+    <el-select
+      class="search-select"
+      :clearable="clearable"
+      :placeholder="placeholder"
+      :multiple="multiple"
+      v-model="selectedValue"
+      @change="onSelectChange"
+    >
+      <el-option
+        :disabled="item[itemDisabled]"
+        v-for="item in list"
+        :key="item[itemValue]"
+        :label="item[itemLabel]"
+        :value="item[itemValue]"
+      ></el-option>
+    </el-select>
+  </el-col>
+</template>
+<script>
+export default {
+  props: {
+    list: {
+      type: Array,
+      required: true,
+      default: () => []
+    },
+    itemDisabled: {
+      type: String,
+      default: 'disabled'
+    },
+    itemLabel: {
+      type: String,
+      default: 'name'
+    },
+    itemValue: {
+      type: String,
+      default: 'id'
+    },
+    clearable: {
+      type: Boolean,
+      default: false
+    },
+    placeholder: {
+      type: String,
+      default: '请选择'
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    span: {
+      type: Number,
+      default: 24
+    },
+    offset: {
+      type: Number,
+      default: 0
+    },
+    value: [String, Number, Array]
+  },
+  methods: {
+    onSelectChange(val) {
+      this.$emit('input', val)
+      this.$emit('change', val)
+    }
+  },
+  computed: {
+    selectedValue: {
+      get: function() {
+        return this.value
+      },
+      set: function(newValue) {
+        this.$emit('input', newValue)
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.search-select {
+  width: 100%;
+  margin-bottom: 10px;
+}
+</style>

+ 10 - 4
src/router/index.js

@@ -1,5 +1,7 @@
 import Vue from 'vue'
 import Router from 'vue-router'
+/* Layout */
+import Layout from '@/layout'
 
 // 解决冗余导航报错
 const originalPush = Router.prototype.push
@@ -10,10 +12,6 @@ Router.prototype.push = function push(location) {
 
 Vue.use(Router)
 
-/* Layout */
-import Layout from '@/layout'
-import ParentView from '@/components/ParentView'
-
 /**
  * Note: 路由配置项
  *
@@ -764,6 +762,14 @@ export const constantRoutes = [{
           title: '选科测评报告'
         }
       },
+      {
+        path: '/elective/dispatch/detail',
+        component: (resolve) => require(['@/views/elective/dispatch/detail'], resolve),
+        name: 'DispatchDetail',
+        meta: {
+          title: '分班详情'
+        }
+      },
       {
         path: '/elective/generation/detail',
         component: (resolve) => require(['@/views/elective/generation/detail'], resolve),

+ 163 - 0
src/views/elective/dispatch/components/choose-class.vue

@@ -0,0 +1,163 @@
+<template>
+  <div>
+    <el-form  :rules="rules" ref="form" :model="form" label-width="80px" @submit.native.prevent>
+      <el-form-item label-width="0">
+        <el-checkbox-group
+          v-model="tmpClassIds"
+          :max="roundGroup.classCount"
+        >
+          <el-checkbox style="margin-bottom: 20px" v-for="item in classList"
+                       :disabled="item.disabled" :label="item.classId" :key="item.classId"
+          >{{ item.className }}
+          </el-checkbox>
+        </el-checkbox-group>
+      </el-form-item>
+      <el-form-item label="输入班级" prop="name" v-if="inputVisible" >
+        <el-tooltip class="item" effect="dark" content="输入班级名称后回车创建班级" :hide-after="1500" placement="bottom-start">
+          <el-input
+            style="width:60%"
+            class="input-new-tag"
+            v-model="form.name"
+            ref="saveTagInput"
+            size="small"
+            @keyup.enter.native="handleInputEnter"
+          >
+          </el-input>
+        </el-tooltip>
+        <div class="size-icon" @click="handleInputConfirm">
+          <i class="icon el-icon-circle-close"></i>
+        </div>
+      </el-form-item>
+    <el-button v-else type="primary" size="small" @click="showInput">新增班级</el-button>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import MxClassTreeTranslateMixin from '@/components/Cache/modules/mx-classTree-translate-mixin.js'
+import * as busiClass from '@/api/webApi/busi-classes.js'
+
+export default {
+  name: 'choose-class',
+  props: {
+    year: {
+      type: Number
+    },
+  },
+  mixins: [MxClassTreeTranslateMixin],
+  data() {
+    return {
+      tmpClasses: [],
+      roundGroup: {},
+      settingContainer: [],
+      inputVisible: false,
+      form: {
+        name: '',
+        type: 1, // 默认为行政班
+        year: '', // 学年
+        gradeId: '', // 年级
+        subjectId: '1,2,3,4,5,6,7,8,9' // 默认为全科目
+      },
+      rules: {
+        name: [
+          { required: true, message: '班级名称不能为空', trigger: 'blur' }
+        ]
+      },
+      tmpClassIds: []
+    }
+  },
+  computed: {
+    classList() {
+      if (!this.classTree?.length) return []
+      if (!this.year) return []
+      // 获取该年份下的年级
+      const classTree = this.classTree[this.classTree.findIndex(i => i.year = this.year)]
+      this.form.gradeId = classTree.gradeId
+      this.form.year = classTree.year
+      return this.classTree[this.classTree.findIndex(i => i.year = this.year)].classList.map(item => {
+        item['disabled'] = this.settingContainer.some(c => c.classId == item.classId && c.groupId !== this.roundGroup.groupId)
+        return item
+      })
+    }
+  },
+  methods: {
+    close() {
+      this.handleInputConfirm()
+    },
+    validate() {
+      //
+      if (this.tmpClassIds.length !== this.roundGroup.classCount) return false
+      return true
+    },
+    open(roundGroup, settingContainer) {
+      this.roundGroup = roundGroup
+      this.settingContainer = settingContainer
+      this.tmpClasses = settingContainer.filter(setting => setting.groupId == roundGroup.groupId)
+      this.tmpClassIds = this.tmpClasses.map(c => c.classId)
+    },
+    confirm() {
+      const mergeClasses = this.tmpClassIds.map(id => {
+        return this.tmpClasses.find(c => c.classId == id) || {
+          classId: id, // 班级Id
+          roundId: this.roundGroup.roundId, // 轮次Id
+          groupId: this.roundGroup.groupId, // 组合Id
+          actualCount: 0, // 应用才产生 实际人数
+          expectedCount: 0, // 期望人数
+          actualCountInMale: 0, // 应用才产生 实际男生
+          actualCountInFemale: 0 // 应用才产生 实际女生
+        }
+      })
+      console.log(mergeClasses)
+      this.tmpClasses.forEach(c => this.settingContainer.remove(c))
+      mergeClasses.forEach(c => this.settingContainer.push(c))
+      this.handleInputConfirm()
+      return this.settingContainer
+    },
+    handleInputConfirm() {
+      this.inputVisible = false
+    },
+    handleInputEnter() {
+      console.log('回车')
+      this.$refs.form.validate(valid => {
+        if (valid) {
+          console.log('提交')
+          this.addClass()
+        }
+      })
+    },
+    showInput() {
+      this.inputVisible = true
+      this.$nextTick(_ => {
+        this.$refs.saveTagInput.$refs.input.focus()
+      })
+    },
+    addClass() {
+      busiClass.add(this.form).then(res => {
+        this.form.name = ''
+        this.msgSuccess('保存成功')
+        // this.getClassTreeByCache(false)
+        this.refreshClassTree()
+      }).finally()
+    },
+    refreshClassTree() {
+      this.$store.dispatch('GetInfo') // 借机清除了用户缓存 // clear cache
+      this.loadTranslateClassTree()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.size-icon {
+  font-size: 25px;
+  cursor: pointer;
+  display: inline-block;
+  height: 100%;
+  position: absolute;
+}
+
+.icon {
+  margin-left: 20px;
+  color: red;
+}
+</style>

+ 242 - 0
src/views/elective/dispatch/components/class-adjust.vue

@@ -0,0 +1,242 @@
+<template>
+  <el-dialog
+    title="班级调整"
+    :visible.sync="show"
+    :close-on-click-modal="false"
+    width="70%"
+  >
+    <el-form ref="form" :model="form" label-width="50px" label-position="right">
+      <el-row>
+        <el-row>
+
+          <el-col class="flex-center-column" :span="12">
+            <el-form-item>
+              <mx-select @change="getStudents('from',$event)" :span="12" :list="display" v-model="fromClassId"
+                         placeholder="请选择" item-label="className" item-value="classId"
+              ></mx-select>
+            </el-form-item>
+            <el-form-item>
+              <class-table :list="studentsTableLeft" @confirm="studentSelected($event,'from')"></class-table>
+            </el-form-item>
+          </el-col>
+          <el-col class="flex-center-column" :span="12">
+            <el-form-item>
+              <mx-select @change="getStudents('to',$event)" :span="12" :list="display" v-model="toClassId"
+                         placeholder="请选择" item-label="className" item-value="classId"
+              ></mx-select>
+            </el-form-item>
+            <el-form-item>
+              <class-table :list="studentsTableRight" @confirm="studentSelected($event,'to')"></class-table>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-col :span="24" class="flex-center" v-if="fromClassId && toClassId" style="margin-top: 20px">
+          <el-transfer
+            v-model="tranRight"
+            :titles="[fromClassId ? `${getClassName(fromClassId)}(添加)` : '未选择', toClassId ? `${getClassName(toClassId)}(添加)`: '未选择']"
+            :data="parentFrame"
+            @change="transferChange"
+          >
+          </el-transfer>
+        </el-col>
+      </el-row>
+    </el-form>
+    <span slot="footer" class="dialog-footer">
+    <el-button @click="close">取 消</el-button>
+    <el-button type="primary" @click="confirm">确 定</el-button>
+  </span>
+  </el-dialog>
+</template>
+
+<script>
+import { classesResult, resultDispatch } from '@/api/webApi/elective/dispatch'
+import MxClassTreeTranslateMixin from '@/components/Cache/modules/mx-classTree-translate-mixin.js'
+import MxSelect from '@/components/mx-select/mx-select'
+import ClassTable from './class-table'
+
+export default {
+  name: 'class-adjust',
+  components: {
+    MxSelect,
+    ClassTable
+  },
+  inject: ['refreshData'],
+  mixins: [MxClassTreeTranslateMixin],
+  data() {
+    return {
+      form: {},
+      studentsTableLeft: [],
+      studentsTableRight: [],
+      fromClassId: '',
+      toClassId: '',
+      group: '',
+      settings: [],
+      selectedLeft: [],
+      selectedRight: [],
+      show: false,
+    }
+  },
+  created() {
+  },
+  computed: {
+    tranRight() {
+      if (!this.selectedLeft.length) return []
+      return this.selectedLeft.map(s => { return s.studentId })
+    },
+    parentFrame() {
+      const selected = this.selectedRight.concat(this.selectedLeft)
+      console.log(selected)
+      return selected.map(item => {
+        return {
+          key: item.studentId,
+          label: item.name,
+          disabled: true
+        }
+      })
+    },
+    display() {
+      return this.settings.map(item => {
+        return {
+          classId: item.classId,
+          className: this.getClassName(item.classId),
+          disabled: this.getDisable(item.classId)
+        }
+      })
+    }
+  },
+  methods: {
+    close() {
+      this.show =false,
+        this.format()
+    },
+    format(){
+      this.form = {}
+      this.studentsTableLeft =[]
+      this.studentsTableRight =[]
+      this.fromClassId = ''
+      this.toClassId = ''
+      this.group = ''
+      this.settings = []
+      this.selectedLeft = []
+      this.selectedRight = []
+      this.show =false
+    },
+    getTranLeft() {
+      // 获取左tran实际的数据
+      // 母框去重右框
+      return this.parentFrame.filter(item => {
+        if (this.tranRight.findIndex(r => r == item.key) == -1) {
+          return item
+        }
+      })
+    },
+    getTranRight() {
+      // 获取左tran实际的数据
+      // 母框去重右框
+      return this.parentFrame.filter(item => {
+        if (this.tranRight.findIndex(r => r == item.key) != -1) return item
+      })
+    },
+    saveResultDispatch(clazzId,students) {
+      // 分派转移
+      resultDispatch({
+        roundId: this.group.roundId,
+        students: students,
+        toClazzId: clazzId
+      }).then(res => {
+        if(res.code == 200) this.$message.success(res.msg)
+        console.log(res)
+      })
+    },
+    confirm() {
+      if( !this.fromClassId || !this.toClassId) {
+        this.$message.warning('请先选择需要调整的两个班级')
+        return
+      }
+      // 左tran 是包含 右边的  (只是不显示右tran的数据)要先去重
+      // 左tran 去掉跟左table重复的数据就是 右tran 需要调换的
+      // 同理右tran 去掉跟右table重复的数据就是 左tran 需要调换的
+      // 1.去重
+      // 左tran实际勾选去除左边table
+      const left = this.getTranLeft().filter(item => {
+        return this.studentsTableLeft.findIndex(s => s.studentId == item.key) == -1
+      }).map(item => item.key)
+      //  右tran实际勾选去除右边table
+      const right = this.getTranRight().filter(item => {
+        return this.studentsTableRight.findIndex(s => s.studentId == item.key) == -1
+      }).map(item => item.key)
+      console.log(left) // 左边的学生放入左边表格
+      if( !left.length && !right.length) {
+        this.$message.warning('调整人数不可为0')
+        return
+      }
+      // 2.分别调用调换接口
+      if (left.length) this.saveResultDispatch(this.fromClassId,left)
+      console.log(left)// 左边的学生放入左边表格
+      console.log(right)// 右边的学生放入右边表格
+      if (right.length) this.saveResultDispatch(this.toClassId,right)
+      this.$nextTick(()=> {
+        this.show = false
+        // 注销数据
+        this.format()
+        this.refreshData()
+      })
+
+      // 2.分别调用调换接口
+
+    },
+    studentSelected(list, type) {
+      // 1 添加至母框
+      if (type == 'from') this.selectedLeft = list
+      if (type == 'to') this.selectedRight = list
+
+    },
+    getStudents(type, classId) {
+      console.log(type)
+      console.log(classId)
+      classesResult({
+        groupId: this.group.groupId,
+        roundId: this.group.roundId,
+        classId: classId
+      }).then(res => {
+        if( type == 'from') {
+          this.studentsTableLeft = res.rows
+        }else if( type == 'to') {
+          this.studentsTableRight = res.rows
+        }
+      })
+    },
+    // 判断是否有这个元素
+    getDisable(itemId) {
+      return this.fromClassId == itemId || this.toClassId == itemId
+    },
+    transferChange(currentVal, direction, array) {
+      if (!this.fromClassId || !this.toClassId) {
+        this.$message.warning('需要选择两个班级')
+        return
+      }
+    },
+    open(row, settings) {
+      console.log(row)
+      console.log(settings)
+      this.group = row
+      this.settings = settings
+      this.show = true
+    }
+  }
+}
+</script>
+
+<style scoped>
+.flex-center-column {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.flex-center {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+}
+</style>

+ 82 - 0
src/views/elective/dispatch/components/class-table.vue

@@ -0,0 +1,82 @@
+<template>
+  <div>
+    <mx-table :propDefines="propDefines" :rows="list" @selection-change="handleSelectionChange">
+    </mx-table>
+    <!-- 操作 确认和分页-->
+    <div class="tmp">
+
+    <!-- <el-button v-if="list.length > 0 && selected.length > 0" type="primary" @click="confirm">确认选择</el-button>-->
+      <el-pagination
+        layout="prev, pager, next"
+        :total="10">
+      </el-pagination>
+    </div>
+  </div>
+
+</template>
+<script>
+export default {
+  props: {
+    type: {
+      String: '',  //1 用于class-adjust 2 用于完成分班展示
+      default: '1'
+    },
+    list: {
+      required: true,
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      propDefines: {
+        selection: {
+          label: '选择',
+          type:'selection',
+          hidden: this.type == '2'
+        },
+        groupName: {
+          label: '组合',
+          hidden: this.type == '1'
+        },
+        className: {
+          label: '班级'
+        },
+        name: {
+          label: '姓名',
+        },
+        sex: {
+          label: '性别',
+        },
+        rankInGroup: {
+          label: '班级排名'
+        },
+        rankInGrade: {
+          label: '年级排名',
+          hidden: this.type == '1'
+        }
+      },
+      selected: [],
+    }
+  },
+  computed: {
+  },
+  methods: {
+    handleSelectionChange(row) {
+      this.selected = row
+      this.$emit('confirm',this.selected)
+    },
+    confirm() {
+    }
+  }
+}
+
+</script>
+<style scoped>
+.tmp{
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  margin: 10px 0;
+}
+</style>

+ 201 - 0
src/views/elective/dispatch/components/dispatch-table.vue

@@ -0,0 +1,201 @@
+<template>
+  <el-row>
+    <mx-table  :propDefines="propDefines" :rows="displayRows">
+      <template #classCount="{row,$index}">
+        <el-input-number size="small" v-model="row.classCount" @change="classCountChange(row,$index)" :min="0"
+                         :disabled="row.classCount != 0" label="label"
+        ></el-input-number>
+      </template>
+      <!-- 分班编辑-->
+      <template #edit="{row}">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="openEditDialog(row)"
+        >编辑
+        </el-button>
+      </template>
+      <template #className="{row}">
+        <div v-if="row.groupClass.length">
+          <span  class="btn-class" @click="toDetail(row,item.classId)" v-for="item in row.groupClass">{{item.className}}</span>
+        </div>
+        <div v-else>
+          <span ></span>
+        </div>
+      </template>
+      <!-- 班级调整 -->
+      <template #adjust="{row}">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="adjust(row)"
+        >调整
+        </el-button>
+      </template>
+      <template #detail="{row}">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="toDetail(row)"
+        >详情
+        </el-button>
+      </template>
+    </mx-table>
+    <edit-group :year="round.year"  ref="editDialog"></edit-group>
+    <class-adjust ref="adjustDialog"></class-adjust>
+  </el-row>
+</template>
+<script>
+import MxClassTreeTranslateMixin from '@/components/Cache/modules/mx-classTree-translate-mixin.js'
+import MxSelectTranslateMixin from '@/components/Cache/modules/mx-select-translate-mixin.js'
+import ClassAdjust from './class-adjust'
+import SetClasscount from './set-classcount'
+import EditGroup from './edit-group'
+import MxTransferMixin from '@/components/mx-transfer-mixin.js'
+
+export default {
+  components: {
+    EditGroup,
+    SetClasscount,
+    ClassAdjust,
+  },
+  mixins: [MxClassTreeTranslateMixin, MxSelectTranslateMixin,MxTransferMixin],
+  props: {
+    loading: {
+      type: Boolean,
+      default: false,
+    },
+    round: {
+      type: Object,
+      default: {},
+    },
+    settings: {
+      type: Array,
+      default: [],
+    }
+  },
+  data() {
+    return {
+      modifyGroupSettings: [],
+      dataList: [],
+      propDefines: {
+        groupName: {
+          label: '组合'
+        },
+        number: {
+          label: '录取人数'
+        },
+        classCount: {
+          label: '班级数',
+          slot: 'classCount'
+        },
+        edit: {
+          label: '分班编辑',
+          slot: 'edit'
+        },
+        groupClass: {
+          label: '班级名称',
+          slot: 'className'
+        },
+        expectedCount: {
+          label: '人数'
+        },
+        adjust: {
+          label: '操作',
+          slot: 'adjust'
+        },
+        proportion: {
+          label: '男女比例',
+        },
+        detail: {
+          label: '详情',
+          slot: 'detail'
+        }
+      }
+    }
+  },
+  computed: {
+    displayRows() {
+      if (!this.classTree.length) return []
+      if (!this.listGroupsOptions.length) return []
+      // if (!this.settings.length) return []
+      if (!this.round.groupList) return []
+      const rows = this.round.roundGroups.map(rg => ({
+          groupId: rg.groupId,
+          roundId: this.round.roundId,
+          groupName: this.translateGroup(rg.groupId),
+          number: this.round.enrollGroupCount[rg.groupId] || 0, // 录取人数
+          classCount: rg.classCount, // 班级数
+          expectedCount: this.settings.filter(item => item.groupId == rg.groupId).map(item => item.expectedCount).toString(),
+          groupClass: this.settings
+            .filter(item => item.groupId == rg.groupId)
+            .map(item => {
+              return {
+                classId: item.classId,
+                className: this.getClassName(item.classId)
+              }
+            })
+        }))
+      console.log('displayRows computed:', rows)
+      return rows
+    }
+  },
+  created() {
+
+  },
+  methods: {
+    openEditDialog(row) {
+      if (!row.classCount) {
+        this.$message.warning('班级数为0时不可分班')
+        return
+      }
+      this.modifyGroupSettings = this.deepClone(this.settings)
+      this.$refs.editDialog.open(row, this.modifyGroupSettings)
+    },
+    adjust(row) {
+      // 调整 未分班不可调整
+      const filterSettings = this.settings.filter(item => item.groupId == row.groupId)
+      if(filterSettings.length == 0){
+        this.$message.warning('未分班不可调整')
+        return
+      }
+      this.$refs.adjustDialog.open(row,this.settings.filter(item => item.groupId == row.groupId))
+    },
+    toDetail(row,classId ='') {
+      console.log(classId)
+      const params = {group: row,classId:classId,groupIds: (this.round.groupIds &&this.round.groupIds.split(','))||[]}
+      const path = '/elective/dispatch/detail'
+      console.log('prev transfer', row, this.round)
+      this.transferTo(path, params)
+    },
+    editCount(row) {
+      // 设定分配人数
+      const filter = this.settings.filter(item => item.groupId == row.groupId)
+      if (filter.length == 0) {
+        this.$message.warning('班级未编辑')
+        return
+      }
+      // if(){
+      //   this.$message.warning('需要选择')
+      // }
+      this.$refs.setClassDialog.open(row, this.settings)
+    },
+    classCountChange(newVal, index) {
+      this.round.groupList[index] = newVal
+    }
+  }
+}
+</script>
+<style scoped>
+.btn-class{
+  margin-right: 10px;
+  cursor: pointer;
+  border-bottom:1px solid #42b983;
+}
+</style>

+ 90 - 0
src/views/elective/dispatch/components/edit-group.vue

@@ -0,0 +1,90 @@
+<template>
+  <el-dialog
+    :title="`分班编辑(${roundGroup.groupName})`"
+    :visible.sync="show"
+    :close-on-click-modal="false"
+    width="70%"
+  >
+    <div>
+        <el-steps :active="active" finish-status="success">
+          <el-step title="设定班级"></el-step>
+          <el-step title="设定班级人数"></el-step>
+          <el-step title="完成"></el-step>
+        </el-steps>
+      <div style="padding: 20px 10px">
+        <choose-class v-if="active == 0" :year="year" ref="editClassDialog"></choose-class>
+        <set-classcount  v-if="active == 1" ref="setClassDialog"></set-classcount>
+        <p>
+          <el-button v-if="active == 0" @click="confirm" type="primary" style="float: right">下一步</el-button>
+        </p>
+      </div>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="show = false">取 消</el-button>
+      <el-button type="primary" @click="save()" :disabled="active != 1">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+<script>
+
+import ChooseClass from '@/views/elective/dispatch/components/choose-class'
+import SetClasscount from '@/views/elective/dispatch/components/set-classcount'
+
+export default {
+  props: {
+    year: {
+      type: Number
+    }
+  },
+  components: {
+    ChooseClass,
+    SetClasscount
+  },
+  data() {
+    return {
+      active: 0,
+      show: false,
+      roundGroup: {},
+      settingContainer: [],
+
+    }
+  },
+  methods: {
+    open(roundGroup, settingContainer) {
+      this.active = 0
+      this.show = true
+      this.roundGroup = roundGroup
+      this.settingContainer = settingContainer
+      this.$nextTick(() => {
+        this.$refs.editClassDialog.open(roundGroup, settingContainer)
+      })
+    },
+    confirm() {
+      // 验证
+      const flag =this.$refs.editClassDialog.validate()
+      if (flag) {
+        // 下一步 保存班级
+        this.$refs.editClassDialog.confirm()
+        this.active = 1
+        // this.modifyGroupSettings = this.deepClone(this.settingContainer.filter(item => item.groupId == row.groupId))
+        this.$nextTick(() => {
+          this.$refs.setClassDialog.init(this.roundGroup,this.settingContainer)
+        })
+        return
+      }
+      this.$message.error(`${this.roundGroup.groupName}设定了${this.roundGroup.classCount}个班级`)
+      console.log(flag)
+    },
+    save() {
+      // 验证
+      const flag =this.$refs.setClassDialog.valid()
+      if(flag) this.show = false
+
+
+    }
+  }
+}
+</script>
+<style>
+
+</style>

+ 199 - 0
src/views/elective/dispatch/components/set-classcount.vue

@@ -0,0 +1,199 @@
+<template>
+    <div>
+      <p style="margin-bottom: 10px;">该组合录取人数为:{{roundGroup.number}}</p>
+      <el-table
+        :data="formatSetting"
+        style="width: 100%"
+      >
+        <el-table-column
+          label="序号"
+          prop="sortIndex"
+          width="100"
+        >
+        </el-table-column>
+        <el-table-column
+          label="组合"
+          width="180"
+        >
+          <template>
+            {{roundGroup.groupName}}
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="className"
+          label="班级名称"
+          width="180"
+        >
+        </el-table-column>
+        <el-table-column
+          label="人数"
+        >
+          <template slot-scope="scope">
+              <el-input-number :min="1" :max="roundGroup.number?setPubMax(scope.$index):Infinity" v-model="scope.row.expectedCount" @change="countEdit(scope.row,scope.$index)" ></el-input-number>
+          </template>
+        </el-table-column>
+<!--        <el-table-column-->
+<!--          label="操作"-->
+<!--        >-->
+<!--          <template slot-scope="scope">-->
+<!--            <el-popover-->
+<!--              placement="bottom"-->
+<!--              title="标题"-->
+<!--              width="200"-->
+<!--              trigger="click"-->
+<!--              content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。">-->
+<!--              <el-switch-->
+<!--                v-model="scope.row.active"-->
+<!--                active-color=""-->
+<!--                active-text="按排名"-->
+<!--                inactive-text="随机"-->
+<!--                inactive-color="#ff4949"-->
+<!--                slot="reference">-->
+<!--              </el-switch>-->
+<!--            </el-popover>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+      </el-table>
+      <el-radio-group class="mt10" v-model="mode">
+        <el-popover
+          placement="bottom-start"
+          width="200"
+          trigger="hover"
+          content="默认分配方式,按成绩平均分配">
+          <el-radio-button class="radio-btn" label="RankBalance" slot="reference">均衡</el-radio-button>
+        </el-popover>
+        <el-popover
+          placement="bottom-start"
+          width="200"
+          trigger="hover"
+          content="按成绩优先分配">
+          <el-radio-button class="radio-btn" label="RankPriority" slot="reference">排名</el-radio-button>
+        </el-popover>
+        <el-popover
+          placement="bottom-start"
+          width="200"
+          trigger="hover"
+          content="随机分配">
+          <el-radio-button class="radio-btn" label="random" slot="reference">随机</el-radio-button>
+        </el-popover>
+      </el-radio-group>
+    </div>
+</template>
+
+<script>
+import MxClassTreeTranslateMixin from '@/components/Cache/modules/mx-classTree-translate-mixin.js'
+import { saveSettings, applySettings } from '@/api/webApi/elective/dispatch'
+export default {
+  name: 'set-classcount',
+  mixins: [MxClassTreeTranslateMixin],
+  data() {
+    return {
+      setShow: false,
+      roundId: '',
+      roundGroup: {},
+      mode: 'RankBalance',
+      // RankBalance, // 按成绩平均分配,默认按这种分配方式,可保证各个班成绩都差不多
+      // RankPriority, // 按成绩优先,会导致成绩好的集中到一起
+      // Random // 随机
+      settingContainer: []
+    }
+  },
+  computed: {
+    // input-number设置公用max
+    setPubMax() {
+      return (data) => {
+        let allSelect = 0
+        let maxQuantit = this.roundGroup.number
+        this.settingContainer.forEach((item, index) => {
+          if (index !== data) {
+            allSelect += item.expectedCount
+          }
+        })
+        return maxQuantit-allSelect
+      }
+    },
+    formatSetting() {
+      return this.settingContainer.map((item,index) => {
+        return {
+          sortIndex: index + 1,
+          expectedCount: item.expectedCount,
+          classId: item.classId,
+          className: this.getClassName(item.classId),
+        }
+      })
+    },
+  },
+  inject: ['refreshData'],
+  methods: {
+    countEdit(newVal,index){
+      this.settingContainer[index].expectedCount = newVal.expectedCount
+    },
+    init(roundGroup,settingContainer) {
+      this.roundGroup = roundGroup
+      this.roundId = roundGroup.roundId
+      if( settingContainer.length > 0) {
+        this.settingContainer = settingContainer.filter(item => item.groupId == roundGroup.groupId)
+        console.log(this.settingContainer)
+        return
+      }
+      // 没有setting就初始化人数
+      const remainder = roundGroup.number % roundGroup.classCount // 余数
+      const divide = Math.floor(roundGroup.number / roundGroup.classCount) // 向下取整的除数
+      this.settingContainer = settingContainer.filter(item => item.groupId == roundGroup.groupId).map((item,index)  => {
+        if (index + 1 <= remainder) {
+          // 余数平分给前面
+          item.expectedCount = divide + 1
+        }else {
+          // 余数分完
+          item.expectedCount = divide
+        }
+        return item
+        console.log(item)
+      })
+    },
+    valid() {
+      // 验证
+      // 累计人数
+      const count =this.formatSetting.reduce((pre, cur) => {
+          return pre + cur.expectedCount
+      }, 0)
+      if(count != this.roundGroup.number) {
+        this.$message.warning('还有学生未分班')
+        return
+      }
+      // 校验通过 保存
+      this.confirm()
+      return true
+    },
+    saveSettings() {
+      saveSettings(
+        {roundId: this.roundId }, this.settingContainer ).then(res => {
+        if (res.code == 200) {
+          this.$message.success(res.msg)
+        }
+      }).finally(res => {
+        this.refreshData()
+      })
+    },
+    confirm() {
+      console.log(this.settingContainer)
+      // 应用分班配置
+      applySettings({
+        roundId:this.roundId,
+        mode:this.mode,
+        groupId:this.roundGroup.groupId,
+      }).then(res => {})
+      // 保存settings
+      this.$nextTick(_ => {
+        this.saveSettings()
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.radio-btn{
+  margin-right: 20px;
+}
+</style>

+ 78 - 0
src/views/elective/dispatch/detail.vue

@@ -0,0 +1,78 @@
+<template>
+  <!-- 分班完成的详情 -->
+  <div class="app-container">
+    <el-card class="box-card" style="margin-bottom: 10px;">
+      <mx-condition :query-params="queryParams" :require-fields="requireFields" :local-data="groupSource"
+                    @query="handleGroupQuery" class="mb10"
+      ></mx-condition>
+    </el-card>
+    <class-table type="2" :list="studentList"></class-table>
+  </div>
+</template>
+<!--:setting-model="settingModel" :group-model-index="groupModelIndex"-->
+<!--:group-model="groupModel" :default-group-id="scoreQueryGroupId"-->
+<script>
+import MxSelectTranslateMixin from '@/components/Cache/modules/mx-select-translate-mixin.js'
+import MxCondition from '@/components/MxCondition/mx-condition'
+import MxTransferMixin from '@/components/mx-transfer-mixin.js'
+import ClassTable from '@/views/elective/dispatch/components/class-table'
+import { classesResult } from '@/api/webApi/elective/dispatch'
+
+export default {
+  mixins: [MxTransferMixin, MxSelectTranslateMixin],
+  components: { ClassTable, MxCondition },
+  name: 'dispatch-detail',
+  data() {
+    return {
+      requireFields: ['localGroupId'],
+      studentList: [],
+      queryParams: null
+    }
+  },
+  computed: {
+    groupSource() {
+      if (!this.listGroupsOptions.length) return {}
+      if (!this.prevData.groupIds?.length) return {}
+      console.log('computed groupSource exec')
+      const source = {
+        groups: this.prevData.groupIds.map(groupId => {
+          return {
+            groupName: this.translateGroup(groupId),
+            groupId: groupId
+          }
+        })
+      }
+      this.$nextTick(_ => this.queryParams = {
+        localGroupId: this.prevData.group.groupId,
+        dispatchClassId: this.prevData.classId || '',
+        dispatchGender: '',
+        dispatchRoundId: this.prevData.group.roundId
+      })
+      return source
+    }
+  },
+  methods: {
+    handleGroupQuery() {
+       this.getDispatchResult()
+    },
+    getDispatchResult() {
+      classesResult({
+        classId: this.queryParams.dispatchClassId,
+        groupId: this.queryParams.localGroupId,
+        roundId: this.queryParams.dispatchRoundId,
+        sex: this.queryParams.dispatchGender || null,
+      }).then(res => {
+        console.log(res)
+        this.studentList = res.rows.map(item => {
+          item.groupName = this.translateGroup(this.queryParams.localGroupId)
+          return item
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 73 - 0
src/views/elective/dispatch/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <div class="app-container">
+    <el-card class="box-card" style="margin-bottom: 10px;">
+      <mx-condition ref="condition" :query-params="queryParams" :require-fields="requireFields" @query="handleQuery"
+                    @invalid="handleInvalidQuery"
+      ></mx-condition>
+    </el-card>
+    <dispatch-table :loading="loading" :round="round" :settings="settings"></dispatch-table>
+  </div>
+</template>
+<script>
+import DispatchTable from "./components/dispatch-table.vue";
+import { getRound,getSettings } from '@/api/webApi/elective/dispatch'
+import MxCondition from '@/components/MxCondition/mx-condition'
+
+export default {
+  components: {
+    DispatchTable,
+    MxCondition
+  },
+  data() {
+    return {
+      requireFields: ['year', 'roundId'],
+      queryParams: {
+        year: '',
+        roundId: ''
+      },
+      loading: false,
+      settings: [],
+      round: { },
+    };
+  },
+  provide() {
+    return {
+      refreshData: this.handleQuery
+    }
+  },
+  methods: {
+    handleQuery() {
+      this.getRound()
+      this.getSettings()
+    },
+    // 获取批次的setting
+    getSettings() {
+      getSettings({
+        roundId:this.queryParams.roundId,
+      }).then(res => {
+        console.log(res)
+        this.settings = res.data
+      })
+    },
+    // 获取批次的组合
+    getRound() {
+      this.loading = true
+      getRound({
+        year:this.queryParams.year,
+        round:this.queryParams.roundId,
+      }).then(res => {
+        this.round = res.data
+        console.log(res)
+      }).finally((res) => {
+        this.loading= false
+      })
+    },
+    handleInvalidQuery() {
+      console.log('query取消')
+      this.round = {}
+    },
+  },
+};
+</script>
+<style scoped>
+</style>

+ 1 - 0
src/views/permission/components/round-score-query.vue

@@ -105,6 +105,7 @@ export default {
   },
   computed: {
     groupSource() {
+      console.log(this.settingModel.roundGroups)
       return { groups: this.settingModel.roundGroups }
     },
     groupQuery() {