Просмотр исходного кода

feat(workPage): 新增跨学科KWL题型组件

新增了kwlPage组件用于处理类型831的跨学科KWL题型,支持已知、想知、已学三栏内容添加删除,带学习反思和语音输入功能,同时在两个工作页组件中注册并启用该题型。
lsc 12 часов назад
Родитель
Сommit
dba22892b3

+ 744 - 0
src/components/pages/workPage/components/kwlPage.vue

@@ -0,0 +1,744 @@
+<template>
+  <div class="kwlPage">
+    <div class="kwlCard">
+      <!-- 头部卡片 -->
+      <div class="kwlHeader">
+        <div class="kwlHeaderLeft">
+          <div class="kwlTitle">KWL表格</div>
+          <div class="kwlSubtitle">写下你已经知道的、还想知道的,学完后再回来看看自己学到了什么</div>
+        </div>
+        <div class="kwlHeaderRight" @click="showHowTo = !showHowTo">
+          <span>怎么做</span>
+          <i :class="['kwlArrow', { 'kwlArrowUp': showHowTo }]">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4 transition-transform "><polyline points="6 9 12 15 18 9"></polyline></svg>
+          </i>
+        </div>
+      </div>
+
+      <!-- 怎么做指引 -->
+      <div class="kwlHowTo" v-show="showHowTo">
+        <div class="kwlHowToItem" v-for="(step, index) in howToSteps" :key="index">
+          <div class="kwlStepNum">{{ index + 1 }}</div>
+          <div class="kwlStepText">{{ step }}</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 任务说明 -->
+    <div class="kwlCard kwlTaskDesc" v-show="work.taskDescription">
+       <!-- @click="showTaskDesc = !showTaskDesc" -->
+      <div class="kwlTaskHeader">
+        <span class="kwlStar">✦</span>
+        <span class="kwlTaskTitle">任务说明</span>
+      </div>
+      <div class="kwlTaskContent" v-show="showTaskDesc">{{ work.taskDescription }}</div>
+    </div>
+
+    <!-- KWL 三列 -->
+    <div class="kwlColumns">
+      <!-- K - 已知 -->
+      <div class="kwlColumn kwlColKnow">
+        <div class="kwlColTitle kwlColTitleKnow">K - 已知</div>
+        <div class="kwlColBody">
+          <div class="kwlEmptyTip" v-if="!work.know || work.know.length === 0">
+            点击下方添加内容
+          </div>
+          <div class="kwlItem" v-for="(item, idx) in work.know" :key="'k-' + idx">
+            <span class="kwlItemText">{{ item }}</span>
+            <span class="kwlItemDel" @click="removeItem('know', idx)">×</span>
+          </div>
+        </div>
+        <div class="kwlAddRow">
+          <input
+            v-model="newKnow"
+            class="kwlInput"
+            placeholder="添加..."
+            @keyup.enter="addItem('know')"
+          />
+          <button class="kwlAddBtn kwlAddBtnKnow" @click="addItem('know')">+</button>
+        </div>
+      </div>
+
+      <!-- W - 想知 -->
+      <div class="kwlColumn kwlColWant">
+        <div class="kwlColTitle kwlColTitleWant">W - 想知</div>
+        <div class="kwlColBody">
+          <div class="kwlEmptyTip" v-if="!work.want || work.want.length === 0">
+            点击下方添加内容
+          </div>
+          <div class="kwlItem" v-for="(item, idx) in work.want" :key="'w-' + idx">
+            <span class="kwlItemText">{{ item }}</span>
+            <span class="kwlItemDel" @click="removeItem('want', idx)">×</span>
+          </div>
+        </div>
+        <div class="kwlAddRow">
+          <input
+            v-model="newWant"
+            class="kwlInput"
+            placeholder="添加..."
+            @keyup.enter="addItem('want')"
+          />
+          <button class="kwlAddBtn kwlAddBtnWant" @click="addItem('want')">+</button>
+        </div>
+      </div>
+
+      <!-- L - 已学 -->
+      <div class="kwlColumn kwlColLearn">
+        <div class="kwlColTitle kwlColTitleLearn">L - 已学</div>
+        <div class="kwlColBody">
+          <div class="kwlEmptyTip" v-if="!work.learned || work.learned.length === 0">
+            点击下方添加内容
+          </div>
+          <div class="kwlItem" v-for="(item, idx) in work.learned" :key="'l-' + idx">
+            <span class="kwlItemText">{{ item }}</span>
+            <span class="kwlItemDel" @click="removeItem('learned', idx)">×</span>
+          </div>
+        </div>
+        <div class="kwlAddRow">
+          <input
+            v-model="newLearned"
+            class="kwlInput"
+            placeholder="添加..."
+            @keyup.enter="addItem('learned')"
+          />
+          <button class="kwlAddBtn kwlAddBtnLearn" @click="addItem('learned')">+</button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 学习反思 -->
+    <div class="kwlCard kwlReflection">
+      <div class="kwlRefTitle">学习反思</div>
+      <div class="kwlRefAreaWrap">
+        <textarea
+          v-model="work.reflection"
+          class="kwlRefTextarea"
+          placeholder="回顾整个学习过程,你有什么新的发现或感想?"
+        ></textarea>
+        <!-- 右下角麦克风按钮:点击开始/停止录音 -->
+        <div
+          class="kwlMicBtn"
+          :class="{ kwlMicBtnActive: recordObj.status == '1' }"
+          :title="recordObj.status == '1' ? '停止录音' : '语音输入'"
+          @click="recordObj.status == '1' ? stopRecord() : startRecord()"
+        >
+          <!-- 录音中:声波动画 -->
+          <svg v-if="recordObj.status == '1'" width="18" height="18" viewBox="0 0 120 20">
+            <rect x="5" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0s" />
+            </rect>
+            <rect x="29" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
+            </rect>
+            <rect x="53" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
+            </rect>
+            <rect x="77" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
+            </rect>
+            <rect x="101" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
+            </rect>
+          </svg>
+          <!-- 空闲:麦克风图标 -->
+          <svg v-else viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
+            <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
+            <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
+            <line x1="12" y1="19" x2="12" y2="23"></line>
+            <line x1="8" y1="23" x2="16" y2="23"></line>
+          </svg>
+        </div>
+      </div>
+    </div>
+
+    <!-- 录音转文字 iframe -->
+    <iframe allow="camera *; microphone *;display-capture;midi;encrypted-media;" :src="iframeUrl" ref="iframeRef"
+      v-show="false"></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    workData: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      work: {
+        know: [],
+        want: [],
+        learned: [],
+        reflection: "",
+        taskDescription: "",
+        showHowTo: false,
+        kplaceholder:"",
+        wplaceholder:"",
+        lplaceholder:"",
+        isShowReflection: true
+      },
+      newKnow: "",
+      newWant: "",
+      newLearned: "",
+      showHowTo: false,
+      showTaskDesc: true,
+      recordObj: {
+        status: "0", // 0:默认  1:录音中
+        loading: false,
+        startTime: 0,
+      },
+      howToSteps: [
+        "在K栏填写:我已经知道什么(Know)",
+        "在W栏填写:我想知道什么(Want to know)",
+        "学习后在L栏填写:我学到了什么(Learned)",
+        "反思:对比K和L栏,总结学习收获"
+      ]
+    };
+  },
+  computed: {
+    iframeUrl() {
+      if (this.lang && this.lang.lang == 'com') {
+        return `https://cloud.cocorobo.com/browser/public/index.html`;
+      } else if (this.lang && this.lang.lang == 'hk') {
+        return `https://cloud.cocorobo.hk/browser/public/index.html`;
+      } else {
+        return `https://beta.cloud.cocorobo.cn/browser/public/index.html`;
+      }
+    }
+  },
+  watch: {
+    work: {
+      deep: true,
+      handler(newValue) {
+        if (JSON.stringify(newValue) != JSON.stringify(this.workData.json)) {
+          this.changeWorkData(newValue);
+        }
+      }
+    },
+    workData: {
+      handler: function (newValue, oldValue) {
+        let _data = JSON.parse(JSON.stringify(this.workData.json || {}));
+        if (JSON.stringify(newValue) != JSON.stringify(oldValue) || JSON.stringify(this.work) != JSON.stringify(_data)) {
+          this.work = Object.assign({
+        know: [],
+        want: [],
+        learned: [],
+        reflection: "",
+        taskDescription: "",
+        showHowTo: false,
+        kplaceholder:"",
+        wplaceholder:"",
+        lplaceholder:"",
+        isShowReflection: true
+          }, _data);
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    changeWorkData(newValue) {
+      const str = JSON.stringify(newValue);
+      this.$emit("changeWorkData", str);
+    },
+    addItem(type) {
+      const inputMap = { know: "newKnow", want: "newWant", learned: "newLearned" };
+      const val = this[inputMap[type]];
+      if (!val || !val.trim()) return;
+      if (!this.work[type]) this.$set(this.work, type, []);
+      this.work[type].push(val.trim());
+      this[inputMap[type]] = "";
+    },
+    removeItem(type, idx) {
+      this.work[type].splice(idx, 1);
+    },
+    // 开始录音
+    startRecord() {
+      let iframe = this.$refs["iframeRef"];
+      if (!iframe || !iframe.contentWindow) {
+        this.$message.error("录音服务未就绪");
+        return;
+      }
+      try {
+        iframe.contentWindow.window.document.getElementById(
+          "languageOptions"
+        ).selectedIndex = 2;
+
+        iframe.contentWindow.window.onRecognizedResult = (e) => {
+          let privText = e.privText;
+          if (!privText) {
+            return;
+          }
+          // 将识别文本追加到反思区域
+          if (this.work.reflection) {
+            this.work.reflection += " " + privText;
+          } else {
+            this.work.reflection = privText;
+          }
+        };
+
+        iframe.contentWindow.ConversationTranscriber();
+        this.recordObj.status = "1";
+        this.recordObj.startTime = new Date().getTime();
+        setTimeout(() => {
+          navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
+            if (permissionStatus.state !== "granted") {
+              this.recordObj.status = "0";
+              this.recordObj.loading = false;
+              iframe.contentWindow.onSessionStopped = null;
+              iframe.contentWindow.window.onRecognizedResult = null;
+              this.$message.warning("未获得麦克风权限,录音已停止");
+              return;
+            }
+          }).catch(() => {});
+        }, 2000);
+      } catch (err) {
+        console.error("startRecord error:", err);
+        this.$message.error("启动录音失败");
+        this.recordObj.status = "0";
+      }
+    },
+    // 停止录音
+    stopRecord() {
+      if (new Date().getTime() - this.recordObj.startTime < 2000) {
+        this.$message.info("录音时间过短");
+        return;
+      }
+      navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
+        if (permissionStatus.state !== "granted") {
+          this.recordObj.status = "0";
+          this.recordObj.loading = false;
+          let iframe = this.$refs["iframeRef"];
+          if (iframe && iframe.contentWindow) {
+            iframe.contentWindow.onSessionStopped = null;
+            iframe.contentWindow.window.onRecognizedResult = null;
+          }
+          this.$message.warning("未获得麦克风权限");
+          return;
+        }
+        if (this.recordObj.status == '1' && !this.recordObj.loading) {
+          let iframe = this.$refs["iframeRef"];
+          if (!iframe || !iframe.contentWindow) {
+            this.recordObj.status = "0";
+            this.recordObj.loading = false;
+            return;
+          }
+          this.recordObj.loading = true;
+          try {
+            iframe.contentWindow.window.document
+              .getElementById("scenarioStopButton")
+              .click();
+          } catch (err) {
+            console.error("stop click error:", err);
+            this.recordObj.status = "0";
+            this.recordObj.loading = false;
+            return;
+          }
+          iframe.contentWindow.onSessionStopped = () => {
+            this.recordObj.status = "0";
+            this.recordObj.loading = false;
+            this.$message.success("录音已停止");
+            iframe.contentWindow.onSessionStopped = null;
+            iframe.contentWindow.window.onRecognizedResult = null;
+          };
+        } else {
+          this.recordObj.status = "0";
+          this.recordObj.loading = false;
+          let iframe = this.$refs["iframeRef"];
+          if (iframe && iframe.contentWindow) {
+            iframe.contentWindow.onSessionStopped = null;
+            iframe.contentWindow.window.onRecognizedResult = null;
+          }
+        }
+      }).catch(() => {
+        this.recordObj.status = "0";
+        this.recordObj.loading = false;
+        let iframe = this.$refs["iframeRef"];
+        if (iframe && iframe.contentWindow) {
+          iframe.contentWindow.onSessionStopped = null;
+          iframe.contentWindow.window.onRecognizedResult = null;
+        }
+        this.$message.success("录音已停止");
+      });
+    }
+  },
+  mounted() {},
+  beforeUnmount() {}
+};
+</script>
+
+<style scoped>
+.kwlPage {
+  width: 100%;
+  min-height: 100%;
+  height: 100vh;
+  box-sizing: border-box;
+  padding: 1rem;
+  background: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+}
+
+.kwlCard {
+  background: #ffffff;
+  border-radius: .75rem;
+  padding: 1.25rem 1.5rem;
+  box-shadow: 0 .0625rem .1875rem rgba(0, 0, 0, 0.06);
+}
+
+/* 头部 */
+.kwlHeader {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.kwlTitle {
+  font-size: 1.25rem;
+  font-weight: 600;
+  color: #1a1a1a;
+  margin-bottom: .375rem;
+}
+
+.kwlSubtitle {
+  font-size: .8125rem;
+  color: #888;
+}
+
+.kwlHeaderRight {
+  display: flex;
+  align-items: center;
+  gap: .375rem;
+  color: #3681FC;
+  font-size: .875rem;
+  cursor: pointer;
+  user-select: none;
+  white-space: nowrap;
+}
+
+.kwlArrow {
+  display: flex;
+  align-items: center;
+  transition: transform 0.2s;
+}
+
+.kwlArrow svg {
+  width: 1rem;
+  height: 1rem;
+}
+
+.kwlArrowUp {
+  transform: rotate(180deg);
+}
+
+/* 怎么做 */
+.kwlHowTo {
+  border-top: .0625rem solid #e0e0e0;
+  display: flex;
+  flex-direction: column;
+  gap: .75rem;
+  padding-top: .9375rem;
+  margin-top: .9375rem;
+}
+
+.kwlHowToItem {
+  display: flex;
+  align-items: flex-start;
+  gap: .75rem;
+}
+
+.kwlStepNum {
+  width: 1.5rem;
+  height: 1.5rem;
+  border-radius: 50%;
+  background: #3681FC;
+  color: #fff;
+  font-size: .75rem;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.kwlStepText {
+  font-size: .875rem;
+  color: #333;
+  line-height: 1.5rem;
+}
+
+/* 任务说明 */
+.kwlTaskDesc {
+  padding: 1rem .9375rem;
+  border-left: .25rem solid #F5A623;
+}
+
+.kwlTaskHeader {
+  display: flex;
+  align-items: center;
+  gap: .5rem;
+  cursor: pointer;
+  user-select: none;
+}
+
+.kwlStar {
+  color: #F5A623;
+  font-size: 1rem;
+}
+
+.kwlTaskTitle {
+  font-size: .9375rem;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.kwlTaskContent {
+  margin-top: .625rem;
+  font-size: .875rem;
+  color: #444;
+  line-height: 1.375rem;
+  padding-left: 1.625rem;
+}
+
+/* 三列 */
+.kwlColumns {
+  display: flex;
+  gap: 1rem;
+}
+
+.kwlColumn {
+  flex: 1;
+  background: #ffffff;
+  border-radius: .75rem;
+  box-shadow: 0 .0625rem .1875rem rgba(0, 0, 0, 0.06);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.kwlColTitle {
+  font-size: .9375rem;
+  font-weight: 600;
+  padding: .875rem 1.125rem;
+  color: #fff;
+}
+
+.kwlColKnow {
+  border-top: .375rem solid #2563eb;
+}
+
+.kwlColTitleKnow {
+  color: #2563eb;
+}
+
+.kwlColWant {
+  border-top: .375rem solid #F56565;
+}
+
+.kwlColTitleWant {
+  color: #F56565;
+}
+
+.kwlColLearn {
+  border-top: .375rem solid #48BB78;
+}
+
+.kwlColTitleLearn {
+  color: #48BB78;
+}
+
+.kwlColBody {
+  flex: 1;
+  min-height: 10rem;
+  padding: 1rem;
+  display: flex;
+  flex-direction: column;
+  gap: .5rem;
+}
+
+.kwlEmptyTip {
+  text-align: center;
+  color: #bbb;
+  font-size: .8125rem;
+  padding: 1.875rem 0;
+  border: .125rem dashed #e0e0e0;
+  border-radius: .5rem;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.kwlItem {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: .5rem .75rem;
+  background: #f5f7fa;
+  border-radius: .375rem;
+  font-size: .8125rem;
+  color: #333;
+}
+
+.kwlItemText {
+  flex: 1;
+  word-break: break-all;
+}
+
+.kwlItemDel {
+  margin-left: .5rem;
+  width: 1.25rem;
+  height: 1.25rem;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: #999;
+  font-size: 1rem;
+  line-height: 1;
+  transition: all 0.2s;
+}
+
+.kwlItemDel:hover {
+  background: #eee;
+  color: #666;
+}
+
+.kwlAddRow {
+  display: flex;
+  align-items: center;
+  gap: .5rem;
+  padding: .75rem 1rem;
+  border-top: .0625rem solid #f0f0f0;
+}
+
+.kwlInput {
+  flex: 1;
+  border: none;
+  outline: none;
+  font-size: .8125rem;
+  padding: .375rem 0;
+  background: transparent;
+  color: #333;
+}
+
+.kwlInput::placeholder {
+  color: #bbb;
+}
+
+.kwlAddBtn {
+  width: 2rem;
+  height: 2rem;
+  border-radius: .5rem;
+  border: none;
+  color: #fff;
+  font-size: 1.125rem;
+  font-weight: 600;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: opacity 0.2s;
+}
+
+.kwlAddBtn:hover {
+  opacity: 0.85;
+}
+
+.kwlAddBtnKnow {
+  background: #3681FC;
+}
+
+.kwlAddBtnWant {
+  background: #F56565;
+}
+
+.kwlAddBtnLearn {
+  background: #48BB78;
+}
+
+/* 学习反思 */
+.kwlReflection {
+  padding: 1.25rem 1.5rem;
+}
+
+.kwlRefTitle {
+  font-size: 1rem;
+  font-weight: 600;
+  color: #1a1a1a;
+  margin-bottom: .75rem;
+}
+
+.kwlRefAreaWrap {
+  position: relative;
+}
+
+.kwlRefTextarea {
+  width: 100%;
+  min-height: 7.5rem;
+  padding: .75rem 3rem .75rem .875rem;
+  border: .0625rem solid #e0e0e0;
+  border-radius: .5rem;
+  font-size: .875rem;
+  line-height: 1.375rem;
+  resize: vertical;
+  outline: none;
+  box-sizing: border-box;
+  font-family: inherit;
+  transition: border-color 0.2s;
+}
+
+.kwlRefTextarea:focus {
+  border-color: #3681FC;
+}
+
+.kwlRefTextarea::placeholder {
+  color: #bbb;
+}
+
+/* 右下角麦克风按钮 */
+.kwlMicBtn {
+  position: absolute;
+  right: .75rem;
+  bottom: .75rem;
+  width: 2rem;
+  height: 2rem;
+  border-radius: 50%;
+  background: #f5f7fa;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: #888;
+  transition: all 0.2s;
+}
+
+.kwlMicBtn:hover {
+  background: #e8ecf1;
+  color: #3681FC;
+}
+
+/* 录音中:蓝色高亮 */
+.kwlMicBtnActive {
+  background: #3681FC;
+  color: #fff;
+}
+
+.kwlMicBtnActive:hover {
+  background: #5B9BFC;
+  color: #fff;
+}
+</style>

+ 18 - 2
src/components/pages/workPage/index_new.vue

@@ -8,6 +8,8 @@
       @changeWorkData="changeWorkData" />
     <photo ref="photoRef" v-if="workData.type === '79'" :workData="workData"
       @changeWorkData="changeWorkData" />
+    <kwlPage ref="kwlPageRef" v-if="workData.type === '831'" :workData="workData"
+      @changeWorkData="changeWorkData" />
   </div>
 </template>
 
@@ -18,12 +20,14 @@ import questionsAndAnswers from "./components/questionsAndAnswers.vue";
 import choiceQuestion from "./components/choiceQuestion.vue";
 import voteQuestion from "./components/voteQuestion.vue";
 import photo from "./components/photo.vue";
+import kwlPage from "./components/kwlPage.vue";
 export default {
   components: {
     questionsAndAnswers,
     choiceQuestion,
     voteQuestion,
-    photo
+    photo,
+    kwlPage,
   },
   data() {
     return {
@@ -279,9 +283,21 @@ export default {
             content: encodeURIComponent(JSON.stringify(this.workData.json)),
             type: "23"
           });
+        } else if (this.workData.type == "831") {
+          //跨学科kwl题
+          params.push({
+            uid: this.userid,
+            cid: this.courseid,
+            stage: 0,
+            task: task,
+            tool: 0,
+            atool: "831",
+            content: encodeURIComponent(JSON.stringify(this.workData.json)),
+            type: "831"
+          });
         }
         if (
-          ["3", "8", "22", "23"].includes(params[0].type) &&
+          ["3", "8", "22", "23", "831"].includes(params[0].type) && 
           params[0].uid &&
           params[0].cid
         ) {

+ 4 - 1
src/components/pages/workPage/setIndex.vue

@@ -11,6 +11,7 @@
     <setQuestionsAndAnswers :workData="workData" v-if="workData.type == 15" @setTestJson="setTestJson" @save="saveTest"/>
     <setVoteQuestion :workData="workData" v-if="workData.type == 78" @setTestJson="setTestJson" @save="saveTest"/>
     <setPhoto :workData="workData" v-if="workData.type == 79" @setTestJson="setTestJson" @save="saveTest"/>
+    <kwlPage :workData="workData" v-if="workData.type == 831" @setTestJson="setTestJson" @save="saveTest"/>
   </div>
 </template>
 
@@ -19,6 +20,7 @@ import setChoiceQuestion from './components/setChoiceQuestion.vue'
 import setQuestionsAndAnswers from './components/setQuestionsAndAnswers.vue'
 import setVoteQuestion from './components/setVoteQuestion.vue'
 import setPhoto from './components/setPhoto.vue'
+import kwlPage from './components/kwlPage.vue'
 
 function debounce(func, wait) {
   let timeout;
@@ -35,7 +37,8 @@ export default {
     setChoiceQuestion,
     setQuestionsAndAnswers,
     setVoteQuestion,
-    setPhoto
+    setPhoto,
+    kwlPage,
   },
   data() {
     return {