浏览代码

generation + 报名柱状图

hare8999@163.com 3 年之前
父节点
当前提交
9085bf56a5

+ 39 - 10
mock/modules/elective-generation.js

@@ -1,7 +1,7 @@
 const Mock = require('mockjs')
 const Random = Mock['Random']
 
-const mockGeneration = 8 // primary
+const mockGeneration = 5 // primary
 const mockGroups = [1, 2, 3, 4, 5, 6]
 const mockPreferenceCount = 3 // 1 or 3 // 1志愿/3志愿
 
@@ -28,7 +28,10 @@ module.exports = [
             rankOut: false
           })),
           allMatched: false,
-          currentGeneration: mockGeneration
+          currentGeneration: mockGeneration,
+
+          // +
+          disenrollCount: Random.integer(100, 200)
         }
       }
     }
@@ -53,7 +56,7 @@ module.exports = [
                   values: mockGroups.map(groupId => ({
                     groupId: groupId,
                     value: Random.integer(120, 400),
-                    color: '',
+                    color: 'B',
                     bold: false,
                     star: false,
                     queryCode: 'abc',
@@ -89,14 +92,14 @@ module.exports = [
             break
           default:
             const accumulateDefine = {
-              generations: [2,4,6,7],
-              factory: ()=>[
+              generations: [2, 4, 6, 7],
+              factory: () => [
                 {
                   category: 'approvedCount',
                   displayName: '正常录取',
                   values: mockGroups.map(groupId => ({
                     groupId: groupId,
-                    value: Random.integer(120, 400),
+                    value: Random.integer(120, 300),
                     color: '',
                     bold: false,
                     star: false,
@@ -109,7 +112,7 @@ module.exports = [
                   displayName: '调剂录取',
                   values: mockGroups.map(groupId => ({
                     groupId: groupId,
-                    value: Random.integer(120, 400),
+                    value: Random.integer(0, 10),
                     color: '',
                     bold: false,
                     star: false,
@@ -136,7 +139,7 @@ module.exports = [
                   values: mockGroups.map(groupId => ({
                     groupId: groupId,
                     value: Random.integer(120, 400),
-                    color: '',
+                    color: 'R',
                     bold: false,
                     star: false,
                     queryCode: 'abc',
@@ -157,7 +160,7 @@ module.exports = [
                       value: Random.integer(120, 400),
                       color: '',
                       bold: false,
-                      star: false,
+                      star: true,
                       queryCode: 'abc',
                       disabled: false
                     }))
@@ -221,7 +224,20 @@ module.exports = [
                 factory: () => [
                   {
                     category: 'matchedApproved',
-                    displayName: '可调剂已填',
+                    displayName: '可调剂同意',
+                    values: mockGroups.map(groupId => ({
+                      groupId: groupId,
+                      value: Random.integer(120, 400),
+                      color: '',
+                      bold: false,
+                      star: false,
+                      queryCode: 'abc',
+                      disabled: false
+                    }))
+                  },
+                  {
+                    category: 'matchedNotOptional',
+                    displayName: '可调剂改选',
                     values: mockGroups.map(groupId => ({
                       groupId: groupId,
                       value: Random.integer(120, 400),
@@ -258,6 +274,19 @@ module.exports = [
                       disabled: false
                     }))
                   },
+                  {
+                    category: 'matchedRankout',
+                    displayName: '可调剂被挤出',
+                    values: mockGroups.map(groupId => ({
+                      groupId: groupId,
+                      value: Random.integer(120, 400),
+                      color: '',
+                      bold: false,
+                      star: false,
+                      queryCode: 'abc',
+                      disabled: false
+                    }))
+                  },
                   {
                     category: 'nonmatchedApproved',
                     displayName: '不可调剂已填',

+ 2 - 0
src/common/mx-config.js

@@ -4,8 +4,10 @@ export default {
     primary: '#47C6A2',
     primary_up: '#51C9A7',
     primary_down: '#D2F1E8',
+    primary_report: '#2EC7C9',
     yellow_up: '#F5AB86',
     yellow_down: '#FDEAE1',
+    yellow_report: '#FFA400',
     blue_up: '#608EDF',
     blue_down: '#DBE8FF',
     audit_bg: {

+ 77 - 77
src/components/EvaluationTitle/index.vue

@@ -1,77 +1,77 @@
-<template>
-  <el-card :class="{ 'box-card': cardStyle, 'evaluation-margin': withMarginBottom }">
-    <el-row :gutter="24">
-      <el-col :span="navBackButton ? 22 - slotSpan : 24 - slotSpan">
-        <div class="evaluation-title-row">
-          {{ title }}
-          <span class="evaulation-title-subItem">{{ subTitle }}</span>
-        </div>
-      </el-col>
-      <el-col v-if="slotSpan" :span="slotSpan">
-        <solt></solt>
-      </el-col>
-      <el-col v-if="navBackButton" :span="2" style="text-align: right">
-        <el-button @click="navAction ? navAction() : $router.go(-1)" size="small" round>返回</el-button>
-      </el-col>
-    </el-row>
-  </el-card>
-</template>
-<script>
-export default {
-  props: {
-    title: {
-      type: String,
-      default: "",
-    },
-    subTitle: {
-      type: String,
-      default: "",
-    },
-    navBackButton: {
-      type: Boolean,
-      default: false,
-    },
-    slotSpan: {
-      type: Number,
-      default: 0,
-    },
-    cardStyle: {
-      type: Boolean,
-      default: true,
-    },
-    withMarginBottom: {
-      type: Boolean,
-      default: true,
-    },
-    navAction: {
-      type: Function,
-      default: null,
-    },
-  },
-};
-</script>
-<style scoped>
-.evaluation-margin {
-  margin-bottom: 15px;
-}
-
-.evaluation-title-row {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-
-  display: flex;
-  justify-content: flex-start;
-  align-items: baseline;
-}
-
-.evaulation-title-subItem {
-  color: #999;
-  font-size: 12px;
-  margin-left: 10px;
-}
-
-/deep/ .el-card__body {
-  padding: 15px;
-}
-</style>
+<template>
+  <el-card :class="{ 'box-card': cardStyle, 'evaluation-margin': withMarginBottom }">
+    <el-row :gutter="24">
+      <el-col :span="navBackButton ? 22 - slotSpan : 24 - slotSpan">
+        <div class="evaluation-title-row">
+          {{ title }}
+          <span class="evaulation-title-subItem">{{ subTitle }}</span>
+        </div>
+      </el-col>
+      <el-col v-if="slotSpan" :span="slotSpan">
+        <solt></solt>
+      </el-col>
+      <el-col v-if="navBackButton" :span="2" style="text-align: right">
+        <el-button @click="navAction ? navAction() : $router.go(-1)" size="small" round>返回</el-button>
+      </el-col>
+    </el-row>
+  </el-card>
+</template>
+<script>
+export default {
+  props: {
+    title: {
+      type: String,
+      default: "",
+    },
+    subTitle: {
+      type: String,
+      default: "",
+    },
+    navBackButton: {
+      type: Boolean,
+      default: false,
+    },
+    slotSpan: {
+      type: Number,
+      default: 0,
+    },
+    cardStyle: {
+      type: Boolean,
+      default: true,
+    },
+    withMarginBottom: {
+      type: Boolean,
+      default: true,
+    },
+    navAction: {
+      type: Function,
+      default: null,
+    },
+  },
+};
+</script>
+<style scoped>
+.evaluation-margin {
+  margin-bottom: 15px;
+}
+
+.evaluation-title-row {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+
+  display: flex;
+  justify-content: flex-start;
+  align-items: baseline;
+}
+
+.evaulation-title-subItem {
+  color: #999;
+  font-size: 12px;
+  margin-left: 10px;
+}
+
+/deep/ .el-card__body {
+  padding: 15px;
+}
+</style>

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

@@ -104,8 +104,9 @@ export default {
       }
     },
     scrollToRight() {
+      // use `setTimeout` instead of `$nextTick`
+      // - because css style not ready in next-tick, flow maybe work well in `nextTick.nextTick`
       setTimeout(() => {
-        console.log('will set table ', this.$refs.table.bodyWrapper.scrollLeft, this.$refs.table.bodyWidth)
         this.$refs.table.bodyWrapper.scrollLeft = Number(this.$refs.table.bodyWidth.replace('px', ''))
       }, 500)
     }

+ 9 - 1
src/router/index.js

@@ -763,8 +763,16 @@ export const constantRoutes = [{
         meta: {
           title: '选科测评报告'
         }
+      },
+      {
+        path: '/elective/generation/detail',
+        component: (resolve) => require(['@/views/elective/generation/detail'], resolve),
+        name: 'ElectiveGenerationDetail',
+        meta: {
+          title: '选科名单详情'
+        }
       }
-      ]
+    ]
   }
 ]
 

+ 0 - 13
src/views/elective/generation/components/elective-generation-bar-chart.vue

@@ -1,13 +0,0 @@
-<template>
-
-</template>
-
-<script>
-export default {
-  name: 'elective-generation-bar-chart'
-}
-</script>
-
-<style scoped>
-
-</style>

+ 167 - 0
src/views/elective/generation/components/elective-generation-charts.vue

@@ -0,0 +1,167 @@
+<template>
+  <div class="fx-row fx-bet-cen">
+    <div class="fx-1">
+      <mx-chart :options="chartOptions" :height="'450px'"></mx-chart>
+    </div>
+    <div>
+      <mx-chart></mx-chart>
+    </div>
+  </div>
+</template>
+
+<script>
+import MxChart from '@/components/MxChart/index'
+import config from '@/common/mx-config'
+
+export default {
+  name: 'elective-generation-charts',
+  components: { MxChart },
+  props: ['chartBinding'],
+  computed: {
+    chartOptions() {
+      const options = this.chartBinding.generation.options
+      let data = this.chartBinding.chartData?.accumulates
+      let generation = this.chartBinding.chartData?.generation
+      let desc = '录取超缺数量'
+      if (this.chartBinding.generation.active == options.primary.value) {
+        generation = options.primary.value // force override primary chart data
+        data = this.chartBinding.generation.summary.find(item => item.generation == generation)?.categories
+        desc = '报名超缺数量'
+      }
+      if (!data?.length) return {}
+      const currentOpt = Object.values(options).find(opt => opt.value == generation)
+
+      // xAxis data - round groups
+      const roundGroups = this.chartBinding.generation.roundGroups
+      const xAxis = roundGroups.map(rg => rg.groupName)
+
+      // yAxis data
+      let series = []
+      if (generation == options.primary.value) {
+        // 初选报名需要单独处理
+        const multiplePreference = data.length > 1
+        const preferenceSeries = data.map((single, idx) => {
+          const prefix = multiplePreference ? `第${idx + 1}志愿/` : ''
+          const stackName = 'preference_' + idx
+          return this.barSeriesMissingAndOverFactory(
+            roundGroups,
+            rg => rg.expectedCount * 1,
+            rg => (single.find(item => item.category == 'actualCount')?.values
+              .find(item => item.groupId == rg.groupId)?.value || 0) * 1,
+            stackName,
+            [prefix + '报名人数', , prefix + '缺少人数', prefix + '超出人数']
+          )
+        })
+        series = preferenceSeries.reduce((prev, cur) => prev.concat(cur), [])
+      } else {
+        // 本来是创建3条线:基础线,缺少线,超出线,但图表的提示展示不方便
+        // 所以这里创建4条线:期望线,实际线,缺少线,超出线。(期望线,实际线保持相同颜色)
+        series = this.barSeriesMissingAndOverFactory(
+          roundGroups,
+          rg => rg.expectedCount * 1,
+          rg => {
+            const approvedCount = data.find(item => item.category == 'approvedCount')?.values
+              .find(item => item.groupId == rg.groupId)?.value || 0
+            const forcedCount = data.find(item => item.category == 'forcedCount')?.values
+              .find(item => item.groupId == rg.groupId)?.value || 0
+            const enrollCount = approvedCount * 1 + forcedCount * 1
+            return enrollCount
+          },
+          'Indicator',
+          []
+        )
+      }
+      return this.barOptionFactory('组合统计/' + currentOpt.title, desc, xAxis, series)
+    }
+  },
+  methods: {
+    barOptionFactory(title, subTitle, xAxis, series) {
+      return {
+        title: {
+          text: title,
+          subtext: subTitle,
+          left: 'center'
+        },
+        xAxis: {
+          type: 'category',
+          data: xAxis
+        },
+        yAxis: {
+          type: 'value'
+        },
+        series: series,
+        tooltip: {
+          trigger: 'item'
+        }
+      }
+    },
+    barSeriesMissingAndOverFactory(sourceArr, baseGetter, actualGetter, stackName, lineNames) {
+      // 本来是创建3条线:基础线,缺少线,超出线,但图表的提示展示不方便
+      // 所以这里创建4条线:期望线,实际线,缺少线,超出线。(期望线,实际线保持相同颜色)
+      const series = []
+      const commonDefines = {
+        type: 'bar',
+        stack: stackName,
+        emphasis: {
+          focus: 'series',
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+      const actualLine = {
+        ...commonDefines,
+        name: lineNames[0] || '录取人数',
+        data: []
+      }
+      const expectedLine = {
+        ...commonDefines,
+        name: lineNames[1] || '设置人数',
+        data: []
+      }
+      const missingLine = {
+        ...commonDefines,
+        name: lineNames[2] || '缺少人数',
+        data: []
+      }
+      const overLine = {
+        ...commonDefines,
+        name: lineNames[3] || '超出人数',
+        data: []
+      }
+      sourceArr.forEach(item => {
+        const settingCount = baseGetter(item)
+        const enrollCount = actualGetter(item)
+        if (settingCount <= 0 || settingCount == enrollCount) {
+          expectedLine.data.push(0)
+          actualLine.data.push({ value: enrollCount, itemStyle: { color: config.color.primary_report } })
+          missingLine.data.push(0)
+          overLine.data.push(0)
+        } else {
+          const subCount = Math.abs(settingCount - enrollCount)
+          const baseCount = Math.min(settingCount, enrollCount)
+          if (enrollCount > settingCount) {
+            expectedLine.data.push({ value: baseCount, itemStyle: { color: config.color.primary_report } })
+            actualLine.data.push(0)
+            missingLine.data.push(0)
+            overLine.data.push({ value: subCount, itemStyle: { color: config.color.error } })
+          } else {
+            expectedLine.data.push(0)
+            actualLine.data.push({ value: baseCount, itemStyle: { color: config.color.primary_report } })
+            missingLine.data.push({ value: subCount, itemStyle: { color: config.color.yellow_report } })
+            overLine.data.push(0)
+          }
+        }
+      })
+      series.push(actualLine, expectedLine, missingLine, overLine)
+      return series
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 30 - 6
src/views/elective/generation/components/elective-generation-master.vue

@@ -2,11 +2,25 @@
   <div class="fx-column">
     <evaluation-empty v-if="isUnPassedStep" :shadow="false" :title="emptyTitle"></evaluation-empty>
     <template v-else>
-      <slot :name="activeKey+'-header'" v-bind="chartBinding"></slot>
+      <div class="fx-row fx-bet-cen mb15">
+        <div>
+          <slot name="header-prefix"></slot>
+        </div>
+        <div class="fx-1">
+          <slot :name="activeKey+'-header'" v-bind="chartBinding"></slot>
+        </div>
+        <div>
+          <slot name="header-suffix"></slot>
+        </div>
+      </div>
       <slot :name="activeKey" v-bind="chartBinding">
         <elective-generation-table :chart-binding="chartBinding"></elective-generation-table>
       </slot>
-      <slot :name="activeKey+'-footer'" v-bind="chartBinding"></slot>
+      <slot name="footer-prefix"></slot>
+      <slot :name="activeKey+'-footer'" v-bind="chartBinding">
+        <elective-generation-bar-chart :chart-binding="chartBinding" class="mt40"></elective-generation-bar-chart>
+      </slot>
+      <slot name="footer-suffix"></slot>
     </template>
   </div>
 </template>
@@ -14,10 +28,11 @@
 <script>
 
 import ElectiveGenerationTable from '@/views/elective/generation/components/elective-generation-table'
+import ElectiveGenerationBarChart from '@/views/elective/generation/components/elective-generation-charts'
 
 export default {
   name: 'elective-generation-master',
-  components: { ElectiveGenerationTable },
+  components: { ElectiveGenerationBarChart, ElectiveGenerationTable },
   props: {
     generation: {
       type: Object
@@ -45,9 +60,18 @@ export default {
     },
     accumulates() {
       // summary - accumulate for display in charts
-      const generationData = this.generation.summary.find(item => item.generation == this.generation.active && item.accumulates?.length)
-        || this.generation.summary.find(item => item.generation == this.generation.active - 1)
-      return generationData?.accumulates
+      let options = this.generation.options
+      let generation = this.generation.active
+      let generationData = this.generation.summary.find(item => item.generation == generation && item.accumulates?.length)
+      if (!generationData) {
+        generation = generation - 1
+        generationData = this.generation.summary.find(item => item.generation == generation)
+      }
+
+      return generationData?.accumulates?.length ? {
+        generation: generation, // 图表激活的进程代,除初选报名外,一般为决策代数据
+        accumulates: generationData.accumulates
+      } : null
     },
     chartBinding() {
       return {

+ 0 - 13
src/views/elective/generation/components/elective-generation-pie-chart.vue

@@ -1,13 +0,0 @@
-<template>
-
-</template>
-
-<script>
-export default {
-  name: 'elective-generation-pie-chart'
-}
-</script>
-
-<style scoped>
-
-</style>

+ 48 - 17
src/views/elective/generation/components/elective-generation-table.vue

@@ -1,29 +1,33 @@
 <template>
   <mx-table ref="table" :prop-defines="resolvedTable.columns" :rows="resolvedTable.rows" border>
-    <template #elective-cell="{value}">
-      {{ value && value.value }}
+    <template #elective-cell="{value, label}">
+      <el-popover trigger="hover" placement="right" :disabled="value&&value.disabled"
+                  popper-class="zero-padding-popover">
+        <div class="fx-column">
+          <el-button plain type="text" @click="goDetails(value, label)">查看名单</el-button>
+        </div>
+        <div slot="reference" :style="getCellStyles(value)">
+          <span v-if="value.star">*</span>
+          <span>{{ value && value.value }}</span>
+        </div>
+      </el-popover>
     </template>
   </mx-table>
 </template>
 
 <script>
-import MxGroupTranslateMixin from '@/components/Cache/modules/mx-select-translate-mixin'
+import config from '@/common/mx-config'
+import MxTransferMixin from '@/components/mx-transfer-mixin'
 
 export default {
-  mixins: [MxGroupTranslateMixin],
+  mixins: [MxTransferMixin],
   name: 'elective-generation-table',
   props: ['chartBinding'],
   computed: {
     resolvedTable() {
-      console.log('resolvedTable called', this.listGroupsOptions, this.chartBinding)
-      if (!this.listGroupsOptions.length) return {}
       // setting data
       const columns = { groupName: { label: '组合', fixed: true }, expectedCount: { label: '设置人数', fixed: true } }
-      const rows = this.chartBinding.generation.status.roundGroups.map(rg => ({
-        groupId: rg.groupId,
-        groupName: this.translateGroup(rg.groupId),
-        expectedCount: rg.personCount
-      }))
+      const rows = this.deepClone(this.chartBinding.generation.roundGroups) // TODO: need clone?
       // generation data
       const mergedColumns = []
       this.chartBinding.tableData.forEach(item => {
@@ -53,16 +57,16 @@ export default {
       })
       // accumulate data
       if (this.chartBinding.generation.active > this.chartBinding.generation.options.primaryDM.value
-        && this.chartBinding.chartData?.length) {
+        && this.chartBinding.chartData?.accumulates?.length) {
         const prefix = 'accumulate_'
         const ext = {
           roundId: this.chartBinding.generation.status.roundId,
-          generation: this.chartBinding.generation.current
+          generation: -1 // for detail page special display
         }
-        this.chartBinding.chartData.forEach(acc => this.resolveTableGeneration(ext, acc, columns, rows, mergedColumns, prefix, true))
+        this.chartBinding.chartData.accumulates.forEach(acc => this.resolveTableGeneration(ext, acc, columns, rows, mergedColumns, prefix, true))
       }
       // completed
-      this.$refs.table.scrollToRight()
+      this.$refs.table?.scrollToRight()
       return {
         mergedColumns,
         columns,
@@ -91,7 +95,7 @@ export default {
         data.values.forEach(val => {
           const row = rowsRef.find(row => row.groupId == val.groupId)
           if (row) {
-            row[prop] = val
+            row[prop] = { ...ext, ...val }
           }
         })
       } else {
@@ -101,13 +105,40 @@ export default {
         // keep value in display row
         const val = data.values.first()
         const row = rowsRef.first()
-        row[prop] = val
+        row[prop] = { ...ext, ...val }
       }
     },
     mergeTable({ row, column, rowIndex }) {
       if (this.resolvedTable.mergedColumns.includes(column.property)) {
         return rowIndex === 0 ? [this.resolvedTable.rows.length, 1] : [0, 0]
       }
+    },
+    getCellStyles(option) {
+      const styles = {}
+      if (option.color) {
+        const map = {
+          R: config.color.error,
+          r: config.color.error,
+          G: config.color.yellow_up,
+          g: config.color.yellow_up,
+          B: config.color.blue_up,
+          b: config.color.blue_up
+        }
+        styles.color = map[option.color] || option.color
+      }
+      return styles
+    },
+    goDetails(option, label) {
+      const path = '/elective/generation/detail'
+      const nextData = {
+        year: this.chartBinding.generation.status.year,
+        roundId: this.chartBinding.generation.status.roundId,
+        roundName: this.chartBinding.generation.status.roundName,
+        queryLabel: label,
+        queryGeneration: option.generation,
+        queryCode: option.queryCode
+      }
+      this.transferTo(path, nextData)
     }
   }
 }

+ 36 - 0
src/views/elective/generation/detail.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="app-container">
+    <evaluation-title :title="title" :sub-title="subTitle" nav-back-button></evaluation-title>
+    查询列表-未实现
+  </div>
+</template>
+
+<script>
+import config from '@/common/mx-config'
+import transferMixin from '@/components/mx-transfer-mixin'
+import { mapGetters } from 'vuex'
+
+export default {
+  mixins: [transferMixin],
+  name: 'generation-detail',
+  computed: {
+    ...mapGetters(['school']),
+    title() {
+      const y = (this.prevData.year + '').tailingFix('学年')
+      const s = this.school.schoolName
+      const n = this.prevData.roundName
+      return y + s + n
+    }, subTitle() {
+      const g = Object.values(config.electiveGenerationOptions).find(opt => opt.value == this.prevData.queryGeneration)
+      const hideGeneration = g == config.electiveGenerationOptions.init || g == config.electiveGenerationOptions.terminate
+      let gName = hideGeneration ? '' : g?.title || ''
+      gName = gName && gName + '/'
+      return gName + this.prevData.queryLabel
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 26 - 9
src/views/elective/generation/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="app-container">
+  <div class="app-container" v-loading="loading">
     <el-card>
       <mx-condition :query-params="queryParams" :require-fields="requireFields"
                     @query="handleQuery" @invalid="handleInvalid"></mx-condition>
@@ -8,26 +8,32 @@
       <template #header>
         <elective-generation-steps v-model="activeStep" :generation="generation"></elective-generation-steps>
       </template>
-      <elective-generation-master :generation="generation"></elective-generation-master>
+      <elective-generation-master :generation="generation">
+        <template #header-prefix>
+          <el-button circle icon="el-icon-refresh" @click="handleQuery"></el-button>
+        </template>
+      </elective-generation-master>
     </el-card>
     <evaluation-empty v-else class="mt20"></evaluation-empty>
   </div>
 </template>
 
 <script>
-import Vue from 'vue'
 import config from '@/common/mx-config'
 import { getElectiveStatus, getElectiveSummary } from '@/api/webApi/elective/generation'
 import MxCondition from '@/components/MxCondition/mx-condition'
 import ElectiveGenerationSteps from '@/views/elective/generation/components/elective-generation-steps'
 import ElectiveGenerationMaster from '@/views/elective/generation/components/elective-generation-master'
+import MxGroupTranslateMixin from '@/components/Cache/modules/mx-select-translate-mixin'
 
 export default {
+  mixins: [MxGroupTranslateMixin],
   name: 'generation-index',
   components: { ElectiveGenerationMaster, ElectiveGenerationSteps, MxCondition },
   data() {
     return {
       // query
+      loading: false,
       queryParams: {
         year: '',
         roundId: ''
@@ -58,6 +64,13 @@ export default {
     active() {
       return this.activeOpt?.value
     },
+    roundGroups() {
+      return this.electiveStatus?.roundGroups.map(rg => ({
+        groupId: rg.groupId,
+        groupName: this.translateGroup(rg.groupId),
+        expectedCount: rg.personCount
+      }))
+    },
     generation() {
       return {
         // generation key value
@@ -69,6 +82,7 @@ export default {
         /// root data inject
         summary: this.electiveSummary,
         status: this.electiveStatus,
+        roundGroups: this.roundGroups,
         /// local display setting
         hiddenGenerations: this.tableHiddenGenerations
       }
@@ -81,12 +95,15 @@ export default {
       this.electiveSummary = []
     },
     async handleQuery() {
-      const resStatus = await getElectiveStatus(this.queryParams)
-      this.electiveStatus = resStatus.data
-      const resSummary = await getElectiveSummary(this.queryParams)
-      this.electiveSummary = resSummary.data
-      window.electiveVue = this
-      console.log('query completed')
+      this.loading = true
+      try {
+        const resStatus = await getElectiveStatus(this.queryParams)
+        this.electiveStatus = resStatus.data
+        const resSummary = await getElectiveSummary(this.queryParams)
+        this.electiveSummary = resSummary.data
+      } finally {
+        this.loading = false
+      }
     }
   }
 }