lrf402788946 5 years ago
parent
commit
47a3960054

+ 2 - 0
src/store/index.js

@@ -10,6 +10,7 @@ import task from '@frame/store/task';
 import subject from '@frame/store/subject';
 import question from '@frame/store/question';
 import questionnaire from '@frame/store/questionnaire';
+import questionanswer from '@frame/store/question-answer';
 import teacher from '@frame/store/teacher';
 import trainplan from '@frame/store/trainplan';
 import duty from '@frame/store/duty';
@@ -53,6 +54,7 @@ export default new Vuex.Store({
     subject,
     question,
     questionnaire,
+    questionanswer,
     teacher,
     trainplan,
     duty,

+ 123 - 19
src/views/statistics/detail.vue

@@ -1,12 +1,34 @@
 <template>
   <div id="detail">
     <detail-frame :title="pageTitle" returns="./index">
-      <el-card>
-        <el-row type="flex" align="middle" justify="center" style="text-align:center">
-          <el-col :span="16">
-            <charts :data="gender" :axis="axis"></charts>
-          </el-col>
-        </el-row>
+      <el-card :header="quest.name">
+        <el-tabs v-model="tabs" :stretch="true" type="card">
+          <el-tab-pane label="图表分析" name="chart">
+            <el-row
+              v-for="(quest, qi) in quest.question"
+              :key="qi"
+              type="flex"
+              align="middle"
+              justify="center"
+              style="text-align:center;border-bottom: 1px solid #EBEEF5;"
+            >
+              <el-col :span="24">
+                <charts :data="quest"></charts>
+              </el-col>
+            </el-row>
+          </el-tab-pane>
+          <el-tab-pane label="具体数据" name="data">
+            <el-collapse v-model="collapse" accordion>
+              <el-collapse-item v-for="(data, qi) in cdata" :key="qi" :title="data.studentname" :name="`${qi}`">
+                <el-row type="flex" align="middle" justify="center" style="border-bottom: 1px solid #EBEEF5;">
+                  <el-col :span="24">
+                    <reports :report="data"></reports>
+                  </el-col>
+                </el-row>
+              </el-collapse-item>
+            </el-collapse>
+          </el-tab-pane>
+        </el-tabs>
       </el-card>
     </detail-frame>
   </div>
@@ -15,29 +37,108 @@
 <script>
 import _ from 'lodash';
 import charts from './g2/quest-chart.vue';
+import reports from './g2/report.vue';
 import detailFrame from '@frame/layout/admin/detail-frame';
 import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: questionnaire } = createNamespacedHelpers('questionnaire');
+const { mapActions: questionanswer } = createNamespacedHelpers('questionanswer');
+
 export default {
   name: 'detail',
   props: {},
-  components: { detailFrame, charts },
+  components: { detailFrame, charts, reports },
   data: function() {
     return {
       mylb: null,
-      axis: {
-        x: 'answer',
-        xAlias: '答案',
-        y: 'value',
-        yAlias: '数量',
-      },
-      gender: [
-        { answer: '男', value: 2 },
-        { answer: '女', value: 1 },
-      ],
+      tabs: 'chart',
+      collapse: '',
+      quest: {},
+      cdata: [], //具体数据
     };
   },
-  created() {},
-  methods: {},
+  created() {
+    this.search();
+  },
+  methods: {
+    ...questionnaire(['fetch']),
+    ...questionanswer(['query']),
+    async search() {
+      let res = await this.fetch(this.querys.questionnaireid);
+      if (!this.$checkRes(res)) return;
+      let ansres = await this.query(this.querys); //{ questionnaireid: this.id, termid: this.termid, batchid: this.batchid, classid: this.classid }
+      if (this.$checkRes(ansres)) {
+        if (_.get(ansres.data, 'length', 0) <= 0) return;
+        let data = _.cloneDeep(_.get(res, 'data'));
+        let quests = _.cloneDeep(_.get(res.data, `question`, []));
+        let allAnswer = _.cloneDeep(ansres.data);
+        let naa = _.flatten(allAnswer.map(i => i.answers)).map(i => {
+          i.answer = JSON.parse(i.answer);
+          return i;
+        });
+        //统计
+        let nq = this.getStatistics(naa, quests);
+        let sta = _.cloneDeep(data);
+        sta.question = nq;
+        this.$set(this, `quest`, sta);
+        //具体数据
+        let concreteData = this.getConcreteData(quests, allAnswer);
+        this.$set(this, `cdata`, concreteData);
+      }
+    },
+    //获取图表分析部分
+    getStatistics(answer, quests) {
+      let allAnswer = _.cloneDeep(answer);
+      let questList = _.cloneDeep(quests);
+      let newquest = questList.map(i => {
+        //单选题
+        if (i.type == 0) i = this.dealRadio(i, allAnswer);
+
+        //多选题
+        if (i.type == 1) i = this.dealCheckBox(i, allAnswer);
+        //简答,应该不需要了
+
+        return i;
+      });
+      return newquest;
+    },
+    dealRadio(quest, answers) {
+      let rl = answers.filter(f => f.questionid == quest._id);
+      let ga = _.groupBy(rl, 'answer');
+      quest.option.map(i => {
+        let r = _.get(ga[i.opname], 'length', 0);
+        i.value = r;
+        return i;
+      });
+      return quest;
+    },
+    dealCheckBox(quest, answers) {
+      let rl = answers.filter(f => f.questionid == quest._id);
+      let mid = _.flatten(rl.map(i => i.answer));
+      quest.option.map(i => {
+        let r = mid.filter(f => f == i.opname);
+        i.value = r.length;
+        return i;
+      });
+      return quest;
+    },
+    //具体数据部分
+    getConcreteData(quests, answers) {
+      let dquests = _.cloneDeep(quests);
+      let danswers = _.cloneDeep(answers);
+      danswers.map(i => {
+        i.answers = i.answers.map(answer => {
+          let r = quests.find(f => f._id == answer.questionid);
+          if (r) {
+            answer.topic = r.topic;
+            answer.type = r.type;
+          }
+          return answer;
+        });
+        return i;
+      });
+      return danswers;
+    },
+  },
   computed: {
     ...mapState(['user']),
     pageTitle() {
@@ -46,6 +147,9 @@ export default {
     id() {
       return this.$route.query.id;
     },
+    querys() {
+      return this.$route.query;
+    },
   },
   metaInfo() {
     return { title: this.$route.meta.title };

+ 1 - 1
src/views/statistics/g2/bar.vue

@@ -13,11 +13,11 @@ export default {
   props: {
     data: { type: [Object, Array], default: () => [] },
     axis: { type: Object, default: () => {} },
+    gid: { type: String, default: `${new Date().getTime()}` },
   },
   components: {},
   data: function() {
     return {
-      gid: `${new Date().getTime()}`,
       chart: null,
       list: [],
     };

+ 1 - 1
src/views/statistics/g2/column.vue

@@ -12,11 +12,11 @@ export default {
   props: {
     data: { type: [Object, Array], default: () => [] },
     axis: { type: Object, default: () => {} },
+    gid: { type: String, default: `${new Date().getTime()}` },
   },
   components: {},
   data: function() {
     return {
-      gid: `${new Date().getTime()}`,
       chart: null,
       list: [],
     };

+ 2 - 2
src/views/statistics/g2/donut.vue

@@ -12,11 +12,11 @@ export default {
   props: {
     data: { type: [Object, Array], default: () => [] },
     axis: { type: Object, default: () => {} },
+    gid: { type: String, default: `${new Date().getTime()}` },
   },
   components: {},
   data: function() {
     return {
-      gid: `${new Date().getTime()}`,
       chart: null,
       list: [],
     };
@@ -42,7 +42,7 @@ export default {
         },
         label: {
           visible: true,
-          type: 'outer-center',
+          type: 'spider',
           formatter: (text, item) => `${item._origin[x]}: ${item._origin[y]}`,
         },
         //stackField + legend 一起使用,前者确定以哪个字段作为选项,后者是显示与否,位置

+ 2 - 2
src/views/statistics/g2/pie.vue

@@ -12,11 +12,11 @@ export default {
   props: {
     data: { type: [Object, Array], default: () => [] },
     axis: { type: Object, default: () => {} },
+    gid: { type: String, default: `${new Date().getTime()}` },
   },
   components: {},
   data: function() {
     return {
-      gid: `${new Date().getTime()}`,
       chart: null,
       list: [],
     };
@@ -42,7 +42,7 @@ export default {
         },
         label: {
           visible: true,
-          type: 'outer-center',
+          type: 'spider',
           formatter: (text, item) => `${item._origin[x]}: ${item._origin[y]}`,
         },
         //stackField + legend 一起使用,前者确定以哪个字段作为选项,后者是显示与否,位置

+ 0 - 53
src/views/statistics/g2/progress.vue

@@ -1,53 +0,0 @@
-<template>
-  <div id="progr">
-    <div :id="`progr${ind}`"></div>
-  </div>
-</template>
-
-<script>
-import _ from 'lodash';
-import { Progress } from '@antv/g2plot';
-import { mapState, createNamespacedHelpers } from 'vuex';
-export default {
-  name: 'progr',
-  props: {
-    ind: { type: [String, Number] },
-    data: { type: Number, require: true },
-  },
-  components: {},
-  data: function() {
-    return {};
-  },
-  created() {},
-  mounted() {
-    this.init();
-  },
-  methods: {
-    init() {
-      const pro = new Progress(`progr${this.ind}`, {
-        width: 100,
-        height: 40,
-        percent: this.data,
-        color: e => this.getColor(e),
-      });
-      pro.render();
-    },
-    getColor(percent) {
-      if (percent >= 0.7) return 'red';
-      if (_.inRange(percent, 0.3, 0.7)) return 'blue';
-      if (percent <= 0.3) return 'gray';
-    },
-  },
-  computed: {
-    ...mapState(['user']),
-    pageTitle() {
-      return `${this.$route.meta.title}`;
-    },
-  },
-  metaInfo() {
-    return { title: this.$route.meta.title };
-  },
-};
-</script>
-
-<style lang="less" scoped></style>

+ 71 - 57
src/views/statistics/g2/quest-chart.vue

@@ -1,57 +1,47 @@
 <template>
   <div id="quest-chart">
-    <el-row type="flex" align="middle" justify="center" class="btn_bar">
-      <el-col :span="10">
-        第一题
-      </el-col>
-    </el-row>
-    <el-row type="flex" align="middle" justify="center" v-if="btnModel.table">
-      <el-col :span="16">
-        <el-table :data="data" size="mini" border stripe>
-          <el-table-column align="center" label="选项" prop="answer"></el-table-column>
-          <el-table-column align="center" label="小计" prop="value"></el-table-column>
-          <el-table-column align="center" label="比例">
-            <template v-slot="{ row, $index }">
-              <el-row type="flex" align="middle" justify="center">
-                <el-col :span="18">
-                  <g2progress :ind="$index" :data="getPercent(row.value)"></g2progress>
-                </el-col>
-                <el-col :span="6">
-                  {{ `${getPercent(row.value) * 100}%` }}
-                </el-col>
-              </el-row>
-            </template>
-          </el-table-column>
-        </el-table>
-      </el-col>
-    </el-row>
-    <el-row type="flex" align="middle" justify="center" class="btn_bar" :gutter="10">
-      <el-col :span="3" v-for="(i, index) in btnList" :key="index">
-        <el-button :type="btnModel[i.model] ? 'primary' : ''" size="mini" @click="changeModel(i.model)">
-          {{ i.text }}
-        </el-button>
-      </el-col>
-    </el-row>
-    <el-row type="flex" align="middle" justify="center" v-if="btnModel.column">
-      <el-col :span="10">
-        <g2column :data="data" :axis="axis"></g2column>
-      </el-col>
-    </el-row>
-    <el-row type="flex" align="middle" justify="center" v-if="btnModel.pie">
-      <el-col :span="10">
-        <g2pie :data="data" :axis="axis"></g2pie>
-      </el-col>
-    </el-row>
-    <el-row type="flex" align="middle" justify="center" v-if="btnModel.donut">
-      <el-col :span="10">
-        <g2donut :data="data" :axis="axis"></g2donut>
-      </el-col>
-    </el-row>
-    <el-row type="flex" align="middle" justify="center" v-if="btnModel.bar">
-      <el-col :span="10">
-        <g2bar :data="data" :axis="axis"></g2bar>
-      </el-col>
+    <el-row type="flex" align="middle" justify="start" class="btn_bar">
+      <el-col :span="10"> {{ data.topic }} [{{ data.type | topicType }}] </el-col>
     </el-row>
+    <template v-if="data.type != 2">
+      <el-row type="flex" align="middle" justify="center" v-if="btnModel.table">
+        <el-col :span="16">
+          <el-table :data="data.option" size="mini" border stripe>
+            <el-table-column align="center" label="选项" prop="opname"></el-table-column>
+            <el-table-column align="center" label="小计" prop="value"></el-table-column>
+            <el-table-column align="center" label="比例">
+              <template v-slot="{ row, $index }">
+                <el-progress :text-inside="true" :stroke-width="20" :percentage="getPercent(row.value)" :color="getPS(getPercent(row.value))"></el-progress>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+      </el-row>
+      <el-row type="flex" align="middle" justify="center" class="btn_bar" :gutter="10">
+        <el-col :span="3" v-for="(i, index) in btnList" :key="index">
+          <el-button :type="btnModel[i.model] ? 'primary' : ''" size="mini" @click="changeModel(i.model)">
+            {{ i.text }}
+          </el-button>
+        </el-col>
+      </el-row>
+      <el-row type="flex" align="middle" justify="center">
+        <el-col :span="16" v-if="btnModel.column">
+          <g2column :data="data.option" :axis="axis" :gid="data._id"></g2column>
+        </el-col>
+        <el-col :span="16" v-if="btnModel.pie">
+          <g2pie :data="data.option" :axis="axis" :gid="data._id"></g2pie>
+        </el-col>
+        <el-col :span="16" v-if="btnModel.donut">
+          <g2donut :data="data.option" :axis="axis" :gid="data._id"></g2donut>
+        </el-col>
+        <el-col :span="16" v-if="btnModel.bar">
+          <g2bar :data="data.option" :axis="axis" :gid="data._id"></g2bar>
+        </el-col>
+      </el-row>
+    </template>
+    <template v-else>
+      <div style="margin:10px">见详情</div>
+    </template>
   </div>
 </template>
 
@@ -61,19 +51,23 @@ import g2column from './column.vue';
 import g2pie from './pie.vue';
 import g2donut from './donut.vue';
 import g2bar from './bar.vue';
-import g2progress from './progress.vue';
 import { mapState, createNamespacedHelpers } from 'vuex';
 export default {
   name: 'quest-chart',
   props: {
     data: { type: [Object, Array], default: () => [] },
-    axis: { type: Object, default: () => {} },
   },
-  components: { g2column, g2progress, g2pie, g2donut, g2bar },
+  components: { g2column, g2pie, g2donut, g2bar },
   data: function() {
     return {
+      axis: {
+        x: 'opname',
+        xAlias: '选项',
+        y: 'value',
+        yAlias: '数量',
+      },
       btnModel: {
-        table: false,
+        table: true,
         pie: false,
         dount: false,
         column: false,
@@ -91,15 +85,28 @@ export default {
   created() {},
   methods: {
     changeModel(model) {
+      if (model !== 'table') {
+        let keys = Object.keys(this.btnModel);
+        for (const key of keys) {
+          if (key != 'table' && key != model) {
+            this.btnModel[key] = false;
+          }
+        }
+      }
       this.btnModel[model] = !this.btnModel[model];
       this.$forceUpdate();
     },
     getPercent(num) {
-      let all = this.data.reduce((prev, next) => prev + next.value * 1, 0);
+      let all = this.data.option.reduce((prev, next) => prev + next.value * 1, 0);
       let percent = (num * 1) / all;
-      percent = _.round(percent, 4);
+      percent = _.round(percent, 4) * 100;
       return percent;
     },
+    getPS(percent) {
+      if (percent >= 70) return 'red';
+      if (_.inRange(percent, 30, 70)) return 'blue';
+      if (percent <= 30) return 'gray';
+    },
   },
   computed: {
     ...mapState(['user']),
@@ -107,6 +114,13 @@ export default {
       return `${this.$route.meta.title}`;
     },
   },
+  filters: {
+    topicType(val) {
+      if (val == 0) return '单选题';
+      if (val == 1) return '多选题';
+      if (val == 2) return '简答题';
+    },
+  },
   metaInfo() {
     return { title: this.$route.meta.title };
   },

+ 48 - 0
src/views/statistics/g2/report.vue

@@ -0,0 +1,48 @@
+<template>
+  <div id="report">
+    <template v-for="(answer, index) in report.answers">
+      <div :key="`${index}${new Date().getTime()}`" style="border-bottom: 1px solid #EBEEF5;">
+        <el-row type="flex" align="middle" justify="center" class="btn_bar">
+          <el-col :span="18"> {{ answer.topic }} [{{ answer.type | topicType }}] </el-col>
+        </el-row>
+        <el-row type="flex" align="middle" justify="center" class="btn_bar">
+          <el-col :span="18" v-if="answer.type == 0">{{ answer.answer }}</el-col>
+        </el-row>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'report',
+  props: {
+    report: { type: Object, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  filters: {
+    topicType(val) {
+      if (val == 0) return '单选题';
+      if (val == 1) return '多选题';
+      if (val == 2) return '简答题';
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 17 - 20
src/views/statistics/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div id="index">
-    <list-frame title="问卷列表页" @query="search" :total="total" :filter="filFields" :needAdd="false">
-      <data-table :fields="fields" :data="list" :opera="opera" @test="test" @data="toData"></data-table>
+    <list-frame title="问卷列表页" @query="search" :needPag="false" :filter="filFields" :needAdd="false">
+      <data-table :fields="fields" :data="list" :opera="opera" @data="toData"></data-table>
     </list-frame>
   </div>
 </template>
@@ -12,7 +12,7 @@ import listFrame from '@frame/layout/admin/list-frame';
 import dataTable from '@frame/components/data-table';
 import { mapState, createNamespacedHelpers } from 'vuex';
 const { mapActions: questionnaire } = createNamespacedHelpers('questionnaire');
-const { mapActions: completion } = createNamespacedHelpers('completion');
+const { mapActions: util } = createNamespacedHelpers('util');
 
 export default {
   name: 'index',
@@ -28,14 +28,8 @@ export default {
         icon: 'el-icon-s-data',
         method: 'data',
       },
-      {
-        label: '测试',
-        icon: 'el-icon-document',
-        method: 'test',
-      },
     ],
     fields: [
-      { label: '问卷序号', prop: 'num' },
       { label: '问卷标题', prop: 'name' },
       { label: '问卷类型', prop: 'type', format: i => (i == '0' ? '常用问卷' : i == '1' ? '非常用问题' : '教师问卷') },
     ],
@@ -55,23 +49,26 @@ export default {
     ...mapState(['user', 'defaultOption']),
   },
   methods: {
-    ...completion({ getCompletion: 'query' }),
+    ...util({ modelFetch: 'fetch' }),
     ...questionnaire(['query', 'delete']),
-    async test({ data }) {
-      let planid = _.get(this.options, 'planid');
-      let termid = _.get(this.options, 'termid');
-      let res = await this.getCompletion({ type: '0', typeid: termid, trainplanid: planid, questionnaireid: data._id });
-      console.log(res);
-    },
     async search({ skip = 0, limit = 10, ...info } = {}) {
-      const res = await this.query({ skip, limit, ...info });
+      let termid = _.get(this.defaultOption, 'termid');
+      if (!termid) return;
+      let unusaul = await this.modelFetch({ model: 'termquest', termid });
+      let uqlist = []; //非常用问卷
+      let usual = []; //常用问卷
+      if (this.$checkRes(unusaul)) uqlist = unusaul.data.questionnaireid;
+      let res = await this.query({ ...info });
       if (this.$checkRes(res)) {
-        this.$set(this, `list`, res.data);
-        this.$set(this, `total`, res.total);
+        usual = res.data.filter(i => i.type == 0 && i.status == 1);
+        let nqulist = res.data.filter(i => i.status == 1 && _.includes(uqlist, i._id));
+        let rlist = [...nqulist, ...usual];
+        this.$set(this, `list`, rlist);
       }
     },
     toData({ data }) {
-      this.$router.push({ path: './detail', query: { id: data.id } });
+      let termid = _.get(this.defaultOption, 'termid');
+      this.$router.push({ path: './detail', query: { questionnaireid: data.id, termid } });
     },
     async toDelete({ data }) {
       const res = await this.delete(data.id);

+ 1 - 1
src/views/train-plan/quest.vue

@@ -67,7 +67,7 @@ export default {
   methods: {
     ...util({ modelFetch: 'fetch' }),
     ...trainplan(['fetch']),
-    ...termquest({ getList: 'fetch', createList: 'create', updateList: 'update' }),
+    ...termquest({ createList: 'create', updateList: 'update' }),
     ...questionnaire(['query', 'delete', 'mergeRequest']),
     async search() {
       let termid = _.get(this.defaultOption, 'termid');

+ 38 - 8
src/views/train-plan/remind-detail.vue

@@ -1,36 +1,66 @@
 <template>
   <div id="remind-detail">
-    <p>remind_detail</p>
+    <detail-frame :title="pageTitle" returns="/train/plan/remind">
+      <data-table :fields="fields" :data="list" :opera="opera" :usePage="false"></data-table>
+      <!-- @msg="toSendMsg" @view="toView" -->
+    </detail-frame>
   </div>
 </template>
 
 <script>
+import _ from 'lodash';
+import dataTable from '@frame/components/filter-page-table';
+import detailFrame from '@frame/layout/admin/detail-frame';
 import { mapState, createNamespacedHelpers } from 'vuex';
 const { mapActions: notice } = createNamespacedHelpers('notice');
 export default {
   name: 'remind-detail',
   props: {},
-  components: {},
+  components: { detailFrame, dataTable },
   data: function() {
-    return {};
+    return {
+      list: [],
+      opera: [
+        {
+          label: '发送确认通知',
+          icon: 'el-icon-message-solid',
+          method: 'msg',
+        },
+        {
+          label: '查看通知情况',
+          icon: 'el-icon-view',
+          method: 'view',
+        },
+      ],
+      fields: [
+        { label: '名称', prop: 'name' },
+        { label: '电话', prop: 'phone' },
+        { label: '邮箱', prop: 'email' },
+      ],
+    };
   },
   created() {
     this.search();
   },
   methods: {
-    ...notice({ getNoticeList: 'query' }),
+    ...notice({ getNoticeList: 'query', getClassInfo: 'classList' }),
     async search() {
-      let res = await this.getNoticeList();
-      console.log(res);
+      let res = await this.getClassInfo(this.id);
+      if (this.$checkRes(res)) {
+        let studList = _.get(res.data, `students`, []);
+        let teaList = _.get(res.data, `teachers`, []);
+        let arr = [...studList, ...teaList];
+        this.$set(this, `list`, _.uniqBy(arr, '_id'));
+      }
     },
   },
   computed: {
-    ...mapState(['user']),
+    ...mapState(['user', 'defaultOption']),
     pageTitle() {
       return `${this.$route.meta.title}`;
     },
     id() {
-      return this.$route.query.id();
+      return this.$route.query.id;
     },
   },
   metaInfo() {