|
@@ -0,0 +1,421 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="course-weight-manage">
|
|
|
|
+ <div class="part-box part-box-pad">
|
|
|
|
+ <p>说明:</p>
|
|
|
|
+ <p>
|
|
|
|
+ 1.请在课程目标考核对应的评价方式表格中打√,并录入权重占比及目标分值;
|
|
|
|
+ </p>
|
|
|
|
+ <p>2.所有课程目标的总目标分值等于100分;</p>
|
|
|
|
+ <p>3.各课程目标下评价方式的总权重应等于100%;</p>
|
|
|
|
+ <p>4.目标整体权重应等于100%,用于计算课程整体达成度。</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="part-box part-box-pad">
|
|
|
|
+ <el-form class="mb-2" inline>
|
|
|
|
+ <el-form-item label="整体权重设置:"></el-form-item>
|
|
|
|
+ <el-form-item label="平均成绩权重:">
|
|
|
|
+ <el-input-number
|
|
|
|
+ v-model="customRate"
|
|
|
|
+ class="width-50"
|
|
|
|
+ size="small"
|
|
|
|
+ :min="0"
|
|
|
|
+ :max="100"
|
|
|
|
+ :step="1"
|
|
|
|
+ step-strictly
|
|
|
|
+ :controls="false"
|
|
|
|
+ >
|
|
|
|
+ </el-input-number>
|
|
|
|
+ <span style="margin-left: 5px">%</span>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="期末成绩权重:">
|
|
|
|
+ <el-input-number
|
|
|
|
+ v-model="defaultRate"
|
|
|
|
+ class="width-50"
|
|
|
|
+ size="small"
|
|
|
|
+ :min="0"
|
|
|
|
+ :max="100"
|
|
|
|
+ :step="1"
|
|
|
|
+ step-strictly
|
|
|
|
+ :controls="false"
|
|
|
|
+ >
|
|
|
|
+ </el-input-number>
|
|
|
|
+ <span style="margin-left: 5px">%</span>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+ <el-table :data="tableSetData">
|
|
|
|
+ <el-table-column
|
|
|
|
+ prop="evaluationName"
|
|
|
|
+ label="评价方式"
|
|
|
|
+ width="140"
|
|
|
|
+ fixed="left"
|
|
|
|
+ ></el-table-column>
|
|
|
|
+ <el-table-column v-for="(target, tindex) in dataList" :key="tindex">
|
|
|
|
+ <template slot="header">
|
|
|
|
+ <el-row type="flex" align="middle">
|
|
|
|
+ <el-col :span="10" :offset="2">
|
|
|
|
+ {{ target.courseTargetName }}
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col :span="12">
|
|
|
|
+ <span style="margin-right: 5px">目标整体权重</span>
|
|
|
|
+ <el-input-number
|
|
|
|
+ v-model="targetTotalWeight[target.courseTargetId]"
|
|
|
|
+ class="width-50"
|
|
|
|
+ size="small"
|
|
|
|
+ :min="0"
|
|
|
|
+ :max="100"
|
|
|
|
+ :step="1"
|
|
|
|
+ step-strictly
|
|
|
|
+ :controls="false"
|
|
|
|
+ >
|
|
|
|
+ </el-input-number>
|
|
|
|
+ <span style="margin-left: 5px">%</span>
|
|
|
|
+ </el-col>
|
|
|
|
+ </el-row>
|
|
|
|
+ </template>
|
|
|
|
+ <el-table-column min-width="340">
|
|
|
|
+ <template slot="header">
|
|
|
|
+ <el-row>
|
|
|
|
+ <el-col :span="10" :offset="2"> 权重 </el-col>
|
|
|
|
+ </el-row>
|
|
|
|
+ </template>
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <el-row type="flex" align="middle">
|
|
|
|
+ <el-col :span="2">
|
|
|
|
+ <el-checkbox
|
|
|
|
+ v-if="scope.$index !== endRowNo"
|
|
|
|
+ v-model="scope.row.targets[tindex].enable"
|
|
|
|
+ :disabled="scope.row.targets[tindex].disabled"
|
|
|
|
+ @change="enableChange(scope.$index, tindex)"
|
|
|
|
+ ></el-checkbox>
|
|
|
|
+ </el-col>
|
|
|
|
+ <el-col :span="10">
|
|
|
|
+ <span v-if="scope.$index === endRowNo">
|
|
|
|
+ {{ scope.row.targets[tindex].weight }}
|
|
|
|
+ </span>
|
|
|
|
+ <el-input-number
|
|
|
|
+ v-else
|
|
|
|
+ v-model="scope.row.targets[tindex].weight"
|
|
|
|
+ :disabled="!scope.row.targets[tindex].enable"
|
|
|
|
+ class="width-80"
|
|
|
|
+ size="small"
|
|
|
|
+ :min="0"
|
|
|
|
+ :max="100"
|
|
|
|
+ :step="1"
|
|
|
|
+ step-strictly
|
|
|
|
+ :controls="false"
|
|
|
|
+ >
|
|
|
|
+ </el-input-number>
|
|
|
|
+ <span style="margin-left: 5px">%</span>
|
|
|
|
+ </el-col>
|
|
|
|
+ </el-row>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ <div class="text-center" style="margin: 20px 0">
|
|
|
|
+ <el-button type="primary" :loading="loading" @click="submit"
|
|
|
|
+ >保存</el-button
|
|
|
|
+ >
|
|
|
|
+ </div>
|
|
|
|
+ <el-table
|
|
|
|
+ v-if="resultDataList.length && cwStatus.currentSettingStatus"
|
|
|
|
+ :data="resultDataList"
|
|
|
|
+ :header-cell-style="{ textAlign: 'center' }"
|
|
|
|
+ :cell-style="{ textAlign: 'center' }"
|
|
|
|
+ :span-method="spanMethod"
|
|
|
|
+ >
|
|
|
|
+ <el-table-column
|
|
|
|
+ prop="courseTargetName"
|
|
|
|
+ label="课程目标"
|
|
|
|
+ width="140"
|
|
|
|
+ ></el-table-column>
|
|
|
|
+ <el-table-column
|
|
|
|
+ prop="degreeRequirement"
|
|
|
|
+ label="支撑毕业要求"
|
|
|
|
+ min-width="200"
|
|
|
|
+ ></el-table-column>
|
|
|
|
+ <el-table-column prop="totalWeight" label="目标整体权重" width="110">
|
|
|
|
+ <template slot-scope="scope">{{ scope.row.totalWeight }}%</template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column label="考核/评价环节及目标分值">
|
|
|
|
+ <el-table-column
|
|
|
|
+ v-for="(ename, eindex) in evaluationData"
|
|
|
|
+ :key="ename"
|
|
|
|
+ :label="ename"
|
|
|
|
+ >
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ {{
|
|
|
|
+ scope.row.evaluationList[eindex].enable
|
|
|
|
+ ? scope.row.evaluationList[eindex].targetScore
|
|
|
|
+ : ""
|
|
|
|
+ }}
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column label="目标分值统计" width="120">
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ {{ getEvaluationSumScore(scope.row.evaluationList) }}
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script>
|
|
|
|
+import { courseWeightDetail, courseWeightSave } from "../../api";
|
|
|
|
+import { omit, pick } from "lodash";
|
|
|
|
+import { calcSum } from "@/plugins/utils";
|
|
|
|
+import { mapState, mapActions } from "vuex";
|
|
|
|
+
|
|
|
|
+export default {
|
|
|
|
+ name: "course-weight-manage",
|
|
|
|
+ props: {
|
|
|
|
+ course: {
|
|
|
|
+ type: Object,
|
|
|
|
+ default() {
|
|
|
|
+ return {};
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ data() {
|
|
|
|
+ return {
|
|
|
|
+ dataList: [],
|
|
|
|
+ defaultRate: 0,
|
|
|
|
+ customRate: 0,
|
|
|
|
+ tableSetData: [],
|
|
|
|
+ targetTotalWeight: {},
|
|
|
|
+ evaluationData: [],
|
|
|
|
+ resultDataList: [],
|
|
|
|
+ loading: false,
|
|
|
|
+ };
|
|
|
|
+ },
|
|
|
|
+ computed: {
|
|
|
|
+ ...mapState("base", ["cwStatus"]),
|
|
|
|
+ endRowNo() {
|
|
|
|
+ return this.tableSetData.length - 1;
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ mounted() {
|
|
|
|
+ this.initData();
|
|
|
|
+ },
|
|
|
|
+ methods: {
|
|
|
|
+ ...mapActions("base", ["updateCwStatus"]),
|
|
|
|
+ async initData() {
|
|
|
|
+ await this.updateCwStatus({
|
|
|
|
+ teachCourseId: this.course.id,
|
|
|
|
+ });
|
|
|
|
+ await this.getList();
|
|
|
|
+ if (this.cwStatus.currentSettingStatus) {
|
|
|
|
+ this.updateResultDataList();
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ async getList() {
|
|
|
|
+ const res = await courseWeightDetail({
|
|
|
|
+ teachCourseId: this.course.id,
|
|
|
|
+ });
|
|
|
|
+ this.defaultRate = res.defaultRate;
|
|
|
|
+ this.customRate = res.customRate;
|
|
|
|
+ this.dataList = res.submitForm || [];
|
|
|
|
+ this.transformData(this.dataList);
|
|
|
|
+ },
|
|
|
|
+ transformData(data) {
|
|
|
|
+ if (!data || !data.length) return [];
|
|
|
|
+
|
|
|
|
+ const tableSetData = [];
|
|
|
|
+ const evaluationMap = {};
|
|
|
|
+ const targetTotalWeight = {};
|
|
|
|
+ data[0].evaluationList.forEach((item, index) => {
|
|
|
|
+ evaluationMap[item.evaluationName] = index;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.evaluationData = data[0].evaluationList.map(
|
|
|
|
+ (item) => item.evaluationName
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ data.forEach((target) => {
|
|
|
|
+ targetTotalWeight[target.courseTargetId] = target.totalWeight;
|
|
|
|
+ const targetData = {
|
|
|
|
+ ...omit(target, ["evaluationList"]),
|
|
|
|
+ totalWeight: target.totalWeight || 0,
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ target.evaluationList.forEach((item) => {
|
|
|
|
+ const index = evaluationMap[item.evaluationName];
|
|
|
|
+ if (!tableSetData[index]) {
|
|
|
|
+ tableSetData[index] = {
|
|
|
|
+ evaluationName: item.evaluationName,
|
|
|
|
+ targets: [],
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ tableSetData[index].targets.push({
|
|
|
|
+ ...targetData,
|
|
|
|
+ enable: item.enable,
|
|
|
|
+ disabled: false,
|
|
|
|
+ weight: item.weight || 0,
|
|
|
|
+ targetScore: item.targetScore || 0,
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.targetTotalWeight = targetTotalWeight;
|
|
|
|
+ this.tableSetData = tableSetData;
|
|
|
|
+ this.updateSetDataDisabled();
|
|
|
|
+ },
|
|
|
|
+ updateSetDataDisabled() {
|
|
|
|
+ this.tableSetData.forEach((item, tindex) => {
|
|
|
|
+ const hasTargetWeight = item.targets.some((t) => t.weight);
|
|
|
|
+ item.targets.forEach((t) => {
|
|
|
|
+ if (hasTargetWeight) {
|
|
|
|
+ t.disabled = !t.weight;
|
|
|
|
+ } else {
|
|
|
|
+ t.disabled = false;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ enableChange(rowIndex, tindex) {
|
|
|
|
+ const target = this.tableSetData[rowIndex].targets[tindex];
|
|
|
|
+ if (!target.enable) {
|
|
|
|
+ target.weight = 0;
|
|
|
|
+ target.targetScore = 0;
|
|
|
|
+ }
|
|
|
|
+ this.tableSetData[rowIndex].targets.forEach((item, dindex) => {
|
|
|
|
+ if (dindex !== tindex) {
|
|
|
|
+ item.disabled = target.enable;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ getEvaluationSumScore(evaluationList) {
|
|
|
|
+ return calcSum(
|
|
|
|
+ evaluationList.map((item) => (item.enable ? item.targetScore : 0))
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
+ spanMethod({ rowIndex, columnIndex }) {
|
|
|
|
+ const lineCount = this.resultDataList.length - 1;
|
|
|
|
+ if (rowIndex === lineCount) {
|
|
|
|
+ if (columnIndex === 0) {
|
|
|
|
+ return [1, 2];
|
|
|
|
+ } else if (columnIndex === 1) {
|
|
|
|
+ return [0, 0];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ updateDataList() {
|
|
|
|
+ const evaluationData = {};
|
|
|
|
+ this.tableSetData.forEach((item) => {
|
|
|
|
+ item.targets.forEach((elem) => {
|
|
|
|
+ const key = `${item.evaluationName}_${elem.courseTargetId}`;
|
|
|
|
+ evaluationData[key] = pick(elem, ["enable", "weight", "targetScore"]);
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.dataList.forEach((target) => {
|
|
|
|
+ target.totalWeight = this.targetTotalWeight[target.courseTargetId];
|
|
|
|
+ target.evaluationList.forEach((item) => {
|
|
|
|
+ const key = `${item.evaluationName}_${target.courseTargetId}`;
|
|
|
|
+ Object.assign(item, evaluationData[key]);
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ updateResultDataList() {
|
|
|
|
+ // 更新合计
|
|
|
|
+ const tatolEvaluation = this.evaluationData.map(
|
|
|
|
+ (evaluationName, eindex) => {
|
|
|
|
+ const targetScore = calcSum(
|
|
|
|
+ this.dataList.map(
|
|
|
|
+ (item) => item.evaluationList[eindex].targetScore || 0
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ return {
|
|
|
|
+ evaluationName,
|
|
|
|
+ enable: true,
|
|
|
|
+ weight: null,
|
|
|
|
+ targetScore,
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+ this.resultDataList = [
|
|
|
|
+ ...this.dataList,
|
|
|
|
+ {
|
|
|
|
+ courseTargetId: "total",
|
|
|
|
+ courseTargetName: "合计",
|
|
|
|
+ degreeRequirement: "",
|
|
|
|
+ totalWeight: calcSum(
|
|
|
|
+ this.dataList.map((item) => item.totalWeight || 0)
|
|
|
|
+ ),
|
|
|
|
+ evaluationList: tatolEvaluation,
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+ },
|
|
|
|
+ checkDataList() {
|
|
|
|
+ if (!this.dataList.length) return;
|
|
|
|
+
|
|
|
|
+ // 整体权重设置
|
|
|
|
+ if (!this.customRate) {
|
|
|
|
+ this.$message.error("请输入平均成绩权重");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (!this.defaultRate) {
|
|
|
|
+ this.$message.error("请输入期末成绩权重");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (this.defaultRate + this.customRate !== 100) {
|
|
|
|
+ this.$message.error("平均成绩权重与期末成绩权重合计不等于100%");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 目标整体权重
|
|
|
|
+ if (Object.values(this.targetTotalWeight).some((item) => !item)) {
|
|
|
|
+ this.$message.error("请设置所有目标的目标整体权重");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const totalWeight = calcSum(
|
|
|
|
+ Object.values(this.targetTotalWeight).map((item) => item || 0)
|
|
|
|
+ );
|
|
|
|
+ if (totalWeight !== 100) {
|
|
|
|
+ this.$message.error("目标整体权重合计不等于100%");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 目标权重统计
|
|
|
|
+ const unvalidTargets = [];
|
|
|
|
+ const tableData = this.tableSetData.slice(0, -1);
|
|
|
|
+ this.dataList.forEach((target, tindex) => {
|
|
|
|
+ const totalWeight = calcSum(
|
|
|
|
+ tableData.map((item) => item.targets[tindex].weight || 0)
|
|
|
|
+ );
|
|
|
|
+ if (totalWeight !== 100) {
|
|
|
|
+ unvalidTargets.push(target.courseTargetName);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ if (unvalidTargets.length) {
|
|
|
|
+ this.$message.error(
|
|
|
|
+ `${unvalidTargets.join()}评价方式权重合计不等于100%`
|
|
|
|
+ );
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+ async submit() {
|
|
|
|
+ if (this.loading) return;
|
|
|
|
+ this.updateDataList();
|
|
|
|
+
|
|
|
|
+ if (!this.checkDataList()) return;
|
|
|
|
+
|
|
|
|
+ this.loading = true;
|
|
|
|
+ const res = await courseWeightSave({
|
|
|
|
+ teachCourseId: this.course.id,
|
|
|
|
+ defaultRate: this.defaultRate,
|
|
|
|
+ customRate: this.customRate,
|
|
|
|
+ submitForm: this.dataList,
|
|
|
|
+ }).catch(() => {});
|
|
|
|
+ this.loading = false;
|
|
|
|
+ if (!res) return;
|
|
|
|
+
|
|
|
|
+ this.initData();
|
|
|
|
+ this.$message.success("保存成功!");
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+</script>
|