zhangjie 5 年 前
コミット
c2d49af1af

+ 70 - 13
src/api.js

@@ -7,6 +7,7 @@ export const login = datas => {
 export const modifyPwd = datas => {
   return $post("/backend/sysuser/resetPwd", datas);
 };
+
 // work-manage
 export const workList = () => {
   return $get("/api/admin/works", {});
@@ -20,10 +21,12 @@ export const activeWork = workId => {
 export const deleteWork = workId => {
   return $del(`/api/admin/works/${workId}`, {});
 };
+
 // work-overview
 export const workOverviewDetail = workId => {
   return $get(`/api/admin/works/${workId}/overview`, {});
 };
+
 // paper-manage
 export const paperPageList = datas => {
   return $get("/api/papers/listByQuestion", datas);
@@ -34,12 +37,29 @@ export const rotatePaper = (imageId, degree) => {
 export const absentPaper = imageId => {
   return $post(`/api/score/missing/${imageId}`, {});
 };
+
 // client-monitor
 export const clientMonitorList = datas => {
   // return $get("/api/papers/listByQuestion", datas);
   return Promise.resolve(datas);
 };
-// client-set
+
+// student-manage
+export const studentPageList = datas => {
+  return $get("/api/students", datas);
+};
+export const uploadStudent = datas => {
+  if (datas.id) {
+    return $put(`/api/students/${datas.id}`, datas);
+  } else {
+    return $post(`/api/students`, datas);
+  }
+};
+export const deleteStudent = studentId => {
+  return $del(`/api/students/${studentId}`, {});
+};
+
+// client-set -------------------------->
 // client-user-set
 export const clientUserPageList = datas => {
   return $get("/api/admin/users/collect", datas);
@@ -68,7 +88,7 @@ export const uploadInspectionUser = datas => {
 export const deleteInspectionUser = userId => {
   return $del(`/api/admin/users/collect/${userId}`, {});
 };
-// client-param-set
+// client-param-set -------------------------->
 // subject-set
 export const subjectList = datas => {
   // return $get("/api/admin/subjects", datas);
@@ -85,17 +105,54 @@ export const deleteSubject = subjectId => {
   return $del(`/api/admin/subject/${subjectId}`, {});
 };
 
-// student-manage
-export const studentPageList = datas => {
-  return $get("/api/students", datas);
+// grading-set -------------------------->
+// grading-level-set
+export const levelList = workId => {
+  return $get(`/api/admin/works/${workId}`, {});
+};
+export const uploadLevel = datas => {
+  return $put(`/api/admin/works/${datas.id}`, datas);
+};
+// grading-rule-set
+export const gradingRuleDetail = workId => {
+  // TODO:
+  return Promise.resolve({});
+  // return $get(`/api/admin/works/${workId}`, {});
+};
+export const saveGradingRule = datas => {
+  // TODO:
+  return $post(`/api/admin/works/${datas.id}`, datas);
+};
+// upload-paper
+// grading-rule-set
+export const uploadPaperDetail = () => {
+  // TODO:
+  return Promise.resolve({});
+  // return $get(`/api/admin/works/${workId}`, {});
 };
-export const uploadStudent = datas => {
-  if (datas.id) {
-    return $put(`/api/students/${datas.id}`, datas);
-  } else {
-    return $post(`/api/students`, datas);
-  }
+
+// mark-set -------------------------->
+// mark-rule-set
+export const markRuleDetail = workId => {
+  // TODO:
+  return Promise.resolve({});
+  // return $get(`/api/admin/works/${workId}`, {});
 };
-export const deleteStudent = studentId => {
-  return $del(`/api/students/${studentId}`, {});
+export const saveMarkRule = datas => {
+  // TODO:
+  return $post(`/api/admin/works/${datas.id}`, datas);
+};
+
+// quality-analysis -------------------------->
+export const qualityAnalysisDetail = datas => {
+  // TODO:
+  return Promise.resolve(datas);
+  // return $get(`/api/admin/works/${workId}`, {});
+};
+
+// student-score -------------------------->
+export const studentScoreList = datas => {
+  // TODO:
+  return Promise.resolve(datas);
+  // return $get(`/api/admin/works/${workId}`, {});
 };

+ 3 - 0
src/assets/styles/base.less

@@ -261,3 +261,6 @@ h3.account-title {
 .tips-error {
   color: @pink;
 }
+.text-center {
+  text-align: center;
+}

+ 1 - 1
src/components/UploadButton.vue

@@ -128,7 +128,7 @@ export default {
       if (response) {
         this.res = {
           success: true,
-          msg: "导入成功!"
+          msg: "上传成功!"
         };
         this.$emit("upload-success", response);
       } else {

+ 381 - 0
src/constants/chartOptions.js

@@ -0,0 +1,381 @@
+function getLineOption(datas, title) {
+  if (!datas.chartLabels.length) return;
+
+  return {
+    grid: {
+      top: "15%",
+      bottom: "18%"
+    },
+    tooltip: { show: true },
+    xAxis: {
+      type: "category",
+      data: datas.chartLabels,
+      axisLabel: {
+        fontSize: 14,
+        fontWeight: "bold"
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    yAxis: {
+      type: "value",
+      splitLine: {
+        show: true,
+        lineStyle: {
+          color: "#e0e0e0"
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: "#333"
+        }
+      },
+      axisLabel: {
+        fontSize: 14
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    series: [
+      {
+        name: "数量",
+        type: "line",
+        smooth: true,
+        data: datas.chartData[0],
+        areaStyle: {
+          color: "#b23f3a",
+          opacity: 0.3
+        }
+      }
+    ]
+  };
+}
+
+function getPieOption(datas, title) {
+  if (!datas.chartLabels.length) return;
+  var seriesData = datas.chartLabels.map(function(item, index) {
+    return {
+      name: item,
+      value: datas.chartData[0][index]
+    };
+  });
+  return {
+    grid: {
+      top: "24%",
+      bottom: "10%"
+    },
+    tooltip: {
+      trigger: "item",
+      formatter: "{a} <br/>{b} : {c} ({d}%)"
+    },
+    legend: {
+      show: true,
+      itemGap: 20,
+      itemWidth: 20,
+      textStyle: {
+        fontSize: 16
+      }
+    },
+    series: [
+      {
+        name: "数量",
+        type: "pie",
+        radius: "70%",
+        data: seriesData,
+        label: {
+          show: false
+        },
+        itemStyle: {
+          emphasis: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: "rgba(0, 0, 0, 0.5)"
+          }
+        }
+      }
+    ]
+  };
+}
+
+function getBarOption(datas, title) {
+  if (!datas.names.length) return;
+  return {
+    title: {
+      text: title || "档位占比分析表",
+      left: 0
+    },
+    grid: {
+      top: "15%",
+      bottom: "18%"
+    },
+    tooltip: {
+      show: true
+    },
+    xAxis: {
+      type: "category",
+      data: datas.names,
+      axisLabel: {
+        fontSize: 14,
+        fontWeight: "bold"
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    yAxis: {
+      type: "value",
+      splitLine: {
+        show: true,
+        lineStyle: {
+          color: "#e0e0e0"
+        }
+      },
+      axisLine: {
+        lineStyle: {
+          color: "#333"
+        }
+      },
+      axisLabel: {
+        fontSize: 14
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    series: [
+      {
+        name: "差值和",
+        type: "bar",
+        barWidth: 30,
+        data: datas.dataList,
+        label: {
+          show: true,
+          position: "top",
+          color: "#333",
+          fontWeight: 600
+        }
+      }
+    ]
+  };
+}
+
+function getBarGroupOption(datas, title) {
+  if (!datas.names.length) return;
+  var onePageMaxBarNum = 20;
+  var barNum = datas.names.length * datas.dataList.length;
+  var xAxis = datas.dataList.map(function(item) {
+    return item.name;
+  });
+  var series = datas.names.map(function(name, index) {
+    var data = datas.dataList.map(function(item) {
+      return item.data[index];
+    });
+    return {
+      name: name,
+      type: "bar",
+      data: data,
+      barMaxWidth: 30,
+      barMinHeight: 2
+      // label: {
+      //   show: true,
+      //   position: "top",
+      //   fontSize: 12,
+      //   color: "#333",
+      //   formatter: function(params) {
+      //     return params.value.toFixed(2) + "%";
+      //   }
+      // }
+    };
+  });
+
+  var options = {
+    title: {
+      text: title || "差值曲线分析表",
+      left: 0
+    },
+    grid: {
+      top: "15%",
+      bottom: "10%"
+    },
+    tooltip: {
+      show: true,
+      trigger: "axis",
+      axisPointer: {
+        type: "shadow"
+      },
+      formatter: function(params) {
+        var label = params[0].axisValueLabel;
+        var infos = params.map(function(item) {
+          return item.seriesName + ":" + item.value.toFixed(2) + "%";
+        });
+        infos.unshift(label);
+        return infos.join("<br/>");
+      }
+    },
+    legend: {
+      data: datas.names,
+      right: 0,
+      itemWidth: 14,
+      textStyle: {
+        fontSize: 16
+      }
+    },
+    xAxis: {
+      type: "category",
+      data: xAxis,
+      axisLabel: {
+        fontSize: 14,
+        fontWeight: "bold"
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    yAxis: {
+      type: "value",
+      splitLine: {
+        show: false
+      },
+      axisLabel: {
+        fontSize: 14,
+        formatter: function(value, index) {
+          return value + "%";
+        }
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    series: series
+  };
+
+  if (barNum > onePageMaxBarNum) {
+    var zoomInitRange = Math.floor((onePageMaxBarNum * 100) / barNum);
+    options.grid.bottom = "16%";
+    options.dataZoom = [
+      {
+        type: "inside",
+        start: 0,
+        end: zoomInitRange
+      },
+      {
+        type: "slider",
+        start: 0,
+        end: zoomInitRange
+      }
+    ];
+  }
+
+  return options;
+}
+
+function getLineGroupOption(datas, title) {
+  if (!datas.length) return;
+  var names = datas.map(function(item) {
+    return item.name;
+  });
+  var xaxis = datas[0].dataList.map(function(item, index) {
+    return index;
+  });
+
+  var series = datas.map(function(item) {
+    return {
+      name: item.name,
+      type: "line",
+      symbol: "circle",
+      smooth: true,
+      itemStyle: {
+        emphasis: {
+          color: "#333"
+        }
+      },
+      data: item.dataList.map(function(num) {
+        return num * num;
+      })
+    };
+  });
+  return {
+    title: {
+      text: title || "差值总和分析表",
+      left: 0
+    },
+    grid: {
+      top: "15%",
+      bottom: "12%"
+    },
+    tooltip: {
+      show: true,
+      trigger: "axis",
+      axisPointer: {
+        type: "shadow"
+      },
+      formatter: function(params) {
+        var label = params[0].axisValueLabel;
+        var infos = params.map(function(item) {
+          return item.seriesName + ":" + Math.sqrt(item.value);
+        });
+        infos.unshift(label);
+        return infos.join("<br/>");
+      }
+    },
+    legend: {
+      data: names,
+      right: 0,
+      itemWidth: 14,
+      textStyle: {
+        fontSize: 16
+      }
+    },
+    xAxis: {
+      type: "category",
+      data: xaxis,
+      axisLabel: {
+        show: false,
+        fontSize: 14
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    yAxis: {
+      type: "value",
+      interval: 1,
+      splitLine: {
+        show: false
+      },
+      axisLabel: {
+        fontSize: 14,
+        formatter: function(value) {
+          var num = Math.sqrt(value);
+          return num % 1 ? "" : parseInt(num);
+        }
+      },
+      axisTick: {
+        show: false
+      }
+    },
+    dataZoom: [
+      {
+        type: "inside",
+        start: 0,
+        end: 30
+      },
+      {
+        type: "slider",
+        start: 0,
+        end: 30
+      }
+    ],
+    series: series
+  };
+}
+
+export default {
+  getLineOption,
+  getPieOption,
+  getBarOption,
+  getBarGroupOption,
+  getLineGroupOption
+};

+ 18 - 0
src/constants/enumerate.js

@@ -32,9 +32,27 @@ export const GENDER_TYPE = {
   FEMALE: "女"
 };
 
+export const BOOLEAN_TYPE = {
+  0: "否",
+  1: "是"
+};
+
 // subject step
 export const SUBJECT_STAGE = {
   INIT: "采集阶段",
   LEVEL: "分档阶段",
   SCORE: "打分阶段"
 };
+
+// grading level
+export const LEVEL_TYPE = {
+  ADMITED: "间隔给分",
+  UNADMIT: "手动给分"
+};
+
+// student score
+export const CODE_TYPE = {
+  0: "考号",
+  1: "试卷密号",
+  2: "任务密号"
+};

+ 1 - 3
src/modules/client-set/ClientAccountSet.vue

@@ -1,9 +1,7 @@
 <template>
   <div class="client-account-set ">
     <div class="part-box-top">
-      <Button type="success" icon="md-cloud-upload" @click="toEdit({})"
-        >新增</Button
-      >
+      <Button type="success" icon="md-add" @click="toEdit({})">新增</Button>
     </div>
     <div class="part-box">
       <Table

+ 217 - 3
src/modules/grading-set/GradingLevelSet.vue

@@ -1,15 +1,229 @@
 <template>
   <div class="grading-level-set">
-    grading-level-set
+    <div class="part-box-top">
+      <Button type="success" icon="md-add" @click="toAdd">新增</Button>
+    </div>
+    <div class="part-box">
+      <table class="table">
+        <tr>
+          <th>档位</th>
+          <th>最低分</th>
+          <th>最高分</th>
+          <th>给分间隔</th>
+          <th>典型值</th>
+          <th>类型</th>
+          <th>给分项</th>
+          <th>考点阀值%</th>
+          <th>占比阀值%</th>
+          <th>操作</th>
+        </tr>
+        <tr v-for="(level, index) in levels" :key="index">
+          <td>
+            <Input
+              v-model="level.code"
+              style="width: 60px"
+              @on-blur="codeChange"
+            ></Input>
+          </td>
+          <td>
+            <InputNumber
+              v-model="level.minScore"
+              :min="0"
+              :max="200"
+            ></InputNumber>
+          </td>
+          <td>
+            <InputNumber
+              v-model="level.maxScore"
+              :min="level.minScore + 1"
+              :max="200"
+            ></InputNumber>
+          </td>
+          <td style="min-width: 100px">
+            <InputNumber
+              v-model="level.intervalScore"
+              :min="1"
+              :max="100"
+              v-if="level.levelType === 'ADMITED'"
+            ></InputNumber>
+          </td>
+          <td>
+            <InputNumber
+              v-model="level.weight"
+              :min="1"
+              :max="100"
+            ></InputNumber>
+          </td>
+          <td>
+            <Select
+              v-model="level.levelType"
+              @on-change="levelTypeChange(level)"
+              style="width: 120px"
+            >
+              <Option
+                v-for="(val, key) in LEVEL_TYPE"
+                :key="key"
+                :value="key"
+                >{{ val }}</Option
+              >
+            </Select>
+          </td>
+          <td style="min-width: 140px">
+            <Input
+              v-model="level.scoreList"
+              v-if="level.levelType === 'UNADMIT'"
+            ></Input>
+          </td>
+          <td>
+            <Input v-model="level.kdpt" :min="1" :max="100"></Input>
+          </td>
+          <td>
+            <Input v-model="level.pt" :min="1" :max="100"></Input>
+          </td>
+          <td>
+            <Button size="small" type="error" @click="toDelete(index)"
+              >删除</Button
+            >
+          </td>
+        </tr>
+      </table>
+
+      <div class="text-center">
+        <Button type="primary" @click="toSubmit" :disabled="isSubmit"
+          >确定</Button
+        >
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { levelList, uploadLevel } from "@/api";
+import { LEVEL_TYPE } from "@/constants/enumerate";
+
+const initLevel = {
+  id: null,
+  workId: null,
+  code: null,
+  levelValue: 0,
+  maxScore: null,
+  minScore: null,
+  intervalScore: null,
+  weight: null,
+  levelType: "ADMITED",
+  scoreList: null,
+  pt: null,
+  kdpt: null
+};
+
 export default {
   name: "grading-level-set",
   data() {
-    return {};
+    return {
+      LEVEL_TYPE,
+      workId: this.$route.params.workId,
+      letterRelateNumber: {},
+      levels: [],
+      worKDetail: {},
+      isSubmit: false
+    };
+  },
+  mounted() {
+    this.initData();
   },
-  methods: {}
+  methods: {
+    initData() {
+      const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+      letters.split("").map((item, index) => {
+        this.letterRelateNumber[item] = index + 1;
+      });
+
+      this.getList();
+    },
+    async getList() {
+      const data = await levelList(this.workId);
+      this.worKDetail = data;
+      this.levels = data.levels;
+    },
+    checkLevelCodeIsContinuous() {
+      let levelIsContinuous = true;
+      for (var i = 0, num = this.levels.length; i < num; i++) {
+        if (i > 0) {
+          const beforeCodeNum = this.letterRelateNumber[
+            this.levels[i - 1].code
+          ];
+          const curCodeNum = this.letterRelateNumber[this.levels[i].code];
+          levelIsContinuous =
+            levelIsContinuous && curCodeNum - beforeCodeNum === 1;
+
+          if (!levelIsContinuous) {
+            return false;
+          }
+        }
+      }
+
+      return true;
+    },
+    levelTypeChange(level) {
+      if (level.levelType === "ADMITED") {
+        level.scoreList = null;
+      } else {
+        level.intervalScore = null;
+      }
+    },
+    codeChange() {
+      this.levels.sort((a, b) => {
+        if (
+          !a.code ||
+          !b.code ||
+          !this.letterRelateNumber[a.code] ||
+          !this.letterRelateNumber[b.code]
+        )
+          return 0;
+
+        return (
+          this.letterRelateNumber[a.code] - this.letterRelateNumber[b.code]
+        );
+      });
+    },
+    getNextLevelCode() {
+      const codeNumbers = this.levels.map(
+        level => this.letterRelateNumber[level.code] || 0
+      );
+      const maxCodeNumber = Math.max.apply(null, codeNumbers);
+      const nextLevel = Object.entries(this.letterRelateNumber).find(
+        ([key, val]) => {
+          return maxCodeNumber < val;
+        }
+      );
+      return nextLevel ? nextLevel[0] : "";
+    },
+    toAdd() {
+      let level = { ...initLevel };
+      level.workId = this.workId;
+      level.code = this.getNextLevelCode();
+      this.levels.push(level);
+    },
+    toDelete(index) {
+      this.levels.splice(index, 1);
+    },
+    async toSubmit() {
+      if (!this.checkLevelCodeIsContinuous()) {
+        this.$Message.error("请保持档位连续!");
+        return;
+      }
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await uploadLevel(this.worKDetail).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$Message.success("保存成功!");
+    }
+  }
 };
 </script>

+ 103 - 4
src/modules/grading-set/GradingRuleSet.vue

@@ -1,15 +1,114 @@
 <template>
-  <div class="grading-rule-set">
-    grading-rule-set
+  <div class="grading-rule-set part-box">
+    <Form
+      ref="modalFormComp"
+      :model="modalForm"
+      :rules="rules"
+      :label-width="120"
+      style="width: 600px;"
+    >
+      <FormItem prop="name" label="仲裁档位差:">
+        <InputNumber
+          v-model.trim="modalForm.arbitrateGradeIntervel"
+          :min="1"
+          :max="100"
+          style="width: 160px;"
+        ></InputNumber>
+      </FormItem>
+      <FormItem prop="name" label="打回累计误差:">
+        <InputNumber
+          v-model.trim="modalForm.refuseTotalCount"
+          :min="1"
+          :max="100"
+          style="width: 160px;"
+        ></InputNumber>
+      </FormItem>
+      <FormItem prop="examNumber" label="系统自动打回:">
+        <RadioGroup v-model="modalForm.autoRefuse">
+          <Radio
+            v-for="(val, key) in BOOLEAN_TYPE"
+            :key="key"
+            :label="val"
+            style="margin-right: 50px;"
+          ></Radio>
+        </RadioGroup>
+      </FormItem>
+      <FormItem prop="examNumber" label="是否过半定档:">
+        <RadioGroup v-model="modalForm.isOverHalfConfirm">
+          <Radio
+            v-for="(val, key) in BOOLEAN_TYPE"
+            :key="key"
+            :label="val"
+            style="margin-right: 50px;"
+          ></Radio>
+        </RadioGroup>
+      </FormItem>
+      <FormItem prop="examNumber" label="阅卷员是否显示所有试卷:">
+        <RadioGroup v-model="modalForm.isShowAllPaper">
+          <Radio
+            v-for="(val, key) in BOOLEAN_TYPE"
+            :key="key"
+            :label="val"
+            style="margin-right: 50px;"
+          ></Radio>
+        </RadioGroup>
+      </FormItem>
+      <FormItem>
+        <Button type="primary" :disabled="isSubmit" @click="submit"
+          >保存</Button
+        >
+      </FormItem>
+    </Form>
   </div>
 </template>
 
 <script>
+import { gradingRuleDetail, saveGradingRule } from "@/api";
+import { BOOLEAN_TYPE } from "@/constants/enumerate";
+
 export default {
   name: "grading-rule-set",
   data() {
-    return {};
+    return {
+      isSubmit: false,
+      workId: this.$route.params.workId,
+      BOOLEAN_TYPE,
+      initModalForm: {
+        arbitrateGradeIntervel: null,
+        refuseTotalCount: null,
+        autoRefuse: 0,
+        isOverHalfConfirm: 0,
+        isShowAllPaper: 0
+      },
+      modalForm: {},
+      rules: {}
+    };
+  },
+  mounted() {
+    this.getData();
   },
-  methods: {}
+  methods: {
+    async getData() {
+      const data = await gradingRuleDetail(this.workId);
+      this.modalForm = Object.assign(data, this.initModalForm);
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await saveGradingRule(this.modalForm).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$Message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
 };
 </script>

+ 22 - 3
src/modules/grading-set/GradingSet.vue

@@ -1,15 +1,34 @@
 <template>
   <div class="grading-set">
-    grading-set
+    <div class="page-navs">
+      <Button
+        :type="curNav.name === nav.name ? 'primary' : 'default'"
+        v-for="(nav, index) in navs"
+        :key="index"
+        @click="switchNav(nav)"
+        >{{ nav.title }}</Button
+      >
+    </div>
+
+    <router-view />
   </div>
 </template>
 
 <script>
+import { gradingSetNavs } from "@/routers/main";
+import menuMixins from "@/components/homeMenuMixins";
+
 export default {
   name: "grading-set",
+  mixins: [menuMixins],
   data() {
-    return {};
+    return {
+      navs: gradingSetNavs,
+      curNav: {}
+    };
   },
-  methods: {}
+  mounted() {
+    this.actCurNav(this.$route);
+  }
 };
 </script>

+ 121 - 4
src/modules/grading-set/UploadPaper.vue

@@ -1,15 +1,132 @@
 <template>
-  <div class="upload-paper">
-    upload-paper
+  <div class="upload-paper part-box">
+    <table class="table">
+      <tr>
+        <th>考区</th>
+        <th v-for="(subject, index) in subjects" :key="index">
+          {{ subject.name }}
+        </th>
+      </tr>
+      <tr v-for="(area, index) in papers" :key="index">
+        <td>{{ area.name }}</td>
+        <td v-for="(subject, index) in area.subjects" :key="index">
+          <upload-button
+            :btn-type="subject.paperUrl ? 'primary' : 'default'"
+            btn-icon="md-cloud-upload"
+            btn-content="上传试卷"
+            :upload-url="subject.uploadUrl"
+            :format="['pdf', 'png', 'jpg']"
+            @upload-success="
+              response => {
+                this.uploadSuccess(response, subject);
+              }
+            "
+          ></upload-button>
+        </td>
+      </tr>
+    </table>
   </div>
 </template>
 
 <script>
+import UploadButton from "@/components/UploadButton";
+import { uploadPaperDetail } from "@/api";
+
 export default {
   name: "upload-paper",
+  components: { UploadButton },
   data() {
-    return {};
+    return {
+      workId: this.$route.params.workId,
+      papers: [
+        {
+          id: "1",
+          name: "考区01",
+          subjects: [
+            {
+              id: "34-SC",
+              name: "色彩",
+              subject: "SC",
+              uploadUrl: "http://sdf",
+              paperUrl: "http"
+            },
+            {
+              id: "34-SM",
+              name: "素描",
+              subject: "SM",
+              uploadUrl: "http://sdf",
+              paperUrl: ""
+            },
+            {
+              id: "34-SX",
+              name: "速写",
+              subject: "SX",
+              uploadUrl: "http://sdf",
+              paperUrl: ""
+            }
+          ]
+        },
+        {
+          id: "2",
+          name: "考区02",
+          subjects: [
+            {
+              id: "34-SC",
+              name: "色彩",
+              subject: "SC",
+              uploadUrl: "http://sdf",
+              paperUrl: ""
+            },
+            {
+              id: "34-SM",
+              name: "素描",
+              subject: "SM",
+              uploadUrl: "http://sdf",
+              paperUrl: ""
+            },
+            {
+              id: "34-SX",
+              name: "速写",
+              subject: "SX",
+              uploadUrl: "http://sdf",
+              paperUrl: ""
+            }
+          ]
+        }
+      ],
+      subjects: [
+        {
+          id: "34-SC",
+          name: "色彩",
+          subject: "SC"
+        },
+        {
+          id: "34-SM",
+          name: "素描",
+          subject: "SM"
+        },
+        {
+          id: "34-SX",
+          name: "速写",
+          subject: "SX"
+        }
+      ]
+    };
   },
-  methods: {}
+  methods: {
+    async getData() {
+      const data = await uploadPaperDetail(this.workId);
+      this.papers = data.map(area => {
+        area.subjects = area.subjects.map(subject => {
+          subject.uploadUrl = ``;
+          return subject;
+        });
+        return area;
+      });
+    },
+    uploadSuccess(response, subject) {
+      subject.paperUrl = response.data.url;
+    }
+  }
 };
 </script>

+ 86 - 3
src/modules/main/QualityAnalysis.vue

@@ -1,15 +1,98 @@
 <template>
   <div class="quality-analysis">
-    quality-analysis
+    <div class="part-box">
+      <Form label-position="left" inline>
+        <FormItem label="科目:">
+          <Select v-model="subjectId" placeholder="请选择科目">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="查询时间段:">
+          <DatePicker
+            v-model="searchTime"
+            type="datetimerange"
+            placeholder="请选择查询时间段"
+            style="width: 400px"
+            transfer
+          ></DatePicker>
+        </FormItem>
+        <FormItem>
+          <Button type="primary" icon="ios-search" @click="toSearch"
+            >查询</Button
+          >
+          <Button type="primary" icon="md-download" @click="toExport"
+            >导出</Button
+          >
+        </FormItem>
+      </Form>
+
+      <div class="analysis-part">
+        <v-chart :options="barOption" v-if="barOption" autoresize></v-chart>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { qualityAnalysisDetail } from "@/api";
+import chartOption from "@/constants/chartOptions";
+
 export default {
   name: "quality-analysis",
   data() {
-    return {};
+    return {
+      subjectId: "",
+      searchTime: [],
+      barOption: {}
+    };
+  },
+  mounted() {
+    this.initData();
   },
-  methods: {}
+  methods: {
+    parseGroupBarData(datas) {
+      if (!datas.length) {
+        return { names: [], dataList: [] };
+      }
+      var names = datas[0].data.map(function(item) {
+        return item.markerName;
+      });
+      var dataList = datas.map(function(item) {
+        return {
+          name: item.name,
+          data: item.data.map(function(elem) {
+            return elem.prop;
+          })
+        };
+      });
+      return {
+        names: names,
+        dataList: dataList
+      };
+    },
+    initData() {
+      const data = [
+        {
+          data: [{ prop: 37.5, markerId: 98, markerName: "pj001" }],
+          name: "A"
+        },
+        { data: [{ prop: 62.5, markerId: 98, markerName: "pj001" }], name: "B" }
+      ];
+      this.barOption = chartOption.getBarGroupOption(
+        this.parseGroupBarData(data),
+        "档位分布图"
+      );
+    },
+    async toSearch() {
+      const datas = {
+        subjectId: this.subjectId,
+        startTime: this.searchTime[0],
+        endTime: this.searchTime[1]
+      };
+      const data = await qualityAnalysisDetail(datas);
+      console.log(data);
+    },
+    toExport() {}
+  }
 };
 </script>

+ 121 - 3
src/modules/main/StudentScore.vue

@@ -1,15 +1,133 @@
 <template>
   <div class="student-score">
-    student-score
+    <div class="part-box">
+      <Form ref="FilterForm" label-position="left" inline>
+        <FormItem label="科目:">
+          <Select v-model="filter.subjectId" placeholder="请选择科目">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+
+        <FormItem label="号码:">
+          <Select
+            v-model="filter.codeType"
+            placeholder="请选择号码类型"
+            style="width: 150px"
+          >
+            <Option
+              v-for="(val, key) in CODE_TYPE"
+              :key="key"
+              :value="key"
+              :label="val"
+            ></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Input
+            v-model.trim="filter.code"
+            placeholder="请输入号码"
+            clearable
+          ></Input>
+        </FormItem>
+        <FormItem label="姓名:">
+          <Input
+            v-model.trim="filter.name"
+            placeholder="请输入姓名"
+            clearable
+          ></Input>
+        </FormItem>
+        <FormItem>
+          <Button type="primary" icon="ios-search" @click="toSearch"
+            >查询</Button
+          >
+        </FormItem>
+      </Form>
+      <!--  -->
+      <image-action-list
+        :data="papers"
+        ref="ImageActionList"
+        style="text-align:center;"
+      ></image-action-list>
+
+      <div class="student-info">
+        <table class="table" v-if="curPaper.id">
+          <tr>
+            <td>姓名</td>
+            <td colspan="3">{{ curPaper.studentName }}</td>
+          </tr>
+          <tr>
+            <td>考号</td>
+            <td colspan="3">{{ curPaper.examNumber }}</td>
+          </tr>
+          <tr>
+            <td>档位</td>
+            <td>A</td>
+            <td>B</td>
+            <td>C</td>
+          </tr>
+          <tr>
+            <td>分数</td>
+            <td>95</td>
+            <td>空</td>
+            <td>空</td>
+          </tr>
+        </table>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { studentScoreList } from "@/api";
+import { CODE_TYPE } from "@/constants/enumerate";
+import ImageActionList from "./components/ImageActionList";
+
 export default {
   name: "student-score",
+  components: { ImageActionList },
   data() {
-    return {};
+    return {
+      filter: {
+        subjectId: "",
+        codeType: "",
+        code: "",
+        name: ""
+      },
+      CODE_TYPE,
+      papers: [
+        {
+          id: "1",
+          title: "2020105133",
+          url:
+            "http://127.0.0.1:9000/api/file/image/download/33/1/833/1?random=fa8244bb-8ec4-46c1-a16e-1bd6f3b8848e",
+          thumbUrl:
+            "http://127.0.0.1:9000/api/file/image/download/33/1/833/2?random=497cc903-c01a-458a-9b4e-82b391cef176"
+        },
+        {
+          id: "2",
+          title: "2020105133",
+          url:
+            "http://127.0.0.1:9000/api/file/image/download/33/1/833/1?random=fa8244bb-8ec4-46c1-a16e-1bd6f3b8848e",
+          thumbUrl:
+            "http://127.0.0.1:9000/api/file/image/download/33/1/833/2?random=497cc903-c01a-458a-9b4e-82b391cef176"
+        },
+        {
+          id: "3",
+          title: "2020105133",
+          url:
+            "http://127.0.0.1:9000/api/file/image/download/33/1/833/1?random=fa8244bb-8ec4-46c1-a16e-1bd6f3b8848e",
+          thumbUrl:
+            "http://127.0.0.1:9000/api/file/image/download/33/1/833/2?random=497cc903-c01a-458a-9b4e-82b391cef176"
+        }
+      ],
+      curPaper: { id: "12", studentName: "张三", examNumber: "12345678912" }
+    };
   },
-  methods: {}
+  methods: {
+    async toSearch() {
+      const data = await studentScoreList(this.filter);
+      console.log(data);
+    }
+  }
 };
 </script>

+ 11 - 3
src/modules/main/WorkOverview.vue

@@ -27,10 +27,16 @@
         </ul>
       </div>
       <div class="overview-actions">
-        <Button type="primary" @click="download(exportGradeScoreUrl)"
+        <Button
+          type="primary"
+          icon="md-download"
+          @click="download(exportGradeScoreUrl)"
           >导出档位成绩</Button
         >
-        <Button type="primary" @click="download(exportScoreUrl)"
+        <Button
+          type="primary"
+          icon="md-download"
+          @click="download(exportScoreUrl)"
           >导出分数成绩</Button
         >
       </div>
@@ -63,7 +69,9 @@
             <p>当前阶段</p>
             <p>{{ subject.markStageName }}</p>
             <div class="subject-actions-detail">
-              <Button @click="download(subject.exportAbsentDataUrl)"
+              <Button
+                icon="md-download"
+                @click="download(subject.exportAbsentDataUrl)"
                 >导出缺考名单</Button
               >
               <Button>评卷管理</Button>

+ 106 - 3
src/modules/mark-set/ExportPaper.vue

@@ -1,6 +1,89 @@
 <template>
   <div class="export-paper">
-    export-paper
+    <div class="part-box">
+      <div class="part-title">
+        <h2>分数图片导出</h2>
+      </div>
+      <Form label-position="left" inline>
+        <FormItem label="图片类型:">
+          <Select v-model="scoreFilter.imageType" placeholder="请选择图片类型">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="考点:">
+          <Select v-model="scoreFilter.areaCode" placeholder="请选择考点">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="学校:">
+          <Select v-model="scoreFilter.schoolId" placeholder="请选择学校">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="考场:">
+          <Select v-model="scoreFilter.examRoom" placeholder="请选择考场">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="科目:">
+          <Select v-model="scoreFilter.subjectId" placeholder="请选择科目">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="命名规则:">
+          <Select v-model="scoreFilter.nameRule" placeholder="请选择命名规则">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="起始分数:">
+          <InputNumber
+            v-model="scoreFilter.startNumber"
+            placeholder="请输入数字"
+            clearable
+          ></InputNumber>
+        </FormItem>
+        <FormItem label="终止分数:">
+          <InputNumber
+            v-model="scoreFilter.endNumber"
+            placeholder="请输入数字"
+            clearable
+          ></InputNumber>
+        </FormItem>
+        <FormItem>
+          <Button type="primary" icon="md-download" @click="toExport('score')"
+            >导出</Button
+          >
+        </FormItem>
+      </Form>
+    </div>
+
+    <div class="part-box">
+      <div class="part-title">
+        <h2>图片解密、重命名导出</h2>
+      </div>
+      <Form label-position="left" inline>
+        <FormItem label="图片类型:">
+          <Select v-model="renameFilter.imageType" placeholder="请选择图片类型">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="考点:">
+          <Select v-model="renameFilter.areaCode" placeholder="请选择考点">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem label="科目:">
+          <Select v-model="renameFilter.subjectId" placeholder="请选择科目">
+            <Option value=""></Option>
+          </Select>
+        </FormItem>
+        <FormItem>
+          <Button type="primary" icon="md-download" @click="toExport('rename')"
+            >导出</Button
+          >
+        </FormItem>
+      </Form>
+    </div>
   </div>
 </template>
 
@@ -8,8 +91,28 @@
 export default {
   name: "export-paper",
   data() {
-    return {};
+    return {
+      scoreFilter: {
+        imageType: "",
+        areaCode: "",
+        examRoom: "",
+        schoolID: "",
+        subjectId: "",
+        nameRule: "",
+        startNumber: null,
+        endNumber: null
+      },
+      renameFilter: {
+        imageType: "",
+        areaCode: "",
+        subjectId: ""
+      }
+    };
   },
-  methods: {}
+  methods: {
+    toExport(type) {
+      console.log(this[`${type}Filter`]);
+    }
+  }
 };
 </script>

+ 73 - 4
src/modules/mark-set/MarkRuleSet.vue

@@ -1,15 +1,84 @@
 <template>
-  <div class="mark-rule-set">
-    mark-rule-set
+  <div class="mark-rule-set part-box">
+    <Form ref="modalFormComp" :model="modalForm" :label-width="180">
+      <FormItem label="分数处理方式:">
+        <Select v-model="modalForm.scoreRule" style="width: 200px">
+          <Option value="">12</Option>
+        </Select>
+      </FormItem>
+      <FormItem label="改档及改档打分:">
+        <RadioGroup v-model="modalForm.changeGradeMarkType">
+          <Radio
+            v-for="(val, key) in BOOLEAN_TYPE"
+            :key="key"
+            :label="val"
+            style="margin-right: 50px;"
+          ></Radio>
+        </RadioGroup>
+      </FormItem>
+      <FormItem label="阅卷员是否显示所有试卷:">
+        <RadioGroup v-model="modalForm.isShowAllPaper">
+          <Radio
+            v-for="(val, key) in BOOLEAN_TYPE"
+            :key="key"
+            :label="val"
+            style="margin-right: 50px;"
+          ></Radio>
+        </RadioGroup>
+      </FormItem>
+      <FormItem>
+        <Button type="primary" :disabled="isSubmit" @click="submit"
+          >保存</Button
+        >
+      </FormItem>
+    </Form>
   </div>
 </template>
 
 <script>
+import { markRuleDetail, saveMarkRule } from "@/api";
+import { BOOLEAN_TYPE } from "@/constants/enumerate";
+
 export default {
   name: "mark-rule-set",
   data() {
-    return {};
+    return {
+      isSubmit: false,
+      workId: this.$route.params.workId,
+      BOOLEAN_TYPE,
+      initModalForm: {
+        scoreRule: "",
+        changeGradeMarkType: 0,
+        isShowAllPaper: 0
+      },
+      modalForm: {}
+    };
+  },
+  mounted() {
+    this.getData();
   },
-  methods: {}
+  methods: {
+    async getData() {
+      const data = await markRuleDetail(this.workId);
+      this.modalForm = Object.assign(data, this.initModalForm);
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate();
+      if (!valid) return;
+
+      if (this.isSubmit) return;
+      this.isSubmit = true;
+      const data = await saveMarkRule(this.modalForm).catch(() => {
+        this.isSubmit = false;
+      });
+
+      if (!data) return;
+
+      this.isSubmit = false;
+      this.$Message.success(this.title + "成功!");
+      this.$emit("modified");
+      this.cancel();
+    }
+  }
 };
 </script>

+ 22 - 3
src/modules/mark-set/MarkSet.vue

@@ -1,15 +1,34 @@
 <template>
   <div class="mark-set">
-    mark-set
+    <div class="page-navs">
+      <Button
+        :type="curNav.name === nav.name ? 'primary' : 'default'"
+        v-for="(nav, index) in navs"
+        :key="index"
+        @click="switchNav(nav)"
+        >{{ nav.title }}</Button
+      >
+    </div>
+
+    <router-view />
   </div>
 </template>
 
 <script>
+import { markSetNavs } from "@/routers/main";
+import menuMixins from "@/components/homeMenuMixins";
+
 export default {
   name: "mark-set",
+  mixins: [menuMixins],
   data() {
-    return {};
+    return {
+      navs: markSetNavs,
+      curNav: {}
+    };
   },
-  methods: {}
+  mounted() {
+    this.actCurNav(this.$route);
+  }
 };
 </script>

+ 2 - 0
src/plugins/VueCharts.js

@@ -5,6 +5,8 @@ import VueCharts from "vue-echarts"; // refers to components/ECharts.vue in webp
 // import ECharts modules manually to reduce bundle size
 // 按需模块:https://github.com/apache/incubator-echarts/blob/master/index.js
 // component
+import "echarts/lib/component/title";
+import "echarts/lib/component/legend";
 import "echarts/lib/component/tooltip";
 import "echarts/lib/component/dataZoom";
 // import "echarts/lib/component/geo";