Browse Source

汉字书写

natasha 1 năm trước cách đây
mục cha
commit
dfd8bbd51b

+ 125 - 3
src/views/exercise_questions/preview/ChinesePreview.vue

@@ -24,6 +24,17 @@
           </template>
           <template v-else-if="data.property.learn_type === 'write'">
             <!-- 书写 -->
+            <div v-for="(items, indexs) in item.imgArr" :key="indexs" class="con-box">
+              <div class="strockplay-newWord" @click="freeWrite(items, index, indexs)">
+                <SvgIcon icon-class="hanzi-writer-bg" class="character-target-bg" />
+                <img
+                  v-if="!play_status && items && items.strokes_image_url"
+                  class="hanzi-writer-img"
+                  :src="items.strokes_image_url"
+                  alt=""
+                />
+              </div>
+            </div>
           </template>
           <template v-else>
             <div class="words-info">
@@ -41,6 +52,20 @@
         </template>
       </div>
     </div>
+    <div v-if="if_free_show" class="practiceBox practice-box-strock">
+      <FreewriteLettle
+        ref="freePaint"
+        :current-tree-i-d="'1234'"
+        :current-hz="current_hz"
+        :curren-hz-data="current_hz_data"
+        :row-index="active_index"
+        :col-index="active_col_index"
+        @closeIfFreeShow="closeIfFreeShow"
+        @changePraShow="changePraShow"
+        @changeCurQue="changeCurQue"
+        @deleteWriteRecord="deleteWriteRecord"
+      />
+    </div>
   </div>
 </template>
 
@@ -48,6 +73,7 @@
 import PreviewMixin from './components/PreviewMixin';
 import Strockplayredline from './components/common/Strockplayredline.vue';
 import Strockred from './components/common/Strockred.vue';
+import FreewriteLettle from './components/common/FreewriteLettle.vue';
 import { GetStaticResources } from '@/api/exercise';
 
 export default {
@@ -55,25 +81,37 @@ export default {
   components: {
     Strockplayredline,
     Strockred,
+    FreewriteLettle,
   },
   mixins: [PreviewMixin],
   data() {
     return {
       hanzi_color: '#404040', // 描红汉字底色
       writer_number: 19, // 书写个数
+      answer_list: {
+        write_model: {},
+      }, // 用户答题数据
+      if_free_show: false,
+      free_img: [],
+      active_index: null,
+      active_col_index: null,
+      current_hz: '', // 当前汉字
+      current_hz_data: null, // 当前汉字数据
+      play_status: false, // 播放状态
     };
   },
   created() {
-    console.log(this.data);
     this.handleData();
   },
   methods: {
     // 初始化数据
     handleData() {
-      // console.log(document.getElementsByClassName('preview-content')[0].clientWidth);
-
+      if (document.getElementsByClassName('preview-content') && document.getElementsByClassName('preview-content')[0]) {
+        this.writer_number = Math.floor(document.getElementsByClassName('preview-content')[0].clientWidth / 64);
+      }
       this.data.option_list.forEach((item) => {
         if (item.content.trim()) {
+          let arr = [];
           let MethodName = 'hz_resource_manager-GetHZStrokesContent';
           let data = {
             hz: item.content.trim(),
@@ -81,9 +119,52 @@ export default {
           GetStaticResources(MethodName, data).then((res) => {
             this.$set(item, 'strokes', res);
           });
+          if (this.data.property.learn_type === 'paint') {
+            this.writer_number -= 5;
+          }
+          for (let i = 0; i < this.writer_number; i++) {
+            arr.push(null);
+          }
+          item.imgArr = arr;
+          this.answer_list.write_model[item.content.trim()] = arr;
         }
       });
     },
+    changePraShow() {
+      this.if_free_show = false;
+    },
+    closeIfFreeShow(data, rowIndex, colIndex) {
+      this.data.option_list[rowIndex].imgArr[colIndex] = data;
+      this.if_free_show = false;
+      this.freeWrite(data, rowIndex, colIndex);
+      this.$forceUpdate();
+    },
+    freeWrite(imgUrl, index, indexs) {
+      let _this = this;
+      _this.if_free_show = true;
+      _this.active_index = index;
+      _this.active_col_index = indexs;
+      _this.current_hz = _this.data.option_list[index].content;
+      _this.current_hz_data = imgUrl;
+    },
+    // 删除记录
+    deleteWriteRecord(rowIndex, colIndex, current_hz) {
+      this.$set(this.data.option_list[rowIndex].imgArr, colIndex, {});
+      let answer = {
+        hz: current_hz,
+        strokes_content: '',
+        strokes_image_url: '',
+      };
+      this.changeCurQue(answer, colIndex);
+      this.$forceUpdate();
+    },
+    changeCurQue(answer, colIndex) {
+      if (answer) {
+        let write_model = this.answer_list.write_model;
+        let hz = answer.hz;
+        write_model[hz][colIndex] = answer;
+      }
+    },
   },
 };
 </script>
@@ -140,6 +221,47 @@ export default {
       margin-top: 9px;
       border: 1px solid #e81b1b;
     }
+
+    .strockplay-newWord {
+      position: relative;
+      width: 64px;
+      height: 64px;
+      border: 1px solid #e81b1b;
+
+      .character-target-bg,
+      .hanzi-writer-img {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        color: #dedede;
+      }
+
+      .hanzi-writer-img {
+        z-index: 1;
+      }
+    }
+  }
+
+  .practiceBox {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 101;
+    box-sizing: border-box;
+    width: 100%;
+    height: 100vh;
+    overflow: hidden;
+    overflow-y: auto;
+    background: rgba(0, 0, 0, 19%);
+
+    &.practice-box-strock {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding-top: 0;
+    }
   }
 }
 </style>

+ 261 - 0
src/views/exercise_questions/preview/components/common/FreeWriteQP.vue

@@ -0,0 +1,261 @@
+<template>
+  <canvas id="panel1" ref="canvas"></canvas>
+</template>
+
+<script>
+export default {
+  props: {
+    width: {
+      type: Number,
+      default: 800,
+    },
+    height: {
+      type: Number,
+      default: 300,
+    },
+    lineWidth: {
+      type: Number,
+      default: 4,
+    },
+    lineColor: {
+      type: String,
+      default: '#000000',
+    },
+    bgColor: {
+      type: String,
+      default: '',
+    },
+    isCrop: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      hasDrew: false,
+      resultImg: '',
+      canvasTxt: null,
+      canvas: null,
+      redrawCanvas: null,
+      isMouseDown: false,
+      lastLoc: { x: 0, y: 0 },
+      history: [],
+      sratio: 1,
+    };
+  },
+  computed: {
+    ratio() {
+      return this.height / this.width;
+    },
+    stageInfo() {
+      return this.$refs.canvas.getBoundingClientRect();
+    },
+    myBg() {
+      return this.bgColor ? this.bgColor : 'rgba(255, 255, 255, 0)';
+    },
+    context() {
+      return this.$refs.canvas.getContext('2d');
+    },
+    redrawCxt() {
+      return this.$refs.canvas.getContext('2d');
+    },
+  },
+  watch: {
+    myBg(newVal) {
+      this.$refs.canvas.style.background = newVal;
+    },
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.$_resizeHandler);
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.$_resizeHandler);
+  },
+  mounted() {
+    const canvas = this.$refs.canvas;
+    canvas.height = this.height;
+    canvas.width = this.width;
+    canvas.style.background = this.myBg;
+    this.$_resizeHandler();
+    // 在画板以外松开鼠标后冻结画笔
+    document.onmouseup = () => {
+      this.isMouseDown = false;
+    };
+    this.canvas = canvas;
+    this.redrawCanvas = canvas;
+    this.init();
+  },
+  methods: {
+    reload() {
+      this.reset();
+      this.redraw(this.redrawCxt);
+    },
+    init() {
+      this.canvas.onmousedown = (e) => {
+        this.isMouseDown = true;
+        this.hasDrew = true;
+        this.lastLoc = this.window2Canvas(e.clientX, e.clientY);
+      };
+      this.canvas.ontouchstart = (e) => {
+        this.isMouseDown = true;
+        this.hasDrew = true;
+        this.lastLoc = this.window2Canvas(e.touches[0].clientX, e.touches[0].clientY);
+      };
+      this.canvas.onmouseout = () => {
+        this.isMouseDown = false;
+      };
+      this.canvas.onmousemove = (e) => {
+        if (this.isMouseDown) {
+          let curLoc = this.window2Canvas(e.clientX, e.clientY); // 获得当前坐标
+          this.draw(this.context, this.lastLoc, curLoc);
+          this.history.push([this.lastLoc, curLoc]);
+          this.lastLoc = Object.assign({}, curLoc); // U know what I mean.
+        }
+      };
+      this.canvas.ontouchmove = (e) => {
+        if (this.isMouseDown) {
+          let curLoc = this.window2Canvas(e.touches[0].clientX, e.touches[0].clientY); // 获得当前坐标
+          this.draw(this.context, this.lastLoc, curLoc);
+          this.history.push([this.lastLoc, curLoc]);
+          this.lastLoc = Object.assign({}, curLoc); // U know what I mean.
+        }
+      };
+      this.canvas.onmouseup = () => {
+        this.isMouseDown = false;
+        if (history.length) {
+          localStorage.setItem('history', JSON.stringify(this.history));
+        }
+      };
+      this.canvas.onTouchend = () => {
+        this.isMouseDown = false;
+        if (history.length) {
+          localStorage.setItem('history', JSON.stringify(this.history));
+        }
+      };
+    },
+    window2Canvas(x, y) {
+      let bbox = this.canvas.getBoundingClientRect();
+      return { x: Math.round(x - bbox.left), y: Math.round(y - bbox.top) };
+    },
+    draw(context, lastLoc, curLoc) {
+      if (context) {
+        context.lineWidth = 5;
+        context.beginPath();
+        context.moveTo(lastLoc.x, lastLoc.y);
+        context.lineTo(curLoc.x, curLoc.y);
+        context.strokeStyle = '#000';
+        context.lineCap = 'round';
+        context.lineJoin = 'round';
+        context.stroke();
+      } else {
+        this.redrawCxt.lineWidth = 5;
+        this.redrawCxt.beginPath();
+        this.redrawCxt.moveTo(lastLoc.x, lastLoc.y);
+        this.redrawCxt.lineTo(curLoc.x, curLoc.y);
+        this.redrawCxt.strokeStyle = '#000';
+        this.redrawCxt.lineCap = 'round';
+        this.redrawCxt.lineJoin = 'round';
+        this.redrawCxt.stroke();
+      }
+    },
+    redraw(context) {
+      if (localStorage.getItem('history')) {
+        let history = JSON.parse(localStorage.getItem('history'));
+        const len = history.length;
+        let i = 0;
+        const runner = () => {
+          i += 1;
+          if (i < len) {
+            this.draw(context, history[i][0], history[i][1]);
+            requestAnimationFrame(runner);
+          }
+        };
+        requestAnimationFrame(runner);
+      }
+    },
+
+    $_resizeHandler() {
+      const canvas = this.$refs.canvas;
+      canvas.style.width = `${this.width}px`;
+      const realw = parseFloat(window.getComputedStyle(canvas).width);
+      canvas.style.height = `${this.ratio * realw}px`;
+      this.canvasTxt = canvas.getContext('2d');
+      this.canvasTxt.scale(Number(this.sratio), Number(this.sratio));
+      this.sratio = realw / this.width;
+      this.canvasTxt.scale(1 / this.sratio, 1 / this.sratio);
+    },
+    // 操作
+    generate() {
+      const pm = new Promise((resolve, reject) => {
+        if (!this.hasDrew) {
+          reject(`Warning: Not Signned!`);
+          return;
+        }
+        let resImgData = this.canvasTxt.getImageData(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
+        this.canvasTxt.globalCompositeOperation = 'destination-over';
+        this.canvasTxt.fillStyle = this.myBg;
+        this.canvasTxt.fillRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
+        this.resultImg = this.$refs.canvas.toDataURL();
+        let resultImg = this.resultImg;
+        this.canvasTxt.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
+        this.canvasTxt.putImageData(resImgData, 0, 0);
+        this.canvasTxt.globalCompositeOperation = 'source-over';
+        if (this.isCrop) {
+          const crop_area = this.getCropArea(resImgData.data);
+          let crop_canvas = document.createElement('canvas');
+          const crop_ctx = crop_canvas.getContext('2d');
+          crop_canvas.width = crop_area[2] - crop_area[0];
+          crop_canvas.height = crop_area[3] - crop_area[1];
+          const crop_imgData = this.canvasTxt.getImageData(...crop_area);
+          crop_ctx.globalCompositeOperation = 'destination-over';
+          crop_ctx.putImageData(crop_imgData, 0, 0);
+          crop_ctx.fillStyle = this.myBg;
+          crop_ctx.fillRect(0, 0, crop_canvas.width, crop_canvas.height);
+          resultImg = crop_canvas.toDataURL();
+          crop_canvas = null;
+        }
+        resolve(resultImg);
+      });
+      return pm;
+    },
+    reset() {
+      this.canvasTxt.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
+      this.$emit('update:bgColor', '');
+      this.$refs.canvas.style.background = 'rgba(255, 255, 255, 0)';
+      this.history = [];
+      this.hasDrew = false;
+      this.resultImg = '';
+    },
+    getCropArea(imgData) {
+      let topX = this.$refs.canvas.width;
+      let btmX = 0;
+      let topY = this.$refs.canvas.height;
+      let btnY = 0;
+      for (let i = 0; i < this.$refs.canvas.width; i++) {
+        for (let j = 0; j < this.$refs.canvas.height; j++) {
+          let pos = (i + this.$refs.canvas.width * j) * 4;
+          if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {
+            btnY = Math.max(j, btnY);
+            btmX = Math.max(i, btmX);
+            topY = Math.min(j, topY);
+            topX = Math.min(i, topX);
+          }
+        }
+      }
+      topX += 1;
+      btmX += 1;
+      topY += 1;
+      btnY += 1;
+      const data = [topX, topY, btmX, btnY];
+      return data;
+    },
+  },
+};
+</script>
+
+<style scoped>
+canvas {
+  display: block;
+  max-width: 100%;
+}
+</style>

+ 596 - 0
src/views/exercise_questions/preview/components/common/FreewriteLettle.vue

@@ -0,0 +1,596 @@
+<!--  -->
+<template>
+  <div class="practice practiceSingleNPC">
+    <img class="close-icon" @click="changePraShow()" />
+    <div class="right-content">
+      <div class="right-strockred">
+        <template v-if="!hasPlay && data && data.strokes_image_url">
+          <img class="img" :src="data.strokes_image_url" alt="" />
+        </template>
+        <FreeWriteQP
+          id="esign"
+          ref="esign"
+          :bg-color.sync="bgColor"
+          :height="height"
+          :is-crop="isCrop"
+          :line-color="hanzicolor"
+          :line-width="hanziweight"
+          :width="width"
+          class="vueEsign"
+        />
+        <a class="clean-btn" @click="resetHuahua"></a>
+      </div>
+      <ul class="nav-list">
+        <li :class="currenHzData && currenHzData.history ? '' : 'disabled'" @click="play()">播放</li>
+        <li @click="handleWriteImg">保存</li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+import FreeWriteQP from './FreeWriteQP.vue';
+export default {
+  components: {
+    FreeWriteQP,
+  },
+  // props: ['currentTreeID', 'currentHz', 'currenHzData', 'closeifFreeShow', 'rowIndex', 'colIndex'],
+  props: {
+    currentTreeID: {
+      type: String,
+      default: '',
+    },
+    currentHz: {
+      type: String,
+      default: '',
+    },
+    currenHzData: {
+      type: Object,
+      default: () => ({
+        strokes_image_url: '',
+        history: [],
+      }),
+    },
+    rowIndex: {
+      type: Number,
+      default: 0,
+    },
+    colIndex: {
+      type: Number,
+      default: 0,
+    },
+  },
+  data() {
+    return {
+      width: 256,
+      height: 256,
+      bgColor: '',
+      isCrop: false,
+      //   learn_mode: "",
+      playStorkes: false,
+      navIndex: 0,
+      colorsList: ['#404040', '#f65d4d', '#19b068', '#52a1ea', '#ff8c49'],
+      weightList: [6, 10],
+      colorIndex: 0,
+      penIndex: 0,
+      hanzicolor: '',
+      hanziweight: '',
+      imgOrCans: false,
+      hasPlay: false,
+      data: null,
+    };
+  },
+  computed: {},
+  watch: {},
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {
+    let _this = this;
+    let color = _this.colorsList[_this.colorIndex];
+    _this.hanzicolor = color;
+    _this.hanziweight = 6;
+    if (_this.currenHzData && _this.currenHzData.strokes_image_url) {
+      _this.imgOrCans = true;
+    }
+    _this.data = JSON.parse(JSON.stringify(_this.currenHzData));
+  },
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted() {},
+  beforeCreate() {}, // 生命周期 - 创建之前
+  beforeMount() {}, // 生命周期 - 挂载之前
+  beforeUpdate() {}, // 生命周期 - 更新之前
+  updated() {}, // 生命周期 - 更新之后
+  beforeDestroy() {}, // 生命周期 - 销毁之前
+  destroyed() {}, // 生命周期 - 销毁完成
+  activated() {},
+  // 方法集合
+  methods: {
+    play() {
+      let _this = this;
+      if (this.currenHzData.history) {
+        if (this.hasPlay) {
+          this.$message.warning('请等待播放完成');
+          return;
+        }
+        this.$refs.esign.reset();
+        this.hasPlay = true;
+        let c = document.getElementById('esign');
+        let cxt = document.getElementById('esign').getContext('2d');
+        cxt.clearRect(0, 0, c.width, c.height);
+        let history = null;
+        history = _this.currenHzData.history;
+        const len = history.length;
+        let i = 0;
+        const runner = () => {
+          i += 1;
+          if (i < len) {
+            _this.$refs.esign.draw(null, history[i][0], history[i][1]);
+            requestAnimationFrame(runner);
+          } else {
+            _this.hasPlay = false;
+          }
+        };
+        requestAnimationFrame(runner);
+      }
+    },
+
+    changeNav(index) {
+      this.navIndex = index;
+    },
+    changeColor(index) {
+      let _this = this;
+      _this.colorIndex = index;
+      let color = _this.colorsList[index];
+      _this.hanzicolor = color;
+    },
+    // changePen(index) {
+    //   let _this = this;
+    //   _this.penIndex = index;
+    //   _this.hanziweight = _this.weightList[_this.penIndex];
+    // },
+    changeLearnMode(mode) {
+      this.learn_mode = mode;
+    },
+    resetHuahua() {
+      let _this = this;
+      if (_this.hasPlay) {
+        _this.$message.warning('请等待播放完成');
+        return;
+      }
+      _this.imgOrCans = false;
+      _this.$refs.esign.reset();
+      if (_this.data) {
+        _this.data.strokes_image_url = '';
+      }
+      _this.$emit('deleteWriteRecord', _this.rowIndex, _this.colIndex, _this.currentHz);
+      // this.removeImage();
+    },
+    removeImage() {
+      let _this = this;
+      if (_this.data) {
+        // let MethodName = 'teaching-practice_manager-DeleteMyHZHandwrittenRecord';
+        // let data = {
+        //   hz_handwritten_record_id: this.data.hz_handwritten_record_id,
+        // };
+        // LearnWebSI(MethodName, data).then((res) => {
+        _this.$message.success('删除成功');
+        _this.data = {};
+        _this.$emit('deleteWriteRecord', _this.rowIndex, _this.colIndex);
+        // });
+      }
+    },
+    // 不保存到记录列表
+    handleWriteImg() {
+      this.$refs.esign.generate().then((res) => {
+        let Book_img = res.replace('data:image/png;base64,', '');
+        let write_img = `data:image/png;base64,${Book_img}`;
+        let answer = {};
+        answer = {
+          hz: this.currentHz,
+          strokes_content: JSON.stringify(this.$refs.esign.history),
+          strokes_image_url: write_img,
+        };
+        this.$emit('changeCurQue', answer, this.colIndex);
+        let obj = {
+          history: this.$refs.esign.history,
+          strokes_image_url: write_img,
+        };
+        this.$emit('closeIfFreeShow', obj, this.rowIndex, this.colIndex);
+        // this.$message.warning("请先书写在保存");
+      });
+    },
+    // 保存到记录列表
+    handleWriteImg_save() {
+      this.$refs.esign
+        .generate()
+        .then((res) => {
+          let Book_img = res.replace('data:image/png;base64,', '');
+          let write_img = `data:image/png;base64,${Book_img}`;
+          let answer = {};
+          answer = {
+            hz: this.currentHz,
+            strokes_content: JSON.stringify(this.$refs.esign.history),
+            strokes_image_url: write_img,
+          };
+          this.$emit('changeCurQue', answer, this.colIndex);
+
+          // let data = {
+          //   courseware_id: this.currentTreeID,
+          //   hz: this.currentHz,
+          //   strokes_content: JSON.stringify(this.$refs.esign.history),
+          //   strokes_image_base64: Book_img,
+          // };
+          // let MethodName = 'teaching-practice_manager-SaveMyHZHandwrittenRecord';
+          // LearnWebSI(MethodName, data).then((res) => {
+          this.$message.success('保存成功!');
+          let obj = {
+            hz_handwritten_record_id: res.hz_handwritten_record_id,
+            history: this.$refs.esign.history,
+            strokes_image_url: write_img,
+          };
+          this.$emit('closeIfFreeShow', obj, this.rowIndex, this.colIndex);
+          // });
+        })
+        .catch(() => {
+          this.$message.warning('请先书写在保存');
+        });
+    },
+    changePraShow() {
+      this.$emit('changePraShow');
+    },
+  }, // 如果页面有keep-alive缓存功能,这个函数会触发
+};
+</script>
+<style lang="scss" scoped>
+.practice {
+  position: relative;
+  width: 320px;
+  max-height: 400px;
+  margin: 0 auto;
+  background: #f3f3f3;
+  border-radius: 8px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 15%);
+
+  .clean-btn {
+    position: absolute;
+    right: 8px;
+    bottom: 8px;
+    width: 16px;
+    height: 16px;
+    margin: 0 4px;
+    cursor: pointer;
+
+    // background: url('../../../../assets/icon/Undo-16-disable-Black.png') center no-repeat;
+    // background-size: cover;
+    // display: block;
+    // &:hover {
+    //   background: url('../../../../assets/icon/Undo-16-normal-Black.png') center no-repeat;
+    //   background-size: cover;
+    // }
+  }
+
+  .close-icon {
+    position: absolute;
+    top: 0;
+    right: 8px;
+    z-index: 2;
+    width: 32px;
+    height: 32px;
+    padding: 8px;
+    cursor: pointer;
+  }
+
+  .Book_content {
+    position: relative;
+    box-sizing: border-box;
+    display: flex;
+    align-items: flex-start;
+    width: 100%;
+    height: 100%;
+  }
+
+  .left-content {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    width: 144px;
+
+    .left-content-pra {
+      height: 162px;
+    }
+  }
+
+  .right-content {
+    position: relative;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: flex-start;
+    width: 288px;
+    height: 360px;
+    padding: 30px 16px;
+    margin: 0 auto;
+    background: #f3f3f3;
+    border-radius: 16px;
+
+    .nav-list {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 100%;
+      height: 34px;
+      padding: 0;
+      margin-top: 16px;
+      list-style: none;
+
+      > li {
+        width: 124px;
+        height: 34px;
+        font-size: 14px;
+        font-style: normal;
+        font-weight: bold;
+        line-height: 34px;
+        color: #fff;
+        text-align: center;
+        cursor: pointer;
+        background: #de4444;
+        border-radius: 8px;
+
+        &:hover {
+          background: #f76565;
+        }
+
+        &:active {
+          background: #c43c3c;
+        }
+
+        &.disabled {
+          color: #fff;
+          cursor: not-allowed;
+          background-color: #c8c9cc;
+          background-image: none;
+          border-color: #c8c9cc;
+        }
+      }
+    }
+
+    .right-strockred {
+      position: relative;
+      width: 256px;
+      height: 256px;
+      overflow: hidden;
+
+      // background: #fff url('../../../../assets/NPC/chinaTianRed.png') center no-repeat;
+      background-size: 100% 100%;
+      border-radius: 8px;
+
+      .img {
+        position: absolute;
+      }
+
+      // .strock-play-box {
+      //   position: absolute;
+      //   width: 32px;
+      //   height: 32px;
+      //   background: #fff
+      //     url("../../../../assets/NPC/strock-play-red-click-big.png") center
+      //     no-repeat;
+      //   background-size: 100% 100%;
+      //   cursor: pointer;
+      //   right: 0;
+      //   top: 0;
+      //   z-index: 2;
+      // }
+    }
+
+    .footer {
+      position: absolute;
+      bottom: 80px;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      width: 100%;
+      padding-right: 40px;
+
+      .pen-colors {
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+
+        .write-icon-3 {
+          width: 20px;
+          height: 20px;
+          margin-right: 12px;
+        }
+
+        .colors-list {
+          display: flex;
+          align-items: center;
+          justify-content: flex-start;
+          padding: 0;
+          margin: 0;
+
+          > li {
+            box-sizing: border-box;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 20px;
+            height: 20px;
+            margin: 0 4px;
+            cursor: pointer;
+            background: #fff;
+            border: 2px solid #fff;
+            border-radius: 50%;
+
+            > span {
+              width: 14px;
+              height: 14px;
+              border-radius: 100%;
+
+              &.color-item0 {
+                background: #404040;
+              }
+
+              &.color-item1 {
+                background: #f65d4d;
+              }
+
+              &.color-item2 {
+                background: #19b068;
+              }
+
+              &.color-item3 {
+                background: #52a1ea;
+              }
+
+              &.color-item4 {
+                background: #ff8c49;
+              }
+            }
+
+            &.color-item-active0 {
+              border: 2px solid #404040;
+            }
+
+            &.color-item-active1 {
+              border: 2px solid #f65d4d;
+            }
+
+            &.color-item-active2 {
+              border: 2px solid #19b068;
+            }
+
+            &.color-item-active3 {
+              border: 2px solid #52a1ea;
+            }
+
+            &.color-item-active4 {
+              border: 2px solid #ff8c49;
+            }
+          }
+        }
+      }
+
+      .pen {
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+
+        > img {
+          width: 21px;
+          height: 19px;
+          margin-left: 4px;
+          cursor: pointer;
+        }
+      }
+    }
+  }
+}
+
+.strockplay {
+  box-sizing: border-box;
+  width: 144px;
+  height: 144px;
+  overflow: hidden;
+  border: 2px solid #de4444;
+  border-radius: 8px;
+
+  .strockplayRedInner {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.left-content .footer {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  cursor: pointer;
+
+  .bg-box {
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 76px;
+    height: 32px;
+    padding: 4px 8px;
+    font-size: 16px;
+    line-height: 150%;
+    color: #000;
+    text-align: center;
+    background: #fff;
+    border: 1px solid rgba(0, 0, 0, 10%);
+    border-radius: 4px;
+
+    // &:nth-child(2) {
+    //   margin: 0 24px;
+    // }
+    img {
+      width: 24px;
+      height: 24px;
+      margin: 0;
+    }
+  }
+
+  .practice-icon {
+    height: 36px;
+    margin-top: 12px;
+  }
+
+  > span {
+    margin-bottom: 9px;
+    font-family: 'FZJCGFKTK';
+    font-size: 24px;
+    font-weight: 600;
+    line-height: 34px;
+    color: #ba7d21;
+
+    /* identical to box height */
+
+    text-align: center;
+  }
+}
+
+.el-tabs {
+  width: 100%;
+}
+</style>
+<style lang="scss">
+.practiceSingleNPC {
+  .el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
+    color: #000;
+  }
+
+  .el-tabs--border-card > .el-tabs__header .el-tabs__item:not(.is-disabled):hover {
+    color: #000;
+  }
+
+  .el-tabs__item,
+  .el-tabs--border-card > .el-tabs__header .el-tabs__item {
+    width: 80px;
+    height: 48px;
+    font-size: 16px;
+    font-weight: normal;
+    line-height: 150%;
+    line-height: 48px;
+    color: #000;
+    text-align: center;
+    border: none;
+  }
+
+  .el-tabs--border-card > .el-tabs__header {
+    background: #f3f3f3;
+    border: none;
+  }
+
+  .el-tabs--border-card > .el-tabs__content {
+    padding: 0;
+  }
+
+  .el-tab-pane {
+    display: flex;
+  }
+}
+</style>

+ 1 - 1
src/views/exercise_questions/preview/components/common/Strockred.vue

@@ -47,7 +47,7 @@ export default {
   },
   computed: {},
   watch: {
-    hanziColor(newVal, oldVal) {
+    hanziColor(newVal) {
       this.updateColor(newVal);
     },
     Book_text: {