问卷调研,支持新增题目,修改题目,题目上移下移,新增选项,选项上移下移,题目跳转功能,支持预览

发布时间 2023-09-15 17:48:45作者: 前端小菜鸡美哥
<template>
  <div class="app-container">
    <el-card shadow="never">
      <el-row :gutter="20" type="flex" justify="space-between">
        <el-col :span="12" style="display: flex;align-items: center;">
          <div style="margin-right: 30px;">科室:{{ baseForm.title }}</div>
          <el-button type="primary" @click="handleAddTopic">增加题目</el-button>
        </el-col>
        <el-col :span="12" style="display: flex;align-items: center;">
          <div style="margin-right: 30px;">
            <span>状态:</span>
            <el-radio
              v-model="baseForm.enable"
              :label="true"
              border
              size="medium"
              >启用</el-radio
            >
            <el-radio
              v-model="baseForm.enable"
              :label="false"
              border
              size="medium"
              >禁用</el-radio
            >
          </div>
          <el-button @click="handlePreview" type="primary" style="margin-right: 30px;"
            >保存并预览</el-button
          >
          <el-button
            type="primary"
            style="margin-right: 30px;"
            @click="handleSubmit"
            >保 存</el-button
          >
        </el-col>
      </el-row>
    </el-card>
    <el-form
      :model="baseForm"
      ref="baseForm"
      label-width="100px"
      class="demo-dynamic"
    >
      <div v-for="(item, index) in baseForm.questions" :key="index">
        <el-card shadow="never" style="margin-top: 20px;">
          <el-form-item :label="`第${index + 1}题`">
            <el-button type="primary" @click="handleMoveUpTopic(index)"
              >上移</el-button
            >
            <el-button type="primary" @click="handleMoveDownTopic(index)"
              >下移</el-button
            >
            <el-button type="danger" @click="handleDelTopic(index, item)"
              >删除</el-button
            >
          </el-form-item>
          <el-row>
            <el-col :span="8">
              <el-form-item
                :prop="'questions.' + index + '.question'"
                label="标题"
                :rules="[
                  {
                    required: true,
                    message: '请输入标题',
                    trigger: 'blur'
                  }
                ]"
              >
                <el-input
                  v-model="item.question"
                  type="textarea"
                  maxlength="64"
                  show-word-limit
                  style="width: 400px;"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="5">
              <el-form-item
                :prop="'questions.' + index + '.questionType'"
                label="题型"
                :rules="[
                  {
                    required: true,
                    message: '请选择题型',
                    trigger: 'change'
                  }
                ]"
              >
                <el-radio-group v-model="item.questionType" @input="handleQuestionType">
                  <el-radio label="SINGLE">单选题</el-radio>
                  <el-radio label="MULTI">多选题</el-radio>
                </el-radio-group>
              </el-form-item>
            </el-col>
          </el-row>

          <div v-for="(itemA, indexA) in item.answers">
            <el-row type="flex">
              <el-form-item
                :key="indexA"
                :label="`选项${indexA + 1}`"
                :rules="{
                  required: true,
                  message: '选项不能为空',
                  trigger: 'blur'
                }"
              >
                <el-input
                  v-model="item.answers[indexA].answer"
                  maxlength="64"
                  show-word-limit
                  style="width: 400px;"
                ></el-input>
                <el-button
                  type="primary"
                  plain
                  @click="handleMoveUpOption(index, indexA)"
                  >上移</el-button
                >
                <el-button
                  type="primary"
                  plain
                  @click="handleMoveDownOption(index, indexA)"
                  >下移</el-button
                >
                <el-button
                  type="danger"
                  plain
                  @click="handleDelOption(index, indexA, itemA)"
                  >删除</el-button
                >
              </el-form-item>
              <el-form-item prop="motifQuestionId" label="题目跳转" v-if="item.questionType == 'SINGLE'">
                <el-tooltip
                  content="设置选项跳转指定题目,未设置时默认跳转下一题,如需实现题目跳转关联请所有选项都关联同一题"
                  placement="top"
                >
                  <i class="el-icon-warning"></i>
                </el-tooltip>
                <el-select
                  v-model="item.answers[indexA].subproblemQuestionId"
                  placeholder="请选择"
                  style="width: 200px;"
                  clearable
                  :disabled="baseForm.questions.length - 1 == index"
                  @focus="handleMotifQuestionFocus(index)"
                >
                  <el-option
                    v-for="(item, j) in questionsListNew"
                    :key="j"
                    :label="item.question"
                    :value="item.questionId"
                    :disabled="item.disabled"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
            </el-row>
          </div>
          <el-form-item>
            <el-button type="primary" plain @click="handleAddOption(index)"
              >新增选项</el-button
            >
          </el-form-item>
        </el-card>
      </div>
    </el-form>
    <preview ref="preview" :dialogVisible="dialogVisible" :questionnaireId="baseForm.questionnaireId" @close="close"></preview>
  </div>
</template>

<script>
import {
  questionnaireInfoApi,
  questionnaireUpdateApi,
  questionnaireDeleteApi,
  questionnaireAnswerDeleteApi
} from "@/api/hospital/department";
import preview from "./preview.vue";
export default {
  name: "diseaseInvestigationEdit",
  components:{preview},
  data() {
    return {
      dialogVisible:false,
      motifQuestionIdList: [],
      questionsList: [],
      questionsListNew: [],
      baseForm: {
        enable: true,
        questionnaireId: null,
        questions: [
          {
            question: "",
            questionType: "SINGLE",
            motifQuestionId: null,
            answers: [
              {
                answer: ""
              }
            ]
          }
        ]
      }
    };
  },
  created() {
    console.log(this.$route.query.questionnaireId);
    if (this.$route.query.questionnaireId) {
      this.baseForm.questionnaireId = this.$route.query.questionnaireId;
    }
    this.getDetails();
  },
  watch: {
    "baseForm.questions": {
      handler(newValue, oldValue) {
        // 处理值变化
        this.questionsList = newValue;
        console.log(this.questionsList, "监听题目变化");
        this.questionsListNew = this.questionsList;
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    handleMotifQuestionFocus(index) {
      this.questionsListNew = [];
      this.questionsList.forEach((obj, indexA) => {
        if (indexA <= index) {
          obj.disabled = true;
        } else {
          obj.disabled = false;
        }
      });
      this.questionsListNew = this.questionsList;
      console.log(this.questionsListNew);
    },
    // 获取详情
    getDetails() {
      questionnaireInfoApi({
        questionnaireId: this.baseForm.questionnaireId
      }).then(res => {
        this.baseForm = res.data;
        for (var i of this.baseForm.questions) {
          i.disabled = false;
        }
      });
    },
    // 新增选项
    handleAddOption(index) {
      this.baseForm.questions[index].answers.push({
        answer: "",
        subproblemQuestionIndex: null,
        subproblemQuestionId: null
      });
    },
    // 删除选项
    handleDelOption(index, indexA, itemA) {
      if (this.baseForm.questions[index].answers.length == 1) {
        this.$message.warning("至少保留一条选项");
        return;
      } else {
        if (itemA.answerId) {
          this.$confirm("此操作将永久删除该选项, 是否继续?", "提示", {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning"
          })
            .then(() => {
              questionnaireAnswerDeleteApi([itemA.answerId])
                .then(res => {
                  this.$message.success(res.message);
                  this.baseForm.questions[index].answers.splice(indexA, 1);
                  this.getDetails();
                })
                .catch(err => {
                  this.$message.error(err.message);
                });
            })
            .catch(() => {});
        } else {
          this.baseForm.questions[index].answers.splice(indexA, 1);
        }
      }
    },
    // 选项上移
    handleMoveUpOption(index, indexA) {
      console.log(indexA);
      if (indexA == 0) {
        this.$message.warning("已经在第一条了");
        return;
      }
      let optionList = this.baseForm.questions[index].answers;
      optionList.splice(
        indexA - 1,
        1,
        ...optionList.splice(indexA, 1, optionList[indexA - 1])
      );
    },
    // 选项下移
    handleMoveDownOption(index, indexA) {
      let optionList = this.baseForm.questions[index].answers;
      optionList.splice(
        indexA,
        1,
        ...optionList.splice(indexA + 1, 1, optionList[indexA])
      );
    },
    // 新增题目
    handleAddTopic() {
      this.baseForm.questions.push({
        disabled: false,
        question: "",
        questionType: "SINGLE",
        motifQuestionId: null,
        answers: [
          {
            answer: "",
            subproblemQuestionIndex: null,
            subproblemQuestionId: null
          }
        ]
      });
    },
    // 题目上移
    handleMoveUpTopic(index) {
      if (index == 0) {
        this.$message.warning("已经在第一条了");
        return;
      }
      let topicList = this.baseForm.questions;
      topicList.splice(
        index - 1,
        1,
        ...topicList.splice(index, 1, topicList[index - 1])
      );
    },
    // 题目下移
    handleMoveDownTopic(index) {
      for (var i of this.baseForm.questions[index].answers) {
        i.subproblemQuestionIndex = null;
        i.subproblemQuestionId = null;
      }
      let topicList = this.baseForm.questions;
      topicList.splice(
        index,
        1,
        ...topicList.splice(index + 1, 1, topicList[index])
      );
    },
    // 删除题目
    handleDelTopic(index, item) {
      if (this.baseForm.questions.length == 1) {
        this.$message.warning("至少保留一条题目");
        return;
      } else {
        if (item.questionId) {
          this.$confirm("此操作将永久删除该题目, 是否继续?", "提示", {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning"
          })
            .then(() => {
              questionnaireDeleteApi([item.questionId])
                .then(res => {
                  this.$message.success(res.message);
                  this.baseForm.questions.splice(index, 1);
                  this.getDetails();
                })
                .catch(err => {
                  this.$message.error(err.message);
                });
            })
            .catch(() => {});
        } else {
          this.baseForm.questions.splice(index, 1);
        }
      }
    },
    // 提交
    handleSubmit() {
      console.log(this.baseForm);
      this.$refs["baseForm"].validate(valid => {
        if (valid) {
          questionnaireUpdateApi(this.baseForm)
            .then(res => {
              this.$message.success(res.message);
              this.getDetails();
            })
            .catch(err => {
              this.$message.error(err.message);
            });
        } else {
          return false;
        }
      });
    },
    // 保存预览
    handlePreview(){
      this.$refs["baseForm"].validate(valid => {
        if (valid) {
          questionnaireUpdateApi(this.baseForm)
            .then(res => {
              this.$message.success(res.message);
              this.getDetails();
              this.dialogVisible = true
              this.$refs.preview.questionsArr = []
              this.$refs.preview.getDetails()
            })
            .catch(err => {
              this.$message.error(err.message);
            });
        } else {
          return false;
        }
      });
    },
    close(){
      this.dialogVisible = false
    },
    handleQuestionType(val){
      console.log(val)
    }
  }
};
</script>
<style lang="scss"></style>

预览功能preview.vue

<template>
  <div>
    <el-dialog
      title="预览"
      :visible.sync="dialogVisible"
      width="30%"
      :before-close="handleClose"
    >
      <div class="upDownBtn">
        <div>
          <el-button type="primary" icon="el-icon-arrow-up" circle @click="handleScrollTop"></el-button>
        </div>
        <div>
          <el-button type="primary" icon="el-icon-arrow-down" circle style="margin-top: 10px;" @click="handleScrollBottom"></el-button>
        </div>
      </div>
      <div class="container" ref="scrollDiv">
        <div class="list">
          <div class="headImg"></div>
          <div class="content">
            你好,我将咨询您一系列问题,请如实回答,以帮助我更快做出诊断
          </div>
        </div>
        <div v-for="(item, index) in questionsArr" :key="index">
          <!-- 单选 -->
          <div class="list" v-if="item.questionType == 'SINGLE'">
            <div class="headImg"></div>
            <div class="question">
              <div class="title">{{ item.question }}</div>
              <div
                class="option"
                v-for="(itemA, indexA) in item.answers"
                :key="indexA"
                @click="handleCheckSingleAnswer(itemA, indexA, item, index)"
                :class="{
                  singleChecked: itemA.answerId == item.answerCheckId,
                  disSelect: item.disSelect
                }"
              >
                {{ itemA.answer }}
              </div>
            </div>
          </div>
          <!-- 多选 -->
          <div class="list" v-if="item.questionType == 'MULTI'">
            <div class="headImg"></div>
            <div class="question">
              <div class="title">
                {{ item.question + "(多选)" }}
              </div>
              <div
                class="option"
                v-for="(itemB, indexB) in item.answers"
                :key="indexB"
                @click="handleCheckMultiAnswer(itemB, indexB, item, index)"
                :class="{
                  singleChecked: itemB.check == true,
                  disSelect: item.disSelect
                }"
              >
                {{ itemB.answer }}
              </div>
              <div
                class="check"
                @click="handleCheckMulti(item, index)"
                :class="{ disSelectBtn: item.disSelect }"
              >
                选好了
              </div>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { questionnaireInfoApi } from "@/api/hospital/department";
export default {
  props: {
    dialogVisible: {
      type: Boolean,
      default: false
    },
    questionnaireId: {
      type: String,
      default: ""
    }
  },
  data() {
    return {
      baseForm: {},
      questionsArr: [],
      questionIndex: null
    };
  },
  methods: {
    // 滚动到顶部
    handleScrollTop() {
      this.$nextTick(() => {
        let scrollElem = this.$refs.scrollDiv;
        scrollElem.scrollTo({ top: 0, behavior: "smooth" });
      });
    },
    // 滚动到底部
    handleScrollBottom() {
      this.$nextTick(() => {
        let scrollElem = this.$refs.scrollDiv;
        scrollElem.scrollTo({
          top: scrollElem.scrollHeight,
          behavior: "smooth"
        });
      });
    },
    handleClose() {
      this.$emit("close");
    },
    // 获取详情
    getDetails() {
      questionnaireInfoApi({
        questionnaireId: this.questionnaireId
      }).then(res => {
        this.baseForm = res.data;
        this.baseForm.questions.forEach(val => {
          val.answerCheckId = "";
          if (val.questionType == "MULTI") {
            val.answers.forEach(e => {
              e.check = false;
            });
          }
        });
        this.questionsArr.push(this.baseForm.questions[0]);
        console.log(this.questionsArr);
      });
    },
    // 单选
    handleCheckSingleAnswer(itemA, indexA, item, index) {
      console.log(itemA, indexA, item, index);
      item.answerCheckId = itemA.answerId;
      item.disSelect = true;
      this.$set(this.questionsArr, index, item); //关键代码,当数组数据发生变化时,页面中动态绑定的样式没有变化
      // 判断跳题
      if (itemA.subproblemQuestionId) {
        var obj = this.baseForm.questions.find(function(item) {
          return item.questionId == itemA.subproblemQuestionId;
        });
        this.questionIndex = this.baseForm.questions.findIndex(
          i => i.questionId == obj.questionId
        );
        this.questionsArr.push(obj);
      } else {
        if (this.questionIndex + 1 >= this.baseForm.questions.length) {
          console.log("最后一道题了");
          this.$message({
            message: "恭喜你,已成功做完本次调研",
            type: "success"
          });
        } else {
          this.questionIndex += 1;
          this.questionsArr.push(this.baseForm.questions[this.questionIndex]);
          this.handleScrollBottom();
        }
      }
      console.log(this.questionIndex, this.baseForm.questions.length);
    },
    // 多选
    handleCheckMultiAnswer(itemB, indexB, item, index) {
      itemB.check = !itemB.check;
      this.$set(this.questionsArr, index, item);
    },
    // 多选选好了
    handleCheckMulti(item, index) {
      console.log(item)
      var isCheck = item.answers.some(function(e) {
          return e.check;
      });
      console.log(isCheck)
      if(isCheck == false){
        this.$message({
          message: "请至少选择一个选项",
          type: "warning"
        });
        return
      }
      item.disSelect = true;
      this.questionIndex = this.baseForm.questions.findIndex(
        i => i.questionId == item.questionId
      );
      console.log(this.questionIndex);
      if (this.questionIndex + 1 >= this.baseForm.questions.length) {
        console.log("最后一道题了");
        this.$message({
          message: "恭喜你,已成功做完本次调研",
          type: "success"
        });
      } else {
        this.questionIndex += 1;
        this.questionsArr.push(this.baseForm.questions[this.questionIndex]);
        this.handleScrollBottom();
      }
    }
  }
};
</script>
<style lang="scss" scoped>
.container {
  width: 375px;
  height: 720px;
  border: 1px solid #ccc;
  margin: 0 auto;
  padding: 12px;
  overflow-y: scroll;
  border-radius: 6px;
  .list {
    display: flex;
    margin-bottom: 12px;
  }
  .headImg {
    width: 39px;
    height: 39px;
    background: #d8d8d8;
    border-radius: 8px;
    margin-right: 9px;
  }
  .content {
    flex: 1;
    background: #f0f4f8;
    border-radius: 0px 19px 19px 19px;
    padding: 10px 15px;
    font-size: 15px;
  }
  .question {
    flex: 1;
    background: #f0f4f8;
    border-radius: 0px 19px 19px 19px;
    padding: 20px;
    .title {
      font-size: 16px;
      font-weight: 600;
      color: #191b21;
      margin-bottom: 16px;
    }
    .option {
      width: 100%;
      background: rgba(255, 255, 255, .4);
      border-radius: 8px;
      opacity: 0.4;
      border: 1px solid #e1e2e6;
      padding: 13px 16px;
      margin-bottom: 12px;
      text-align: center;
    }
    .check {
      font-size: 15px;
      color: #3d7ef0;
      text-align: center;
    }
  }
  .singleChecked {
    border: 2px solid #73d8c0 !important;
    font-size: 15px;
    background: #FFFFFF !important;
    color: #191B21;
  }
  .disSelect {
    pointer-events: none;
  }
  .disSelectBtn {
    pointer-events: none;
    color: rgba(61, 126, 240, 0.3) !important;
  }

}
.upDownBtn {
    position: absolute;
    right: 20px;
    bottom: 30px;
    display: flex;
    flex-direction: column;
  }
</style>