Commit 627f3fff60919eba80372a7b18a69bc537c6d65b

Authored by LH_PC
1 parent ef16e57e

fix:前端知识点组件代码

src/assets/images/example-backup.jpg 0 → 100644

519 KB

src/assets/images/example.png 0 → 100644

36.6 KB

src/components/knowledgePoints.vue 0 → 100644
  1 +<template>
  2 + <el-container class="default-knowledge-body" v-loading="loading">
  3 + <el-main class="knowledge-main">
  4 + <div>
  5 + <el-row :gutter="20" class="knowledge-row" v-if="false">
  6 + <el-col :span="8" style="float: right;">
  7 + <div class="grid-content bg-purple">
  8 + <el-input placeholder="搜索知识点" v-model="knowledgKeyword">
  9 + <el-button slot="append" @click="_knowledgSerach" icon="el-icon-search" />
  10 + </el-input>
  11 + </div>
  12 + </el-col>
  13 + </el-row>
  14 + <el-row :gutter="20" class="knowledge-row">
  15 + <el-col>
  16 + <label class="input-tags-label">已设置知识点:</label>
  17 + <div class="input-tags">
  18 + <el-tag class="knowledgTag" v-for="(knowledg, knowledgIndex) in knowledgValues"
  19 + :key="knowledgIndex" closable @close="_knowledgRemove(knowledgIndex)">
  20 + {{ knowledg[knowledg.length - 1] }}
  21 + </el-tag>
  22 + </div>
  23 + </el-col>
  24 + </el-row>
  25 + <el-row :gutter="20" class="knowledge-row" style="margin:0px !important;">
  26 + <el-cascader-panel :value="knowledgValues" ref="knowledgeCascader" @change="_knowledgeChange"
  27 + @expand-change="_knowledgeExpandChange" :props="knowledgeOptions" :options="knowledgeList"
  28 + :default-expanded-keys="[]" placeholder="请选择知识点" :filterable="true" />
  29 + </el-row>
  30 + </div>
  31 + </el-main>
  32 + <el-footer class="knowledge-footer">
  33 + <el-button @click="_opration('cancel')">取 消</el-button>
  34 + <el-button type="primary" @click="_opration('confirm')">确 定</el-button>
  35 + </el-footer>
  36 + </el-container>
  37 +</template>
  38 +<script>
  39 +export default {
  40 + props: {
  41 + knowledges: "",
  42 + ///学段
  43 + sectionName: "",
  44 + ///学科
  45 + subjectName: "",
  46 + },
  47 + data() {
  48 + return {
  49 + loading: false,
  50 + ///级联选择框参数
  51 + knowledgeOptions: {
  52 + //多选
  53 + multiple: true,
  54 + //父子节点不关联
  55 + checkStrictly: true
  56 + },
  57 + knowledgMaxLength: 5,
  58 + knowledgeData: [],
  59 + knowledgeList: [],
  60 + knowledgKeyword: "",
  61 + knowledgValues: [],
  62 + }
  63 + },
  64 + methods: {
  65 + _opration(opration) {
  66 + var outKnowledges = this.knowledgValues.map(mo => {
  67 + return '#' + mo.join('#');
  68 + });
  69 + this.$emit('opration', {
  70 + opration: opration,
  71 + knowledges: outKnowledges
  72 + });
  73 + },
  74 + _knowledgeChange(values) {
  75 +
  76 + if (values.length > this.knowledgMaxLength) {
  77 + this.$message.error(`最多只能选择 ${this.knowledgMaxLength} 项`);
  78 + this.$nextTick(() => {
  79 + this.$refs.knowledgeCascader.checkedValue = this.knowledgValues;
  80 + this.$refs.knowledgeCascader.checkedNodePaths = this.$refs.knowledgeCascader.checkedNodePaths.splice(5, 1)
  81 + this.$refs.knowledgeCascader.syncMultiCheckState();
  82 + this.$refs.knowledgeCascader.syncActivePath();
  83 +
  84 + })
  85 + this._clearNotChecked();
  86 + return;
  87 + }
  88 + this._clearNotChecked();
  89 + this.knowledgValues = values;
  90 + },
  91 + _knowledgeExpandChange(values) {
  92 + this._clearNotChecked();
  93 + },
  94 + _knowledgRemove(knowledgIndex) {
  95 + this.knowledgValues.splice(knowledgIndex, 1);
  96 + this.$refs.knowledgeCascader.checkedValue = this.knowledgValues;
  97 + this.$refs.knowledgeCascader.syncMultiCheckState();
  98 + },
  99 + _knowledgSerach() {
  100 +
  101 + },
  102 + _knowledgFilter(data, query) {
  103 + return data
  104 + .map((item) => {
  105 + const children = item.children ? this._knowledgFilter(item.children, query) : [];
  106 + if (children.length || item.value.toLowerCase().indexOf(query) >= 0) {
  107 + return {
  108 + ...item,
  109 + children: children.length ? children : undefined,
  110 + };
  111 + }
  112 + return null;
  113 + })
  114 + .filter((item) => item);
  115 + },
  116 + async _knowledgInit() {
  117 +
  118 + var { data, info, status } = await this.$request.gKnowledge(this.$props.sectionName, this.$props.subjectName);
  119 +
  120 + if (status != 0) {
  121 +
  122 + this.$message.error(info);
  123 +
  124 + return;
  125 + }
  126 +
  127 + this.knowledgeData = [...data];
  128 +
  129 + this.knowledgeList = [...this.knowledgeData]
  130 +
  131 + if (this.$props.knowledges && this.$props.knowledges.length >= 1) {
  132 +
  133 + var propsKnowledges = this.$props.knowledges?.split(',').map(item => item?.split('#').filter(fdl => fdl?.length >= 1));
  134 +
  135 + this.knowledgValues = propsKnowledges;
  136 + }
  137 + },
  138 + _clearNotChecked() {
  139 + this.$nextTick(() => {
  140 + if (!this.$refs.knowledgeCascader.$el) return;
  141 + var checkedPath = this.$refs.knowledgeCascader.$el.querySelectorAll('.in-checked-path');
  142 +
  143 + for (var ofi = 0; ofi < checkedPath.length; ofi++) {
  144 + var checkPath = checkedPath[ofi];
  145 + var checkPathIsChecked = checkPath.querySelectorAll('.is-checked');
  146 + if (checkPathIsChecked.length < 1) {
  147 + checkPath.classList.remove('in-checked-path');
  148 + }
  149 + }
  150 +
  151 + });
  152 + }
  153 + },
  154 + mounted() {
  155 +
  156 + },
  157 + async created() {
  158 +
  159 + this.loading = true;
  160 +
  161 + await this._knowledgInit();
  162 +
  163 + this.loading = false;
  164 +
  165 + this.$nextTick(() => {
  166 + if (!this.$refs.knowledgeCascader.menus) return;
  167 + this.$refs.knowledgeCascader.menus = [this.$refs.knowledgeCascader.menus[0]]
  168 + })
  169 +
  170 + this._clearNotChecked();
  171 + }
  172 +}
  173 +</script>
  174 +<style lang="scss">
  175 +.default-knowledge-body {
  176 +
  177 +
  178 + padding: 0px !important;
  179 +
  180 + .knowledge-footer {
  181 + text-align: right;
  182 + padding: 10px;
  183 + }
  184 +
  185 + .el-main {
  186 + padding: 0px !important;
  187 + }
  188 +
  189 + .knowledge-main {
  190 + width: 100%;
  191 + padding: 0px 10px !important;
  192 +
  193 + .el-cascader-node.in-active-path {
  194 + background: #cddbe8 !important;
  195 + }
  196 +
  197 + .el-cascader-node.in-active-path:not(.in-checked-path) {
  198 + color: #606266 !important;
  199 + }
  200 +
  201 + .el-cascader-node.is-selectable.in-active-path {
  202 + background: #ecf5ff;
  203 + }
  204 +
  205 + .el-cascader-panel {
  206 + width: 100% !important;
  207 + }
  208 +
  209 + .input-tags-label {
  210 + float: left;
  211 + line-height: 40px;
  212 + margin-right: 12px;
  213 + }
  214 +
  215 + .input-tags {
  216 + border: 1px solid #DCDFE6;
  217 + min-height: 40px;
  218 + margin-left: 130px;
  219 + border-radius: 5px;
  220 + padding: 5px 5px 0px 5px;
  221 + }
  222 +
  223 + .knowledge-row {
  224 + .knowledgTag {
  225 + margin-right: 5px;
  226 + margin-bottom: 5px;
  227 + }
  228 +
  229 + margin-bottom: 10px;
  230 +
  231 + .el-input-group__append {
  232 + background-color: transparent !important;
  233 + border-left: none !important;
  234 + padding-right: 10px !important;
  235 +
  236 + .el-icon-search {
  237 + font-size: 20px;
  238 + }
  239 + }
  240 +
  241 + .el-input__inner {
  242 + border-right: none !important;
  243 + }
  244 + }
  245 + }
  246 +}
  247 +</style>
0 \ No newline at end of file 248 \ No newline at end of file
src/components/preview.vue 0 → 100644
  1 +<template>
  2 + <div class="preview">
  3 + <iframe v-if="type == 'html'" ref="iframe" :src="$props.src" style="width: 100%; height: 500px;" />
  4 + <el-image v-if="type == 'image'" ref="iframe" :src="$props.src" style="width: 100%; height: 500px;" />
  5 + </div>
  6 +</template>
  7 +<script>
  8 +export default {
  9 + name: "PreView",
  10 + props: {
  11 + src: "",
  12 + },
  13 + watch: {
  14 + $props: {
  15 + handler: function (val) {
  16 + console.log('change', val)
  17 + },
  18 + deep: false,
  19 + }
  20 + },
  21 + data() {
  22 + return {
  23 + type: ""
  24 + }
  25 + },
  26 + watch: {
  27 + '$props.src'() {
  28 + this._isCheckType();
  29 + }
  30 + },
  31 + methods: {
  32 + _isImagePath(path) {
  33 + return /\.(jpg|jpeg|png|gif|bmp|svg|webp)$/i.test(path);
  34 + },
  35 + _isCheckType() {
  36 + if (this._isImagePath(this.$props.src)) {
  37 + this.type = 'image'
  38 + } else {
  39 + this.type = 'html'
  40 + }
  41 + }
  42 + },
  43 + created() {
  44 + this._isCheckType();
  45 + }
  46 +}
  47 +</script>
  48 +<style scoped lang="scss">
  49 +.preview {
  50 +
  51 + iframe,
  52 + .el-image__inner {
  53 + height: calc(100%-10px) !important;
  54 + width: calc(100%-10px) !important;
  55 + z-index: -1;
  56 + }
  57 +}
  58 +</style>
0 \ No newline at end of file 59 \ No newline at end of file
src/views/basic/askTestQuestion/components/testScoreSet.vue 0 → 100644
  1 +<template>
  2 + <div class="set-container" ref="scoreSet" v-loading="loading">
  3 + <div class="back">
  4 + <div class="back-l" @click="closeScoreSet">
  5 + <i class="fa fa-mail-reply-all"></i>
  6 + <span>答卷录分</span>
  7 + </div>
  8 + </div>
  9 + <div class="set-content">
  10 + <div class="test-title">
  11 + <el-button class="import-btn" round @click="diaUp = true"
  12 + >从excel文件导入</el-button
  13 + >
  14 + <el-button
  15 + class="save-btn"
  16 + type="primary"
  17 + round
  18 + :loading="loadingSave"
  19 + @click="_SubmitScore"
  20 + >保存</el-button
  21 + >
  22 + <p class="p1">{{ title }}</p>
  23 + <p class="p2">卷面总分:{{ examScore }}分</p>
  24 + </div>
  25 + <el-table
  26 + :data="tableData"
  27 + border
  28 + style="width: 100%"
  29 + :max-height="tableMaxHeight"
  30 + >
  31 + <el-table-column
  32 + prop="studentCode"
  33 + label="学号"
  34 + align="center"
  35 + fixed
  36 + ></el-table-column>
  37 + <el-table-column
  38 + prop="studentName"
  39 + label="学号"
  40 + align="center"
  41 + fixed
  42 + ></el-table-column>
  43 + <el-table-column prop="score" label="总得分" align="center" fixed
  44 + ><template slot-scope="scoped">
  45 + <el-input
  46 + v-if="showAllSetScore"
  47 + type="number"
  48 + :min="0"
  49 + v-model="scoped.row.all"
  50 + ></el-input>
  51 + <template v-else>{{ scoped.row.all }}</template>
  52 + </template></el-table-column
  53 + >
  54 + <el-table-column prop="objectiveScore" label="客观题分" align="center"
  55 + ><template slot-scope="scoped">
  56 + <!-- <el-input
  57 + v-if="scoped.row.showSetScore"
  58 + type="number"
  59 + :min="0"
  60 + v-model="scoped.row.object"
  61 + @input="setOtherScore($event, scoped.row)"
  62 + ></el-input> -->
  63 + <template>{{ scoped.row.object }}</template>
  64 + </template></el-table-column
  65 + >
  66 + <el-table-column prop="subjectiveScore" label="主观题分" align="center">
  67 + <template slot-scope="scoped">
  68 + <!-- <el-input
  69 + v-if="scoped.row.showSetScore"
  70 + type="number"
  71 + :min="0"
  72 + v-model="scoped.row.subject"
  73 + @input="setOtherScore($event, scoped.row)"
  74 + ></el-input> -->
  75 + <template>{{ scoped.row.subject }}</template>
  76 + </template>
  77 + </el-table-column>
  78 + <!-- <el-table-column v-for="(item, index) in questionList" :key="index" :label="'第' + cNum[index] + '大题'" -->
  79 + <el-table-column
  80 + v-for="(question, index) in questionList"
  81 + :key="index"
  82 + :label="'Q' + question.questionId"
  83 + align="center"
  84 + >
  85 + <template slot-scope="scoped">
  86 + <el-input
  87 + v-if="showSetScore"
  88 + type="number"
  89 + :min="0"
  90 + :max="question.questionScore"
  91 + @input="
  92 + setScore(
  93 + $event,
  94 + question.questionScore,
  95 + scoped.row.scoreMap,
  96 + question.questionId,
  97 + scoped.row
  98 + )
  99 + "
  100 + v-model="scoped.row.scoreMap[question.questionId]"
  101 + ></el-input>
  102 + <template v-else>{{
  103 + scoped.row.scoreMap[question.questionId]
  104 + }}</template>
  105 + </template>
  106 + </el-table-column>
  107 + </el-table>
  108 + </div>
  109 +
  110 + <el-dialog
  111 + :close-on-click-modal="false"
  112 + :append-to-body="true"
  113 + title="答卷录分"
  114 + :visible.sync="diaUp"
  115 + width="600px"
  116 + top="120px"
  117 + >
  118 + <upload
  119 + :url="url"
  120 + :examId="id"
  121 + @upSuccess="upSuccess"
  122 + fileName="主观题分数"
  123 + v-loading="loadingDown"
  124 + >
  125 + <template slot="down">
  126 + <p class="down-txt">
  127 + 第一步:<el-link type="danger" @click="downExcel">下载模板</el-link
  128 + >,并编辑完成学生分数。
  129 + </p>
  130 + <p class="down-txt">第二步:上传完成编辑的模板文件并导入。</p>
  131 + </template>
  132 + </upload>
  133 + <div class="dialog-footer" slot="footer">
  134 + <el-button @click="diaUp = false">取 消</el-button>
  135 + </div>
  136 + </el-dialog>
  137 + </div>
  138 + </template>
  139 + <script>
  140 + import { downloadFile, cNum } from "utils";
  141 + export default {
  142 + name: "testScoreSet",
  143 + props: {
  144 + title: String,
  145 + id: String,
  146 + role: String,
  147 + examScore: {
  148 + type: Number,
  149 + default: 0,
  150 + },
  151 + diaScoreSet: false,
  152 + showAllSetScore: false,
  153 + showSetScore: false,
  154 + },
  155 + data() {
  156 + return {
  157 + diaUp: false,
  158 + loading: false,
  159 + loadingDown: false,
  160 + loadingSave: false,
  161 + url: "/api_html/teaching/importScore",
  162 + tableData: [],
  163 + questionList: [],
  164 + cNum: cNum,
  165 + tableMaxHeight: 300,
  166 + };
  167 + },
  168 + watch: {
  169 + diaScoreSet: {
  170 + handler: function (nVal) {
  171 + if (nVal) {
  172 + this._QueryData();
  173 + }
  174 + },
  175 + },
  176 + },
  177 + methods: {
  178 + //设置小题分
  179 + setScore(val, max, obj, questionId, rowData) {
  180 + if (max && Number(val) > Number(max)) {
  181 + obj[questionId] = max;
  182 + }
  183 + let { score, object, subject } = this.getScore(rowData);
  184 + rowData.all = score;
  185 + rowData.object = object;
  186 + rowData.subject = subject;
  187 + },
  188 + //计算总分
  189 + setAllScore(obj) {
  190 + let { score } = this.getScore(obj);
  191 + return score;
  192 + },
  193 + //计算客观总分
  194 + setObjectScore(obj) {
  195 + let { object } = this.getScore(obj);
  196 + return object;
  197 + },
  198 + //计算主观总分
  199 + setSubjectScore(obj) {
  200 + let { subject } = this.getScore(obj);
  201 + return subject;
  202 + },
  203 + getScore(obj) {
  204 + let score = 0;
  205 + let subject = 0;
  206 + let object = 0;
  207 + this.questionList.map((question) => {
  208 + let keys = question.questionId;
  209 + if (!obj.scoreMap[keys]) {
  210 + if (obj.scoreMap[keys] == 0) {
  211 + obj.scoreMap[keys] = 0;
  212 + } else {
  213 + obj.scoreMap[keys] = "";
  214 + }
  215 + } else {
  216 + let num = Number(obj.scoreMap[keys]);
  217 + obj.scoreMap[keys] = num;
  218 + score += num;
  219 + if (question.questionType == 5) {
  220 + subject += num;
  221 + } else {
  222 + object += num;
  223 + }
  224 + }
  225 + });
  226 + return { score, subject, object };
  227 + },
  228 + closeScoreSet() {
  229 + this.$emit("closeScoreSet");
  230 + },
  231 + setTableHeight() {
  232 + this.tableMaxHeight = this.$refs.scoreSet.offsetHeight - 135;
  233 + },
  234 + async _QueryData() {
  235 + this.loading = true;
  236 + const { data, status, info } =
  237 + await this.$request.listStudentsAndQuestions({
  238 + examId: this.id,
  239 + });
  240 + this.loading = false;
  241 + if (status === 0) {
  242 + let studentList = data.students || [];
  243 + this.questionList = data?.questionList || [];
  244 + if (this.questionList.length == 0) {
  245 + this.questionList = Object.keys(studentList[0].scoreMap).map(
  246 + (item) => {
  247 + return {
  248 + questionId: item.split('=')[0],
  249 + questionIndex: item.split('=')[1],
  250 + questionScore: 20,
  251 + };
  252 + }
  253 + );
  254 + }
  255 + this.tableData =
  256 + studentList.map((item) => {
  257 + item.all = item.all || 0; //总分
  258 + item.object = item.object || 0; //客观题分数
  259 + item.subject = item.subject || 0; //主观题分数
  260 + if (!item.scoreMap) item.scoreMap = {};
  261 + this.questionList.map((question) => {
  262 + let keys = question.questionId;
  263 + if (!item.scoreMap[keys]) {
  264 + if (item.scoreMap[keys] == 0) {
  265 + item.scoreMap[keys] = 0;
  266 + } else {
  267 + item.scoreMap[keys] = "";
  268 + }
  269 + } else {
  270 + let num = Number(item.scoreMap[keys]);
  271 + item.scoreMap[keys] = num;
  272 + }
  273 + });
  274 +
  275 + return item;
  276 + }) || [];
  277 + this.setTableHeight();
  278 + } else {
  279 + this.$message.error(info);
  280 + }
  281 + },
  282 + async _SubmitScore() {
  283 + this.loadingSave = true;
  284 + let list = this.tableData.map((item) => {
  285 + let scoreMap = {};
  286 + for (let keys in item.scoreMap) {
  287 + if (item.scoreMap[keys] !== "") {
  288 + scoreMap[keys] = item.scoreMap[keys] + "";
  289 + }
  290 + }
  291 + scoreMap.all = item.all + "";
  292 + scoreMap.object = item.object + "";
  293 + scoreMap.subject = item.subject + "";
  294 + return {
  295 + studentCode: item.studentCode,
  296 + scores: scoreMap,
  297 + };
  298 + });
  299 + const { data, status, info } = await this.$request.submitScore({
  300 + examId: this.id,
  301 + list,
  302 + });
  303 + this.loadingSave = false;
  304 + if (status === 0) {
  305 + this.$message.success(info);
  306 + this.closeScoreSet();
  307 + this.$emit("SuccessScoreSet");
  308 + } else {
  309 + this.$message.error(info);
  310 + }
  311 + },
  312 +
  313 + //导入成功
  314 + upSuccess(res) {
  315 + this.$message.success("导入成功");
  316 + this.diaUp = false;
  317 + // this.closeScoreSet();
  318 + this.$emit("SuccessScoreSet");
  319 + },
  320 + async downExcel() {
  321 + //模板下载
  322 + this.loadingDown = true;
  323 + let data = await this.$request.scoreTemplate({
  324 + examId: this.id,
  325 + });
  326 + this.loadingDown = false;
  327 + if (data && !data.code) {
  328 + let blob = new Blob([data], {
  329 + type: "application/vnd.ms-excel;charset=utf-8",
  330 + });
  331 + downloadFile(`答卷录分模版.xlsx`, blob);
  332 + } else {
  333 + this.$message.error(data.info);
  334 + }
  335 + },
  336 + },
  337 + };
  338 + </script>
  339 +
  340 + <style lang="scss" scoped>
  341 + .set-container {
  342 + position: fixed;
  343 + left: 200px;
  344 + top: 50px;
  345 + width: calc(100% - 200px);
  346 + height: calc(100% - 70px);
  347 + background: #fff;
  348 + z-index: 2000;
  349 + overflow-y: auto;
  350 + }
  351 +
  352 + .back {
  353 + width: 100%;
  354 + height: 56px;
  355 + border-bottom: 1px solid #e2e2e2;
  356 + display: flex;
  357 + justify-content: space-between;
  358 + align-items: center;
  359 + padding: 0 20px;
  360 + box-sizing: border-box;
  361 +
  362 + .back-l {
  363 + display: flex;
  364 + align-items: center;
  365 + cursor: pointer;
  366 + flex-shrink: 0;
  367 + font-size: 18px;
  368 + font-weight: 500;
  369 + }
  370 +
  371 + .back-r {
  372 + flex: 1;
  373 + display: flex;
  374 + justify-content: flex-end;
  375 + }
  376 +
  377 + .fa-mail-reply-all {
  378 + font-size: 28px;
  379 + color: #b3b3b3;
  380 + margin-right: 12px;
  381 + }
  382 + }
  383 +
  384 + .set-content {
  385 + padding: 0 20px;
  386 +
  387 + .test-title {
  388 + width: 100%;
  389 + text-align: center;
  390 + position: relative;
  391 + padding: 10px 0;
  392 +
  393 + .import-btn {
  394 + position: absolute;
  395 + right: 100px;
  396 + top: 15px;
  397 + }
  398 +
  399 + .save-btn {
  400 + position: absolute;
  401 + right: 0;
  402 + top: 15px;
  403 + }
  404 +
  405 + .p1 {
  406 + font-size: 18px;
  407 + font-weight: 700;
  408 + }
  409 + }
  410 + }
  411 +
  412 + .btn-box {
  413 + padding-top: 12px;
  414 + text-align: right;
  415 + }
  416 + </style>
0 \ No newline at end of file 417 \ No newline at end of file