diff --git a/angular.json b/angular.json
index 5952480a9ced5a16dc48a17e46fb1669d4e89426..52fb36708fbe391cb5bd6b42c6ea244b60adbb02 100644
--- a/angular.json
+++ b/angular.json
@@ -128,5 +128,8 @@
       }
     }
   },
-  "defaultProject": "ng-template-generator"
-}
+  "defaultProject": "ng-template-generator",
+  "cli": {
+    "analytics": false
+  }
+}
\ No newline at end of file
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index a0995a75739d55061953bdc16761ee2f6524aed1..1e65864e21f0d3dbfc95f1b4554f059478b36c0d 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -24,6 +24,9 @@ import {AudioRecorderComponent} from './common/audio-recorder/audio-recorder.com
 import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
 import { fas } from '@fortawesome/free-solid-svg-icons';
 import { far } from '@fortawesome/free-regular-svg-icons';
+import { CustomActionComponent } from './common/custom-action/custom-action.component';
+import { UploadDragonBoneComponent } from './common/upload-dragon-bone/upload-dragon-bone.component';
+import { SubTemplateComponent } from './common/sub-template/sub-template.component';
 
 registerLocaleData(zh);
 
@@ -40,8 +43,11 @@ registerLocaleData(zh);
     TimePipe,
     UploadVideoComponent,
     CustomHotZoneComponent,
+    SubTemplateComponent,
 
-    PlayerContentWrapperComponent
+    PlayerContentWrapperComponent,
+    CustomActionComponent,
+    UploadDragonBoneComponent
 
   ],
   imports: [
diff --git a/src/app/common/audio-recorder/audio-recorder.component.html b/src/app/common/audio-recorder/audio-recorder.component.html
index e174c34da6591aeefc1a0d1f9fb3576e6499d645..3c8d2d215843514f4ebc6a139ffde3b3307be3d0 100644
--- a/src/app/common/audio-recorder/audio-recorder.component.html
+++ b/src/app/common/audio-recorder/audio-recorder.component.html
@@ -35,7 +35,8 @@
     <ng-template #truthyTemplate >
 
       <div class="btn-delete" (click)="onBtnDeleteAudio()">
-        <fa-icon icon="close"></fa-icon>
+        <!-- <fa-icon icon="close"></fa-icon> -->
+        <i nz-icon nzType="close" nzTheme="outline"></i>
       </div>
 
     </ng-template>
diff --git a/src/app/common/audio-recorder/audio-recorder.component.ts b/src/app/common/audio-recorder/audio-recorder.component.ts
index 605db0ab773819d944f4b9932a82f5cfb6111f94..609ed49f10a526596589ea163bb6cb0696abe73f 100644
--- a/src/app/common/audio-recorder/audio-recorder.component.ts
+++ b/src/app/common/audio-recorder/audio-recorder.component.ts
@@ -26,7 +26,7 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
   uploadData;
 
   @Input()
-  needRemove = false;
+  needRemove = true;
 
   @Input()
   audioItem: any = null;
@@ -62,8 +62,25 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
       this.uploadUrl = url;
       this.uploadData = data;
     };
+
+    this.setUploadUrl();
   }
 
+
+  setUploadUrl() {
+
+    if (!this.uploadUrl) {
+
+      this.uploadUrl = (<any> window).courseware.uploadUrl();
+      this.uploadData = (<any> window).courseware.uploadData();
+
+      setTimeout(() => {
+        this.setUploadUrl();
+      }, 500);
+    }
+  }
+
+
   init() {
     this.playIcon = 'play';
     this.isPlaying = false;
@@ -157,6 +174,7 @@ export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
 
   onBtnDeleteAudio() {
     this.audioUrl = null;
+    this.audioUploaded.emit({});
     this.audioRemoved.emit();
   }
 
diff --git a/src/app/common/custom-action/custom-action.component.html b/src/app/common/custom-action/custom-action.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..1b8b57f06b62712d0aecd432219d2674d5af13ac
--- /dev/null
+++ b/src/app/common/custom-action/custom-action.component.html
@@ -0,0 +1,153 @@
+<div class="position-relative">
+
+
+  <button nz-button (click)="setAnimaBtnClick()" style=" border-radius: 0.5rem; border: 1px solid #ddd;" >
+    <i nz-icon nzType="tool" nzTheme="outline"></i>
+    {{btnName}}
+  </button>
+
+
+  <!--配置龙骨面板-->
+  <nz-modal [(nzVisible)]="panelVisible" (nzAfterClose)="closePanel()" [nzTitle]="btnName" (nzOnCancel)="panelCancel()" (nzOnOk)="panelOk()" nzOkText="保存">
+    <div>
+      <h4>基础内容:</h4>
+     
+      <div style="margin-bottom: 10px; width: 80%; margin: auto;">
+
+        <!-- <nz-radio-group [ngModel]="item.type" (ngModelChange)="radioChange($event)">
+          <label nz-radio nzValue="pic">图片</label>
+          <label nz-radio nzValue="text">文本</label>
+        </nz-radio-group> -->
+
+        <div *ngIf="item.type == 'pic'">
+          <app-upload-image-with-preview
+            [picUrl]="item?.pic_url"
+            (imageUploaded)="onItemImgUploadSuccess($event, item)">
+          </app-upload-image-with-preview>
+        </div>
+  
+        <div *ngIf="item.type == 'text'">
+          <input type="text" nz-input [(ngModel)]="item.text" (blur)="saveText(item)">
+        </div>
+
+        <div *ngIf="item.type == 'anima'" style="margin-left: 100px;">
+          <app-upload-dragon-bone
+            [skeJsonData]=item.skeJsonData
+            [texJsonData]=item.texJsonData
+            [texPngData]=item.texPngData
+            (save)="saveAnima($event)"
+          >
+          </app-upload-dragon-bone>
+        </div>
+      </div>      
+     
+
+      <nz-divider style="margin-top: 10px;"></nz-divider>
+
+      <h4>状态设置:</h4>
+      <div style="display: flex; justify-content: space-around;">
+
+        <div style="width: 30%; display: flex; align-items: center; flex-direction: column;">
+          <h5> 开始状态 </h5>
+          <div *ngFor="let op of item.changeOption; let i = index" style="margin-bottom: 5px; ">
+            <div style="display: flex; align-items: center; justify-content: center;">
+              <span>{{op[0]}}: </span> <input type="text" nz-input [(ngModel)]="op[1]" (blur)="saveText(op)" style="margin-left: 5px;">
+            </div>
+          </div>
+        </div>
+
+
+        <div style="width: 30%; display: flex; align-items: center; flex-direction: column; ">
+          <h5> 切换时间 </h5>
+          <div style="display: flex; width: 100%; align-items: center; justify-content: center;">
+            <span>time: </span> <input type="text" nz-input [(ngModel)]="item.changeTime" (blur)="saveText(item.changeTime)" style="width: 80px; margin-left: 5px;">
+          </div>
+        </div>
+
+
+        <div style="width: 30%; display: flex; align-items: center; flex-direction: column;">
+          <h5> 结束状态 </h5>
+          <div *ngFor="let op of item.changeOption; let i = index" style="margin-bottom: 5px; ">
+            <div style="display: flex; align-items: center; justify-content: center;">
+              <span>{{op[0]}}: </span> <input [disabled]="op[2] == null" type="text" nz-input [(ngModel)]="op[2]" (blur)="saveText(op)" style="margin-left: 5px;">
+            </div>
+          </div>
+        </div>
+
+      </div>
+
+      <div style="display: flex; align-items: center; justify-content: center; margin-top: 10px;">
+        <button style="margin-left: 20px; margin-top: -5px" nz-button (click)="copyChangeData()"> 复制数据 </button>
+        <div style="margin-left: 20px; margin-top: -5px" >
+  
+          <span>粘贴数据: </span>
+          <input style="width: 100px;" type="text" nz-input [(ngModel)]="pasteData" >
+          <button style="margin-left: 5px;" nz-button [disabled]="pasteData==''" nzType="primary" (click)="importData()">导入</button>
+  
+        </div>
+      </div>
+     
+
+      <nz-divider style="margin-top: 10px;"></nz-divider>
+
+      <h4> 音频设置 </h4>
+
+      <app-audio-recorder
+        style="margin: auto"
+        [audioUrl]="item?.audio_url"
+        (audioUploaded)="onItemAudioUploadSuccess($event, item)"
+      ></app-audio-recorder>
+     
+    </div>
+   
+
+
+<!-- 
+    <div class="anima-upload-btn">
+      <span style="margin-right: 10px">上传 ske_json 文件: </span>
+      <nz-upload [nzShowUploadList]="false" 
+                nzAccept="application/json"
+                [nzAction]="uploadUrl"
+                [nzData]="uploadData"
+                (nzChange)="skeJsonHandleChange($event)">
+        <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+      </nz-upload>
+      <i *ngIf="isSkeJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+      <span *ngIf="skeJsonData && skeJsonData['name']" style="margin-left: 10px"><u> {{skeJsonData['name']}} </u></span>
+    </div>
+
+    <div class="anima-upload-btn">
+      <span style="margin-right: 10px">上传 tex_json 文件: </span>
+      <nz-upload [nzShowUploadList]="false"
+                nzAccept="application/json"
+                [nzAction]="uploadUrl"
+                [nzData]="uploadData"
+                (nzChange)="texJsonHandleChange($event)">
+        <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+      </nz-upload>
+      <i *ngIf="isTexJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+      <span *ngIf="texJsonData && texJsonData['name']" style="margin-left: 10px"><u> {{texJsonData['name']}} </u></span>
+    </div>
+
+    <div class="anima-upload-btn">
+      <span style="margin-right: 10px">上传 tex_png 文件: </span>
+      <nz-upload [nzShowUploadList]="false"
+                nzAccept = "image/*"
+                [nzAction]="uploadUrl"
+                [nzData]="uploadData"
+                (nzChange)="texPngHandleChange($event)">
+        <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+      </nz-upload>
+      <i *ngIf="isTexPngLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+      <span *ngIf="texPngData && texPngData['name']" style="margin-left: 10px"><u> {{texPngData['name']}} </u></span>
+    </div>
+
+    <div class="anima-upload-btn" *ngIf="animaNames && animaNames.length > 0">
+      提示:需包含动画: {{animaNames.toString()}}.
+    </div> -->
+
+  </nz-modal>
+
+</div>
+
+
diff --git a/src/app/common/custom-action/custom-action.component.scss b/src/app/common/custom-action/custom-action.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e9c56bfc8de8aec772d05405514870f9bb6c30f6
--- /dev/null
+++ b/src/app/common/custom-action/custom-action.component.scss
@@ -0,0 +1,110 @@
+@import '../../style/common_mixin.css';
+
+.p-image-uploader {
+  position: relative;
+  display: block;
+  width: 100%;
+  height: 0;
+  padding-bottom: 56.25%;
+  .p-box {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    border: 2px dashed #ddd;
+    border-radius: 0.5rem;
+    background-color: #fafafa;
+    text-align: center;
+    color: #aaa;
+    .p-upload-icon {
+      text-align: center;
+      margin: auto;
+      .anticon-upload {
+        color: #888;
+        font-size: 5rem;
+      }
+      .p-progress-bar {
+        position: relative;
+        width: 20rem;
+        height: 1.5rem;
+        border: 1px solid #ccc;
+        border-radius: 1rem;
+        .p-progress-bg {
+          background-color: #1890ff;
+          border-radius: 1rem;
+          height: 100%;
+        }
+        .p-progress-value {
+          position: absolute;
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 100%;
+          text-shadow: 0 0 4px #000;
+          color: white;
+          text-align: center;
+          font-size: 0.9rem;
+          line-height: 1.5rem;
+        }
+      }
+    }
+    .p-preview {
+      width: 100%;
+      height: 100%;
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: 50% 50%;
+      //background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
+    }
+  }
+  .d-flex{
+    display: flex;
+  }
+}
+
+.p-btn-delete {
+  position: absolute;
+  right: -0.5rem;
+  top: -0.5rem;
+  width: 2rem;
+  height: 2rem;
+  border: 0.2rem solid white;
+  border-radius: 50%;
+  font-size: 1.2rem;
+  background-color: #fb781a;
+  color: white;
+  text-align: center;
+}
+
+.p-upload-progress-bg {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  & .i-text {
+    position: absolute;
+    text-align: center;
+    color: white;
+    text-shadow: 0 0 2px rgba(0, 0, 0, .85);
+  }
+  & .i-bg {
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+    background-color: #27b43f;
+    border-radius: 0.5rem;
+  }
+}
+
+.anima-upload-btn {
+  padding: 10px;
+}
+
+
+:host ::ng-deep .ant-upload {
+  display: block;
+}
diff --git a/src/app/common/custom-action/custom-action.component.ts b/src/app/common/custom-action/custom-action.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4fda2662506e0269759f77659f5597fb0f0fe671
--- /dev/null
+++ b/src/app/common/custom-action/custom-action.component.ts
@@ -0,0 +1,216 @@
+import {ApplicationRef, Component, EventEmitter, Input, OnChanges, OnDestroy, Output} from '@angular/core';
+import { NzMessageService, UploadXHRArgs, UploadFile } from 'ng-zorro-antd';
+
+
+@Component({
+  selector: 'app-custom-action',
+  templateUrl: './custom-action.component.html',
+  styleUrls: ['./custom-action.component.scss']
+})
+export class CustomActionComponent implements OnDestroy, OnChanges {
+  uploading = false;
+  progress = 0;
+  @Input()
+  btnName = '配置变化状态';
+
+  @Input()
+  option = []
+
+  @Input()
+  item;
+
+  @Input()
+  type = 'text'
+  
+
+  @Output()
+  save = new EventEmitter();
+
+  @Output()
+  refreshEmitter = new EventEmitter();
+
+  uploadUrl;
+  uploadData;
+
+  panelVisible = false;
+  pasteData = '';
+
+
+
+  constructor(private appRef: ApplicationRef, private nzMessageService: NzMessageService) {
+
+    window['air'].getUploadCallback = (url, data) => {
+      this.uploadUrl = url;
+      this.uploadData = data;
+    };
+
+    const tmpId = setInterval(() => {
+      console.log(' in setInterval')
+      this.uploadUrl = (<any> window).courseware.uploadUrl();
+      this.uploadData = (<any> window).courseware.uploadData();
+      if (this.uploadUrl) {
+        clearInterval(tmpId);
+      }
+    }, 500)
+
+  }
+
+  ngOnInit() {
+    this.initItem();
+  }
+
+  ngOnChanges() {
+ 
+  }
+
+  initItem() {
+
+    console.log('```this.item: ', this.item);
+    
+    if (!this.item) {
+      this.item = {
+        type: this.type,
+        skeJsonData: {},
+        texJsonData: {},
+        texPngData: {},
+
+
+        changeStart: [
+        
+        ],
+        changeEnd: [
+        
+        ],
+        changeTime: 0.3
+      };
+
+      if (this.option) {
+        this.item.changeOption = JSON.parse(JSON.stringify(this.option));
+      }
+    }
+
+    console.log('initItem', this.item);
+
+  }
+
+  setAnimaBtnClick() {
+    this.panelVisible = true;
+    this.refresh();
+  }
+
+  panelCancel() {
+    this.panelVisible = false;
+    this.refresh();
+  }
+
+  panelOk() {
+    // this.sendItemDragonBoneData();
+    this.panelVisible = false;
+    this.refresh();
+    this.save.emit(this.item);
+  }
+
+  saveText(t) {
+
+  }
+
+  radioChange(e) {
+    this.item.type = e;
+    console.log('e: ', e);
+  }
+
+
+  onItemImgUploadSuccess(e, item) {
+    item.pic_url = e.url;
+    this.refresh();
+  }
+
+
+  onItemAudioUploadSuccess(e, item) {
+    item.audio_url = e.url;
+    this.refresh();
+  }
+
+  saveAnima(e) {
+    console.log("~ e: ", e);
+  }
+
+  copyChangeData() {
+
+    console.log('this.item: ', this.item);
+
+    const jsonData = {
+      changeOption: this.item.changeOption,
+      changeTime : this.item.changeTime
+      // bgItem,
+      // hotZoneItemArr,
+      // isHasRect: this.isHasRect,
+      // isHasPic: this.isHasPic,
+      // isHasText: this.isHasText,
+      // isHasAudio: this.isHasAudio,
+      // isHasAnima: this.isHasAnima,
+      // hotZoneFontObj: this.hotZoneFontObj,
+      // defaultItemType: this.defaultItemType,
+      // hotZoneImgSize: this.hotZoneImgSize,
+    };
+
+
+    const oInput = document.createElement('input');
+    oInput.value = JSON.stringify(jsonData);
+    document.body.appendChild(oInput);
+    oInput.select(); // 选择对象
+    document.execCommand('Copy'); // 执行浏览器复制命令
+
+    document.body.removeChild(oInput);
+    this.nzMessageService.success('复制成功');
+    
+  }
+
+  importData() {
+    if (!this.pasteData) {
+      return;
+    }
+
+
+    try {
+      const data = JSON.parse(this.pasteData);
+      console.log('data:', data);
+      const {
+        changeOption,
+        changeTime
+      } = data;
+
+      this.item.changeOption = changeOption;
+      this.item.changeTime = changeTime;
+
+      this.pasteData = '';
+    } catch (e) {
+      console.log('err: ', e);
+      this.nzMessageService.error('导入失败');
+    }
+  }
+
+
+  /**
+   * 刷新 渲染页面
+   */
+  refresh() {
+
+    // this.refreshEmitter.emit();
+    setTimeout(() => {
+      this.appRef.tick();
+    }, 1);
+  }
+
+
+  closePanel() {
+    console.log(' in closePanel ');
+    this.refresh();
+  }
+
+
+
+  ngOnDestroy() {
+  }
+
+}
diff --git a/src/app/common/custom-hot-zone/Unit.ts b/src/app/common/custom-hot-zone/Unit.ts
index 7b99189ae1e79f71bf621d6c98beff55cb7df827..3345e2404d2ab972fd91322dec9d33410cf69f50 100644
--- a/src/app/common/custom-hot-zone/Unit.ts
+++ b/src/app/common/custom-hot-zone/Unit.ts
@@ -329,6 +329,7 @@ export class MySprite extends Sprite {
     this.scaleX = this.scaleY = value;
   }
 
+
   getBoundingBox() {
 
     const getParentData = (item) => {
@@ -1956,6 +1957,10 @@ export class HotZoneItem extends MySprite {
   audio_url;
   pic_url;
   text;
+  gIdx;
+
+  isAnimaStyle = false;
+
   private _itemType;
   private shapeRect: ShapeRect;
 
@@ -2056,7 +2061,10 @@ export class HotZoneItem extends MySprite {
     this.hideLabel();
   }
 
-
+  setAnimaStyle(isAnimaStyle) {
+    this.isAnimaStyle = isAnimaStyle;
+    console.log('in  setAnimaStyle ')
+  }
 
   drawArrow() {
     if (!this.arrow) { return; }
@@ -2068,13 +2076,16 @@ export class HotZoneItem extends MySprite {
     this.arrow.update();
 
 
-    this.arrowTop.x = rect.x + rect.width / 2;
-    this.arrowTop.y = rect.y;
-    this.arrowTop.update();
-
-    this.arrowRight.x = rect.x + rect.width;
-    this.arrowRight.y = rect.y + rect.height / 2;
-    this.arrowRight.update();
+    if (!this.isAnimaStyle) {
+      this.arrowTop.x = rect.x + rect.width / 2;
+      this.arrowTop.y = rect.y;
+      this.arrowTop.update();
+  
+      this.arrowRight.x = rect.x + rect.width;
+      this.arrowRight.y = rect.y + rect.height / 2;
+      this.arrowRight.update();
+    }
+   
   }
 
   drawFrame() {
@@ -2197,10 +2208,155 @@ export class HotZoneLabel extends Label {
 
     this.drawFrame();
   }
+
+
+  getLabelRect() {
+    
+
+    const rect = this.getBoundingBox();
+    const width = rect.width / this.scaleX;
+    const height = this.height * this.scaleY;
+    const x = this.x - width / 2;
+    const y = this.y - height / 2;
+
+    return {width, height, x, y};
+  }
+
 }
 
+export class HotZoneAction extends MySprite {
+
+  
+}
+
+export class DragItem extends MySprite {
+  
+  lineDashFlag = true;
+  item;
+
+  init() {
+    this.anchorX = 0.5;
+    this.anchorY = 0.5;
+
+    this.initCenterCircle();
+
+  }
+
+  setSize(w, h) {
+
+    this.anchorX = 0.5;
+    this.anchorY = 0.5;
+
+    this.width = w;
+    this.height = h;
+
+
+    // const rect = new ShapeRect(this.ctx);
+    // rect.x = -w / 2;
+    // rect.y = -h / 2;
+    // rect.setSize(w, h);
+    // rect.fillColor = '#000000';
+    // rect.alpha = 0.1;
+    // this.addChild(rect);
+
+
+  }
+
+  initCenterCircle() {
+    const circle = new ShapeCircle(this.ctx);
+    circle.setRadius(10);
+    circle.fillColor = '#ffa568'
+    this.addChild(circle);
+
+    this.width = circle.width;
+    this.height = circle.height;
+  }
+
+  getPosition() {
+    return {x: this.x, y: this.y};
+  }
+
+  drawLine() {
+    if (!this.item) {
+      return;
+    }
+
+    
+    this.ctx.save();
+
+    const rect = this.getBoundingBox();
+
+    const w = rect.width;
+    const h = rect.height;
+    const x = rect.x //+ w / 2;
+    const y = rect.y //+ h / 2;
+   
+    this.ctx.setLineDash([4, 4]);
+    this.ctx.lineWidth = 1;
+    this.ctx.strokeStyle = '#ffa568';
+
+    this.ctx.beginPath();
+
+    this.ctx.moveTo( x + w / 2 , y + h / 2 );
+
+    this.ctx.lineTo(this.item.x, this.item.y);
+
+    // this.ctx.fill();
+    this.ctx.stroke();
+
+
+    this.ctx.restore();
+  }
+
+  drawFrame() {
+
+
+    this.ctx.save();
+
+    const rect = this.getBoundingBox();
+
+    const w = rect.width;
+    const h = rect.height;
+    const x = rect.x //+ w / 2;
+    const y = rect.y //+ h / 2;
+
+    this.ctx.setLineDash([4, 4]);
+    this.ctx.lineWidth = 2;
+    this.ctx.strokeStyle = '#ffa568';
+    // this.ctx.fillStyle = '#ffffff';
+
+    this.ctx.beginPath();
+
+    this.ctx.moveTo( x - w / 2, y - h / 2);
+
+    this.ctx.lineTo(x + w / 2, y - h / 2);
 
+    this.ctx.lineTo(x + w / 2, y + h / 2);
 
+    this.ctx.lineTo(x - w / 2, y + h / 2);
+
+    this.ctx.lineTo(x - w / 2, y - h / 2);
+
+    // this.ctx.fill();
+    this.ctx.stroke();
+
+
+
+
+    this.ctx.restore();
+
+  }
+
+  draw() {
+    super.draw();
+
+    if (this.lineDashFlag) {
+      this.drawLine();
+      // this.drawFrame();
+      // this.drawArrow();
+    }
+  }
+}
 
 export class EditorItem extends MySprite {
 
@@ -2208,6 +2364,8 @@ export class EditorItem extends MySprite {
   arrow: MySprite;
   label: Label;
   text;
+  arrowTop;
+  arrowRight;
 
   showLabel(text = null) {
 
@@ -2246,6 +2404,13 @@ export class EditorItem extends MySprite {
       this.arrow.load('assets/common/arrow.png', 1, 0);
       this.arrow.setScaleXY(0.06);
 
+      // this.arrowTop = new MySprite(this.ctx);
+      // this.arrowTop.load('assets/common/arrow_top.png', 0.5, 0);
+      // this.arrowTop.setScaleXY(0.06);
+      //
+      // this.arrowRight = new MySprite(this.ctx);
+      // this.arrowRight.load('assets/common/arrow_right.png', 1, 0.5);
+      // this.arrowRight.setScaleXY(0.06);
     }
 
     this.showLabel();
@@ -2262,16 +2427,25 @@ export class EditorItem extends MySprite {
     this.hideLabel();
   }
 
-
+  refreshLabelScale() {}
 
   drawArrow() {
+
     if (!this.arrow) { return; }
 
     const rect = this.getBoundingBox();
     this.arrow.x = rect.x + rect.width;
     this.arrow.y = rect.y;
-
     this.arrow.update();
+
+
+    // this.arrowTop.x = rect.x + rect.width / 2;
+    // this.arrowTop.y = rect.y;
+    // this.arrowTop.update();
+    //
+    // this.arrowRight.x = rect.x + rect.width;
+    // this.arrowRight.y = rect.y + rect.height / 2;
+    // this.arrowRight.update();
   }
 
   drawFrame() {
@@ -2325,784 +2499,3 @@ export class EditorItem extends MySprite {
 }
 
 
-
-//
-//
-// import TWEEN from '@tweenjs/tween.js';
-//
-//
-// class Sprite {
-//   x = 0;
-//   y = 0;
-//   color = '';
-//   radius = 0;
-//   alive = false;
-//   margin = 0;
-//   angle = 0;
-//   ctx;
-//
-//   constructor(ctx) {
-//     this.ctx = ctx;
-//   }
-//   update($event) {
-//     this.draw();
-//   }
-//   draw() {
-//
-//   }
-//
-// }
-//
-//
-//
-//
-//
-// export class MySprite extends Sprite {
-//
-//   width = 0;
-//   height = 0;
-//   _anchorX = 0;
-//   _anchorY = 0;
-//   _offX = 0;
-//   _offY = 0;
-//   scaleX = 1;
-//   scaleY = 1;
-//   alpha = 1;
-//   rotation = 0;
-//   visible = true;
-//
-//   children = [this];
-//
-//   img;
-//   _z = 0;
-//
-//
-//   init(imgObj = null, anchorX:number = 0.5, anchorY:number = 0.5) {
-//
-//     if (imgObj) {
-//
-//       this.img = imgObj;
-//
-//       this.width = this.img.width;
-//       this.height = this.img.height;
-//     }
-//
-//     this.anchorX = anchorX;
-//     this.anchorY = anchorY;
-//   }
-//
-//
-//
-//   update($event = null) {
-//     if (this.visible) {
-//       this.draw();
-//     }
-//   }
-//   draw() {
-//
-//     this.ctx.save();
-//
-//     this.drawInit();
-//
-//     this.updateChildren();
-//
-//     this.ctx.restore();
-//   }
-//
-//   drawInit() {
-//
-//     this.ctx.translate(this.x, this.y);
-//
-//     this.ctx.rotate(this.rotation * Math.PI / 180);
-//
-//     this.ctx.scale(this.scaleX, this.scaleY);
-//
-//     this.ctx.globalAlpha = this.alpha;
-//
-//   }
-//
-//   drawSelf() {
-//     if (this.img) {
-//       this.ctx.drawImage(this.img, this._offX, this._offY);
-//     }
-//   }
-//
-//   updateChildren() {
-//
-//     if (this.children.length <= 0) { return; }
-//
-//     for (let i = 0; i < this.children.length; i++) {
-//
-//       if (this.children[i] === this) {
-//
-//         this.drawSelf();
-//       } else {
-//
-//         this.children[i].update();
-//       }
-//     }
-//   }
-//
-//
-//   load(url, anchorX = 0.5, anchorY = 0.5) {
-//
-//     return new Promise((resolve, reject) => {
-//       const img = new Image();
-//       img.onload = () => resolve(img);
-//       img.onerror = reject;
-//       img.src = url;
-//     }).then(img => {
-//
-//       this.init(img, anchorX, anchorY);
-//       return img;
-//     });
-//   }
-//
-//   addChild(child, z = 1) {
-//     if (this.children.indexOf(child) === -1) {
-//       this.children.push(child);
-//       child._z = z;
-//       child.parent = this;
-//     }
-//
-//     this.children.sort((a, b) => {
-//       return a._z - b._z;
-//     });
-//
-//   }
-//   removeChild(child) {
-//     const index = this.children.indexOf(child);
-//     if (index !== -1) {
-//       this.children.splice(index, 1);
-//     }
-//   }
-//
-//   set anchorX(value) {
-//     this._anchorX = value;
-//     this.refreshAnchorOff();
-//   }
-//   get anchorX() {
-//     return this._anchorX;
-//   }
-//   set anchorY(value) {
-//     this._anchorY = value;
-//     this.refreshAnchorOff();
-//   }
-//   get anchorY() {
-//     return this._anchorY;
-//   }
-//   refreshAnchorOff() {
-//     this._offX = -this.width * this.anchorX;
-//     this._offY = -this.height * this.anchorY;
-//   }
-//
-//   setScaleXY(value) {
-//     this.scaleX = this.scaleY = value;
-//   }
-//
-//   getBoundingBox() {
-//
-//     const x = this.x + this._offX * this.scaleX;
-//     const y = this.y + this._offY * this.scaleY;
-//     const width = this.width * this.scaleX;
-//     const height = this.height * this.scaleY;
-//
-//     return {x, y, width, height};
-//   }
-//
-// }
-//
-//
-//
-//
-//
-// export class Item extends MySprite {
-//
-//   baseX;
-//
-//   move(targetY, callBack) {
-//
-//     const self = this;
-//
-//     const tween = new TWEEN.Tween(this)
-//       .to({ y: targetY }, 2500)
-//       .easing(TWEEN.Easing.Quintic.Out)
-//       .onComplete(function() {
-//
-//         self.hide(callBack);
-//         // if (callBack) {
-//         //   callBack();
-//         // }
-//       })
-//       .start();
-//
-//   }
-//
-//   show(callBack = null) {
-//
-//     const tween = new TWEEN.Tween(this)
-//       .to({ alpha: 1 }, 800)
-//       // .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
-//       .onComplete(function() {
-//         if (callBack) {
-//           callBack();
-//         }
-//       })
-//       .start(); // Start the tween immediately.
-//
-//   }
-//
-//   hide(callBack = null) {
-//
-//     const tween = new TWEEN.Tween(this)
-//       .to({ alpha: 0 }, 800)
-//       // .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
-//       .onComplete(function() {
-//         if (callBack) {
-//           callBack();
-//         }
-//       })
-//       .start(); // Start the tween immediately.
-//   }
-//
-//
-//   shake(id) {
-//
-//
-//     if (!this.baseX) {
-//       this.baseX = this.x;
-//     }
-//
-//     const baseX = this.baseX;
-//     const baseTime = 50;
-//     const sequence = [
-//       {target: {x: baseX + 40 * id}, time: baseTime - 25},
-//       {target: {x: baseX - 20 * id}, time: baseTime},
-//       {target: {x: baseX + 10 * id}, time: baseTime},
-//       {target: {x: baseX - 5 * id}, time: baseTime},
-//       {target: {x: baseX + 2 * id}, time: baseTime},
-//       {target: {x: baseX - 1 * id}, time: baseTime},
-//       {target: {x: baseX}, time: baseTime},
-//
-//     ];
-//
-//
-//     const self = this;
-//
-//     function runSequence() {
-//
-//       if (self['shakeTween']) {
-//         self['shakeTween'].stop();
-//       }
-//
-//       const tween = new TWEEN.Tween(self);
-//
-//       if (sequence.length > 0) {
-//         // console.log('sequence.length: ', sequence.length);
-//         const action = sequence.shift();
-//         tween.to(action['target'], action['time']);
-//         tween.onComplete( () => {
-//           runSequence();
-//         });
-//         tween.start();
-//
-//         self['shakeTween'] = tween;
-//       }
-//     }
-//
-//     runSequence();
-//
-//   }
-//
-//
-//
-//   drop(targetY, callBack = null) {
-//
-//     const self = this;
-//
-//     const time = Math.abs(targetY - this.y) * 2.4;
-//
-//     this.alpha = 1;
-//
-//     const tween = new TWEEN.Tween(this)
-//       .to({ y: targetY }, time)
-//       .easing(TWEEN.Easing.Cubic.In)
-//       .onComplete(function() {
-//
-//         // self.hideItem(callBack);
-//         if (callBack) {
-//           callBack();
-//         }
-//       })
-//       .start();
-//
-//
-//   }
-//
-//
-// }
-//
-//
-// export class EndSpr extends MySprite {
-//
-//   show(s) {
-//
-//     this.scaleX = this.scaleY = 0;
-//     this.alpha = 0;
-//
-//     const tween = new TWEEN.Tween(this)
-//       .to({ alpha: 1, scaleX: s, scaleY: s }, 800)
-//       .easing(TWEEN.Easing.Elastic.Out) // Use an easing function to make the animation smooth.
-//       .onComplete(function() {
-//
-//       })
-//       .start(); // Start the tween immediately.
-//
-//   }
-// }
-//
-//
-//
-// export class ShapeRect extends MySprite {
-//
-//   fillColor = '#FF0000';
-//
-//   setSize(w, h) {
-//     this.width = w;
-//     this.height = h;
-//
-//     console.log('w:', w);
-//     console.log('h:', h);
-//   }
-//
-//   drawShape() {
-//
-//     this.ctx.fillStyle = this.fillColor;
-//     this.ctx.fillRect(this._offX, this._offY, this.width, this.height);
-//
-//   }
-//
-//
-//   drawSelf() {
-//     super.drawSelf();
-//     this.drawShape();
-//   }
-// }
-//
-//
-// export class HotZoneItem extends MySprite {
-//
-//
-//   lineDashFlag = false;
-//   arrow: MySprite;
-//   label: Label;
-//   title;
-//
-//   arrowTop;
-//   arrowRight;
-//
-//   audio_url;
-//   pic_url;
-//   text;
-//   private _itemType;
-//   private shapeRect: ShapeRect;
-//
-//   get itemType() {
-//     return this._itemType;
-//   }
-//   set itemType(value) {
-//     this._itemType = value;
-//   }
-//
-//   setSize(w, h) {
-//     this.width = w;
-//     this.height = h;
-//
-//
-//     const rect = new ShapeRect(this.ctx);
-//     rect.x = -w / 2;
-//     rect.y = -h / 2;
-//     rect.setSize(w, h);
-//     rect.fillColor = '#ffffff';
-//     rect.alpha = 0.2;
-//     this.addChild(rect);
-//   }
-//
-//   showLabel(text = null) {
-//
-//
-//     if (!this.label) {
-//       this.label = new Label(this.ctx);
-//       this.label.anchorY = 0;
-//       this.label.fontSize = '40px';
-//       this.label.textAlign = 'center';
-//       this.addChild(this.label);
-//       // this.label.scaleX = 1 / this.scaleX;
-//       // this.label.scaleY = 1 / this.scaleY;
-//
-//       this.refreshLabelScale();
-//
-//     }
-//
-//     if (text) {
-//       this.label.text = text;
-//     } else if (this.title) {
-//       this.label.text = this.title;
-//     }
-//     this.label.visible = true;
-//
-//   }
-//
-//   hideLabel() {
-//     if (!this.label) { return; }
-//
-//     this.label.visible = false;
-//   }
-//
-//   refreshLabelScale() {
-//     if (this.scaleX == this.scaleY) {
-//       this.label.setScaleXY(1);
-//     }
-//
-//     if (this.scaleX > this.scaleY) {
-//       this.label.scaleX = this.scaleY / this.scaleX;
-//     } else {
-//       this.label.scaleY = this.scaleX / this.scaleY;
-//     }
-//   }
-//
-//   showLineDash() {
-//     this.lineDashFlag = true;
-//
-//     if (this.arrow) {
-//       this.arrow.visible = true;
-//     } else {
-//       this.arrow = new MySprite(this.ctx);
-//       this.arrow.load('assets/common/arrow.png', 1, 0);
-//       this.arrow.setScaleXY(0.06);
-//
-//       this.arrowTop = new MySprite(this.ctx);
-//       this.arrowTop.load('assets/common/arrow_top.png', 0.5, 0);
-//       this.arrowTop.setScaleXY(0.06);
-//
-//       this.arrowRight = new MySprite(this.ctx);
-//       this.arrowRight.load('assets/common/arrow_right.png', 1, 0.5);
-//       this.arrowRight.setScaleXY(0.06);
-//     }
-//
-//     this.showLabel();
-//   }
-//
-//   hideLineDash() {
-//
-//     this.lineDashFlag = false;
-//
-//     if (this.arrow) {
-//       this.arrow.visible = false;
-//     }
-//
-//     this.hideLabel();
-//   }
-//
-//
-//
-//   drawArrow() {
-//     if (!this.arrow) { return; }
-//
-//     const rect = this.getBoundingBox();
-//     this.arrow.x = rect.x + rect.width;
-//     this.arrow.y = rect.y;
-//
-//     this.arrow.update();
-//
-//
-//     this.arrowTop.x = rect.x + rect.width / 2;
-//     this.arrowTop.y = rect.y;
-//     this.arrowTop.update();
-//
-//     this.arrowRight.x = rect.x + rect.width;
-//     this.arrowRight.y = rect.y + rect.height / 2;
-//     this.arrowRight.update();
-//   }
-//
-//   drawFrame() {
-//
-//
-//     this.ctx.save();
-//
-//
-//     const rect = this.getBoundingBox();
-//
-//     const w = rect.width;
-//     const h = rect.height;
-//     const x = rect.x + w / 2;
-//     const y = rect.y + h / 2;
-//
-//     this.ctx.setLineDash([5, 5]);
-//     this.ctx.lineWidth = 2;
-//     this.ctx.strokeStyle = '#1bfff7';
-//     // this.ctx.fillStyle = '#ffffff';
-//
-//     this.ctx.beginPath();
-//
-//     this.ctx.moveTo( x - w / 2, y - h / 2);
-//
-//     this.ctx.lineTo(x + w / 2, y - h / 2);
-//
-//     this.ctx.lineTo(x + w / 2, y + h / 2);
-//
-//     this.ctx.lineTo(x - w / 2, y + h / 2);
-//
-//     this.ctx.lineTo(x - w / 2, y - h / 2);
-//
-//     // this.ctx.fill();
-//     this.ctx.stroke();
-//
-//
-//
-//
-//     this.ctx.restore();
-//
-//   }
-//
-//   draw() {
-//     super.draw();
-//
-//     if (this.lineDashFlag) {
-//       this.drawFrame();
-//       this.drawArrow();
-//     }
-//   }
-// }
-//
-//
-// export class EditorItem extends MySprite {
-//
-//   lineDashFlag = false;
-//   arrow: MySprite;
-//   label:Label;
-//   text;
-//
-//   showLabel(text = null) {
-//
-//
-//     if (!this.label) {
-//       this.label = new Label(this.ctx);
-//       this.label.anchorY = 0;
-//       this.label.fontSize = '50px';
-//       this.label.textAlign = 'center';
-//       this.addChild(this.label);
-//       this.label.setScaleXY(1 / this.scaleX);
-//     }
-//
-//     if (text) {
-//       this.label.text = text;
-//     } else if (this.text) {
-//       this.label.text = this.text;
-//     }
-//     this.label.visible = true;
-//
-//   }
-//
-//   hideLabel() {
-//     if (!this.label) { return; }
-//
-//     this.label.visible = false;
-//   }
-//
-//   showLineDash() {
-//     this.lineDashFlag = true;
-//
-//     if (this.arrow) {
-//       this.arrow.visible = true;
-//     } else {
-//       this.arrow = new MySprite(this.ctx);
-//       this.arrow.load('assets/common/arrow.png', 1, 0);
-//       this.arrow.setScaleXY(0.06);
-//
-//     }
-//
-//     this.showLabel();
-//   }
-//
-//   hideLineDash() {
-//
-//     this.lineDashFlag = false;
-//
-//     if (this.arrow) {
-//       this.arrow.visible = false;
-//     }
-//
-//     this.hideLabel();
-//   }
-//
-//
-//
-//   drawArrow() {
-//     if (!this.arrow) { return; }
-//
-//     const rect = this.getBoundingBox();
-//     this.arrow.x = rect.x + rect.width;
-//     this.arrow.y = rect.y;
-//
-//     this.arrow.update();
-//   }
-//
-//   drawFrame() {
-//
-//
-//     this.ctx.save();
-//
-//
-//     const rect = this.getBoundingBox();
-//
-//     const w = rect.width;
-//     const h = rect.height;
-//     const x = rect.x + w / 2;
-//     const y = rect.y + h / 2;
-//
-//     this.ctx.setLineDash([5, 5]);
-//     this.ctx.lineWidth = 2;
-//     this.ctx.strokeStyle = '#1bfff7';
-//     // this.ctx.fillStyle = '#ffffff';
-//
-//     this.ctx.beginPath();
-//
-//     this.ctx.moveTo( x - w / 2, y - h / 2);
-//
-//     this.ctx.lineTo(x + w / 2, y - h / 2);
-//
-//     this.ctx.lineTo(x + w / 2, y + h / 2);
-//
-//     this.ctx.lineTo(x - w / 2, y + h / 2);
-//
-//     this.ctx.lineTo(x - w / 2, y - h / 2);
-//
-//     // this.ctx.fill();
-//     this.ctx.stroke();
-//
-//
-//
-//
-//     this.ctx.restore();
-//
-//   }
-//
-//   draw() {
-//     super.draw();
-//
-//     if (this.lineDashFlag) {
-//       this.drawFrame();
-//       this.drawArrow();
-//     }
-//   }
-// }
-//
-//
-//
-// export class Label extends MySprite {
-//
-//   text:String;
-//   fontSize:String = '40px';
-//   fontName:String = 'Verdana';
-//   textAlign:String = 'left';
-//
-//
-//   constructor(ctx) {
-//     super(ctx);
-//     this.init();
-//   }
-//
-//   drawText() {
-//
-//     // console.log('in drawText', this.text);
-//
-//     if (!this.text) { return; }
-//
-//     this.ctx.font = `${this.fontSize} ${this.fontName}`;
-//     this.ctx.textAlign = this.textAlign;
-//     this.ctx.textBaseline = 'middle';
-//     this.ctx.fontWeight = 900;
-//
-//     this.ctx.lineWidth = 5;
-//     this.ctx.strokeStyle = '#ffffff';
-//     this.ctx.strokeText(this.text, 0, 0);
-//
-//     this.ctx.fillStyle = '#000000';
-//     this.ctx.fillText(this.text, 0, 0);
-//
-//
-//   }
-//
-//
-//   drawSelf() {
-//     super.drawSelf();
-//     this.drawText();
-//   }
-//
-// }
-//
-//
-//
-// export function getPosByAngle(angle, len) {
-//
-//   const radian = angle * Math.PI / 180;
-//   const x = Math.sin(radian) * len;
-//   const y = Math.cos(radian) * len;
-//
-//   return {x, y};
-//
-// }
-//
-// export function getAngleByPos(px, py, mx, my) {
-//
-//   // const _x = p2x - p1x;
-//   // const _y = p2y - p1y;
-//   // const tan = _y / _x;
-//   //
-//   // const radina = Math.atan(tan); // 用反三角函数求弧度
-//   // const angle = Math.floor(180 / (Math.PI / radina)); //
-//   //
-//   // console.log('r: ' , angle);
-//   // return angle;
-//   //
-//
-//
-//
-//   const x = Math.abs(px - mx);
-//   const y = Math.abs(py - my);
-//   // const x = Math.abs(mx - px);
-//   // const y = Math.abs(my - py);
-//   const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
-//   const cos = y / z;
-//   const radina = Math.acos(cos); // 用反三角函数求弧度
-//   let angle = Math.floor(180 / (Math.PI / radina) * 100) / 100; // 将弧度转换成角度
-//
-//   if(mx > px && my > py) {// 鼠标在第四象限
-//     angle = 180 - angle;
-//   }
-//
-//   if(mx === px && my > py) {// 鼠标在y轴负方向上
-//     angle = 180;
-//   }
-//
-//   if(mx > px && my === py) {// 鼠标在x轴正方向上
-//     angle = 90;
-//   }
-//
-//   if(mx < px && my > py) {// 鼠标在第三象限
-//     angle = 180 + angle;
-//   }
-//
-//   if(mx < px && my === py) {// 鼠标在x轴负方向
-//     angle = 270;
-//   }
-//
-//   if(mx < px && my < py) {// 鼠标在第二象限
-//     angle = 360 - angle;
-//   }
-//
-//   // console.log('angle: ', angle);
-//   return angle;
-//
-// }
diff --git a/src/app/common/custom-hot-zone/custom-hot-zone.component.html b/src/app/common/custom-hot-zone/custom-hot-zone.component.html
index 9028527061890eefb1557c53f49394c7ea827b11..c96f3ac4de355552085113a21f73c3a934edd32d 100644
--- a/src/app/common/custom-hot-zone/custom-hot-zone.component.html
+++ b/src/app/common/custom-hot-zone/custom-hot-zone.component.html
@@ -32,33 +32,137 @@
 
         <nz-divider style="margin-top: 10px;"></nz-divider>
 
+        <div style="margin-top: -20px; margin-bottom: 5px; width: 100%;">
 
-        <div style="margin-top: -20px; margin-bottom: 5px">
-          <nz-radio-group [ngModel]="it.itemType" (ngModelChange)="radioChange($event, it)">
-            <label *ngIf="isHasRect" nz-radio nzValue="rect">矩形</label>
-            <label *ngIf="isHasPic" nz-radio nzValue="pic">图片</label>
-            <label *ngIf="isHasText" nz-radio nzValue="text">文本</label>
-          </nz-radio-group>
-        </div>
+          <div *ngIf="customTypeGroupArr">
+            <nz-radio-group [ngModel]="it.gIdx" (ngModelChange)="customRadioChange($event, it)"  style="display: flex; align-items: center; justify-content: center; flex-wrap: wrap;">
+              <div *ngFor="let group of customTypeGroupArr; let gIdx = index" style="display: flex; ">
+                <label nz-radio nzValue="{{gIdx}}">{{group.name}}</label>
+              </div>
+            </nz-radio-group>
 
-        <div *ngIf="it.itemType == 'pic'">
-          <app-upload-image-with-preview
-            [picUrl]="it?.pic_url"
-            (imageUploaded)="onItemImgUploadSuccess($event, it)">
-          </app-upload-image-with-preview>
-        </div>
+          </div>
+
+          <!-- <div *ngIf="!customTypeGroupArr">
+            <nz-radio-group [ngModel]="it.itemType" (ngModelChange)="radioChange($event, it)"  style="display: flex; align-items: center; justify-content: center">
+
+              <label *ngIf="isHasRect" nz-radio nzValue="rect">矩形</label>
+              <label *ngIf="isHasPic" nz-radio nzValue="pic">图片</label>
+              <label *ngIf="isHasText" nz-radio nzValue="text">文本</label>
+
+            </nz-radio-group>
+          </div> -->
 
-        <div *ngIf="it.itemType == 'text'">
-          <input type="text" nz-input [(ngModel)]="it.text" (blur)="saveText(it)">
         </div>
 
-        <div style="width: 100%; margin-top: 5px;">
-          <app-audio-recorder
-            [audioUrl]="it.audio_url"
-            (audioUploaded)="onItemAudioUploadSuccess($event, it)"
-          ></app-audio-recorder>
+        <div *ngIf="customTypeGroupArr && customTypeGroupArr[it.gIdx]">
+
+          <div *ngIf="customTypeGroupArr[it.gIdx].pic">
+            <app-upload-image-with-preview
+              [picUrl]="it?.pic_url"
+              (imageUploaded)="onItemImgUploadSuccess($event, it)">
+            </app-upload-image-with-preview>
+          </div>
+
+          <div *ngIf="customTypeGroupArr[it.gIdx].text" style="margin-top: 5px">
+            <input type="text" nz-input [(ngModel)]="it.text" (blur)="saveText(it)">
+          </div>
+
+          <div *ngIf="customTypeGroupArr[it.gIdx].anima" align="center" style="margin-top: 5px">
+            <button nz-button (click)="setAnimaBtnClick(i)" >
+              <i nz-icon nzType="tool" nzTheme="outline"></i>
+              配置龙骨动画
+            </button>
+          </div>
+
+          <div *ngIf="customTypeGroupArr[it.gIdx].animaSmall" align="center" style="margin-top: 5px">
+            <button nz-button (click)="setAnimaSmallBtnClick(i)" >
+              <i nz-icon nzType="tool" nzTheme="outline"></i>
+              配置龙骨动画(小)
+            </button>
+          </div>
+
+
+          <div *ngIf="customTypeGroupArr[it.gIdx].audio" style="display: flex;align-items: center; margin-top: 5px">
+            <app-audio-recorder
+              style="margin: auto"
+              [audioUrl]="it.audio_url"
+              (audioUploaded)="onItemAudioUploadSuccess($event, it)"
+            ></app-audio-recorder>
+          </div>
+
+          <div *ngIf="customTypeGroupArr[it.gIdx]?.action" style="display: flex;align-items: center; margin-top: 5px">
+            <app-custom-action
+              style="margin: auto"
+              [item]="it ? it['actionData_' + it.gIdx] : {}"
+              [type]="customTypeGroupArr[it.gIdx].action.type"
+              [option]="customTypeGroupArr[it.gIdx].action.option"
+              (save)="onSaveCustomAction($event, it)">
+            ></app-custom-action>
+          </div>
+
+          <div *ngIf="customTypeGroupArr[it.gIdx]?.isShowPos" style="display: flex; align-items: center; justify-content: center; margin-top: 5px;">
+            x: <input type="text" nz-input [(ngModel)]="it.posX" style="width: 50px; margin-right: 15px;" (blur)="saveItemPos(it)">
+            y: <input type="text" nz-input [(ngModel)]="it.posY" style="width: 50px" (blur)="saveItemPos(it)">
+          </div>
+
+          <div *ngIf="customTypeGroupArr[it.gIdx]?.select" align="center" >
+            <nz-select [(ngModel)]="it.selectType" nzAllowClear nzPlaceHolder="Choose" style="width: 70%; margin-top: 5px;">
+              <nz-option *ngFor="let s of customTypeGroupArr[it.gIdx]?.select" [nzValue]="s.value" [nzLabel]="s.label">
+              </nz-option>
+            </nz-select>
+          </div>
+
+          <div *ngIf="customTypeGroupArr[it.gIdx]?.label" align="center" style="margin-top: 5px; display: flex; align-items: center; justify-content: center;">
+            <span style="width: 30%;">{{customTypeGroupArr[it.gIdx].label + ':'}}</span>
+            <input type="text" nz-input [(ngModel)]="it.labelText" (blur)="saveText(it)">
+          </div>
+
+
+          <div *ngIf="customTypeGroupArr[it.gIdx]?.isCopy" align="center" style="margin-top: 5px; display: flex; align-items: center; justify-content: center;">
+            <button nz-button (click)="copyItem(it)" >
+              <i nz-icon nzType="copy" nzTheme="outline"></i>
+              复制粘贴
+            </button>  
+          </div>
+
         </div>
 
+    
+
+        <!-- <div *ngIf="!customTypeGroupArr">
+
+          <div *ngIf="it.itemType == 'pic'">
+            <app-upload-image-with-preview
+              [picUrl]="it?.pic_url"
+              (imageUploaded)="onItemImgUploadSuccess($event, it)">
+            </app-upload-image-with-preview>
+          </div>
+
+          <div *ngIf="it.itemType == 'text'">
+            <input type="text" nz-input [(ngModel)]="it.text" (blur)="saveText(it)">
+          </div>
+
+          <div *ngIf="isHasAudio"
+               style="width: 100%; margin-top: 5px; display: flex; align-items: center; justify-items: center;">
+            <app-audio-recorder
+              style="margin: auto"
+              [audioUrl]="it.audio_url"
+              (audioUploaded)="onItemAudioUploadSuccess($event, it)"
+            ></app-audio-recorder>
+          </div>
+
+          <div *ngIf="it.itemType == 'rect' && isHasAnima" align="center" style="margin-bottom: 5px">
+            <button nz-button (click)="setAnimaBtnClick(i)" >
+              <i nz-icon nzType="tool" nzTheme="outline"></i>
+              配置龙骨动画
+            </button>
+          </div>
+
+        </div> -->
+
+
+
       </div>
 
     </div>
@@ -83,12 +187,29 @@
 
   <div class="save-box">
 
-    <button class="save-btn" nz-button nzType="primary" [nzSize]="'large'" nzShape="round"
+    <button style="margin-left: 200px" class="save-btn" nz-button nzType="primary" [nzSize]="'large'" nzShape="round"
             (click)="saveClick()" >
       <i nz-icon nzType="save"></i>
       Save
     </button>
 
+    <div *ngIf="isCopyData" style="height: 40px; display: flex; justify-items: center;" >
+
+      <label style="margin-left: 40px" nz-checkbox [(ngModel)]="bgItem.isShowDebugLine">显示辅助框</label>
+
+      <button style="margin-left: 20px; margin-top: -5px" nz-button (click)="copyHotZoneData()"> 复制基础数据 </button>
+      <div style="margin-left: 10px; margin-top: -5px" >
+
+        <span>粘贴数据: </span>
+        <input style="width: 100px;" type="text" nz-input [(ngModel)]="pasteData" >
+        <button style="margin-left: 5px;" nz-button [disabled]="pasteData==''" nzType="primary" (click)="importData()">导入</button>
+
+      </div>
+
+
+    </div>
+
+
   </div>
 
 
@@ -98,3 +219,52 @@
 <label style="opacity: 0; position: absolute; top: 0px; font-family: 'BRLNSR_1'">1</label>
 
 
+<!--龙骨面板-->
+<nz-modal [(nzVisible)]="animaPanelVisible" nzTitle="配置资源文件"   (nzAfterClose)="closeAfterPanel()" (nzOnCancel)="animaPanelCancel()" (nzOnOk)="animaPanelOk()" nzOkText="保存">
+
+  <div class="anima-upload-btn">
+    <span style="margin-right: 10px">上传 ske_json 文件: </span>
+    <nz-upload [nzShowUploadList]="false"
+               nzAccept="application/json"
+               [nzAction]="uploadUrl"
+               [nzData]="uploadData"
+               (nzChange)="skeJsonHandleChange($event)">
+      <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+    </nz-upload>
+    <i *ngIf="isSkeJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+    <span *ngIf="skeJsonData['name']" style="margin-left: 10px"><u> {{skeJsonData['name']}} </u></span>
+  </div>
+
+  <div class="anima-upload-btn">
+    <span style="margin-right: 10px">上传 tex_json 文件: </span>
+    <nz-upload [nzShowUploadList]="false"
+               nzAccept="application/json"
+               [nzAction]="uploadUrl"
+               [nzData]="uploadData"
+               (nzChange)="texJsonHandleChange($event)">
+      <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+    </nz-upload>
+    <i *ngIf="isTexJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+    <span *ngIf="texJsonData['name']" style="margin-left: 10px"><u> {{texJsonData['name']}} </u></span>
+  </div>
+
+  <div class="anima-upload-btn">
+    <span style="margin-right: 10px">上传 tex_png 文件: </span>
+    <nz-upload [nzShowUploadList]="false"
+               nzAccept = "image/*"
+               [nzAction]="uploadUrl"
+               [nzData]="uploadData"
+               (nzChange)="texPngHandleChange($event)">
+      <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+    </nz-upload>
+    <i *ngIf="isTexPngLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+    <span *ngIf="texPngData['name']" style="margin-left: 10px"><u> {{texPngData['name']}} </u></span>
+  </div>
+
+  <div class="anima-upload-btn" *ngIf="customTypeGroupArr && customTypeGroupArr[curDragonBoneGIdx] && customTypeGroupArr[curDragonBoneGIdx].animaNames">
+    提示:需包含以下动画: {{customTypeGroupArr[curDragonBoneGIdx].animaNames.toString()}}
+  </div>
+
+</nz-modal>
+
+
diff --git a/src/app/common/custom-hot-zone/custom-hot-zone.component.scss b/src/app/common/custom-hot-zone/custom-hot-zone.component.scss
index 9e2c2bf053571db2d78708132b23fe87e0acc9b2..a8f75bfa284d09475309022adfcd8552a212cea4 100644
--- a/src/app/common/custom-hot-zone/custom-hot-zone.component.scss
+++ b/src/app/common/custom-hot-zone/custom-hot-zone.component.scss
@@ -81,6 +81,10 @@
   }
 }
 
+.anima-upload-btn {
+  padding: 10px;
+}
+
 h5 {
   margin-top: 1rem;
 }
@@ -89,8 +93,8 @@ h5 {
 
 @font-face
 {
-  font-family: 'BRLNSR_1';
-  src: url("/assets/font/BRLNSR_1.TTF") ;
+ font-family: 'ahronbd-1';
+ src: url("/assets/font/ahronbd-1.ttf") ;
 }
 
 
@@ -105,106 +109,3 @@ h5 {
 
 
 
-
-
-//@import '../../../style/common_mixin';
-//
-//.p-image-uploader {
-//  position: relative;
-//  display: block;
-//  width: 100%;
-//  height: 0;
-//  padding-bottom: 56.25%;
-//  .p-box {
-//    position: absolute;
-//    left: 0;
-//    top: 0;
-//    right: 0;
-//    bottom: 0;
-//    border: 2px dashed #ddd;
-//    border-radius: 0.5rem;
-//    background-color: #fafafa;
-//    text-align: center;
-//    color: #aaa;
-//    .p-upload-icon {
-//      text-align: center;
-//      margin: auto;
-//      .anticon-upload {
-//        color: #888;
-//        font-size: 5rem;
-//      }
-//      .p-progress-bar {
-//        position: relative;
-//        width: 20rem;
-//        height: 1.5rem;
-//        border: 1px solid #ccc;
-//        border-radius: 1rem;
-//        .p-progress-bg {
-//          background-color: #1890ff;
-//          border-radius: 1rem;
-//          height: 100%;
-//        }
-//        .p-progress-value {
-//          position: absolute;
-//          top: 0;
-//          left: 0;
-//          width: 100%;
-//          height: 100%;
-//          text-shadow: 0 0 4px #000;
-//          color: white;
-//          text-align: center;
-//          font-size: 0.9rem;
-//          line-height: 1.5rem;
-//        }
-//      }
-//    }
-//    .p-preview {
-//      width: 100%;
-//      height: 100%;
-//      //background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
-//      @include k-img-bg();
-//    }
-//  }
-//}
-//
-//.p-btn-delete {
-//  position: absolute;
-//  right: -0.5rem;
-//  top: -0.5rem;
-//  width: 2rem;
-//  height: 2rem;
-//  border: 0.2rem solid white;
-//  border-radius: 50%;
-//  font-size: 1.2rem;
-//  background-color: #fb781a;
-//  color: white;
-//  text-align: center;
-//}
-//
-//.p-upload-progress-bg {
-//  position: absolute;
-//  width: 100%;
-//  height: 100%;
-//  display: flex;
-//  align-items: center;
-//  justify-content: center;
-//  & .i-text {
-//    position: absolute;
-//    text-align: center;
-//    color: white;
-//    text-shadow: 0 0 2px rgba(0, 0, 0, .85);
-//  }
-//  & .i-bg {
-//    position: absolute;
-//    left: 0;
-//    top: 0;
-//    height: 100%;
-//    background-color: #27b43f;
-//    border-radius: 0.5rem;
-//  }
-//}
-//
-//
-//:host ::ng-deep .ant-upload {
-//  display: block;
-//}
diff --git a/src/app/common/custom-hot-zone/custom-hot-zone.component.ts b/src/app/common/custom-hot-zone/custom-hot-zone.component.ts
index dc2a4cf04d4592cc6a399cf5708c713dbb3439fd..3c7cbc84a9fabf1cb2d0068f500d57e614030a04 100644
--- a/src/app/common/custom-hot-zone/custom-hot-zone.component.ts
+++ b/src/app/common/custom-hot-zone/custom-hot-zone.component.ts
@@ -1,4 +1,6 @@
 import {
+  ApplicationRef,
+  ChangeDetectorRef,
   Component,
   ElementRef,
   EventEmitter,
@@ -12,10 +14,11 @@ import {
 } from '@angular/core';
 import {Subject} from 'rxjs';
 import {debounceTime} from 'rxjs/operators';
-import {EditorItem, HotZoneImg, HotZoneItem, HotZoneLabel, Label, MySprite, removeItemFromArr} from './Unit';
+import {DragItem, EditorItem, HotZoneImg, HotZoneItem, HotZoneLabel, Label, MySprite, removeItemFromArr, ShapeRect, ShapeRectNew} from './Unit';
 import TWEEN from '@tweenjs/tween.js';
-import {getMinScale} from "../../play/Unit";
-import {tar} from "compressing";
+import {getMinScale} from '../../play/Unit';
+import {tar} from 'compressing';
+import {NzMessageService} from 'ng-zorro-antd';
 
 
 @Component({
@@ -25,18 +28,14 @@ import {tar} from "compressing";
 })
 export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
+  @ViewChild('canvas', {static: true}) canvas: ElementRef;
+  @ViewChild('wrap', {static: true}) wrap: ElementRef;
+
 
-  @Input()
-  imgItemArr = null;
   @Input()
   hotZoneItemArr = null;
   @Input()
   hotZoneArr = null;
-  @Output()
-  save = new EventEmitter();
-  @ViewChild('canvas', {static: true}) canvas: ElementRef;
-  @ViewChild('wrap', {static: true}) wrap: ElementRef;
-
   @Input()
   isHasRect = true;
   @Input()
@@ -44,16 +43,31 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
   @Input()
   isHasText = true;
   @Input()
-  hotZoneFontObj = {
-    size: 50,
-    name: 'BRLNSR_1',
-    color: '#8f3758'
-  }
+  isHasAudio = true;
+  @Input()
+  isHasAnima = false;
+  @Input()
+  customTypeGroupArr; // [{name:string, rect:boolean, pic:boolean, text:boolean, audio:boolean, anima:boolean, animaNames:['name1', ..]}, ...]
+  @Input()
+  // hotZoneFontObj;
+  @Input()
+  isCopyData = false;
+  
+  hotZoneFontObj;
+  // hotZoneFontObj = {
+  //   size: 50,
+  //   name: 'ahronbd-1',
+  //   color: '#8f3758'
+  // }
   @Input()
   defaultItemType = 'text';
-
   @Input()
-  hotZoneImgSize = 190;
+  hotZoneImgSize = 0;
+
+
+  @Output()
+  save = new EventEmitter();
+
 
   saveDisabled = true;
 
@@ -86,8 +100,42 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
   changeSizeFlag = false;
   changeTopSizeFlag = false;
   changeRightSizeFlag = false;
-
-  constructor() {
+  animaPanelVisible = false;
+
+  uploadUrl;
+  uploadData;
+  skeJsonData = {};
+  texJsonData = {};
+  texPngData = {};
+  animaName = '';
+  curDragonBoneIndex;
+  curDragonBoneGIdx;
+  pasteData = '';
+
+  isSkeJsonLoading = false;
+  isTexJsonLoading = false;
+  isTexPngLoading = false;
+
+  isAnimaSmall = false;
+
+  savePropertyArr = [
+    'id',
+    'gIdx',
+    'selectType',
+    'labelText',
+    'posX',
+    'posY'
+  ]
+
+
+  constructor(private nzMessageService: NzMessageService, private appRef: ApplicationRef, private changeDetectorRef: ChangeDetectorRef) {
+    this.uploadUrl = (<any> window).courseware.uploadUrl();
+    this.uploadData = (<any> window).courseware.uploadData();
+
+    window['air'].getUploadCallback = (url, data) => {
+      this.uploadUrl = url;
+      this.uploadData = data;
+    };
   }
 
   _bgItem = null;
@@ -110,11 +158,14 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
   ngOnInit() {
 
+
     this.initListener();
 
     // this.init();
     this.update();
 
+    this.refresh();
+
   }
 
   ngOnDestroy() {
@@ -136,10 +187,12 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
   onItemImgUploadSuccess(e, item) {
     item.pic_url = e.url;
     this.loadHotZonePic(item.pic, e.url);
+    this.refresh();
   }
 
   onItemAudioUploadSuccess(e, item) {
     item.audio_url = e.url;
+    this.refresh();
   }
 
 
@@ -150,37 +203,61 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
       this.renderArr.push(this.bg);
     }
 
+    if (!this.bgItem.url) {
+      this.bgItem.url = 'http://teach.cdn.ireadabc.com/8ebb1858564340ea0936b83e3280ad7d.jpg';
+    }
+
     const bg = this.bg;
-    if (this.bgItem.url) {
+    // if (this.bgItem.url) {
 
-      bg.load(this.bgItem.url).then(() => {
-        const rate1 = this.canvasWidth / bg.width;
-        const rate2 = this.canvasHeight / bg.height;
+    bg.load(this.bgItem.url).then(() => {
+      const rate1 = this.canvasWidth / bg.width;
+      const rate2 = this.canvasHeight / bg.height;
 
-        const rate = Math.min(rate1, rate2);
-        bg.setScaleXY(rate);
+      const rate = Math.min(rate1, rate2);
+      bg.setScaleXY(rate);
+      
+      bg.x = this.canvasWidth / 2;
+      bg.y = this.canvasHeight / 2;
 
+      bg.removeChildren();
 
-        bg.x = this.canvasWidth / 2;
-        bg.y = this.canvasHeight / 2;
+      const bgBorder = new ShapeRectNew(this.ctx);
+      bgBorder.setSize(bg.width, bg.height, 0);
+      bgBorder.fillColor = '#ff0000';
+      bgBorder.fill = false;
+      bgBorder.stroke = true;
+      bgBorder.x = -bg.width / 2;
+      bgBorder.y = -bg.height / 2;
+      bgBorder.lineWidth = 0.5;
+      bg.addChild(bgBorder);
 
-        if (callBack) {
-          callBack();
-        }
-      });
-    }
+
+      if (callBack) {
+        callBack();
+      }
+
+      this.refresh();
+
+    });
+    // }
 
   }
 
 
-  addBtnClick() {
+  addBtnClick(data=null) {
     // this.imgArr.push({});
     // this.hotZoneArr.push({});
 
-    const item = this.getHotZoneItem();
+    const item = this.getHotZoneItem(data);
     this.hotZoneArr.push(item);
 
-    this.refreshItem(item);
+
+    if (this.customTypeGroupArr) {
+      this.refreshCustomItem(item);
+    } else {
+      this.refreshItem(item);
+    }
 
     this.refreshHotZoneId();
 
@@ -191,6 +268,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
     const item = this.hotZoneArr.splice(index, 1)[0];
     removeItemFromArr(this.renderArr, item.pic);
     removeItemFromArr(this.renderArr, item.textLabel);
+    removeItemFromArr(this.renderArr, item.drag);
 
     this.refreshHotZoneId();
 
@@ -199,6 +277,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
   onImgUploadSuccessByImg(e, img) {
     img.pic_url = e.url;
     this.refreshImage(img);
+    this.refresh();
   }
 
   refreshImage(img) {
@@ -221,6 +300,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
       }
     }
+    this.refresh();
   }
 
 
@@ -245,10 +325,18 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
     item.anchorX = 0.5;
     item.anchorY = 0.5;
 
+
     item.x = this.canvasWidth / 2;
     item.y = this.canvasHeight / 2;
 
-    item.itemType = this.defaultItemType;
+    item.itemType = this.getDefaultItemType();
+    item.gIdx = '0';
+
+
+    item['id'] = this.createItemId() // new Date().getTime().toString();
+    item['data'] = saveData;
+
+    console.log('item.x: ', item.x);
 
     if (saveData) {
 
@@ -257,41 +345,203 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
       item.scaleY = saveRect.height / item.height;
       item.x = saveRect.x + saveRect.width / 2;
       item.y = saveRect.y + saveRect.height / 2;
+      item.gIdx = saveData.gIdx;
+
+      item['id'] = saveData.id;
+      item['skeJsonData'] = saveData.skeJsonData;
+      item['texJsonData'] = saveData.texJsonData;
+      item['texPngData'] = saveData.texPngData;
+
+
+      item['actionData_' + item.gIdx] = saveData['actionData_' + item.gIdx];
+
+      this.savePropertyArr.forEach((key) => {
+        if (saveData[key]) {
+          item[key] = saveData[key];
+        }
+      })
+
 
     }
 
+    console.log('item.x:~~ ', item.x);
+
     item.showLineDash();
 
-    const pic = new HotZoneImg(this.ctx);
+    // const pic = new HotZoneImg(this.ctx);
+  
+
+    this.setItemPic(item, saveData);
+
+    this.setItemLabel(item, saveData);
+
+    this.setItemAnima(item, saveData);
+
+    this.setItemDrag(item, saveData);
+
+
+    return item;
+  }
+
+  setItemPic(item, saveData) {
+
+    console.log('in setItemPic ', saveData);
+    const pic = new EditorItem(this.ctx);
     pic.visible = false;
     item['pic'] = pic;
-    if (saveData && saveData.pic_url) {
-      this.loadHotZonePic(pic, saveData.pic_url);
+    if (saveData) {
+
+      let picUrl = saveData.pic_url;
+      const actionData = saveData['actionData_' + item.gIdx];
+      if (actionData && actionData.type == 'pic') {
+        picUrl = actionData.pic_url;
+      }
+
+      console.log('saveData: ', saveData);
+      console.log('picUrl: ', picUrl);
+
+      if (picUrl) {
+        this.loadHotZonePic(pic, picUrl, saveData.imgScale);
+      }
     }
+
     pic.x = item.x;
     pic.y = item.y;
     this.renderArr.push(pic);
+  }
+
+
+  setItemDrag(item, saveData) {
+
+    console.log('in setItemDrag ', saveData);
+    const dragItem = new DragItem(this.ctx);
+    dragItem.init();
+    dragItem.item = item;
+    item['drag'] = dragItem;
+
+    dragItem.visible = false;
+
+    dragItem.x = item.x;
+    dragItem.y = item.y;
+    this.renderArr.push(dragItem);
+
+    if (saveData) {
+      if (saveData.dragDot) {
+        dragItem.x = saveData.dragDot.x / saveData.mapScale * this.mapScale;
+        dragItem.y = saveData.dragDot.y / saveData.mapScale * this.mapScale;
+      }  
+    }
+
+
+    // console.log('item.itemType: ', item.itemType);
+    // let w = item.width;
+    // let h = item.height;
+    // if (saveData) {
+    //   switch (saveData.itemType) {
+    //     case 'rect':
+    //       w = saveData.rect.width;
+    //       h = saveData.rect.height;
+    //       break;
+    //     case 'pic':
+    //       w = saveData.imgSizeW * saveData.imgScale;
+    //       h = saveData.imgSizeH * saveData.imgScale;;
+    //       break;
+    //     case 'text':
+    //       w = saveData.rect.width;
+    //       h = saveData.rect.height;
+    //       break;
+    //   }
+    // }
+    
+    // dragItem.setSize(w, h);
+
+
+   
+  }
+
+  setItemAnima(item, saveData) {
+    console.log('in setItemAnima ', saveData);
+    // const pic = new EditorItem(this.ctx);
+    // pic.visible = false;
+    // item['pic'] = pic;
+    // if (saveData) {
+
+    //   let picUrl = saveData.pic_url;
+    //   const actionData = saveData['actionData_' + item.gIdx];
+    //   if (actionData && actionData.type == 'pic') {
+    //     picUrl = actionData.pic_url;
+    //   }
+
+    //   console.log('saveData: ', saveData);
+    //   console.log('picUrl: ', picUrl);
+
+    //   if (picUrl) {
+    //     this.loadHotZonePic(pic, picUrl, saveData.imgScale);
+    //   }
+    // }
+
+    // pic.x = item.x;
+    // pic.y = item.y;
+    // this.renderArr.push(pic);
+  }
+
+
+  setItemLabel(item, saveData) {
 
     const textLabel = new HotZoneLabel(this.ctx);
-    textLabel.fontSize = this.hotZoneFontObj.size;
-    textLabel.fontName = this.hotZoneFontObj.name;
-    textLabel.fontColor = this.hotZoneFontObj.color;
+    if (this.hotZoneFontObj) {
+      textLabel.fontSize = this.hotZoneFontObj.size;
+      textLabel.fontName = this.hotZoneFontObj.name;
+      textLabel.fontColor = this.hotZoneFontObj.color;
+    }
+
     textLabel.textAlign = 'center';
     // textLabel.setOutline();
-    // console.log('saveData:', saveData);
     item['textLabel'] = textLabel;
     textLabel.setScaleXY(this.mapScale);
-    if (saveData && saveData.text) {
-      textLabel.text = saveData.text;
+    if (saveData) {
+
+      if (saveData.text && this.hotZoneFontObj) {
+        textLabel.text = saveData.text
+      }
+
+      this.setActionFont(textLabel, saveData['actionData_' + item.gIdx]);
       textLabel.refreshSize();
+
     }
     textLabel.x = item.x;
     textLabel.y = item.y;
     this.renderArr.push(textLabel);
+  }
 
-    return item;
+  setActionFont(textLabel, actionData) {
+    if (actionData && actionData.type == 'text') {
+
+      textLabel.text = actionData.text;
+
+      for (let i=0; i<actionData.changeOption.length; i++) {
+        const op = actionData.changeOption[i];
+        textLabel[op[0]] = op[1];
+      }
+    }
   }
 
+  getDefaultItemType() {
+    if (this.customTypeGroupArr) {
+      const group = this.customTypeGroupArr[0];
+      if (group.rect) {
+        return 'rect';
+      }
+      if (group.pic) {
+        return 'pic';
+      }
+      if (group.text) {
+        return 'text';
+      }
+    } else {
+      return this.defaultItemType;
+    }
+  }
 
   getPicItem(img, saveData = null) {
 
@@ -327,9 +577,11 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
         item.y = saveRect.y + saveRect.height / 2;
 
       } else {
-        item.showLineDash();
+        // item.showLineDash();
       }
 
+      item.showLineDash();
+      console.log('aaa');
     });
     return item;
   }
@@ -337,6 +589,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
   onAudioUploadSuccessByImg(e, img) {
     img.audio_url = e.url;
+    this.refresh();
   }
 
   deleteItem(e, i) {
@@ -345,15 +598,111 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
     this.hotZoneArr.splice(i, 1);
     this.refreshHotZoneId();
+    this.refresh();
   }
+  
+
 
+  // radioChange(e, item) {
+  //   item.itemType = e;
 
-  radioChange(e, item) {
-    item.itemType = e;
+  //   this.refreshItem(item);
+  //   this.refresh();
+  //   // console.log(' in radioChange e: ', e);
+  // }
 
-    this.refreshItem(item);
+  customRadioChange(e, item) {
+
+    console.log('in customRadioChange', e);
+    item.gIdx = -1;
+    setTimeout(() => {
+      item.gIdx = e;
+      this.refreshCustomItem(item);
+      this.refresh();
+    }, 1);
+    
+    // const curGroup = this.customTypeGroupArr[e];
 
-    // console.log(' in radioChange e: ', e);
+  }
+
+  refreshCustomItem(item) {
+    this.hideHotZoneItem(item);
+    const group = this.customTypeGroupArr[item.gIdx];
+    if (!group) {
+      return;
+    }
+
+    if (group.text) {
+      this.showItemLabel(item);
+    }
+
+    if (group.rect) {
+      this.showItemRect(item, !group.isFixed);
+    }
+
+    if (group.pic && !group.anima) {
+     this.showItemPic(item);
+    }
+
+    if (group.action) {
+      if (group.action.type == 'pic') {
+        this.showItemPic(item);
+      }
+      if (group.action.type == 'text') {
+        this.showItemLabel(item);
+      }
+      if (group.action.type == 'anima') {
+        this.showItemRect(item);
+      }
+    }
+
+    item.drag.visible = group.drag;
+
+    item.setAnimaStyle(group.animaSmall)
+  
+
+  }
+
+  showItemDrag(item) {
+    item.drag.visible = true;
+    // item.dragBody.visible = true;
+    // item.itemType = 'pic';
+    // this.showArrowTop(item)
+  }
+
+
+  showItemPic(item) {
+    item.pic.visible = true;
+    item.itemType = 'pic';
+    this.showArrowTop(item)
+  }
+
+  showItemLabel(item) {
+    item.textLabel.visible = true;
+    item.itemType = 'text';
+    this.showArrowTop(item)
+  }
+
+  showItemRect(item, isShowArrowTop = true) {
+    item.visible = true;
+    item.itemType = 'rect';
+    this.showArrowTop(item, isShowArrowTop)
+  }
+
+  showArrowTop(item, isShowArrowTop = false) {
+    if (isShowArrowTop) {
+      item.arrowTop.visible = true;
+      item.arrowRight.visible = true;
+    } else {
+      item.arrowTop.visible = false;
+      item.arrowRight.visible = false;
+    }
+  }
+
+  hideHotZoneItem(item) {
+    item.visible = false;
+    item.pic.visible = false;
+    item.textLabel.visible = false;
   }
 
   refreshItem(item) {
@@ -368,33 +717,30 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
         this.setTextState(item);
         break;
       default:
-
+        this.setNoneState(item);
     }
   }
 
 
   init() {
 
+    console.log('init');
     this.initData();
     this.initCtx();
-
     this.initItem();
   }
 
   initItem() {
+
+    this.changeDetectorRef.markForCheck();
+    this.changeDetectorRef.detectChanges();
+
     if (!this.bgItem) {
       this.bgItem = {};
     } else {
       this.refreshBackground(() => {
-
-        // if (!this.imgItemArr) {
-        //   this.imgItemArr = [];
-        // } else {
-        //   this.initImgArr();
-        // }
-
-        // console.log('aaaaa');
-
+      
+      
         if (!this.hotZoneItemArr) {
           this.hotZoneItemArr = [];
         } else {
@@ -404,6 +750,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
       });
     }
 
+    this.refresh();
   }
 
   initHotZoneArr() {
@@ -427,6 +774,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
     this.hotZoneArr = [];
     const arr = this.hotZoneItemArr.concat();
+    console.log('this.hotZoneItemArr: ', this.hotZoneItemArr);
     for (let i = 0; i < arr.length; i++) {
 
       const data = JSON.parse(JSON.stringify(arr[i]));
@@ -449,9 +797,15 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
       item.pic_url = data.pic_url;
       item.text = data.text;
       item.itemType = data.itemType;
-      this.refreshItem(item);
 
-      console.log('item: ', item);
+      if (this.customTypeGroupArr) {
+        this.refreshCustomItem(item);
+      } else {
+        this.refreshItem(item);
+      }
+
+
+      console.log('1 item: ', item);
       this.hotZoneArr.push(item);
 
     }
@@ -463,48 +817,6 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
   }
 
 
-  initImgArr() {
-
-    console.log('this.imgItemArr: ', this.imgItemArr);
-    let curBgRect;
-    if (this.bg) {
-      curBgRect = this.bg.getBoundingBox();
-    } else {
-      curBgRect = {x: 0, y: 0, width: this.canvasWidth, height: this.canvasHeight};
-    }
-
-    let oldBgRect = this.bgItem.rect;
-    if (!oldBgRect) {
-      oldBgRect = curBgRect;
-    }
-
-    const rate = curBgRect.width / oldBgRect.width;
-
-    console.log('rate: ', rate);
-
-    this.imgArr = [];
-    const arr = this.imgItemArr.concat();
-    for (let i = 0; i < arr.length; i++) {
-
-      const data = JSON.parse(JSON.stringify(arr[i]));
-      const img = {pic_url: data.pic_url};
-
-      data.rect.x *= rate;
-      data.rect.y *= rate;
-      data.rect.width *= rate;
-      data.rect.height *= rate;
-
-      data.rect.x += curBgRect.x;
-      data.rect.y += curBgRect.y;
-
-      img['picItem'] = this.getPicItem(img, data);
-      img['audio_url'] = arr[i].audio_url;
-      this.imgArr.push(img);
-    }
-    this.refreshImageId();
-  }
-
-
   initData() {
 
     this.canvasWidth = this.wrap.nativeElement.clientWidth;
@@ -528,9 +840,28 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
     this.oldPos = {x: this.mx, y: this.my};
 
-    for (let i = 0; i < this.hotZoneArr.length; i++) {
+    
+    // 先检测拖拽点
+    for (let i = this.hotZoneArr.length - 1; i >= 0; i--) {
+      
+      const item = this.hotZoneArr[i];
+
+      if (item && item.drag && item.drag.visible) {
+
+        if (this.checkClickTarget(item.drag)) {
+
+          this.clickedDragPoint(item.drag);
+          return;
+        }
+      }
+    }
 
+
+    for (let i = this.hotZoneArr.length - 1; i >= 0; i--) {
+      
       const item = this.hotZoneArr[i];
+
+      console.log('mapDown item: ', item);
       let callback;
       let target;
       switch (item.itemType) {
@@ -548,7 +879,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
           break;
       }
 
-      if (this.checkClickTarget(target)) {
+      if (target && this.checkClickTarget(target)) {
         callback(target);
         return;
       }
@@ -578,15 +909,18 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
       const addY = this.my - this.oldPos.y;
       this.curItem.x += addX;
       this.curItem.y += addY;
+
+      this.curItem.posX = this.curItem.x;
+      this.curItem.posY = this.curItem.y;
     }
 
     this.oldPos = {x: this.mx, y: this.my};
 
-    this.saveDisabled = true;
+    // this.saveDisabled = false;
 
   }
 
-  mapUp(event) {
+  mapUp(event=null) {
     this.curItem = null;
     this.changeSizeFlag = false;
     this.changeTopSizeFlag = false;
@@ -685,10 +1019,13 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
   changeCurItem(item) {
 
+    console.log(' in changeCurItem', item)
+
     this.hideAllLineDash();
 
     this.curItem = item;
     this.curItem.showLineDash();
+
   }
 
   hideAllLineDash() {
@@ -711,9 +1048,7 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
     // 清除画布内容
     this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
 
-    for (let i = 0; i < this.renderArr.length; i++) {
-      this.renderArr[i].update(this);
-    }
+  
 
     // for (let i = 0; i < this.imgArr.length; i++) {
     //   const picItem = this.imgArr[i].picItem;
@@ -722,8 +1057,16 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
     //   }
     // }
 
+    for (let i = 0; i < this.renderArr.length; i++) {
+      this.renderArr[i].update(this);
+    }
+
     this.updateArr(this.hotZoneArr);
-    this.updatePos()
+
+   
+
+    this.updatePos();
+
 
 
     TWEEN.update();
@@ -833,7 +1176,9 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
 
   checkClickTarget(target) {
-
+    if (!target || !target.visible) {
+      return;
+    }
     const rect = target.getBoundingBox();
     if (this.checkPointInRect(this.mx, this.my, rect)) {
       return true;
@@ -852,9 +1197,21 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
 
   saveClick() {
+
+    const sendData = this.getSendData();
+    this.save.emit(sendData);
+  }
+
+  getSendData() {
+
     const bgItem = this.bgItem;
     if (this.bg) {
-      bgItem['rect'] = this.bg.getBoundingBox();
+      const rect = this.bg.getBoundingBox();
+      bgItem['rect'] = rect;
+      rect.x = Math.round(rect.x * 100) / 100;
+      rect.y = Math.round(rect.y * 100) / 100;
+      rect.width = Math.round(rect.width * 100) / 100;
+      rect.height = Math.round(rect.height * 100) / 100;
     } else {
       bgItem['rect'] = {
         x: 0,
@@ -870,19 +1227,52 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
     for (let i = 0; i < hotZoneArr.length; i++) {
 
       const hotZoneItem = {
+        id: hotZoneArr[i].id,
         index: hotZoneArr[i].index,
         pic_url: hotZoneArr[i].pic_url,
         text: hotZoneArr[i].text,
         audio_url: hotZoneArr[i].audio_url,
         itemType: hotZoneArr[i].itemType,
-        fontSize: this.hotZoneFontObj.size,
-        fontName: this.hotZoneFontObj.name,
-        fontColor: this.hotZoneFontObj.color,
         fontScale: hotZoneArr[i].textLabel ? hotZoneArr[i].textLabel.scaleX : 1,
         imgScale: hotZoneArr[i].pic ? hotZoneArr[i].pic.scaleX : 1,
-        mapScale: this.mapScale
+        imgSizeW: hotZoneArr[i].pic ? hotZoneArr[i].pic.width : 0,
+        imgSizeH: hotZoneArr[i].pic ? hotZoneArr[i].pic.height : 0,
+        mapScale: this.mapScale,
+        skeJsonData: hotZoneArr[i].skeJsonData,
+        texJsonData: hotZoneArr[i].texJsonData,
+        texPngData: hotZoneArr[i].texPngData,
+        dragDot: hotZoneArr[i].drag.getPosition(),
+        gIdx: hotZoneArr[i].gIdx,
       };
-      hotZoneItem['rect'] = hotZoneArr[i].getBoundingBox();
+
+      this.savePropertyArr.forEach((key) => {
+        if (hotZoneArr[i][key]) {
+          hotZoneItem[key] = hotZoneArr[i][key];
+        }
+      })
+
+      hotZoneItem['actionData_' + hotZoneItem.gIdx] = hotZoneArr[i]['actionData_' + hotZoneArr[i].gIdx]
+
+
+      if (this.hotZoneFontObj) {
+        hotZoneItem['fontSize'] = this.hotZoneFontObj.size;
+        hotZoneItem['fontName'] = this.hotZoneFontObj.name;
+        hotZoneItem['ontColor'] = this.hotZoneFontObj.color;
+      }
+
+
+      if (hotZoneArr[i].itemType == 'pic') {
+        hotZoneItem['rect'] = hotZoneArr[i].pic.getBoundingBox();
+      } else if (hotZoneArr[i].itemType == 'text') {
+        hotZoneArr[i].textLabel.refreshSize();
+        hotZoneItem['rect'] = hotZoneArr[i].textLabel.getLabelRect();
+        // hotZoneItem['rect'].width =  hotZoneItem['rect'].width / hotZoneArr[i].textLabel.scaleX;
+        // hotZoneItem['rect'].height = hotZoneArr[i].textLabel.height * hotZoneArr[i].textLabel.scaleY;
+
+      } else {
+        hotZoneItem['rect'] = hotZoneArr[i].getBoundingBox();
+      }
+      // hotZoneItem['rect'] = hotZoneArr[i].getBoundingBox();
       hotZoneItem['rect'].x = Math.round((hotZoneItem['rect'].x - bgItem['rect'].x) * 100) / 100;
       hotZoneItem['rect'].y = Math.round((hotZoneItem['rect'].y - bgItem['rect'].y) * 100) / 100;
       hotZoneItem['rect'].width = Math.round((hotZoneItem['rect'].width) * 100) / 100;
@@ -894,7 +1284,360 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
     console.log('hotZoneItemArr: ', hotZoneItemArr);
 
-    this.save.emit({bgItem, hotZoneItemArr});
+    return {bgItem, hotZoneItemArr};
+  }
+
+  saveText(item) {
+    if (item.itemType == 'text') {
+      item.textLabel.text = item.text;
+    }
+  }
+
+  saveItemPos(item) {
+  
+    console.log('item.posX: ', item.posX)
+    console.log('item.posY: ', item.posY)
+
+      item.x = Number(item.posX || 0)
+      item.y = Number(item.posY || 0)
+  
+
+ 
+    // this.changeCurItem(item);
+
+
+
+    // this.curItem.x = item.posX || 0;
+    // this.curItem.y = item.posY || 0;
+
+    // this.mapUp();
+
+
+  }
+
+  onSaveCustomAction(e, item) {
+  
+    const data = e;
+    item['actionData_' + item.gIdx] = data;
+
+    if (data.type == 'pic') {
+      let picUrl = data.pic_url;
+      if (picUrl) {
+        this.loadHotZonePic(item.pic, picUrl);
+      }
+    }
+   
+    if (data.type == 'text') {
+      this.setActionFont(item.textLabel, data);
+      item.textLabel.refreshSize();
+    }
+   
+    // if (data.type == 'anima') {
+    //   this.setActionAnima(item.anima, data);
+    // }
+    
+    // this.loadHotZonePic(item.pic, e.url);
+    this.refresh()
+  }
+ 
+  setActionAnima() {
+
+  }
+  
+
+  setAnimaBtnClick(index) {
+
+    console.log('aaaa')
+    this.isAnimaSmall = false;
+
+    this.setCurDragonBone(index);
+
+    // this.curDragonBoneIndex = index;
+    // this.curDragonBoneGIdx = Number(this.hotZoneArr[index].gIdx);
+
+    // const {skeJsonData, texJsonData, texPngData} = this.hotZoneArr[index];
+
+    // this.skeJsonData = skeJsonData || {};
+    // this.texJsonData = texJsonData || {};
+    // this.texPngData = texPngData || {};
+
+    // this.animaPanelVisible = true;
+
+    // this.refresh();
+  }
+
+  
+  setAnimaSmallBtnClick(index) {
+
+
+    console.log('bbb')
+    this.isAnimaSmall = true;
+
+    this.setCurDragonBone(index);
+
+  }
+
+  setCurDragonBone(index) {
+    this.curDragonBoneIndex = index;
+    this.curDragonBoneGIdx = Number(this.hotZoneArr[index].gIdx);
+
+    const {skeJsonData, texJsonData, texPngData} = this.hotZoneArr[index];
+
+    this.skeJsonData = skeJsonData || {};
+    this.texJsonData = texJsonData || {};
+    this.texPngData = texPngData || {};
+
+    this.animaPanelVisible = true;
+
+    this.refresh();
+  }
+
+  setItemSizeByAnima(jsonData, item) {
+    console.log('json: ', jsonData);
+
+    const request = new XMLHttpRequest();
+    //通过url获取文件,第二个选项是文件所在的具体地址
+    request.open('GET', jsonData.url, true);
+    request.send(null);
+    request.onreadystatechange = ()=> {
+    if (request.readyState === 4 && request.status === 200) {
+        var type = request.getResponseHeader('Content-Type');
+              if (type.indexOf("text") !== 1) {
+                  //返回一个文件内容的对象
+
+                  const data = JSON.parse(request.responseText);
+                  console.log('request.responseText;', data)
+                  const animaSize = data.armature[0].canvas;
+                  item.width = animaSize.width;
+                  item.height = animaSize.height;
+                  // return request.responseText;
+              }
+          }
+    }
+  }
+
+  animaPanelCancel() {
+    this.animaPanelVisible = false;
+    this.refresh();
+  }
+
+  animaPanelOk() {
+
+    this.setItemDragonBoneData(this.hotZoneArr[this.curDragonBoneIndex]);
+    console.log('this.hotZoneArr: ', this.hotZoneArr);
+    this.animaPanelVisible = false;
+
+    if (this.isAnimaSmall) {
+      this.setItemSizeByAnima(this.skeJsonData, this.hotZoneArr[this.curDragonBoneIndex]);
+    }
+
+    this.refresh();
+  }
+
+  setItemDragonBoneData(item) {
+    item['skeJsonData'] = this.skeJsonData;
+    item['texJsonData'] = this.texJsonData;
+    item['texPngData'] = this.texPngData;
+  }
+
+  skeJsonHandleChange(e) {
+    console.log('e: ', e);
+    switch (e.type) {
+      case 'start':
+        this.isSkeJsonLoading = true;
+        break;
+
+      case 'success':
+        this.skeJsonData['url'] = e.file.response.url;
+        this.skeJsonData['name'] = e.file.name;
+        this.nzMessageService.success('上传成功');
+        this.isSkeJsonLoading = false;
+        break;
+
+      case 'progress':
+        break;
+    }
+  }
+
+  texJsonHandleChange(e) {
+    console.log('e: ', e);
+    switch (e.type) {
+      case 'start':
+        this.isTexJsonLoading = true;
+        break;
+
+      case 'success':
+        this.texJsonData['url'] = e.file.response.url;
+        this.texJsonData['name'] = e.file.name;
+        this.nzMessageService.success('上传成功');
+        this.isTexJsonLoading = false;
+        break;
+
+      case 'progress':
+        break;
+    }
+  }
+
+  texPngHandleChange(e) {
+    console.log('e: ', e);
+    switch (e.type) {
+      case 'start':
+        this.isTexPngLoading = true;
+        break;
+
+      case 'success':
+        this.texPngData['url'] = e.file.response.url;
+        this.texPngData['name'] = e.file.name;
+        this.nzMessageService.success('上传成功');
+        this.isTexPngLoading = false;
+        break;
+
+      case 'progress':
+        break;
+    }
+  }
+
+  copyItem(it) {
+
+    const {hotZoneItemArr} = this.getSendData();
+    let itemData;
+    hotZoneItemArr.forEach((data) => {
+      if (data.id == it.id) {
+        itemData = data;
+      }
+    })
+
+
+    let curBgRect;
+    if (this.bg) {
+      curBgRect = this.bg.getBoundingBox();
+    } else {
+      curBgRect = {x: 0, y: 0, width: this.canvasWidth, height: this.canvasHeight};
+    }
+
+    let oldBgRect = this.bgItem.rect;
+    if (!oldBgRect) {
+      oldBgRect = curBgRect;
+    }
+
+    const rate = curBgRect.width / oldBgRect.width;
+
+    console.log('rate: ', rate);
+
+
+    const data = itemData
+  
+    data.rect.x *= rate;
+    data.rect.y *= rate;
+    data.rect.width *= rate;
+    data.rect.height *= rate;
+
+    data.rect.x += curBgRect.x;
+    data.rect.y += curBgRect.y;
+
+
+    const item = this.getHotZoneItem(data);
+    item.audio_url = data.audio_url;
+    item.pic_url = data.pic_url;
+    item.text = data.text;
+    item.itemType = data.itemType;
+
+    this.hotZoneArr.push(item);
+
+    if (this.customTypeGroupArr) {
+      this.refreshCustomItem(item);
+    } else {
+      this.refreshItem(item);
+    }
+
+    this.refreshHotZoneId();
+
+    item['id'] = this.createItemId();
+
+  }
+
+  createItemId() {
+    return new Date().getTime().toString();
+  }
+
+  copyHotZoneData() {
+
+    const {bgItem, hotZoneItemArr} = this.getSendData();
+
+    // const hotZoneItemArrNew = [];
+    // const tmpArr = JSON.parse(JSON.stringify(hotZoneItemArr));
+    // tmpArr.forEach((item) => {
+    //   if (item.gIdx === '0') {
+    //     hotZoneItemArr.push(item);
+    //   }
+    // })
+
+    const jsonData = {
+      bgItem,
+      hotZoneItemArr,
+      isHasRect: this.isHasRect,
+      isHasPic: this.isHasPic,
+      isHasText: this.isHasText,
+      isHasAudio: this.isHasAudio,
+      isHasAnima: this.isHasAnima,
+      hotZoneFontObj: this.hotZoneFontObj,
+      defaultItemType: this.defaultItemType,
+      hotZoneImgSize: this.hotZoneImgSize,
+    };
+
+
+    const oInput = document.createElement('input');
+    oInput.value = JSON.stringify(jsonData);
+    document.body.appendChild(oInput);
+    oInput.select(); // 选择对象
+    document.execCommand('Copy'); // 执行浏览器复制命令
+
+    document.body.removeChild(oInput);
+    this.nzMessageService.success('复制成功');
+    // alert('复制成功');
+
+
+  }
+
+  importData() {
+    if (!this.pasteData) {
+      return;
+    }
+
+
+    try {
+      const data = JSON.parse(this.pasteData);
+      console.log('data:', data);
+      const {
+        bgItem,
+        hotZoneItemArr,
+        isHasRect,
+        isHasPic,
+        isHasText,
+        isHasAudio,
+        isHasAnima,
+        hotZoneFontObj,
+        defaultItemType,
+        hotZoneImgSize,
+      } = data;
+
+      this.hotZoneItemArr = hotZoneItemArr;
+      this.isHasRect = isHasRect;
+      this.isHasPic = isHasPic;
+      this.isHasText = isHasText;
+      this.isHasAudio = isHasAudio;
+      this.isHasAnima = isHasAnima;
+      this.hotZoneFontObj = hotZoneFontObj;
+      this.defaultItemType = defaultItemType;
+      this.hotZoneImgSize = hotZoneImgSize;
+
+      this.bgItem = bgItem;
+
+      this.pasteData = '';
+    } catch (e) {
+      console.log('err: ', e);
+      this.nzMessageService.error('导入失败');
+    }
   }
 
   private updatePos() {
@@ -917,12 +1660,15 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
           break;
       }
 
-      item.x = x;
-      item.y = y;
-      item.pic.x = x;
-      item.pic.y = y;
-      item.textLabel.x = x;
-      item.textLabel.y = y;
+      if (x != undefined && y != undefined) {
+        item.x = x;
+        item.y = y;
+        item.pic.x = x;
+        item.pic.y = y;
+        item.textLabel.x = x;
+        item.textLabel.y = y;
+      }
+
     });
   }
 
@@ -944,6 +1690,12 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
     item.textLabel.visible = true;
   }
 
+  private setNoneState(item: any) {
+    item.visible = false;
+    item.pic.visible = false;
+    item.textLabel.visible = false;
+  }
+
   private clickedHotZoneRect(item: any) {
     if (this.checkClickTarget(item)) {
 
@@ -962,6 +1714,17 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
 
   private clickedHotZonePic(item: any) {
     if (this.checkClickTarget(item)) {
+
+      if (item.lineDashFlag && this.checkClickTarget(item.arrow)) {
+        this.changeItemSize(item);
+        // } else if (item.lineDashFlag && this.checkClickTarget(item.arrowTop)) {
+        //   this.changeItemTopSize(item);
+        // } else if (item.lineDashFlag && this.checkClickTarget(item.arrowRight)) {
+        //   this.changeItemRightSize(item);
+      } else {
+        this.changeCurItem(item);
+      }
+
       this.curItem = item;
     }
   }
@@ -972,15 +1735,42 @@ export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
     }
   }
 
-  saveText(item) {
-    item.textLabel.text = item.text;
+  clickedDragPoint(item) {
+    this.curItem = item;
   }
 
-  private loadHotZonePic(pic: HotZoneImg, url) {
-    const baseLen = this.hotZoneImgSize * this.mapScale;
+  private loadHotZonePic(pic: HotZoneImg, url, scale = null) {
+
+    let baseLen;
+    if (this.hotZoneImgSize) {
+      baseLen = this.hotZoneImgSize * this.mapScale;
+    }
+
     pic.load(url).then(() => {
-      const s = getMinScale(pic, baseLen);
-      pic.setScaleXY(s);
+      if (!scale) {
+        if (baseLen) {
+          scale = getMinScale(pic, baseLen);
+        } else {
+          scale = this.bg.scaleX;
+        }
+      } 
+
+      pic.setScaleXY(scale);
     });
   }
+
+  
+  closeAfterPanel() {
+    this.refresh();
+  }
+  /**
+   * 刷新 渲染页面
+   */
+  refresh() {
+    setTimeout(() => {
+      this.appRef.tick();
+    }, 1);
+  }
+
+
 }
diff --git a/src/app/common/sub-template/sub-template.component.html b/src/app/common/sub-template/sub-template.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..676c4257cea8257b2649431b3d87d6cdb6fa1f86
--- /dev/null
+++ b/src/app/common/sub-template/sub-template.component.html
@@ -0,0 +1,53 @@
+<div class="content">
+
+  <div style="display: flex;  margin-bottom: 5px;"> 
+    <button nz-button nzType="dashed" (click)="selectSubTemplate()" style=" border-radius: 0.5rem; border: 1px solid #ddd;">
+      <i nz-icon nzType="appstore" nzTheme="outline"></i>
+      选择子模板类型
+    </button>
+  
+    <h2 *ngIf="_item.template" style="margin-left: 10px; margin-top: 1px;">
+      {{_item.template.description}}
+    </h2>
+
+    <button nz-button nzType="danger" (click)="deleteSubTemplate()" style="position: absolute; right: 30px; border-radius: 0.5rem; border: 1px solid #ddd;">
+      <i nz-icon nzType="delete" nzTheme="outline"></i>
+      删除
+    </button>
+  
+  </div>
+ 
+
+    <iframe #iframe id="iframe" [src]="safeUrl" style="width: 98%; height: 70vh; border: 1px solid #ccc; padding: 5px;" frameborder="0">
+    </iframe>
+
+
+
+
+ 
+
+ 
+
+ 
+  <nz-modal [(nzVisible)]="isShowTemplate" (nzAfterClose)="closePanel()" nzTitle="选择子模板" [nzFooter]="null"
+    (nzOnCancel)="panelCancelClicked()" nzWidth="80%" nzHeight="80%">
+
+    <div class="template-container">
+
+      <div *ngFor="let template of templateArr" >
+
+          <div (click)="templateClick(template)" style="width: 150px; height: 100px; border: 1px solid #ccc; margin: 5px; cursor:pointer; user-select: none;">
+            <div style="width: 100%; height: 70%; border-bottom: 1px solid #ccc; overflow: hidden;">
+              <img [src]="template.cover" style="width: 100%; height: auto;">
+            </div>
+            <div style="width: 100%; height: 30%; display: flex; align-items: center; justify-content: center; ">
+              <label  style="width: 90%; text-align: center; text-overflow: ellipsis; white-space:nowrap; overflow: hidden; ">{{template.description}}</label>
+            </div>
+          </div>
+        </div>
+
+    </div>
+  </nz-modal>
+   
+
+</div>
\ No newline at end of file
diff --git a/src/app/common/sub-template/sub-template.component.scss b/src/app/common/sub-template/sub-template.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..2270cc0bf5b6b13b5822988ade8cb380ab20729f
--- /dev/null
+++ b/src/app/common/sub-template/sub-template.component.scss
@@ -0,0 +1,18 @@
+@import '../../style/common_mixin.css';
+
+.content {
+  // width: 100%;
+  height: 100%;
+  border: 2px solid #ccc;
+  padding: 10px;
+  border-radius: 5px;
+}
+
+.template-container{
+  width: 100%;
+  display: flex;
+  // align-items: center;
+  // justify-content: space-around;
+  flex-wrap: wrap;
+
+}
\ No newline at end of file
diff --git a/src/app/common/sub-template/sub-template.component.ts b/src/app/common/sub-template/sub-template.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8e462fc35ac65dab6e68a1906c8b399645f76598
--- /dev/null
+++ b/src/app/common/sub-template/sub-template.component.ts
@@ -0,0 +1,368 @@
+import {ApplicationRef, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, ViewChild} from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+import { NzMessageService, UploadXHRArgs, UploadFile } from 'ng-zorro-antd';
+
+
+@Component({
+  selector: 'app-sub-template',
+  templateUrl: './sub-template.component.html',
+  styleUrls: ['./sub-template.component.scss']
+})
+export class SubTemplateComponent implements OnDestroy, OnChanges {
+  uploading = false;
+  progress = 0;
+ 
+  
+  @Output()
+  save = new EventEmitter();
+
+  @Output()
+  deleteFunc = new EventEmitter();
+
+  @Output()
+  refreshEmitter = new EventEmitter();
+
+  @Input()
+  set item(v) {
+
+    console.log('set item: ', v);
+
+    if (this.uploadUrl) {
+      this._item = v;
+      console.log(' __  refreshSafeUrl 3')
+      this.refreshSafeUrl();
+    }
+    this._data = v;
+  }
+
+  get item() {
+    return this._data;
+  }
+
+  _item;
+  _data;
+
+
+
+  @Input()
+  key = '';
+
+  uploadUrl;
+  uploadData;
+
+  @ViewChild('iframe', {static: true }) iframe: ElementRef;
+  isShowTemplate = false;
+
+  constructor(private el:ElementRef, private appRef: ApplicationRef, private nzMessageService: NzMessageService, private sanitizer: DomSanitizer) {
+
+
+    console.log(' in constructor 1111:', window['air']);
+
+    
+    this.uploadUrl = (<any> window).courseware.uploadUrl();
+    this.uploadData = (<any> window).courseware.uploadData();
+
+    window['air'].getUploadCallback = (url, data) => {
+
+      this.uploadUrl = url;
+      this.uploadData = data;
+    };
+
+    this.setUploadUrl();
+  }
+
+
+  setUploadUrl() {
+
+    if (!this.uploadUrl) {
+
+      setTimeout(() => {
+        this.setUploadUrl();
+      }, 500);
+    } else {
+
+      if (this._data) {
+
+        this._item = this._data;
+        this.refreshSafeUrl()
+      }
+
+    }
+  }
+
+  safeUrl;
+  refreshSafeUrl(formUrl=null) {
+    if (!formUrl) {
+      formUrl = this._item.formUrl || '';
+    }
+
+    this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(formUrl); 
+
+  }
+
+  
+  ngOnChanges() {
+ 
+  }
+
+  isInit = false;
+  ngOnInit(): void {
+
+
+
+    console.log(' in ngOnInit ')
+    if (!this._item) {
+      this._item = {};
+    }
+
+    
+    this.initListener();
+
+  }
+
+  
+
+  templateArr = [];
+  async selectSubTemplate() {
+
+    if (this.templateArr.length == 0) {
+      await this.getAllTemplateData().then((data : []) => {
+        this.templateArr = data;
+      });
+    }
+
+    this.isShowTemplate = true;
+  }
+
+  deleteSubTemplate() {
+    
+    console.log('in deleteFunc');
+    this.refreshSafeUrl('');
+    this.deleteFunc.emit({});
+  }
+
+
+  templateClick(template) {
+    console.log("template: ", template);
+
+    this._item = {};
+
+    const {name, last_version, form_url} = template;
+    const formUrl = `http://staging-teach.cdn.ireadabc.com/h5template/${name}/v${last_version}/${form_url}&key=${this.key}`
+
+    this._item.formUrl = formUrl;
+    this._item.template = template;
+
+    console.log('formUrl: ', formUrl)
+    
+    this.refreshSafeUrl();
+    this.closePanel();
+    this.sendData();
+
+    console.log('this.safeUrl 2 : ', this.safeUrl)
+
+    // const key = this.getQueryVariable(formUrl, 'key');
+    // console.log("key: ", key);
+    // console.log("url: ",  this._item['safeUrl']);
+  }
+
+  sendData() {
+    this.save.emit(this._item)
+  }
+
+  getQueryVariable(url, variable) {
+    var query = url.substring(1);
+    var vars = query.split("&");
+    for (var i=0;i<vars.length;i++) {
+      var pair = vars[i].split("=");
+      if(pair[0] == variable){return pair[1];}
+    }
+    return(false);
+  }
+
+  saveSubTemplate() {
+
+  }
+
+  initListener() {
+
+  
+    const msgFunc = (e) => {
+
+
+      if (!this.iframe || !this.iframe.nativeElement || !this.iframe.nativeElement.contentWindow) {
+        console.log('this.iframe not exist'); 
+        window.removeEventListener('message', msgFunc);
+        return;
+      }
+
+      let msgData = e.data;
+  
+      if (msgData.action === "getData") {
+
+        console.log("getData e: ", e);
+
+        if (this.iframe) {
+          console.log('ifram exist: ', this.iframe);
+        }
+  
+
+        if (!msgData.urlParams) {
+          console.log(' !msgData.urlParams: ' , msgData)
+          return;
+        }
+  
+        const key = this.getQueryVariable(msgData.urlParams, 'key');
+        if (key != this.key) {
+          console.log(' key != this.key ')
+          return;
+        }
+
+
+         const data = { msg: 'success', data:  JSON.stringify(this._item.data)};
+        //  iframeContent.postMessage( { action: 'getData', data: JSON.stringify(data) }, '*');
+        this.iframe.nativeElement.contentWindow.postMessage( { action: 'getData', data: JSON.stringify(data) }, '*');
+
+      }
+  
+
+      if (msgData.action === "setData") {
+
+        if (!msgData.urlParams) {
+          console.log(' !msgData.urlParams: ' , msgData)
+          return;
+        }
+  
+        const key = this.getQueryVariable(msgData.urlParams, 'key');
+        if (key != this.key) {
+          console.log(' key != this.key ')
+          return;
+        }
+
+        console.log("setData e: ", e);
+
+
+        
+        if (typeof(msgData.data)  === "string") {
+          console.log('msgData is string');
+          msgData.data = JSON.parse(msgData.data);
+          console.log('msgData.data: ', msgData.data);
+        }
+        this._item.data = msgData.data;
+        this.sendData();
+      }
+     
+
+      if (msgData.action === "getUpload") {
+
+        console.log( ' in getUpload')
+
+
+
+        // const iframeContent = this.el.nativeElement.querySelector('#iframe').contentWindow;
+   
+        
+        this.iframe.nativeElement.contentWindow.postMessage({
+          action: 'getUpload',
+          uploadUrl: this.uploadUrl,
+          uploadData: this.uploadData
+        }, '*');
+      }
+    }
+
+
+    if (this.iframe?.nativeElement?.contentWindow) {
+      console.log(' init Listener !');
+      window.addEventListener('message', msgFunc);
+
+    }
+    
+  }
+
+  async getAllTemplateData() {
+
+    return new Promise((resolve, reject) => {
+
+      const c = window['courseware']
+
+      try {
+        if (c && c.getTemplates) {
+          c.getTemplates((data) => {
+
+
+            // data =
+            // [
+            //   {"id": 26, "uuid":"c94fb1b0-3395-42ca-9ae3-03ca7c242d3f", "name": "hw_001", "description": "音频课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/3feddb01-4350-42ad-909f-236ad052fbb2.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 16},
+            //   {"id": 27, "uuid":"b8bedc68-9ef6-42a6-ba99-513daa244a6d", "name": "hw_002", "description": "视频课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/1b11d370-9d8b-4358-98d3-4757f825d2a4.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 16},
+            //   {"id": 28, "uuid":"068fcffb-4e99-4148-bc8e-2a89ee9bfe7c", "name": "hw_003", "description": "转盘课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/de3d8498-7523-4601-934e-932ac219a408.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 17},
+            //   {"id": 29, "uuid":"b2bfd8df-e0e4-41c9-b65f-12160628e1a3", "name": "hw_004", "description": "连线课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/824989ac-78fb-4db7-848a-790cba00effe.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 9 },
+            //   {"id": 30, "uuid":"d7be3c21-fc25-482d-a4a6-1a371970457a", "name": "hw_005", "description": "分类课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/aa69d85a-3430-48b2-91bf-a804dee1826d.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 19},
+            //   {"id": 31, "uuid":"34e98c65-f3f0-46e4-a7b8-5caf72954847", "name": "hw_006", "description": "翻卡游戏", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/fe54031b-7879-4c65-8de7-102fa5e5e9f5.png", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 5 }
+            // ]
+            // resolve(data);
+            // return
+            if (data) {
+              console.log('data~~~:', data);
+              resolve(JSON.parse(data));
+            } else {
+              console.log('data none');
+              resolve([]);
+            }
+          })
+        }
+        
+      } catch (error) {
+        reject(error);
+      }
+    })
+    // const data =
+    // [
+    //   {"id": 26, "uuid":"c94fb1b0-3395-42ca-9ae3-03ca7c242d3f", "name": "hw_001", "description": "音频课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/3feddb01-4350-42ad-909f-236ad052fbb2.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 16},
+    //   {"id": 27, "uuid":"b8bedc68-9ef6-42a6-ba99-513daa244a6d", "name": "hw_002", "description": "视频课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/1b11d370-9d8b-4358-98d3-4757f825d2a4.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 16},
+    //   {"id": 28, "uuid":"068fcffb-4e99-4148-bc8e-2a89ee9bfe7c", "name": "hw_003", "description": "转盘课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/de3d8498-7523-4601-934e-932ac219a408.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 17},
+    //   {"id": 29, "uuid":"b2bfd8df-e0e4-41c9-b65f-12160628e1a3", "name": "hw_004", "description": "连线课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/824989ac-78fb-4db7-848a-790cba00effe.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 9 },
+    //   {"id": 30, "uuid":"d7be3c21-fc25-482d-a4a6-1a371970457a", "name": "hw_005", "description": "分类课件", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/aa69d85a-3430-48b2-91bf-a804dee1826d.jpg", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 19},
+    //   {"id": 31, "uuid":"34e98c65-f3f0-46e4-a7b8-5caf72954847", "name": "hw_006", "description": "翻卡游戏", "cover": "http://iplayabc-teach-yun.oss-cn-beijing.aliyuncs.com/fe54031b-7879-4c65-8de7-102fa5e5e9f5.png", "type": 1, "t_type": 2, "form_url": "index.html?type=form", "play_url": "index.html", "last_version": 5 }
+    // ]
+
+    // return data;
+  }
+
+
+
+
+
+
+  /**
+   * 刷新 渲染页面
+   */
+  refresh() {
+
+    // this.refreshEmitter.emit();
+    setTimeout(() => {
+      this.appRef.tick();
+    }, 1);
+  }
+
+
+  closePanel() {
+    console.log(' in closePanel ');
+    // this.refresh();
+    this.isShowTemplate = false;
+  }
+
+  panelCancelClicked() {
+    this.isShowTemplate = false;
+
+  }
+
+  panelOkClicked() {
+    this.isShowTemplate = false;
+
+  }
+
+  ngOnDestroy() {
+
+  }
+
+}
diff --git a/src/app/common/upload-dragon-bone/upload-dragon-bone.component.html b/src/app/common/upload-dragon-bone/upload-dragon-bone.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..935479752d531c52e99fa7bfb39b36d49dcc06fc
--- /dev/null
+++ b/src/app/common/upload-dragon-bone/upload-dragon-bone.component.html
@@ -0,0 +1,50 @@
+<div class="position-relative">
+
+
+  <button nz-button (click)="setAnimaBtnClick()" style=" border-radius: 0.5rem; border: 1px solid #ddd;">
+    <i nz-icon nzType="tool" nzTheme="outline"></i>
+    {{btnName}}
+  </button>
+
+
+  <!--配置龙骨面板-->
+  <nz-modal [(nzVisible)]="animaPanelVisible" (nzAfterClose)="closePanel()" nzTitle="配置资源文件"
+    (nzOnCancel)="animaPanelCancel()" (nzOnOk)="animaPanelOk()" nzOkText="保存">
+
+    <div class="anima-upload-btn">
+      <span style="margin-right: 10px">上传 ske_json 文件: </span>
+      <nz-upload [nzShowUploadList]="false" nzAccept="application/json" [nzAction]="uploadUrl" [nzData]="uploadData"
+        (nzChange)="skeJsonHandleChange($event)">
+        <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+      </nz-upload>
+      <i *ngIf="isSkeJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+      <span *ngIf="skeJsonData && skeJsonData['name']" style="margin-left: 10px"><u> {{skeJsonData['name']}} </u></span>
+    </div>
+
+    <div class="anima-upload-btn">
+      <span style="margin-right: 10px">上传 tex_json 文件: </span>
+      <nz-upload [nzShowUploadList]="false" nzAccept="application/json" [nzAction]="uploadUrl" [nzData]="uploadData"
+        (nzChange)="texJsonHandleChange($event)">
+        <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+      </nz-upload>
+      <i *ngIf="isTexJsonLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+      <span *ngIf="texJsonData && texJsonData['name']" style="margin-left: 10px"><u> {{texJsonData['name']}} </u></span>
+    </div>
+
+    <div class="anima-upload-btn">
+      <span style="margin-right: 10px">上传 tex_png 文件: </span>
+      <nz-upload [nzShowUploadList]="false" nzAccept="image/*" [nzAction]="uploadUrl" [nzData]="uploadData"
+        (nzChange)="texPngHandleChange($event)">
+        <button nz-button><i nz-icon nzType="upload"></i><span>Upload</span></button>
+      </nz-upload>
+      <i *ngIf="isTexPngLoading" style="margin-left: 10px;" nz-icon [nzType]="'loading'"></i>
+      <span *ngIf="texPngData && texPngData['name']" style="margin-left: 10px"><u> {{texPngData['name']}} </u></span>
+    </div>
+
+    <div class="anima-upload-btn" *ngIf="animaNames && animaNames.length > 0">
+      提示:需包含动画: {{animaNames.toString()}}.
+    </div>
+
+  </nz-modal>
+
+</div>
\ No newline at end of file
diff --git a/src/app/common/upload-dragon-bone/upload-dragon-bone.component.scss b/src/app/common/upload-dragon-bone/upload-dragon-bone.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e9c56bfc8de8aec772d05405514870f9bb6c30f6
--- /dev/null
+++ b/src/app/common/upload-dragon-bone/upload-dragon-bone.component.scss
@@ -0,0 +1,110 @@
+@import '../../style/common_mixin.css';
+
+.p-image-uploader {
+  position: relative;
+  display: block;
+  width: 100%;
+  height: 0;
+  padding-bottom: 56.25%;
+  .p-box {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    border: 2px dashed #ddd;
+    border-radius: 0.5rem;
+    background-color: #fafafa;
+    text-align: center;
+    color: #aaa;
+    .p-upload-icon {
+      text-align: center;
+      margin: auto;
+      .anticon-upload {
+        color: #888;
+        font-size: 5rem;
+      }
+      .p-progress-bar {
+        position: relative;
+        width: 20rem;
+        height: 1.5rem;
+        border: 1px solid #ccc;
+        border-radius: 1rem;
+        .p-progress-bg {
+          background-color: #1890ff;
+          border-radius: 1rem;
+          height: 100%;
+        }
+        .p-progress-value {
+          position: absolute;
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 100%;
+          text-shadow: 0 0 4px #000;
+          color: white;
+          text-align: center;
+          font-size: 0.9rem;
+          line-height: 1.5rem;
+        }
+      }
+    }
+    .p-preview {
+      width: 100%;
+      height: 100%;
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: 50% 50%;
+      //background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
+    }
+  }
+  .d-flex{
+    display: flex;
+  }
+}
+
+.p-btn-delete {
+  position: absolute;
+  right: -0.5rem;
+  top: -0.5rem;
+  width: 2rem;
+  height: 2rem;
+  border: 0.2rem solid white;
+  border-radius: 50%;
+  font-size: 1.2rem;
+  background-color: #fb781a;
+  color: white;
+  text-align: center;
+}
+
+.p-upload-progress-bg {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  & .i-text {
+    position: absolute;
+    text-align: center;
+    color: white;
+    text-shadow: 0 0 2px rgba(0, 0, 0, .85);
+  }
+  & .i-bg {
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+    background-color: #27b43f;
+    border-radius: 0.5rem;
+  }
+}
+
+.anima-upload-btn {
+  padding: 10px;
+}
+
+
+:host ::ng-deep .ant-upload {
+  display: block;
+}
diff --git a/src/app/common/upload-dragon-bone/upload-dragon-bone.component.ts b/src/app/common/upload-dragon-bone/upload-dragon-bone.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a99c3e4cf7ff4618296ff2f64c0f081e4aaef1e7
--- /dev/null
+++ b/src/app/common/upload-dragon-bone/upload-dragon-bone.component.ts
@@ -0,0 +1,181 @@
+import {ApplicationRef, Component, EventEmitter, Input, OnChanges, OnDestroy, Output} from '@angular/core';
+import { NzMessageService, UploadXHRArgs, UploadFile } from 'ng-zorro-antd';
+
+
+@Component({
+  selector: 'app-upload-dragon-bone',
+  templateUrl: './upload-dragon-bone.component.html',
+  styleUrls: ['./upload-dragon-bone.component.scss']
+})
+export class UploadDragonBoneComponent implements OnDestroy, OnChanges {
+  uploading = false;
+  progress = 0;
+  @Input()
+  btnName = '配置龙骨动画';
+  @Input()
+  animaNames = [];
+
+  @Input()
+  skeJsonData = {};
+  @Input()
+  texJsonData = {};
+  @Input()
+  texPngData = {};
+  
+  @Output()
+  save = new EventEmitter();
+
+  @Output()
+  refreshEmitter = new EventEmitter();
+
+  // @Input()
+  // picUrl;
+  // @Input()
+  // canDelete = false;
+  // @Output()
+  // imageUploaded = new EventEmitter();
+  // @Output()
+  // imageUploadFailure = new EventEmitter();
+  // @Output()
+  // delete = new EventEmitter();
+  // @Input()
+  // picItem = null;
+  // @Input()
+  // iconSize = 2;
+  // @Input()
+  // TIP = 'Click here to upload image';
+  // @Input()
+  // disableUpload = false;
+
+
+  uploadUrl;
+  uploadData;
+
+  animaPanelVisible = false;
+
+  isSkeJsonLoading = false;
+  isTexJsonLoading = false;
+  isTexPngLoading = false;
+
+  constructor(private appRef: ApplicationRef, private nzMessageService: NzMessageService) {
+
+    this.uploadUrl = (<any> window).courseware.uploadUrl();
+    this.uploadData = (<any> window).courseware.uploadData();
+
+    window['air'].getUploadCallback = (url, data) => {
+      this.uploadUrl = url;
+      this.uploadData = data;
+    };
+
+  }
+  ngOnChanges() {
+ 
+  }
+
+  setAnimaBtnClick() {
+    this.animaPanelVisible = true;
+    this.refresh();
+  }
+
+  animaPanelCancel() {
+    this.animaPanelVisible = false;
+    this.refresh();
+  }
+
+  animaPanelOk() {
+    this.sendItemDragonBoneData();
+    this.animaPanelVisible = false;
+    this.refresh();
+  }
+
+  sendItemDragonBoneData() {
+    const data = {};
+    data['skeJsonData'] = this.skeJsonData;
+    data['texJsonData'] = this.texJsonData;
+    data['texPngData'] = this.texPngData;
+    this.save.emit(data);
+  }
+
+
+  skeJsonHandleChange(e) {
+    console.log('e: ', e);
+    switch (e.type) {
+      case 'start':
+        this.isSkeJsonLoading = true;
+        break;
+
+      case 'success':
+        this.skeJsonData['url'] = e.file.response.url;
+        this.skeJsonData['name'] = e.file.name;
+        this.nzMessageService.success('上传成功');
+        this.isSkeJsonLoading = false;
+        break;
+
+      case 'progress':
+        break;
+    }
+  }
+
+  texJsonHandleChange(e) {
+    console.log('e: ', e);
+    switch (e.type) {
+      case 'start':
+        this.isTexJsonLoading = true;
+        break;
+
+      case 'success':
+        this.texJsonData['url'] = e.file.response.url;
+        this.texJsonData['name'] = e.file.name;
+        this.nzMessageService.success('上传成功');
+        this.isTexJsonLoading = false;
+        break;
+
+      case 'progress':
+        break;
+    }
+  }
+
+  texPngHandleChange(e) {
+    console.log('e: ', e);
+    switch (e.type) {
+      case 'start':
+        this.isTexPngLoading = true;
+        break;
+
+      case 'success':
+        this.texPngData['url'] = e.file.response.url;
+        this.texPngData['name'] = e.file.name;
+        this.nzMessageService.success('上传成功');
+        this.isTexPngLoading = false; 
+        break;
+
+      case 'progress':
+        break;
+    }
+  }
+
+
+
+  /**
+   * 刷新 渲染页面
+   */
+  refresh() {
+
+    // this.refreshEmitter.emit();
+    setTimeout(() => {
+      this.appRef.tick();
+    }, 1);
+  }
+
+
+  closePanel() {
+    console.log(' in closePanel ');
+    this.refresh();
+  }
+
+
+
+  ngOnDestroy() {
+  }
+
+}
diff --git a/src/app/common/upload-image-with-preview/upload-image-with-preview.component.ts b/src/app/common/upload-image-with-preview/upload-image-with-preview.component.ts
index f4694a1f1ae4b582b3c121e601c6322437888820..ec51b2fbee594a12c3eefb1a0b2e1cb41fdd4652 100644
--- a/src/app/common/upload-image-with-preview/upload-image-with-preview.component.ts
+++ b/src/app/common/upload-image-with-preview/upload-image-with-preview.component.ts
@@ -42,7 +42,28 @@ export class UploadImageWithPreviewComponent implements OnDestroy, OnChanges {
       this.uploadData = data;
     };
 
+    this.setUploadUrl();
   }
+
+
+  setUploadUrl() {
+
+    if (!this.uploadUrl) {
+      console.log('this.uploadUrl -=- 1 : ', this.uploadUrl)
+      this.uploadUrl = (<any> window).courseware.uploadUrl();
+      this.uploadData = (<any> window).courseware.uploadData();
+      console.log('this.uploadUrl -=- 2 : ', this.uploadUrl)
+
+      setTimeout(() => {
+        this.setUploadUrl();
+      }, 500);
+    }
+
+    console.log('this.uploadUrl -=- 3 : ', this.uploadUrl)
+
+  }
+
+
   ngOnChanges() {
     if (!this.picItem) {
       return;
diff --git a/src/app/common/upload-video/upload-video.component.html b/src/app/common/upload-video/upload-video.component.html
index 8f1533993f2bdcf3d5f3e0cd4337f3fa96a1ebd7..5ba637166c426dbe6b160d8007b14d2e6c307688 100644
--- a/src/app/common/upload-video/upload-video.component.html
+++ b/src/app/common/upload-video/upload-video.component.html
@@ -81,8 +81,8 @@
     </div>
   </div>
   <div class="p-preview" *ngIf="!uploading && videoUrl " >
-    <!--<video crossorigin="anonymous" [src]="videoUrl" controls #videoNode></video>-->
-    <video [src]="safeVideoUrl(videoUrl)" controls #videoNode></video>
+    <video crossorigin="anonymous" [src]="videoUrl" controls #videoNode></video>
+    <!-- <video [src]="safeVideoUrl(videoUrl)" controls #videoNode></video> -->
   </div>
 </div>
 <div [style.display]="!checkVideoExists?'none':''">
diff --git a/src/app/form/form.component.css b/src/app/form/form.component.css
index 41f7757290161973406aeec2177e0feef3ffcdaa..5d78cd06c5f047021baddbe66edf8dae0954fb5d 100644
--- a/src/app/form/form.component.css
+++ b/src/app/form/form.component.css
@@ -6,3 +6,30 @@
   height: 100%;
 
 }
+
+.radioPaire {
+  float: left;
+  margin: 3px;
+  border-style: dashed;
+  border-color: #000;
+  border-width: 1px;
+}
+
+.border {
+  border-radius: 20px;
+  border-style: dashed;
+  padding: 20px;
+  margin: 20px;
+  /*width: 500px; */
+  /*//border-radius: 20px;*/
+  /*//border-width: 2px;*/
+  /*//border-color: #000000;*/
+}
+
+
+.border-lite {
+  border: 2px dashed #ddd;
+  border-radius: 0.5rem;
+  padding: 10px;
+  margin: 10px;
+}
diff --git a/src/app/form/form.component.html b/src/app/form/form.component.html
index a51aa6ba14036cd5e1b9a4544b55a7bf0b75c1c8..f1f9998a13620627b8190417702bff092fd89ebd 100644
--- a/src/app/form/form.component.html
+++ b/src/app/form/form.component.html
@@ -1,25 +1,299 @@
-
 <div class="model-content">
 
+ 
+  <div style="padding: 20px;">
+
+    
+    <div style="width: 400px; display: flex; justify-content: center; margin-bottom: 50px;">
 
-  <div style="position: absolute; left: 200px; top: 100px; width: 800px;">
+      <h2 style="width: 60px;">标题:</h2>
+      <input type="text" nz-input [(ngModel)]="item.title" (blur)="save()" style="margin-left: 15px">
+    </div>
 
-    <input type="text" nz-input [(ngModel)]="item.text" (blur)="save()">
 
-    <app-upload-image-with-preview
-      [picUrl]="item.pic_url"
-      (imageUploaded)="onImageUploadSuccess($event, 'pic_url')"
-    ></app-upload-image-with-preview>
+    <h2>热区配置:</h2>
+    <app-custom-hot-zone
+      [bgItem]="item.bgItem"
+      [hotZoneItemArr]="item.hotZoneItemArr"
+      [customTypeGroupArr]="customTypeGroupArr"
+      (save)="saveHotZone(item, $event)"
+      >
 
-    <app-audio-recorder
-      [audioUrl]="item.audio_url"
-      (audioUploaded)="onAudioUploadSuccess($event, 'audio_url')"
-    ></app-audio-recorder>
-    <app-custom-hot-zone></app-custom-hot-zone>
-    <app-upload-video></app-upload-video>
-    <app-lesson-title-config></app-lesson-title-config>
-  </div>
+    </app-custom-hot-zone>
+
+
+    <div>
+      <nz-radio-group [(ngModel)]="item.ques_type" style="margin-top: 50px" (ngModelChange)="save()">
+        <h2 nz-radio nzValue="single_choice">选择题</h2>
+        <h2 nz-radio nzValue="short_answer">简答题</h2>
+      </nz-radio-group>
+    </div>
+  
+    <!-- 选择题 -->
+
+    <div *ngIf="item.ques_type == 'single_choice'">
+
+      <div style="margin-top: 30px; display: flex; width: 1000px;">
+        <div style="width: 300px; margin-top: 10px; margin-right: 15px;">
+          <span style="margin-right: 5px;">问题:</span>
+  
+          <app-upload-image-with-preview
+            [picUrl]="item.ques_pic_url"
+            (imageUploaded)="onImageUploadSuccess($event, 'ques_pic_url')">
+          </app-upload-image-with-preview>
+        </div>
+       
+        <div style="width: 300px;">
+  
+          <div style="display: flex; align-items: center; height: 30px;">
+            <span style="margin-right: 5px; margin-left: 15px;">小标题:</span>
+  
+            <app-audio-recorder
+              [audioUrl]="item.title_audio_url"
+              (audioUploaded)="onAudioUploadSuccess($event, 'title_audio_url')"
+            ></app-audio-recorder>
+          </div>
+          
+          <app-upload-image-with-preview
+            [picUrl]="item.title_pic_url"
+            (imageUploaded)="onImageUploadSuccess($event, 'title_pic_url')">
+          </app-upload-image-with-preview>
+        </div>
+        
+      </div>
+  
+  
+      <div *ngFor="let op of item.optionArr" style="border: 1px solid #ccc; margin: 10px; padding: 10px; border-radius: 5px;">
+        <div style="margin-top: 20px; display: flex; align-items: center; width: 500px;">
+          <span style="margin-right: 5px;">{{op.id}}:</span>
+          <div style="width: 300px;">
+            <app-upload-image-with-preview
+              [picUrl]="op.pic_url"
+              (imageUploaded)="onImageUploadSuccess($event, 'pic_url', op)">
+            </app-upload-image-with-preview>
+          </div>
+          <div style="margin-left: 20px;">
+            <span> 正确: </span> 
+            <nz-switch [(ngModel)]="op.is_right" (ngModelChange)="save()"></nz-switch>
+          </div>
+          
+        </div>
+  
+        <nz-divider style="margin-top: 20px;"></nz-divider>
+  
+  
+        <div *ngIf="!op.is_right" style="padding: 20px; display: flex; align-items: center;">
+  
+          
+          <div style="width: 300px;">
+            纠错视频:
+            <app-upload-video
+              (videoUploaded)="onVideoUploadSuccess($event, op)"
+              [item]="op"
+              [videoUrl]="op.video_url">
+            </app-upload-video>
+          </div>
+         
+          <div style="margin-left: 20px;">
+  
+            <div style="display: flex; align-items: center;">
+              <span style="margin-right: 5px;">小标题2:</span>
+              <app-audio-recorder
+                [audioUrl]="op.title_audio_url"
+                (audioUploaded)="onAudioUploadSuccess($event, 'title_audio_url', op)"
+              ></app-audio-recorder>
+            </div>
+            <div style="width: 300px;">
+              <app-upload-image-with-preview
+                [picUrl]="op.title_pic_url"
+                (imageUploaded)="onImageUploadSuccess($event, 'title_pic_url', op)">
+              </app-upload-image-with-preview>
+            </div>
+  
+            <span style="margin-left: 15px;" >问题2:</span>
+            <div style="width: 300px;">
+              <app-upload-image-with-preview
+                [picUrl]="op.ques_pic_url"
+                (imageUploaded)="onImageUploadSuccess($event, 'ques_pic_url', op)">
+              </app-upload-image-with-preview>
+            </div>
+  
+          </div>
+  
+          <div style="margin-left: 20px;">
+  
+            <div *ngFor="let op2 of op.optionArr" style="display: flex; align-items: center; margin-top: 5px;">
+              {{op2.id}} 
+              <div style="width: 150px; margin-left: 5px;">
+                <app-upload-image-with-preview
+                  [picUrl]="op2.pic_url"
+                  (imageUploaded)="onImageUploadSuccess($event, 'pic_url', op2)">
+                </app-upload-image-with-preview>
+              </div>
+  
+              <div style="margin-left: 5px;">
+                <span> 正确: </span> 
+                <nz-switch [(ngModel)]="op2.is_right" (ngModelChange)="save()"></nz-switch>
+              </div>
+            </div>
+           
+          </div>
+        
+        </div>
+      </div>
+
+    </div>
+
+    
+
+    <!-- 简答题 -->
+
+    <div *ngIf="item.ques_type == 'short_answer'">
+
+
+      <div style="margin-top: 30px; display: flex; width: 1000px;">
+        <div style="width: 300px; margin-top: 10px; margin-right: 15px;">
+          <span style="margin-right: 5px;">选择题问题:</span>
+  
+          <app-upload-image-with-preview
+            [picUrl]="item.choice_ques_pic_url"
+            (imageUploaded)="onImageUploadSuccess($event, 'choice_ques_pic_url')">
+          </app-upload-image-with-preview>
+        </div>
+       
+        <div style="width: 300px;">
+  
+          <div style="display: flex; align-items: center; height: 30px;">
+            <span style="margin-right: 5px; margin-left: 15px;">选择小标题:</span>
+  
+            <app-audio-recorder
+              [audioUrl]="item.choice_title_audio_url"
+              (audioUploaded)="onAudioUploadSuccess($event, 'choice_title_audio_url')"
+            ></app-audio-recorder>
+          </div>
+          
+          <app-upload-image-with-preview
+            [picUrl]="item.choice_title_pic_url"
+            (imageUploaded)="onImageUploadSuccess($event, 'choice_title_pic_url')">
+          </app-upload-image-with-preview>
+        </div>
+        
+      </div>
+  
+  
+      <div *ngFor="let op of item.optionArr" style="border: 1px solid #ccc; margin: 10px; padding: 10px; border-radius: 5px;">
+        <div style="margin-top: 20px; display: flex; align-items: center; width: 500px;">
+          <span style="margin-right: 5px;">{{op.id}}:</span>
+          <div style="width: 300px;">
+            <app-upload-image-with-preview
+              [picUrl]="op.pic_url"
+              (imageUploaded)="onImageUploadSuccess($event, 'pic_url', op)">
+            </app-upload-image-with-preview>
+          </div>
+          <div style="margin-left: 20px;">
+            <span> 正确: </span> 
+            <nz-switch [(ngModel)]="op.is_right" (ngModelChange)="save()"></nz-switch>
+          </div>
+          
+        </div>
+  
+        <nz-divider style="margin-top: 20px;"></nz-divider>
+  
+  
+        
+      </div>
+
+      <nz-divider style="margin-top: 50px;"></nz-divider>
+
+      <div style="display: flex; align-items: center; margin-bottom: 50px;">
+
+        <div style="width: 400px; margin-top: 10px; margin-right: 15px;">
+          <span style="margin-right: 5px;">新简答问题:</span>
+  
+          <app-upload-image-with-preview
+            [picUrl]="item.answer_ques_pic_url"
+            (imageUploaded)="onImageUploadSuccess($event, 'answer_ques_pic_url')">
+          </app-upload-image-with-preview>
+        </div>
+  
+        <div style="width: 400px;">
+    
+          <div style="display: flex; align-items: center; height: 30px;">
+            <span style="margin-right: 5px; margin-left: 15px;">新简答小标题:</span>
+  
+            <app-audio-recorder
+              [audioUrl]="item.answer_title_audio_url"
+              (audioUploaded)="onAudioUploadSuccess($event, 'answer_title_audio_url')"
+            ></app-audio-recorder>
+          </div>
+          
+          <app-upload-image-with-preview
+            [picUrl]="item.answer_title_pic_url"
+            (imageUploaded)="onImageUploadSuccess($event, 'answer_title_pic_url')">
+          </app-upload-image-with-preview>
+        </div>
+
+        <div style="width: 400px; margin-left: 15px;">
+          <span style="margin-right: 5px; ">新简答题 答案:</span>
+          <div style="width: 200px; display: flex; justify-content: center; align-items: center;">
+            <input type="text" nz-input [(ngModel)]="item.new_answer" (blur)="save()" style="margin-top: 5px">
+          </div>
+        </div>
+  
+      </div>
+     
+
+    </div>
+
+    
+
+<!-- 
+    <div style="width: 100%; margin-top: 15px; margin-bottom: 50px;" align="center">
+      <h2>气泡文本</h2>
+      <div style="width: 80%; display: flex; justify-content: center; align-items: center;">
+        <input type="text" nz-input [(ngModel)]="item.title" (blur)="save()" style="margin-top: 5px">
  
+      </div>
+  
+    </div>
+   
+    <app-custom-hot-zone
+    [bgItem]="item.bgItem"
+    [hotZoneItemArr]="item.hotZoneItemArr"
+    [customTypeGroupArr]="customTypeGroupArr"
+    (save)="saveHotZone(item, $event)"
+    >
+
+    </app-custom-hot-zone> -->
+    <!-- <div style="width: 300px;" align='center'>
+      <span>图1: </span>
+      <app-upload-image-with-preview
+        [picUrl]="item.pic_url"
+        (imageUploaded)="onImageUploadSuccess($event, 'pic_url')">
+      </app-upload-image-with-preview>
+    </div>
+   
+    <div style="width: 300px; margin-top: 5px;" align='center'>
+      <span>图2: </span>
+      <app-upload-image-with-preview
+        [picUrl]="item.pic_url_2"
+        (imageUploaded)="onImageUploadSuccess($event, 'pic_url_2')">
+      </app-upload-image-with-preview>
+    </div>
+
+    <div style="width: 300px; margin-top: 15px;">
+      <span>文本: </span>
+      <input type="text" nz-input [(ngModel)]="item.text" (blur)="save()">
+    </div>
+
+    <div style="margin-top: 5px">
+      <span>音频: </span>
+      <app-audio-recorder
+        [audioUrl]="item.audio_url"
+        (audioUploaded)="onAudioUploadSuccess($event, 'audio_url')"
+      ></app-audio-recorder>
+    </div> -->
+
+  </div>
 
 </div>
- 
\ No newline at end of file
diff --git a/src/app/form/form.component.ts b/src/app/form/form.component.ts
index 8bf084e921171b4cf11f2779fd6dfc49daa67fdb..1f2309962108619d806808c3246d50f7cb24ddf4 100644
--- a/src/app/form/form.component.ts
+++ b/src/app/form/form.component.ts
@@ -1,5 +1,5 @@
-import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
-
+import { Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef } from '@angular/core';
+import { JsonPipe } from '@angular/common';
 
 
 @Component({
@@ -10,47 +10,154 @@ import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, Ap
 export class FormComponent implements OnInit, OnChanges, OnDestroy {
 
   // 储存数据用
-  saveKey = "test_0011";
+  saveKey = "JM_MATH01";
   // 储存对象
   item;
 
+  customTypeGroupArr = [
+
+    {
+      name: '轮播图片',
+      audio: true,
+      pic: true,
+      // rect: true,
+      // isShowPos: true,
+      // isCopy: true,
+
+      // label: '比对',
+    },
+    {
+      name: '选择区域',
+      rect: true,
+    },
+    {
+      name: '简答问题',
+      pic: true,
+    },
+    {
+      name: '简答填空区',
+      rect: true,
+      label: '答案',
+    },
+  ]
+
+
+  constructor(private appRef: ApplicationRef, private changeDetectorRef: ChangeDetectorRef) {
 
-  constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) {
+  }
 
+  createShell() {
+    this.item.wordList.push({
+      word: '',
+      audio: '',
+      backWord: '',
+      backWordAudio: '',
+    });
+    this.save();
   }
 
+  removeShell(idx) {
+    this.item.wordList.splice(idx, 1);
+    this.save();
+  }
 
   ngOnInit() {
 
     this.item = {};
 
     // 获取存储的数据
-    (<any> window).courseware.getData((data) => {
+    (<any>window).courseware.getData((data) => {
 
       if (data) {
         this.item = data;
       }
 
+      console.log('this.item: ', this.item);
+
       this.init();
       this.changeDetectorRef.markForCheck();
       this.changeDetectorRef.detectChanges();
       this.refresh();
 
     }, this.saveKey);
-
   }
 
-
   ngOnChanges() {
   }
 
   ngOnDestroy() {
   }
 
+  init() {
+
+    if (!this.item.sentenceArr) {
+      this.item.sentenceArr = [];
+    }
+    if (!this.item.templateArr) {
+     this.item.templateArr = [];
+    }
+    
+    if (!this.item.optionArr) {
+      this.item.optionArr = [
+        {id: 'A', optionArr: [{id: 'A'}, {id: 'B'}]},
+        {id: 'B', optionArr: [{id: 'A'}, {id: 'B'}]},
+        {id: 'C', optionArr: [{id: 'A'}, {id: 'B'}]},
+        {id: 'D', optionArr: [{id: 'A'}, {id: 'B'}]},
+      ];
+    }
+
+    if (!this.item.ques_type) {
+      this.item.ques_type = 'single_choice'
+    }
+  }
+
+  addSubTemplate() {
+    this.item.templateArr.push({
+    })
+
+    this.save();
+  }
+
+  onDeleteTemplate(e, index) {
+    this.item.templateArr[index] = {};
+    this.item.templateArr.splice(index, 1);
+    this.save();
+  }
+
+  onSaveTemplate(e, index) {
+
+    this.item.templateArr[index] = e;
+    this.save();
+  }
 
 
-  init() {
 
+  addBtnClick() {
+    this.item.sentenceArr.push({});
+    this.save();
+  }
+
+  deleteBtnClick(index) {
+    this.item.sentenceArr.splice(index, 1);
+    this.save();
+
+  }
+
+  onSaveCustomAction(e) {
+    console.log('e:', e);
+    this.item.customAction = e;
+    this.save();
+  }
+
+
+  saveHotZone(group, e) {
+    console.log('e: ', e);
+    const {bgItem, hotZoneItemArr} = e;
+  
+    group.bgItem = bgItem;
+    group.hotZoneItemArr = hotZoneItemArr;
+
+    this.save();
   }
 
 
@@ -58,30 +165,55 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
    * 储存图片数据
    * @param e
    */
-  onImageUploadSuccess(e, key) {
+  onImageUploadSuccess(e, key, item=null) {
+    if (!item) {
+      item = this.item;
+    }
 
-      this.item[key] = e.url;
-      this.save();
+    item[key] = e.url;
+    this.save();
   }
 
   /**
    * 储存音频数据
    * @param e
    */
-  onAudioUploadSuccess(e, key) {
+  onAudioUploadSuccess(e, key, item=null) {
+    if (item == null) {
+      item = this.item;
+    }
+    item[key] = e.url;
+    this.save();
+  }
 
-    this.item[key] = e.url;
+  onWordAudioUploadSuccess(e, idx) {
+    this.item.wordList[idx].audio = e.url;
     this.save();
   }
 
+  onBackWordAudioUploadSuccess(e, idx) {
+    this.item.wordList[idx].backWordAudio = e.url;
+    this.save();
+  }
 
+  onVideoUploadSuccess(e, item=null) {
+
+    console.log(' in onVideoUploadSuccess')
+    if (!item) {
+      item = this.item;
+    }
+    item.video_url = e.url;
+    this.save();
+  }
 
   /**
    * 储存数据
    */
   save() {
-    (<any> window).courseware.setData(this.item, null, this.saveKey);
+    (<any>window).courseware.setData(this.item, null, this.saveKey);
+
     this.refresh();
+    console.log('this.item = ' + JSON.stringify(this.item));
   }
 
   /**
@@ -93,5 +225,4 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
     }, 1);
   }
 
-}
-
+}
\ No newline at end of file
diff --git a/src/app/play/Unit.ts b/src/app/play/Unit.ts
index b78f3f2c65d4e7535cb12b7da1dff9bb746eb0d2..5b74947641828f611993629e402115b43d608965 100644
--- a/src/app/play/Unit.ts
+++ b/src/app/play/Unit.ts
@@ -1,5 +1,6 @@
 
 import TWEEN from '@tweenjs/tween.js';
+import { randomInt } from 'crypto';
 
 interface AirWindow  extends Window {
   air: any;
@@ -77,7 +78,9 @@ export class MySprite extends Sprite {
   _offCtx;
   isCircleStyle = false; // 切成圆形
   parent;
-  _maskSpr;
+
+  _maskSprArr = [];
+  _maskType = "destination-in" // destination-out
 
 
   init(imgObj = null, anchorX: number = 0.5, anchorY: number = 0.5) {
@@ -89,6 +92,8 @@ export class MySprite extends Sprite {
       this.width = this.img.width;
       this.height = this.img.height;
 
+      // this.img.setAttribute("crossOrigin",'Anonymous')
+
     }
 
     this.anchorX = anchorX;
@@ -114,16 +119,20 @@ export class MySprite extends Sprite {
     this._radius = r;
   }
 
-  setMaskSpr(spr) {
-    this._maskSpr = spr;
+  addMaskSpr(spr) {
+    this._maskSprArr.push(spr);
     this._createOffCtx();
   }
 
+  setMaskType(t) {
+    this._maskType = t;
+  }
+
   _createOffCtx() {
     if (!this._offCtx) {
       this._offCanvas = document.createElement('canvas'); // 创建一个新的canvas
-      this.width = this._offCanvas.width = this.width; // 创建一个正好包裹住一个粒子canvas
-      this.height = this._offCanvas.height = this.height;
+      this._offCanvas.width = this.width; // 创建一个正好包裹住一个粒子canvas
+      this._offCanvas.height = this.height;
       this._offCtx = this._offCanvas.getContext('2d');
     }
   }
@@ -220,12 +229,22 @@ export class MySprite extends Sprite {
 
     this._offCtx.clearRect(0, 0, this.width, this.height);
 
+
+
     this._offCtx.drawImage(this.img, 0, 0);
-    if (this._maskSpr) {
-      this._offCtx.globalCompositeOperation = 'destination-in';
-      this._offCtx.drawImage(this._maskSpr.img, this._maskSpr.x + this._maskSpr._offX - this._offX , this._maskSpr.y + this._maskSpr._offY - this._offY);
+    this._offCtx.globalCompositeOperation = this._maskType;
+
+    if (this._maskSprArr && this._maskSprArr.length > 0) {
+      
+      for (let i=0; i<this._maskSprArr.length; i++) {
+        this._maskSprArr[i].ctx = this._offCtx;
+        this._maskSprArr[i].update();
+      }
+    
     }
 
+
+
     this.ctx.drawImage(this._offCanvas, this._offX, this._offY);
 
     this._offCtx.restore();
@@ -293,6 +312,8 @@ export class MySprite extends Sprite {
       child.alpha = this.alpha;
     }
 
+    child.ctx = this.ctx;
+
   }
   removeChild(child) {
     const index = this.children.indexOf(child);
@@ -567,6 +588,7 @@ export class Label extends MySprite {
   // fontSize:String = '40px';
   fontName = 'Verdana';
   textAlign = 'left';
+  textBaseline = 'middle';
   fontSize = 40;
   fontColor = '#000000';
   fontWeight = 900;
@@ -596,7 +618,7 @@ export class Label extends MySprite {
 
     this.ctx.font = `${this.fontSize}px ${this.fontName}`;
     this.ctx.textAlign = this.textAlign;
-    this.ctx.textBaseline = 'middle';
+    this.ctx.textBaseline = this.textBaseline;
     this.ctx.fontWeight = this.fontWeight;
 
     this._width = this.ctx.measureText(this.text).width;
@@ -819,11 +841,18 @@ export class RichTextOld extends Label {
 
 
 
+
 export class RichText extends Label {
 
 
-  disH = 30;
+  topH = 0;
+  disH = 10;
   offW = 10;
+  row = 1;
+  textBaseline = "middle";
+
+  isShowWordBg = false;
+  wordBgData;
 
   constructor(ctx?: any) {
     super(ctx);
@@ -831,7 +860,93 @@ export class RichText extends Label {
     // this.dataArr = dataArr;
   }
 
+  getLineNum() {
+    this.drawSelf();
+    return this.row;
+  }
+
+  getAreaHeight() {
+    this.drawSelf();
+    const tmpTextH = this.row * this.fontSize;
+    const tmpDisH = (this.row - 1) * this.disH
+    return tmpTextH + tmpDisH;
+  }
+
+  getSubTextRectGroup(text, targetIndex = 0) {
 
+    console.log('!!!wordBgData: ', this.wordBgData);
+
+    const rectGroup = [];
+    const subTextArr = text.split(' ');
+    let baseIndex = targetIndex;
+    console.log('subTextArr: ', subTextArr);
+    for (let i=0; i<subTextArr.length; i++) {
+      const subText = subTextArr[i];
+      if (!subText) {
+        continue;
+      }
+      const subData = this.getSubTextRect(subText, baseIndex)
+      if (subData) {
+        console.log('baseIndex1 : ', baseIndex);
+        rectGroup.push(subData);
+        baseIndex = Number( subData.index ) + subData.text.length;
+        console.log('baseIndex2 : ', baseIndex);
+      }
+    }
+
+    return rectGroup;
+  }
+
+  getSubTextRect(subText, targetIndex=0) {
+
+ 
+
+    subText = subText.trim();
+    if (!subText) {
+      return;
+    }
+    
+
+    this.isShowWordBg = true;
+    this.update();
+
+
+    const tmpLabel = new RichText();
+    tmpLabel.fontSize = this.fontSize;
+    tmpLabel.fontName = this.fontName;
+    tmpLabel.textAlign = this.textAlign;
+    tmpLabel.textBaseline = this.textBaseline;
+    tmpLabel.fontWeight = this.fontWeight;
+    tmpLabel.width = this.width;
+    tmpLabel.height = this.height;
+    // console.log('subText: ', subText);
+    // console.log('this.text: ', this.text);
+    // console.log('targetIndex: ', targetIndex);
+    // const indexArr = searchSubStr(this.text, subText);
+    // console.log('indexArr: ', indexArr);
+    // const index = indexArr[targetIndex];
+
+    const index = this.text.indexOf(subText, targetIndex);
+
+    // console.log('index: ', index);
+    if (index == -1) {
+      return;
+    }
+    
+    // console.log('this.wordBgData: ', this.wordBgData);
+    // const index = this.text.indexOf(subText);
+
+    // console.log('!!!index: ', index);
+
+    const data = this.wordBgData[index.toString()];
+    // console.log('!!!wordBgData: ', this.wordBgData);
+    console.log('!!!data: ', data);
+
+    return data;
+
+
+
+  }
 
   drawText() {
 
@@ -839,12 +954,24 @@ export class RichText extends Label {
       return;
     }
 
+    let curCtx = this.ctx;
 
-    this.ctx.font = `${this.fontSize}px ${this.fontName}`;
-    this.ctx.textAlign = this.textAlign;
-    this.ctx.textBaseline = 'middle';
-    this.ctx.fontWeight = this.fontWeight;
-    this.ctx.fillStyle = this.fontColor;
+    if (this._offCtx) {
+
+
+
+      this._offCtx.save();
+      this._offCtx.clearRect(0, 0, this.width, this.height);
+
+
+      curCtx = this._offCtx;
+    }
+
+    curCtx.font = `${this.fontSize}px ${this.fontName}`;
+    curCtx.textAlign = this.textAlign;
+    curCtx.textBaseline = this.textBaseline;
+    curCtx.fontWeight = this.fontWeight;
+    curCtx.fillStyle = this.fontColor;
 
 
     const selfW = this.width * this.scaleX;
@@ -856,39 +983,83 @@ export class RichText extends Label {
     const w = selfW - this.offW * 2;
     const disH = (this.fontSize + this.disH) * this.scaleY;
 
+    let isPushWordData = false;
+    if (this.isShowWordBg && !this.wordBgData) {
+      this.wordBgData = {};
+      isPushWordData = true;
+    }
+
+    let wordIndex = -1;
+    for (const c of  chr) {
 
+      if (isPushWordData) {
+      }
 
 
-    for (const c of  chr) {
-      if (this.ctx.measureText(temp).width < w && this.ctx.measureText(temp + (c)).width <= w) {
+      if (c == '\n') {
+        row.push(temp);
+        temp = '';
+      } else if (curCtx.measureText(temp).width < w && curCtx.measureText(temp + (c)).width <= w) {
         temp += ' ' + c;
       } else {
         row.push(temp);
         temp = ' ' + c;
       }
+
+      if (isPushWordData) {
+        wordIndex = this.text.indexOf(c, wordIndex+1);
+        const key = wordIndex.toString();
+        const rectX = curCtx.measureText(temp).width
+        const rectY = ( row.length ) * disH / this.scaleY
+        const rectW = curCtx.measureText(c).width;
+        const rectH = this.fontSize;
+        this.wordBgData[key] = {rect: {x: rectX, y: rectY, width: rectW, height: rectH}, text: c, index: wordIndex}
+      }
+
     }
     row.push(temp);
+    
+    this.row = row.length;
 
     const x = 0;
-    const y = -row.length * disH / 2;
+    const y = this.topH//-row.length * disH / 2;
     // for (let b = 0 ; b < row.length; b++) {
-    //   this.ctx.strokeText(row[b], x, y + ( b + 1 ) * disH ); // 每行字体y坐标间隔20
+    //   curCtx.strokeText(row[b], x, y + ( b + 1 ) * disH ); // 每行字体y坐标间隔20
     // }
 
 
     if (this._outlineFlag) {
-      this.ctx.lineWidth = this._outLineWidth;
-      this.ctx.strokeStyle = this._outLineColor;
+      curCtx.lineWidth = this._outLineWidth;
+      curCtx.strokeStyle = this._outLineColor;
       for (let b = 0 ; b < row.length; b++) {
-        this.ctx.strokeText(row[b], x, y + ( b + 1 ) * disH ); // 每行字体y坐标间隔20
+        curCtx.strokeText(row[b], x, y + ( b + 0 ) * disH / this.scaleY ); // 每行字体y坐标间隔20
       }
-      // this.ctx.strokeText(this.text, 0, 0);
+      // curCtx.strokeText(this.text, 0, 0);
     }
 
 
-    // this.ctx.fillStyle = '#ff7600';
+    // curCtx.fillStyle = '#ff7600';
     for (let b = 0 ; b < row.length; b++) {
-      this.ctx.fillText(row[b], x, y + ( b + 1 ) * disH ); // 每行字体y坐标间隔20
+      curCtx.fillText(row[b], x, y + ( b + 0 ) * disH / this.scaleY ); // 每行字体y坐标间隔20
+    }
+
+
+    if (this._offCtx) { 
+
+
+      this._offCtx.globalCompositeOperation = this._maskType;
+
+      if (this._maskSprArr && this._maskSprArr.length > 0) {
+        
+        for (let i=0; i<this._maskSprArr.length; i++) {
+          this._maskSprArr[i].ctx = this._offCtx;
+          this._maskSprArr[i].update();
+        }
+        // console.log('aaaaa1');
+      }
+
+      this.ctx.drawImage(this._offCanvas, this._offX, this._offY);
+      this._offCtx.restore();
     }
 
   }
@@ -954,9 +1125,31 @@ export class ShapeRect extends MySprite {
 
   drawShape() {
 
-    this.ctx.fillStyle = this.fillColor;
-    this.ctx.fillRect(this._offX, this._offY, this.width, this.height);
+    if (this._offCtx) {
+      this._offCtx.save();
+      this._offCtx.clearRect(0, 0, this.width, this.height);
+  
+      this._offCtx.fillStyle = this.fillColor;
+      this._offCtx.fillRect(this._offX, this._offY, this.width, this.height);
+      this._offCtx.globalCompositeOperation = this._maskType;
+
+      if (this._maskSprArr && this._maskSprArr.length > 0) {
+        for (let i=0; i<this._maskSprArr.length; i++) {
+          this._maskSprArr[i].ctx = this._offCtx;
+          this._maskSprArr[i].update();
+        }
+      }
+  
+      this.ctx.drawImage(this._offCanvas, this._offX, this._offY);
+
+      this._offCtx.restore();
 
+    } else {
+      this.ctx.fillStyle = this.fillColor;
+      this.ctx.fillRect(this._offX, this._offY, this.width, this.height);
+  
+    }
+  
   }
 
 
@@ -1765,6 +1958,78 @@ export function showPopParticle(img, pos, parent, num = 20, minLen = 40, maxLen
 }
 
 
+export function RandomInt(a, b = 0) {
+  let max = Math.max(a, b);
+  let min = Math.min(a, b);
+  return Math.floor(Math.random() * (max - min) + min);
+}
+
+export function showShapeParticle(shapeType, shapeW, shapeH, pos, parent, num = 20, minLen = 40, maxLen = 80, showTime = 0.4) {
+
+
+  const randomColorArr = [
+    '#6996af',
+    '#4da940',
+    '#911b40',
+  ]
+
+  for (let i = 0; i < num; i ++) {
+
+    let particle;
+    switch(shapeType) {
+      case 'rect': 
+        particle = new ShapeRectNew();
+        particle.setSize(shapeW, shapeH, 0);
+        particle.fillColor = randomColorArr[RandomInt(0, randomColorArr.length)];
+        break;
+    }
+
+    particle.x = pos.x;
+    particle.y = pos.y;
+    parent.addChild(particle);
+
+    const randomR = 360 * Math.random();
+    // particle.rotation = randomR;
+
+    const randomS = 0.3 + Math.random() * 0.7;
+    particle.setScaleXY(randomS * 0.3);
+
+    const randomX = Math.random() * 20 - 10;
+    particle.x += randomX;
+
+    const randomY = Math.random() * 20 - 10;
+    particle.y += randomY;
+
+    const randomL = minLen + Math.random() * (maxLen - minLen);
+    const randomA = 360 * Math.random();
+    const randomT = getPosByAngle(randomA, randomL);
+    moveItem(particle, particle.x + randomT.x, particle.y + randomT.y, showTime, () => {
+
+
+
+    }, TWEEN.Easing.Exponential.Out);
+
+    // scaleItem(particle, 0, 0.6, () => {
+    //
+    // });
+
+    scaleItem(particle, randomS, 0.6, () => {
+
+    }, TWEEN.Easing.Exponential.Out);
+
+    setTimeout(() => {
+
+      hideItem(particle, 0.4, () => {
+
+      }, TWEEN.Easing.Cubic.In);
+
+    }, showTime * 0.5 * 1000);
+
+
+  }
+}
+
+
 
 export function shake(item, time = 0.5, callback = null, rate = 1) {
 
@@ -1835,6 +2100,8 @@ export class MyVideo extends MySprite {
     this.width = this.element.videoWidth;
     this.height = this.element.videoHeight;
 
+    console.log('this.width: ', this.width);
+    console.log('this.height: ', this.height);
     this.element.currentTime = 0.01;
   }
 
@@ -1846,6 +2113,403 @@ export class MyVideo extends MySprite {
   }
 }
 
+export class InputView extends MySprite {
+
+  element;
+  callback;
+  _isShowScroll = false;
+
+  constructor(ctx=null) {
+    super(ctx);
+    this._createInput()
+  }
+
+  set isShowScroll(v) {
+    this._isShowScroll = v;
+    if (!v) {
+      this.element.style.overflow = 'hidden';
+    }
+  }
+
+  get isShowScroll() {
+    return this._isShowScroll;
+  }
+
+  _createInput() {
+    const input = document.createElement('textarea');
+    input.style.resize = 'none';
+    input.style.border = 'none';
+    
+    input.style.position = 'absolute';
+    input.onblur = this.onblur.bind(this); 
+
+    const div = document.getElementById('div_input');
+    div.appendChild(input);
+
+    this.element = input;
+  }
+
+  onblur() {
+
+    if (this.callback) {
+      this.callback(this.element.value);
+    }
+    this.callback = null;
+    this.hide();
+  }
+
+  set text(v) {
+    this.element.value = v;
+  }
+
+  show() {
+    this.element.hidden = false;
+    setTimeout(() => {
+      this.element.focus();
+    }, 1);
+  }
+
+  hide() {
+    this.element.hidden = true;
+  }
+
+  setStyle(style) {
+    for ( let key in style ) {
+      console.log('key: ', key)
+      console.log('value: ', style[key])
+      this.element.style[key] = style[key];
+    }
+    console.log('this.element: ``````', this.element)
+  }
+
+  refreshInputStyle() {
+    this.element.style.left = this.x + 'px';
+    this.element.style.top = this.y + 'px';
+    this.element.style.width = this.width + 'px';
+    this.element.style.height = this.height + 'px';
+  }
+
+}
+
+export class ScrollView extends MySprite {
+
+  static ScrollSideType = {
+    VERTICAL : 'VERTICAL',
+    HORIZONTAL : 'HORIZONTAL',
+  }
+
+  _offCtx;
+  _offCanvas;
+  _scrollBar;
+
+  content;
+  bgColor;
+  barColor = '#fbe9b7'
+  scrollSide = ScrollView.ScrollSideType.VERTICAL;
+
+  scorllBarWidth;
+  scrollBarHeight;
+  itemArr = [];
+
+  constructor(ctx = null) {
+    super(ctx);
+
+    this._createOffCtx();
+    this._createScrollBar();
+  }
+
+  _createOffCtx() {
+    if (!this._offCtx) {
+      this._offCanvas = document.createElement('canvas'); // 创建一个新的canvas
+      this._offCanvas.width = this.width; // 创建一个正好包裹住一个粒子canvas
+      this._offCanvas.height = this.height;
+      this._offCtx = this._offCanvas.getContext('2d');
+
+      this.content = new MySprite(this._offCtx);
+    }
+  }
+
+  _createScrollBar() {
+    this._scrollBar = new ShapeRectNew();
+    this._scrollBar.anchorY = 0;
+    this._scrollBar.anchorX = 1;
+    this._scrollBar.setSize(10, 100, 5);
+    this._scrollBar.fillColor = this.barColor;
+    this.addChild(this._scrollBar);
+  }
+ 
+  setBgColor(color) {
+    this.bgColor = color;
+  }
+
+  setShowSize(w, h) {
+
+    this.width = w;
+    this.height = h;
+
+    if (this.content.width < this.width) {
+      this.content.width = this._offCanvas.width = this.width;
+    }
+    if (this.content.height < this.height) {
+      this.content.height = this._offCanvas.height = this.height;
+    }
+
+    this.refreshScrollBar();
+  }
+  
+
+  setContentSize(w, h) {
+    this.content.width = w;
+    this.content.height = h;
+
+    this._offCanvas.width = w;
+    this._offCanvas.height = h;
+  }
+
+  addItem(sprite) {
+    this.itemArr.push(sprite);
+    this.content.addChild(sprite);
+    sprite.ctx = this._offCtx;
+    this.refreshContentSize();
+  }
+
+  refreshContentSize() {
+    const children = this.itemArr;
+    // console.log('children: ', children);
+
+    
+    let maxW = 0;
+    let maxH = 0;
+    for (let i=0; i<children.length; i++) {
+
+      const box = children[i].getBoundingBox();
+      const curChild = children[i];
+      // console.log('box: ', box);
+      const boxEdgeX = curChild.x + (1 - curChild.anchorX) * curChild.width * curChild.scaleX;
+      const boxEdgeY = curChild.y + (1 - curChild.anchorY) * curChild.height * curChild.scaleY;
+
+      if (!children[i].colorRect) {
+        // const rect = new ShapeRectNew();
+        // rect.fillColor = '#ff0000';
+        // rect.setSize(curChild.width * curChild.scaleX, curChild.height * curChild.scaleY, 0);
+        // rect.alpha = 0.3;
+        // rect.x =  boxEdgeX - curChild.width * curChild.scaleX;
+        // rect.y =  boxEdgeY - curChild.height * curChild.scaleY;
+
+        // this.content.addChild(rect);
+        // children[i].colorRect = rect;
+      }
+      
+      // console.log('boxEdgeY: ', boxEdgeY);
+      if (boxEdgeX > maxW) {
+        maxW = boxEdgeX;
+      }
+      if (boxEdgeY > maxH) {
+        maxH = boxEdgeY;
+      }
+    }
+
+    // console.log('maxW: ', maxW);s
+    // console.log('maxH: ', maxH);
+    // console.log('this.y: ', this.y);
+    // console.log(this.getBoundingBox().height);
+
+    // const box = this.getBoundingBox();
+    this.content.width = maxW;
+    this.content.height = maxH + 10 //+ 500;
+    
+
+    this.refreshScrollBar();
+  }
+
+  setScrollBarSize(w, h) {
+    this.scorllBarWidth = w;
+    this.scrollBarHeight = h;
+  }
+
+  setContentScale(s) {
+    this.content.setScaleXY( 1 / s);
+    this.refreshScrollBar();
+  }
+
+  refreshScrollBar() {
+    let w = this.scorllBarWidth;
+    if (!w) {
+      w = this.width / 50;
+    }
+
+
+    const rect1 = this.getBoundingBox();
+    const rect2 = this.content.getBoundingBox();
+    const sRate = rect1.height / this.height; 
+
+    let rate = rect1.height / rect2.height;
+    // let rate = this.height / this.content.height;
+    // let rate = this.height * this.scaleY / this.content.height / this.content.scaleY;
+    if (rate >= 1) {
+      this._scrollBar.visible = false;
+      rate = 1;
+    } else {
+      this._scrollBar.visible = true;
+    }
+    const h = rate * this.height / sRate;
+    const r = w / 2;
+    this._scrollBar.setSize(w, h, r);
+
+    this._scrollBar.x = this.width;
+  }
+
+  refreshScrollBarPos() {
+    const rect1 = this.getBoundingBox();
+    const rect2 = this.content.getBoundingBox();
+    let rate = this.height / this.content.height;
+
+    
+    this._scrollBar.y = -this.content.y * (rate );
+  }
+
+
+  drawSelf() {
+
+    super.drawSelf();
+
+    this._offScreenRender();
+
+  }
+
+
+
+
+
+
+  touchStartPos;
+  touchStartContentPos;
+  onTouchStart(x, y) {
+
+    if (!this._scrollBar.visible) {
+      return;
+    }
+
+    this.touchStartPos = { x, y };
+    this.touchStartContentPos = { x: this.content.x, y: this.content.y };
+  }
+
+  onTouchMove(x, y) {
+
+    if (!this.touchStartPos) {
+      return;
+    }
+
+
+    if (!this.touchStartContentPos) {
+      return;
+    }
+
+
+    const offsetX = x - this.touchStartPos.x;
+    const offsetY = y - this.touchStartPos.y;
+    const rect = this.getBoundingBox();
+    const rect2 = this.content.getBoundingBox();
+
+    if (this.scrollSide == ScrollView.ScrollSideType.VERTICAL) {
+      this.content.y = between(this.touchStartContentPos.y + offsetY, 0, this.height - this.content.height);
+    } else {
+      this.content.x = between(this.touchStartContentPos.x + offsetX, 0, this.width  - this.content.width);
+    }
+    this.refreshScrollBarPos();
+  }
+
+  onTouchEnd(x, y) {
+    this.touchStartPos = null;
+    this.touchStartContentPos = null;
+  }
+
+  onWheelUp(offsetY) {
+    if (!this._scrollBar.visible) {
+      return;
+    }
+    const rect = this.getBoundingBox();
+
+    if (this.scrollSide == ScrollView.ScrollSideType.VERTICAL) {
+      this.content.y = between(this.content.y + 40, 0, this.height - this.content.height);
+    } else {
+      this.content.x = between(this.content.x + 40, 0, this.width - this.content.width);
+    }
+    this.refreshScrollBarPos();
+
+  }
+
+  onWheelDown(offsetY) {
+    if (!this._scrollBar.visible) {
+      return;
+    }
+
+    const rect = this.getBoundingBox();
+
+    if (this.scrollSide == ScrollView.ScrollSideType.VERTICAL) {
+      this.content.y = between(this.content.y - 40, 0,  this.height - this.content.height);
+    } else {
+      this.content.x = between(this.content.x - 40, 0,  this.width - this.content.width);
+    }
+    this.refreshScrollBarPos();
+
+  }
+
+  setContentSpr() {
+
+  }
+
+
+
+  _offScreenRender() {
+
+    this._offCtx.save();
+
+    this._offCtx.clearRect(0, 0, this._offCanvas.width, this._offCanvas.height);
+
+
+
+
+    if (this.bgColor) {
+      this._offCtx.fillStyle = this.bgColor;
+      this._offCtx.fillRect(this._offX, this._offY, this.width, this.height);
+      this._offCtx.globalCompositeOperation =  "source-atop";
+
+    } else {
+      this._offCtx.fillStyle = '#ffffff'
+      this._offCtx.fillRect(this._offX, this._offY, this.width, this.height);
+      this._offCtx.globalCompositeOperation =  "xor";
+
+    }
+
+
+    this.content.update();
+
+    this.ctx.drawImage(this._offCanvas, this._offX, this._offY);
+
+
+
+    
+
+
+    // this.ctx.drawImage(this._offCanvas, this._offX, this._offY);
+
+       
+    // this.ctx.drawImage(this._offCanvas, this._offX, this._offY, this.content.width, this.content.height, this._offX + this.content.x, this._offY + this.content.y, this.width, this.height);
+
+
+    this._offCtx.restore();
+
+  }
+ 
+}
+
+export function between(a, b, c) {
+  return [a, b, c].sort((x, y) => x - y)[1];
+}
+
+
+
 
 // --------------- custom class --------------------
 
diff --git a/src/app/play/Util.js b/src/app/play/Util.js
new file mode 100644
index 0000000000000000000000000000000000000000..7c7d8ae298ba36c8af5e83a50ad3a5af0622b39d
--- /dev/null
+++ b/src/app/play/Util.js
@@ -0,0 +1,34 @@
+
+
+
+
+export async function loadFonts (fontName, fontFile) {
+
+
+  // const font = new FontFace(
+  //   "DroidSansFallback",
+  //   "url(" + "../../assets/play/font/DroidSansFallback.ttf" + ")"
+  // );
+  
+  try {
+    console.log(location.href.split("#")[0] + "assets/play/font/" + fontFile);
+    const font = new FontFace(
+      fontName,
+      "url(" + location.href.split("#")[0] + "../../assets/play/font/" + fontFile + ")"
+    );
+    
+    console.log('start: ', new Date().getTime())
+    await font.load();
+    console.log('end: ', new Date().getTime())
+    document.fonts.add(font);
+    document.body.classList.add("fonts-loaded");
+
+  } catch (error) {
+    console.log(error);
+  }
+  
+}
+
+
+// --------------- custom class --------------------
+
diff --git a/src/app/play/checker.ts b/src/app/play/checker.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4fe05b5121ddf710d086d7c4640384a3907ab469
--- /dev/null
+++ b/src/app/play/checker.ts
@@ -0,0 +1,312 @@
+import { data } from "./words";
+
+export function checkAnswer(type, string, word) {
+  console.log('word: ', word);
+  switch (type) {
+    case 'device':
+    case 'side':
+    case 'work':
+    case 'adj':
+      return WordData.getInstance().check(type, string);
+    case 'name':
+    case 'city':
+      return checkCity(string);
+    case 'time':
+      return checkTime(string);
+    case 'date':
+      return checkDate(string);
+    case 'word':
+      return checkWord(string, word);
+    case 'wordList':
+      return checkWordInList(string, word);
+    default:
+      throw "错误的格式";
+  }
+}
+
+function checkCity(string) {
+  const bigCase = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ').split('');
+  const smallCase = ('abcdefghijklmnopqrstuvwxyz').split('');
+  const right = string.split(' ')
+    .filter(word => word != '')
+    .every(word => {
+      return word.split('')
+        .every((letter, idx) => {
+          if (idx == 0) {
+            return bigCase.includes(letter)
+              && !smallCase.includes(letter);
+          } else {
+            return !bigCase.includes(letter)
+              && smallCase.includes(letter);
+          }
+        });
+    });
+
+  return {
+    right: right,
+    info: '专有名词首字母应该大写。',
+  }
+}
+
+function checkTime(string) {
+  const rightResult = {
+    right: true,
+    info: '',
+  }
+  const wrongResult = {
+    right: false,
+    info: 'time格式填写错误。正确格式为“时 分”, 例如:ten thirty',
+  };
+  if (string.includes(":")) {
+    const words = string.split(":");
+    if (words.length != 2) {
+      return wrongResult;
+    }
+
+    if (words.some(word => word.length > 2)) {
+      return wrongResult;
+    }
+
+    const hour = parseInt(words[0]);
+    const minute = parseInt(words[1]);
+    if (hour < 0 || 12 < hour) {
+      return wrongResult;
+    }
+    if (minute < 0 || 59 < minute) {
+      return wrongResult;
+    }
+    return rightResult;
+  }
+
+  const hours = [
+    "one", "two", "three", "four", "five", "six",
+    "seven", "eight", "nine", "ten", "eleven", "twelve"
+  ];
+
+  const minutes = [
+    "o'clock", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
+    "eleven", "twelve", "thirteen", "fourteen", "fifteen", "a quarter", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
+    "twenty-one", "twenty-two", "twenty-three", "twenty-four", "twenty-five", "twenty-six", "twenty-seven", "twenty-eight", "twenty-nine", "thirty", "half",
+    "thirty-one", "thirty-two", "thirty-three", "thirty-four", "thirty-five", "thirty-six", "thirty-seven", "thirty-eight", "thirty-nine", "forty",
+    "forty-one", "forty-two", "forty-three", "forty-four", "forty-five", "three quarter", "forty-six", "forty-seven", "forty-eight", "forty-nine", "fifty",
+    "fifty-one", "fifty-two", "fifty-three", "fifty-four", "fifty-five", "fifty-six", "fifty-seven", "fifty-eight", "fifty-nine",
+  ];
+
+  const middles = [
+    'to',
+    'past'
+  ];
+
+  const middleWord = middles.find(word => string.includes(word));
+  if (middleWord) {
+    const strings = string.split(middleWord);
+    if (minutes.some(word => strings[0].trim().toLowerCase() == word)) {
+      if (hours.some(word => strings[1].trim().toLowerCase() == word)) {
+        return rightResult;
+      }
+    }
+    return wrongResult;
+  }
+
+  const strings = string.split(' ');
+  if (hours.some(word => strings[0].trim().toLowerCase() == word)) {
+    if (minutes.some(word => strings[1].trim().toLowerCase() == word)) {
+      return rightResult;
+    }
+  }
+  return wrongResult;
+}
+
+function checkDate(string: string) {
+  const wrongResult = {
+    right: false,
+    info: 'date格式填写错误。正确格式为“日 月 年”, 例如:15th August 2008',
+  };
+  const rightResult = {
+    right: true,
+    info: '',
+  }
+
+  let numberList = [];
+
+  if (string.includes('.')) {
+    numberList = string.split('.').map(string => parseInt(string));
+  } else {
+    const days1 = [
+      'none',
+      'First', 'second', 'third', 'fourth',
+      'fifth', 'sixth', 'seventh', 'eighth',
+      'ninth', 'tenth', 'eleventh', 'twelfth',
+      'thirteenth', 'fourteenth', 'fifteenth', 'sixteenth',
+      'seventeenth', 'eighteenth', 'nineteenth', 'Twentieth',
+      'twenty-first', 'twenty-second', 'Twenty-third', 'twenty-fourth',
+      'Twenty-fifth', 'Twenty-sixth', 'twenty-seventh', 'Twenty-eighth',
+      'twenty-ninth', 'Thirtieth', 'Thirty-first'
+    ];
+    const days2 = [
+      'none',
+      '1st', '2nd', '3rd', '4th',
+      '5th', '6th', '7th', '8th',
+      '9th', '10th', '11th', '12th',
+      '13th', '14th', '15th', '16th',
+      '17th', '18th', '19th', '20th',
+      '21st', '22nd', '23rd', '24th',
+      '25th', '26th', '27th', '28th',
+      '29th', '30th', '31st'
+    ]
+    const months = [
+      'none',
+      'January', 'February', 'March', 'April',
+      'May', 'June', 'July', 'August',
+      'September', 'October', 'November', 'December'
+    ];
+    let yearFlg = false;
+    let monthFlg = false;
+    let dayFlg = false;
+    numberList = string.split(' ').map(string => {
+      if (!monthFlg) {
+        const month = months.findIndex(str => str == string);
+        if (month >= 1) {
+          monthFlg = true;
+          return month;
+        }
+      }
+      if (!dayFlg) {
+        let day = days1.findIndex(str => str == string);
+        if (day >= 1) {
+          dayFlg = true;
+          return day;
+        }
+        day = days2.findIndex(str => str == string);
+        if (day >= 1) {
+          dayFlg = true;
+          return day;
+        }
+      }
+      if (!yearFlg) {
+        const year: number = parseInt(string);
+        if (!isNaN(year)) {
+          yearFlg = true;
+          return year;
+        }
+      }
+    });
+  }
+
+  console.log('numberList = ' + JSON.stringify(numberList));
+
+  if (numberList.length < 2 || 3 < numberList.length) {
+    return wrongResult;
+  }
+
+  if (numberList.some(num => !num)) {
+    // 有null或者undefind
+    return wrongResult;
+  }
+
+  if (numberList.some(num => num <= 0)) {
+    // è´Ÿæ•°
+    return wrongResult;
+  }
+
+  let rightFlg = true;
+  let yearFlg = false;
+  let monthFlg = false;
+  let dayFlg = false;
+  numberList.forEach((number, idx) => {
+    if (number > 31) {
+      if (!yearFlg) {
+        if (idx == 1) {
+          rightFlg = false;
+        } else {
+          yearFlg = true;
+        }
+      } else {
+        rightFlg = false;
+      }
+    } else if (number > 12) {
+      if (!dayFlg) {
+        dayFlg = true;
+      } else {
+        rightFlg = false;
+      }
+    } else {
+      if (!monthFlg) {
+        monthFlg = true;
+      } else if (!dayFlg) {
+        dayFlg = true;
+      } else {
+        rightFlg = false;
+      }
+    }
+  });
+
+  if (!monthFlg || !dayFlg) {
+    rightFlg = false;
+  }
+
+  if (rightFlg) {
+    return rightResult;
+  }
+
+  return wrongResult;
+}
+
+function checkWord(string: string, word: string) {
+  if (string.trim() != word.replace(/_/g, " ").trim()) {
+    return { right: false, info: word.replace(/_/g, " ").trim() };
+  }
+  return { right: true, info: '' };
+}
+
+function checkWordInList(string: any, word: any) {
+  const wordList: Array<string> = word;
+  if (wordList.includes(string)) {
+    return {
+      right: wordList.includes(string),
+      info: '错误的单词。'
+    };
+  } else {
+    return {
+      right: false,
+      info: '错误的单词。'
+    }
+  }
+}
+
+export class WordData {
+
+  static _instance = null;
+  static getInstance(): WordData {
+    if (!WordData._instance) {
+      WordData._instance = new WordData();
+    }
+    return WordData._instance;
+  }
+
+  constructor() {
+    this.device = data.device;
+    this.side = data.side;
+    this.work = data.work;
+    this.adj = data.adj;
+  }
+
+  device = ['TV', 'heater'];
+  side = ['north'];
+  work = ['teacher', 'worker'];
+  adj = ['big', 'small'];
+
+  check(type: any, string: any) {
+    const wrongString = {
+      device: '电器名错误。',
+      side: '方向名词错误。',
+      work: '工作名错误。',
+      adj: '形容词错误。',
+    }
+    return {
+      right: this[type].includes(string.trim()),
+      info: wrongString[type],
+    };
+  }
+}
+
diff --git a/src/app/play/play.component.css b/src/app/play/play.component.css
index bbda49566c0a6aa2c621e2f863caf1849e492572..5ffd4bb6b12a684b61a6597d079ec95fa70acdd6 100644
--- a/src/app/play/play.component.css
+++ b/src/app/play/play.component.css
@@ -3,6 +3,8 @@
   height: 100%;
   background: #ffffff;
   background-size: cover;
+
+  
 }
 
 #canvas {
@@ -10,10 +12,28 @@
 }
 
 
-
 @font-face
 {
   font-family: 'BRLNSDB';
-  src: url("../../assets/font/BRLNSDB.TTF") ;
+  src: url("../../assets/play/font/BRLNSDB_1.TTF")
+}
+
+@font-face
+{
+  font-family: 'Aileron-Black';
+  src: url("../../assets/play/font/Aileron-Black.ttf")
+}
+
+@font-face
+{
+  font-family: 'Aileron-Bold';
+  src: url("../../assets/play/font/Aileron-Bold.ttf")
 }
 
+
+
+@font-face
+{
+  font-family: 'DroidSansFallback';
+  src: url("../../assets/play/font/DroidSansFallback.ttf");
+}
\ No newline at end of file
diff --git a/src/app/play/play.component.html b/src/app/play/play.component.html
index 960fb495a359c3c7803c4370d8c7d8457517d70e..fe15e8d7bd87865cd914b9699aab9a570664a5b5 100644
--- a/src/app/play/play.component.html
+++ b/src/app/play/play.component.html
@@ -1,3 +1,24 @@
-<div class="game-container" #wrap>
-  <canvas id="canvas" #canvas></canvas>
+<span style="font-family:'DroidSansFallback'" ></span>
+<span style="font-family:'Aileron-Black'" ></span>
+<span style="font-family:'Aileron-Bold'" ></span>
+
+
+
+<ng-template #tpl>
+  <video></video>
+</ng-template>
+<div  style="display: none" #videoContainer></div>
+
+
+<div class="game-container" #wrap >
+  <canvas id="canvas" style="width: 100%; height: 100%;" #canvas></canvas>
 </div>
+
+<div id='div_input'>
+
+</div>
+
+<!-- <div style="position: absolute; top: 0">
+  <label style="font-family: Aileron-Black;"></label>
+  <label style="font-family: DroidSansFallback;"></label>
+</div> -->
diff --git a/src/app/play/play.component.ts b/src/app/play/play.component.ts
index 722f061f157a6447b2ffe05ba064b845a7fc5085..c0a3284f1fd42a4227664f65aca24d451414b119 100644
--- a/src/app/play/play.component.ts
+++ b/src/app/play/play.component.ts
@@ -1,17 +1,28 @@
-import {Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener} from '@angular/core';
+import { Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener, ApplicationRef, ViewContainerRef, TemplateRef } from '@angular/core';
 
 import {
+  MySprite,
   Label,
-  MySprite, tweenChange,
+  moveItem,
+  rotateItem,
+  removeItemFromArr,
+  ShapeRect, scaleItem, tweenChange, showPopParticle, hideItem, showItem, LineRect, shake, RichText, ShapeRectNew, showShapeParticle, delayCall, randomSortByArr, ScrollView, InputView, ShapeCircle, MyVideo, getMinScale,
 
 } from './Unit';
-import {res, resAudio} from './resources';
+import { res, resAudio } from './resources';
 
-import {Subject} from 'rxjs';
-import {debounceTime} from 'rxjs/operators';
+import { Subject } from 'rxjs';
+import { debounceTime } from 'rxjs/operators';
 
+import * as _ from 'lodash';
 import TWEEN from '@tweenjs/tween.js';
 
+import { loadFonts } from './Util'
+import { faTshirt } from '@fortawesome/free-solid-svg-icons';
+import { DomSanitizer } from '@angular/platform-browser';
+import { checkAnswer } from './checker';
+import { HttpClient } from '@angular/common/http';
+
 
 
 
@@ -21,23 +32,47 @@ import TWEEN from '@tweenjs/tween.js';
   styleUrls: ['./play.component.css']
 })
 export class PlayComponent implements OnInit, OnDestroy {
+  // 数据
+  _data;
+
+  @Input()
+  set data(data) {
+    this._data = data;
+  }
+  get data() {
+    return this._data;
+  }
 
-  @ViewChild('canvas', {static: true }) canvas: ElementRef;
-  @ViewChild('wrap', {static: true }) wrap: ElementRef;
+  @Input()
+  sid;
+
+  @ViewChild('canvas', { static: true }) canvas: ElementRef;
+  @ViewChild('wrap', { static: true }) wrap: ElementRef;
+  @ViewChild('iframeContent', {static: true }) iframeContent: ElementRef;
+
+  @ViewChild('videoNode', {static: true }) videoNode: ElementRef;
+  @ViewChild('tpl', {static: true }) tpl: TemplateRef<void>;
+  @ViewChild('videoContainer', {static: true }) videoContainer: ElementRef;
+
+  
+
+  canvasScale = 2;
+
+  canvasWidth = 2388;
+  canvasHeight = 1688;
+
+  canvasBaseW = 2388;
+  canvasBaseH = 1688;
 
-  // 数据
-  data;
 
   ctx;
+  fps = 0;
+  frametime = 0;  // 上一帧动画的时间,   两帧时间差
 
-  canvasWidth = 1280; // canvas实际宽度
-  canvasHeight = 720; // canvas实际高度
 
-  canvasBaseW = 1280; // canvas 资源预设宽度
-  canvasBaseH = 720;  // canvas 资源预设高度
+  mx;
+  my; // 点击坐标
 
-  mx; // 点击x坐标
-  my; // 点击y坐标
 
 
   // 资源
@@ -49,137 +84,3743 @@ export class PlayComponent implements OnInit, OnDestroy {
   animationId: any;
   winResizeEventStream = new Subject();
 
-  audioObj = {};
 
+  audioObj = {};
   renderArr;
   mapScale = 1;
+  mapScaleMax = 1;
 
   canvasLeft;
   canvasTop;
 
-  saveKey = 'test_0011';
 
+  canTouch = true;
 
-  btnLeft;
-  btnRight;
-  pic1;
-  pic2;
 
-  canTouch = true;
+  KEY = 'JM_MATH01';
+
+  // -----
+  picArr;
+  titleLabel;
+  light;
+  particleLayer;
+  shadowArr;
+  answerTarget;
+  answerCurrent;
+  bgRect;
+  starBgArr;
+  clickedSuccessArr;
+
+
+  pageLabel;
+  leftBtn;
+  rightBtn;
+  bgLayer;
+  curPageIndex;
+
+  hotZoneArr;
+
+  picIndex = 0;
+
+
+  bgArr;
+  endPageArr;
+  gameEndFlag;
+  showPetalFlag;
+  bg;
+  topArr;
+
 
-  curPic;
 
   @HostListener('window:resize', ['$event'])
   onResize(event) {
     this.winResizeEventStream.next();
+
+  }
+
+
+  constructor(private http: HttpClient, private sanitizer: DomSanitizer, private appRef: ApplicationRef, private container: ViewContainerRef) {
+
+  }
+
+  isTeacher = false;
+
+  async ngOnInit() {
+
+
+
+    await loadFonts('DroidSansFallback', 'DroidSansFallback.ttf');
+    await loadFonts('Aileron-Black', 'Aileron-Black.ttf');
+    await loadFonts('Aileron-Bold', 'Aileron-Bold.ttf');
+
+    const getData = (<any>window).courseware.getData;
+    getData((data, aspect) => {
+
+
+      if (window['air']?.airClassInfo?.user?.classRole == 'tea') {
+        this.isTeacher = true;
+      }
+			if (aspect) {
+        console.log('aspect : ', aspect);
+				this.initPageData(aspect);
+				this.initStoreData(aspect);
+      }
+
+      if (data && typeof data == 'object') {
+        this.data = { contentObj: data };
+      } else {
+
+        this.initDefaultData();
+      }
+
+      console.log('data:', data);
+      if (!this.data) {
+        this.data.contentObj = {};
+      }
+
+
+
+
+      this.initAudio();
+      this.initImg();
+      this.preloadVideo();
+      this.initListener();
+
+
+    }, this.KEY);
+
+
+  }
+
+
+
+  	// 用于存储私有数据 当翻页时清空 
+	storeData;
+	initStoreData(aspect) {
+		const storeDataStr = aspect?.user_aspect;
+		if (!storeDataStr) {
+			this.storeData = {};
+		} else {
+			this.storeData = JSON.parse(storeDataStr);
+		}
+		console.log('this.storeData: ', this.storeData);
+	}
+
+  setStoreData(key, value, isSend=true) {
+
+		if (this.storeData) {
+
+			console.log('this.storeData: ', this.storeData);
+			this.storeData[key] = value;
+			if (isSend) {
+				this.sendStoreData();
+			}
+		}
+
+  }
+
+  getStoreData(key) {
+		return this.storeData && this.storeData[key];
+  }
+
+  sendStoreData() {
+
+		const data = this.storeData;
+
+		if (window && window['courseware']) {
+			let storeAspect = window['courseware'].storeAspect;
+			if (storeAspect) {
+				storeAspect(data);
+			}
+		}
+	}
+
+
+
+	// 用于存储公共数据 当翻页时清空 
+	pageData;
+	initPageData(aspect) {
+		const pageDataStr = aspect?.page_aspect?.pagedata;
+		if (!pageDataStr) {
+			this.pageData = {};
+		} else {
+			this.pageData = JSON.parse(pageDataStr);
+		}
+		console.log('this.pageData: ', this.pageData);
+	}
+
+	setPageData(key, value, isSend=true) {
+
+		if (this.pageData) {
+
+			console.log('this.pageData: ', this.pageData);
+			this.pageData[key] = value;
+			if (isSend) {
+				this.sendPageData();
+			}
+		}
+	}
+
+	getPageData(key) {
+		if (this.pageData && this.pageData[key] != null) {
+			return this.pageData[key];
+		}
+	}
+
+	sendPageData() {
+
+		const data = this.pageData;
+
+		if (window && window['courseware']) {
+			let storePageAspect = window['courseware'].storePageAspect;
+			if (storePageAspect) {
+				storePageAspect(data);
+			}
+		}
+	}
+
+
+
+  initDefaultData() {
+
+    const data = {"sentenceArr":[],"templateArr":[],"bg_url":"http://staging-teach.cdn.ireadabc.com/1aaccbaa8f3a32df099f3308feac49e7.png","title":"new word","bgItem":{"url":"http://staging-teach.cdn.ireadabc.com/e3672206ffa9736c449b64d05f688b30.png","rect":{"x":102.83,"y":0,"width":485.33,"height":273}},"hotZoneItemArr":[{"id":"1634265930328","index":0,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"JJ","posX":282.5,"posY":52,"rect":{"x":13.35,"y":22.52,"width":136.5,"height":25.94}},{"id":"1634265945079","index":1,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"RB","posX":552.5,"posY":59,"rect":{"x":197.63,"y":27.98,"width":136.5,"height":24.57}},{"id":"1634265955820","index":2,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"WP","posX":281.5,"posY":129,"rect":{"x":12.67,"y":73.71,"width":136.5,"height":28.67}},{"id":"1634265962408","index":3,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"CD","posX":548.5,"posY":132,"rect":{"x":194.9,"y":75.08,"width":136.5,"height":30.03}},{"id":"1634265967558","index":4,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"IN","posX":284.5,"posY":213,"rect":{"x":14.72,"y":131.72,"width":136.5,"height":27.3}},{"id":"1634266027751","index":5,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"VB","posX":292.5,"posY":320,"rect":{"x":20.18,"y":202.7,"width":136.5,"height":31.4}},{"id":"1634266044165","index":6,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"NN","posX":544.5,"posY":308,"rect":{"x":192.17,"y":194.51,"width":136.5,"height":31.4}},{"id":"1634266050496","index":7,"itemType":"rect","fontScale":0.53984375,"imgScale":1,"imgSizeW":0,"imgSizeH":0,"mapScale":0.53984375,"dragDot":{"x":345.5,"y":133.26904532304724},"gIdx":"0","selectType":"NN","posX":759.5,"posY":323,"rect":{"x":338.91,"y":204.75,"width":136.5,"height":31.4}}]}
+
+    this.data = { contentObj: data };
+    
+
+  }
+
+  ngOnDestroy() {
+    window['curCtx'] = null;
+    window.cancelAnimationFrame(this.animationId);
+    this.gameEndFlag = true;
+  }
+
+  sentenceArr = [];
+  submitCount = 0;
+  safeDefaultUrl;
+  resultAnswerArr;
+
+  scrollView;
+  resultSv;
+  userResultPic;
+  userResultData;
+  uploadUrl;
+
+
+
+  templateQuesType;
+  TYPE_SHORT_ANSWER = 'short_answer';
+  TYPE_SINGLE_CHOICE = 'single_choice';
+  initData() {
+    
+
+    this.canvasWidth = this.wrap.nativeElement.clientWidth * this.canvasScale;
+    this.canvasHeight = this.wrap.nativeElement.clientHeight * this.canvasScale;
+
+    this.canvas.nativeElement.width = this.wrap.nativeElement.clientWidth * this.canvasScale;
+    this.canvas.nativeElement.height = this.wrap.nativeElement.clientHeight * this.canvasScale;
+
+
+    const sx = this.canvasWidth / this.canvasBaseW;
+    const sy = this.canvasHeight / this.canvasBaseH;
+    const s = Math.min(sx, sy);
+    this.mapScale = s;
+    
+    console.log('this.mapScale: ', this.mapScale);
+
+    this.mapScaleMax = Math.max(sx, sy);
+
+   
+    this.renderArr = [];
+    this.topArr = [];
+    this.userResultPic = null;
+    this.userResultData = null;
+
+    console.log(' in initData', this.data);
+
+
+    this.templateQuesType = this.data.contentObj.ques_type;
+
+    
+
+    this.uploadUrl = (<any> window).courseware.uploadUrl();
+    window['air'].getUploadCallback = (url, data) => {
+      this.uploadUrl = url;
+    };
+
+
+
+    this.canTouch = true;
+
+    this.submitCount = 0;
+    this.replaceSentenceText();
+
+    this.safeDefaultUrl = this.sanitizer.bypassSecurityTrustResourceUrl(''); 
+
+    this.resultAnswerArr = [];
+
+    this.resultSv = null;
+
+  }
+
+  replaceSentenceText() {
+    const arr = this.data.contentObj.sentenceArr;
+    if (!arr) {
+      console.error('没有数据')
+      return;
+    }
+
+    this.sentenceArr = JSON.parse(JSON.stringify(arr));
+    for (let i=0; i<this.sentenceArr.length; i++) {
+      const text = this.sentenceArr[i].text;
+      this.sentenceArr[i].text = text.replace(/_/, "____________________")
+    }
+  }
+
+
+
+  initAudio() {
+
+    const contentObj = this.data.contentObj;
+    if (!contentObj) { return; }
+
+
+
+    // const addAudio = (key) => {
+    //   const audioUrl = contentObj[key];
+    //   if (audioUrl) {
+    //     const audio = new Audio();
+    //     audio.src = audioUrl;
+    //     audio.load();
+    //     this.audioObj[key] = audio;
+    //   }
+    // }
+    //
+    // for (let i = 0; i < 4; i ++) {
+    //   const key = i + '_audio_url';
+    //   addAudio(key);
+    // }
+    //
+    // addAudio('audio_url');
+    //
+
+
+    const addUrlToAudioObj = (audioUrl) => {
+
+      if (audioUrl) {
+
+        // console.log('audioUrl:', audioUrl);
+        const audio = new Audio();
+        audio.src = audioUrl;
+        audio.load();
+        this.audioObj[audioUrl] = audio;
+      }
+    };
+
+
+    let arr = contentObj.hotZoneItemArr;
+    addUrlToAudioObj(contentObj.title_audio_url);
+
+
+    if (arr) {
+      // console.log('arr: ', arr);
+      for (let i = 0; i < arr.length; i++) {
+        addUrlToAudioObj(arr[i].audio_url);
+      }
+    }
+
+    const optionArr = contentObj.optionArr;
+    for (let i=0; i<optionArr.length; i++) {
+      addUrlToAudioObj(arr[i].audio_url);
+
+    }
+
+
+
+
+
+
+
+    const audioObj = this.audioObj;
+    const addOneAudio = (key, url, vlomue = 1, loop = false, callback = null) => {
+
+      const audio = new Audio();
+      audio.src = url;
+      audio.load();
+      audio.loop = loop;
+      audio.volume = vlomue;
+
+      audioObj[key] = audio;
+
+      if (callback) {
+        audio.onended = () => {
+          callback();
+        };
+      }
+    };
+
+
+    addOneAudio('click', this.rawAudios.get('click'), 1);
+    addOneAudio('more', this.rawAudios.get('more'), 0.8);
+    addOneAudio('submit', this.rawAudios.get('submit'), 0.8);
+  
+    // addOneAudio('cow_start', this.rawAudios.get('cow_start'), 1);
+    // addOneAudio('eat', this.rawAudios.get('eat'), 1);
+    // addOneAudio('grass', this.rawAudios.get('grass'), 1);
+    // addOneAudio('happy', this.rawAudios.get('happy'), 1);
+    // addOneAudio('right', this.rawAudios.get('right'), 1);
+    // addOneAudio('wrong', this.rawAudios.get('wrong'), 1);
+
+
+
+    //
+    // const titleUrl = this.data.contentObj.title_audio_url;
+    // if (titleUrl) {
+    //
+    //   this.titleAudio.src = titleUrl;
+    //   this.titleAudio.load();
+    // }
+
+
+
+    // this.bgAudio.src = 'assets/bat-mail/music/bg.mp3';
+    // this.bgAudio.load();
+    // this.bgAudio.loop = true;
+    // this.bgAudio.volume = 0.5;
+    //
+    // this.wrongAudio.src = 'assets/common/music/wrong.mp3';
+    // this.wrongAudio.load();
+    //
+    // this.rightAudio.src = 'assets/common/music/right.mp3';
+    // this.rightAudio.load();
+    //
+    // this.successAudio.src = 'assets/magic-hat/music/finish.mp3';
+    // this.successAudio.load();
+    //
+    // this.successAudio.onended = () => {
+    //   // this.showSuccessAudio();
+    // };
+
+
+  }
+
+  videoObj;
+  preloadVideo() {
+
+    this.videoObj = {};
+    const addVideoUrl = (video_url) => {
+
+      if (!video_url) {
+        return;
+      }
+
+      const videoElement = document.createElement('video');
+      videoElement.src = video_url;
+      this.videoContainer.nativeElement.appendChild(videoElement);
+
+      console.log('tmpV: ', videoElement);
+
+      const video = new MyVideo();
+      video.initVideoElement(videoElement);
+      video.setScaleXY(this.mapScale);
+      video.element.src = video_url;
+      video.element.load();
+      this.videoObj[video_url] = video;
+
+      video.element.addEventListener('canplaythrough', () => {
+        this.refreshVideoSize(video);
+      });
+    }
+
+    
+    const optionArr = this.data.contentObj.optionArr;
+    for (let i=0; i<optionArr.length; i++) {
+      addVideoUrl(optionArr[i].video_url || '');
+      
+      const op2Arr = optionArr[i];
+      for (let j=0; j<op2Arr.length; j++) {
+        addVideoUrl(op2Arr[j].pic_url || '');
+      }
+    }
+  }
+
+  initImg() {
+
+    const contentObj = this.data.contentObj;
+    if (contentObj) {
+
+
+      const addPicUrl = (url) => {
+        if (url) {
+          this.rawImages.set(url, url);
+        }
+      };
+
+      let arr = contentObj.hotZoneItemArr;
+      if (arr) {
+
+        for (let i = 0; i < arr.length; i++) {
+
+          console.log('arr[i].pic_url: ', arr[i].pic_url);
+          addPicUrl(arr[i].pic_url);
+        }
+      }
+
+      addPicUrl(contentObj.bgItem?.url || '');
+      addPicUrl(contentObj.bg_url || '');
+      addPicUrl(contentObj.ques_pic_url || '');
+      addPicUrl(contentObj.title_pic_url || '');
+
+
+      const optionArr = contentObj.optionArr;
+      for (let i=0; i<optionArr.length; i++) {
+        addPicUrl(optionArr[i].pic_url || '');
+        addPicUrl(optionArr[i].title_pic_url || '');
+
+        
+        const optionArr2 = optionArr[i].optionArr;
+        for (let j=0; j<optionArr2.length; j++) {
+          addPicUrl(optionArr2[j].pic_url || '');
+        }
+
+        addPicUrl(optionArr[i].ques_pic_url);
+        
+      }
+
+    }
+
+
+   
+
+    // this.initFontImg();
+
+
+    
+
+    // 预加载资源
+ 
+
+
+    this.loadResources().then(() => {
+      // this.setfontData();
+
+
+      this.addServerListener();
+      window['air'].hideAirClassLoading(this.KEY, this.data);
+
+
+    });
+
+  }
+
+  loadEnd(cb) {
+    this.init();
+    this.update();
+
+    cb()
+  }
+
+  addServerListener() {
+    console.log(' in addServerListener');
+
+		const c = window['courseware'];
+		if (!c) {
+			console.log(' window.courseware not exist !!!!!!!!')
+
+			return;
+		}
+
+    const air = window['air']
+		if (!air) {
+			console.log(' window air err !!!!!!!!')
+			return;
+		}
+
+		air.onCourseInScreen = (next) => {
+			//各种逻辑
+			// this.showTemplateStartAnima();
+      console.log('in onCourseInScreen')
+			this.loadEnd(() => {
+				next();
+			});
+		}
+
+
+		c.onEvent('game_start', (data, next) => {
+      
+      
+			this.gameStart();
+      if (next) {
+        next();
+      }
+		});
+  }
+
+
+	sendServerEvent(key, data) {
+
+    const c = window['courseware'];
+		if (c) {
+			c.sendEvent(key, data);
+		}
+  }
+
+  localStartTime = new Date().getTime();
+  gameStart() {
+    // this.localStartTime = new Date().getTime();
+    // console.log(' in gameStart ');
+    // if (this.isTeacher) {
+    //   this.sendBtn.visible = false;
+    //   this.sendBtn.shadowSpr.visible = false;
+    //   this.maskPic.visible = true;
+    // } else {
+    //   // this.changeBtnOn();
+    //   this.maskPic.visible = false;
+
+    // }
   }
 
 
-  ngOnInit() {
 
-    this.data = {};
 
-    // 获取数据
-    const getData = (<any> window).courseware.getData;
-    getData((data) => {
 
-      if (data && typeof data == 'object') {
-        this.data = data;
-      }
-      // console.log('data:' , data);
 
-      // 初始化 各事件监听
-      this.initListener();
 
-      // 若无数据 则为预览模式 需要填充一些默认数据用来显示
-      this.initDefaultData();
+  init() {
+
+    this.initData();
+    this.initCtx();
+
+    this.initView();
+
+
+  }
+
+  initCtx() {
+
+    this.ctx = this.canvas.nativeElement.getContext('2d');
+    this.canvas.nativeElement.width = this.canvasWidth;
+    this.canvas.nativeElement.height = this.canvasHeight;
+
+    window['curCtx'] = this.ctx;
+
+
+  }
+
+
+
+
+
+
+
+  initView() {
+
+
+    this.initBg();
+    this.initHotZone();
+    // this.setOneBtn();
+    // this.initMaskPic();
+    // this.relink();
+  
+    // this.checkShowSubmit();
+
+
+    this.initVideo();
+
+
+
+    if (this.data.contentObj.ques_type == this.TYPE_SINGLE_CHOICE) {
+      this.playHotZoneItemSC();
+    } else {
+      this.playHotZoneItemSA();
+    }
+
+
+  }
+
+  videoCloseBtn;
+  videoBg;
+  videoMask;
+  replayBtn;
+  initVideo() {
+
+    const videoMask = new ShapeRect();
+    videoMask.setSize(this.canvasWidth, this.canvasHeight);
+    videoMask.fillColor = '#f4ecd8';
+    videoMask.alpha = 0.7;
+    this.topArr.push(videoMask);
+    this.videoMask = videoMask;
+    videoMask.visible = false;
+
+    const videoBg = this.getMySprite('video_bg');
+    videoBg.setScaleXY(this.mapScale);
+    videoBg.visible = false;
+
+    // videoBg.anchorX = 1;
+    videoBg.x = this.canvasWidth / 2 - videoBg.width / 2 * videoBg.scaleX;
+    videoBg.y = this.canvasHeight / 2;
+    this.topArr.push(videoBg); 
+    this.videoBg = videoBg;
+
+    const videoCloseBtn = this.getMySprite('btn_close');
+    videoCloseBtn.x = videoBg.width / 2 - 100;
+    videoCloseBtn.y = -videoBg.height / 2 + 53;
+    videoBg.addChild(videoCloseBtn);
+
+    this.videoCloseBtn = videoCloseBtn;
+
+    const replayBtn = this.getMySprite('btn_replay')
+    this.videoBg.addChild(replayBtn);
+    replayBtn.y = this.videoBg.height / 2 - 130;
+    replayBtn.visible = false;
+    this.replayBtn = replayBtn;
+
+
+  }
+
+  curVideo;
+  initVideoPlayer(data) {
+    
+    if (this.curVideo) {
+      this.curVideo.parent.removeChild(this.curVideo);
+      this.curVideo = null;
+    }
+    const video = this.videoObj[data.video_url];
+    if (video) {
+
+      this.videoBg.addChild(video);
+      this.refreshVideoSize(video);
+      video.element.currentTime = 0.01;
+      video.element.play();
+      this.curVideo = video;
+
+      this.replayBtn.visible = false;
+
+
+      video.element.onended = () => {
+        this.replayBtn.visible = true;
+      }
+
+    }
+  }
+
+  refreshVideoSize(video) {
+    if (!video.parent) {
+      return;
+    }
+   
+    video.width = video.element.videoWidth;
+    video.height = video.element.videoHeight;
+    const sx = video.parent.width / video.element.videoWidth;
+    const sy = video.parent.height / 2 / video.element.videoHeight;
+
+
+    console.log('sx: ', sx);
+    console.log('sy: ', sy);
+
+
+    video.setScaleXY(Math.min(sx, sy));
+
+    video.x = - video.width / 2 * video.scaleX;
+    video.y = - video.height / 2 * video.scaleY;
+
+  }
+
+  playHotZoneItemSA() {
+    const arr = this.hotZoneArr;
+    console.log('in showHotZonePic arr: ', arr);
+    if (!arr || arr.length == 0) {
+      return;
+    }
+
+    console.log('in showHotZonePic 1');
+
+    let index = 0;
+    const showOneHotZone = () => {
+
+      console.log(' in showOneHotZone')
+      if (index > arr.length - 1) {
+
+        return;
+      }
+
+      
+      const {data} = arr[index];
+      if (data.gIdx == '0' || data.gIdx == '2' || data.gIdx == '3') {
+
+        this.showHotZonePic(arr[index]);
+
+        if (data.gIdx == '0') {
+          this.lastHotZoneItem = arr[index]; //记录最后一个显示item 后面要替换
+        }
+
+        if (data.audio_url) {
+          this.playAudio(data.audio_url, true, () => {
+
+            index ++;
+            showOneHotZone();
+          })
+        } else {
+          index ++;
+          showOneHotZone();
+        }
+
+
+      } else {
+        index ++;
+        showOneHotZone();
+      }
+    }
+
+    showOneHotZone();
+  }
+
+
+  lastHotZoneItem;
+  playHotZoneItemSC() {
+
+    const arr = this.hotZoneArr;
+    console.log('in showHotZonePic arr: ', arr);
+    if (!arr || arr.length == 0) {
+      return;
+    }
+
+    console.log('in showHotZonePic 1');
+
+    let index = 0;
+    const showOneHotZone = () => {
+
+      console.log(' in showOneHotZone')
+      if (index > arr.length - 1) {
+
+        return;
+      }
+
+      
+      const {data} = arr[index];
+      if (data.gIdx == '0' || data.gIdx == '1') {
+
+        this.showHotZonePic(arr[index]);
+
+        if (data.gIdx == '0') {
+          this.lastHotZoneItem = arr[index]; //记录最后一个显示item 后面要替换
+        }
+
+        if (data.audio_url) {
+          this.playAudio(data.audio_url, true, () => {
+
+            index ++;
+            showOneHotZone();
+          })
+        } else {
+          index ++;
+          showOneHotZone();
+        }
+
+
+      } else {
+        index ++;
+        showOneHotZone();
+      }
+    }
+
+    showOneHotZone();
+
+  }
+
+  showSmallTitle(data) {
+
+    const imgObj = this.images.get(data.title_pic_url);
+    this.lastHotZoneItem.init(imgObj);
+    this.showHotZonePic(this.lastHotZoneItem);
+
+
+    if (data.title_audio_url) {
+      this.playAudio(data.title_audio_url, true)
+    }
+
+  }
+
+  showHotZonePic(item) {
+    console.log('item: ', item);
+    item.visible = true;
+    item.alpha = 0;
+
+    tweenChange(item, {alpha: 1}, 0.5);
+  }
+
+  
+
+  showSubmit() {
+    if(this.submitBtn){
+      this.submitBtn.visible = true;
+      this.submitBtn.alpha = 1;
+      this.submitBtn.shadowSpr.visible = true;
+      this.submitBtn.shadowSpr.alpha = 1;
+    }
+  }
+  
+  hideSubmit() {
+    if(this.submitBtn){
+      this.submitBtn.visible = false;
+      this.submitBtn.alpha = 0;
+      this.submitBtn.shadowSpr.visible = false;
+      this.submitBtn.shadowSpr.alpha = 0;
+    }
+  }
+
+  removeSubmit() {
+    if(this.submitBtn){
+      this.submitBtn.parent.removeChild(this.submitBtn);
+      this.submitBtn.shadowSpr.parent.removeChild(this.submitBtn.shadowSpr);
+
+      this.submitBtn = null;
+    }
+  }
+
+
+  testInput() {
+   
+    const tmpRect = this.sentenceEmptyArr[0].getBoundingBox();
+
+    const svBox = this.scrollView.getBoundingBox();
+
+   
+    const x = svBox.x +  tmpRect.x * this.mapScale;
+    const y = svBox.y + tmpRect.y * this.mapScale;
+    const width = tmpRect.width * this.mapScale;
+    const height = tmpRect.height * this.mapScale;
+    const rect = {x, y, width, height};
+
+
+
+    console.log('rect: ', rect);
+  
+
+    
+    // const shape = new ShapeRectNew();
+    // shape.setSize(200, 200, 1);
+    // shape.fillColor = '#0000ff'
+    // shape.x = 500;
+    // shape.y = 500;
+    // this.topArr.push(shape);
+
+     
+  
+  }
+
+  inputView;
+  showInputView(style, rect, value, cb) {
+    if (!this.inputView) {
+      this.inputView = new InputView();
+      this.inputView.isShowScroll = true;
+    }
+
+    const inputView = this.inputView;
+
+    if (inputView.callback) {
+      inputView.onblur();
+    }
+
+    inputView.show();
+
+    inputView.x = rect.x  / this.canvasScale ;  // this.canvasWidth / 2;
+    inputView.y = rect.y / this.canvasScale ; // this.canvasHeight / 2;
+    inputView.width = rect.width / this.canvasScale ;
+    inputView.height = rect.height / this.canvasScale ;
+    inputView.text = value;
+    inputView.callback = cb;
+    inputView.setStyle(style);
+    
+
+    inputView.refreshInputStyle();
+  }
+
+  hideInputView() {
+    if (!this.inputView) {
+      return;
+    }
+    this.inputView.hide();
+  }
+
+
+
+  relink() {
+
+    const progress = this.getPageData('progress');
+
+    if (this.isTeacher) {
+      if (progress != null) {
+        this.gameStart();
+        return;
+      }
+      return;
+    }
+
+
+    if (progress == '1') {
+
+      this.gameStart();
+
+      const resultData = this.getStoreData('resultData')
+      if (resultData) {
+        this.initResultView(resultData);
+        return;
+      }
+
+      return;
+    }
+
+    // if (progress == '2' || progress == '3') {
+    //   const submitCount = this.getPageData('submitCount')
+    //   this.submitCount = submitCount;
+    //   this.checkShowSubTemplateOne();
+    //   return;
+    // }
+   
+  }
+
+
+  maskPic;
+  initMaskPic() {
+    const bgUrl = this.data.contentObj.bg_url;  
+    console.log('bgUrl', bgUrl);
+    
+    const maskPic = this.getMySprite(bgUrl);
+    const sx = this.canvasWidth / maskPic.width;
+    const sy = this.canvasHeight / maskPic.height;
+    const s = Math.max(sx, sy);
+    maskPic.setScaleXY(s);
+    maskPic.x = this.canvasWidth / 2;
+    maskPic.y = this.canvasHeight / 2;
+
+    if (this.isTeacher) {
+      maskPic.visible = false;
+    }
+
+    this.maskPic = maskPic;
+  }
+
+
+  getMySprite(resName, isSetScale = true) {
+    const spr = new MySprite();
+    spr.init(this.images.get(resName))
+    if (isSetScale) {
+      spr.setScaleXY(this.mapScale);
+    }
+    return spr;
+  }
+
+
+  setRectRight(data) {
+
+    console.log("in setRectRight : ", data);
+
+    for (let i=0; i<this.hotZoneArr.length; i++) {
+      this.hotZoneArr[i].result = data[i];
+      if (data[i] && data[i].right) {
+        this.hotZoneArr[i].isRight = true;
+      } else {
+        this.hotZoneArr[i].isRight = false;
+      }
+    }
+  }
+
+  resultLayer;
+  resultPanel;
+  moreBtn;
+  initResultView(data = null) {
+
+
+    if (data) {
+      console.log('initResultView data : ', data)
+      this.setRectRight(data);
+    } else {
+      const resultData = this.getResultData();
+      this.userResultData = resultData;
+      this.sendResult();
+      this.setStoreData('resultData', resultData);
+    }
+
+
+    const resultLayer = new MySprite()
+    resultLayer.x = this.canvasWidth / 2;
+    resultLayer.y = this.canvasHeight / 2;
+    
+
+    const bg = new MySprite();
+    bg.init(this.images.get("result_bg"))
+    bg.x = 0
+    bg.y = 0;
+    bg.setScaleXY(this.mapScaleMax);
+    resultLayer.addChild(bg);
+
+    const bgItem = new MySprite();
+    bgItem.init(this.images.get("result_bg_item"));
+    bg.addChild(bgItem);
+
+    const panelOffY = 0 //-50 * this.mapScale;
+    const panel = new MySprite();
+    panel.init(this.images.get("result_panel"));
+    panel.setScaleXY(this.mapScale);
+    panel.y = panelOffY;
+    resultLayer.addChild(panel, 1);
+
+    const panelItem = new MySprite();
+    panelItem.init(this.images.get("result_panel_item"));
+    panelItem.y = -600;
+    panel.addChild(panelItem);
+
+    const itemLabel = new RichText();
+    itemLabel.fontSize = 52;
+    // itemLabel.y = 5;
+    itemLabel.width = panelItem.width;
+    itemLabel.fontColor = '#ffffff'
+    itemLabel.fontName = 'Aileron-Black';
+    itemLabel.text = 'Check'
+    itemLabel.y = 10;
+    itemLabel.textAlign = 'center';
+    panelItem.addChild(itemLabel, 5);
+
+    const panelShadow = new MySprite();
+    panelShadow.init(this.images.get("result_panel_shadow"));
+    // panelShadow.setScaleXY(this.mapScale);
+    panelShadow.y = ( panel.height / 2 - 20 );
+    panel.addChild(panelShadow);
+
+    const btn = new MySprite();
+    btn.init(this.images.get("big_btn"));
+    btn.setScaleXY(this.mapScale);
+    btn.y = panel.y + ( panel.height / 2 + 120 ) * panel.scaleY;
+    // resultLayer.addChild(btn, 2);
+    // this.moreBtn = btn;
+
+    const btnShadow = new MySprite();
+    btnShadow.init(this.images.get("big_btn_shadow"));
+    btnShadow.setScaleXY(this.mapScale);
+    btnShadow.y = btn.y + 30 * this.mapScale;
+    // resultLayer.addChild(btnShadow);
+
+
+    const btnLabel = new Label();
+    btnLabel.fontSize = 64;
+    btnLabel.fontName = 'Aileron-Black';
+    btnLabel.textAlign = 'center';
+    btnLabel.text = '更多练习';
+    btnLabel.y = 5;
+    btnLabel.fontColor = '#ffffff';
+    btn.addChild(btnLabel, 3);
+
+    
+
+    this.topArr.push(resultLayer);
+    
+    this.resultPanel = panel;
+    this.resultLayer = resultLayer;
+
+
+
+    this.initResultSentence();
+
+ 
+
+  }
+
+  getResultData() {
+    const dataArr = [];
+    for (let i=0; i<this.hotZoneArr.length; i++) {
+      const result = this.hotZoneArr[i].result;
+      result.userAnswer = this.hotZoneArr[i].label?.text || '';
+      dataArr.push(result);
+    }
+    return dataArr;
+  }
+
+
+
+  initResultSentence() {
+
+    const resultSv = new ScrollView();
+    resultSv.setShowSize(this.resultPanel.width, this.resultPanel.height * 0.85);
+    resultSv.setBgColor("#3e9e33");
+    resultSv.x = -this.resultPanel.width / 2;
+    resultSv.y = -this.resultPanel.height / 2 + 120;
+    resultSv.setScrollBarSize(20 * this.mapScale, 5);
+    // resultSv.setContentScale(this.mapScale);
+
+    this.resultPanel.addChild(resultSv);
+    this.resultSv = resultSv;
+
+    console.log('hotZoneArr: ', this.hotZoneArr);
+
+    const tmpLabel = new Label();
+    tmpLabel.fontSize = 48;
+    tmpLabel.fontColor = '#ffffff'
+    tmpLabel.fontName = 'DroidSansFallback';
+
+
+    let baseY = 0 //-500;
+    for (let i=0; i<this.hotZoneArr.length; i++) {
+
+      const result = this.hotZoneArr[i].result;
+
+      const richText = new RichText();
+      richText.disH = 5;
+      // richText.x = -this.panel.width / 2 * 0.9
+      richText.x = 0.05 * this.resultPanel.width;
+      richText.y = baseY + 80;
+   
+      richText.fontSize = 48;
+      richText.width = this.resultPanel.width * 0.9;
+      richText.fontColor = '#ffffff'
+      richText.fontName = 'DroidSansFallback';
+      richText.text = result.userAnswer;
+
+      // this.resultPanel.addChild(richText, 1)
+
+
+
+      // const rect = richText.getBoundingBox();
+      // richText.refreshSize();
+
+      tmpLabel.text = richText.text;
+      tmpLabel.refreshSize();
+      let rectW = tmpLabel.width < 100 ? 100 : tmpLabel.width;
+
+      const rect = {x: richText.x, y: richText.y, width: rectW, height: richText.fontSize};
+
+      const addW = 18
+      rect.width += addW;
+      rect.height += 16;
+      // rect.x += addW / 2;
+      const colorRect = new ShapeRectNew();
+      colorRect.setSize(rect.width, rect.height, 10);
+      // colorRect.alpha = 0.3;
+     
+      colorRect.fillColor = '#007b3e'
+      const offX = 9;
+      colorRect.x = rect.x // + offX;
+      colorRect.y = rect.y - rect.height / 2;
+      // colorRect.alpha = 0.5;
+      resultSv.addItem(colorRect);
+      resultSv.addItem(richText);
+
+
+
+      baseY = richText.y + richText.getAreaHeight();
+      if (result.isAi) {
+        colorRect.alpha = 0;
+
+        const infoBg = new ShapeRectNew();
+        // infoBg.init(this.images.get("info_bg"));
+        infoBg.setSize(1838, 120, 60);
+        infoBg.fillColor = '#3b962e';
+        infoBg.y = baseY //+ 50;
+        infoBg.x = (this.resultPanel.width - infoBg.width) / 2//* this.mapScale;
+        // infoBg.x = this.resultPanel.width / 2 //* this.mapScale;
+        resultSv.addItem(infoBg);
+        // this.resultPanel.addChild(infoBg);
+        // baseY += 130;
+
+        const infoIcon = new MySprite();
+        infoIcon.init(this.images.get("ai_icon"));
+        infoIcon.x =  100;
+        infoIcon.y = 60;
+        infoBg.addChild(infoIcon);
+
+
+        this.addResultLabelCircle(richText, result);
+        const tmpH = this.getInfoLabelLayer(infoBg, result);
+        if (tmpH < 60) {
+          infoBg.visible = false;
+        } else {
+          baseY += tmpH;
+          infoBg.height = tmpH;
+          resultSv.refreshContentSize();
+        }
+   
+
+
+      } else if (!this.hotZoneArr[i].isRight) {
+        colorRect.fillColor = '#d71818'
+
+        const infoBg = new MySprite();
+        infoBg.init(this.images.get("info_bg"));
+        infoBg.y = baseY + 50;
+        infoBg.x = this.resultPanel.width / 2 //* this.mapScale;
+        resultSv.addItem(infoBg);
+        // this.resultPanel.addChild(infoBg);
+        baseY += 130;
+
+        const infoIcon = new MySprite();
+        infoIcon.init(this.images.get("info_icon"));
+        infoIcon.x = -infoBg.width / 2 + 100;
+        infoBg.addChild(infoIcon);
+
+        const infoLabel = new RichText();
+        infoLabel.x = infoIcon.x + 50;
+        infoLabel.fontSize = 48;
+        infoLabel.y = 5;
+        infoLabel.width = infoBg.width;
+        infoLabel.fontColor = '#ffe9b1'
+        infoLabel.fontName = 'Aileron-Black';
+        infoLabel.text = this.hotZoneArr[i].result?.info || '';
+        infoBg.addChild(infoLabel);
+
+      } else {
+        const labelAnswer = this.getFillLabel();
+        labelAnswer.fontSize = 48;
+        labelAnswer.fontColor = '#ffffff'
+        labelAnswer.fontName = 'Aileron-Black';
+        labelAnswer.text =  this.hotZoneArr[i].result?.info || '';
+        labelAnswer.setScaleXY(1);
+        labelAnswer.textAlign = 'center';
+
+        labelAnswer.refreshSize();
+        labelAnswer.x = colorRect.width / 2;
+        labelAnswer.y = colorRect.height / 2;
+
+        // colorRect.addChild(labelAnswer);
+      }
+
+
+      if (i != this.hotZoneArr.length - 1) {
+        const line = new MySprite();
+        line.init(this.images.get("line"));
+        line.x = this.resultPanel.width / 2;
+        line.y = baseY + 30;
+
+        resultSv.addItem(line);
+        // this.resultPanel.addChild(line);
+
+        baseY = line.y
+      }
+
+
+    }
+    
+    console.log('this.resultSv.height: ', this.resultSv.heigth);
+    console.log('this.resultSv.box height: ', this.resultSv.getBoundingBox().height);
+    
+  }
+
+  getInfoLabelLayer(infoBg, result) {
+
+    const {resultTextArr, iconData} = result;
+
+    const arr = [];
+    for (let k in iconData) {
+      iconData[k].index = k ;
+      arr.push(iconData[k])
+    }
+    
+    const layer = new MySprite();
+
+    let baseIndex = 0;
+    let tmpH = 40;
+    for (let i=0; i<arr.length; i++) {
+
+      const circle = new ShapeCircle();
+      circle.setRadius(30);
+      circle.x = 200;
+      circle.y = tmpH + 20;
+      circle.fillColor = '#dcc077'
+      infoBg.addChild(circle);
+
+      const label = new Label();
+      label.text = (i+1).toString();
+      label.fontColor = '#ffffff';
+      label.textAlign = 'center';
+      label.fontName = 'Aileron-Bold';
+      label.y = 3;
+      circle.addChild(label);
+
+      const infoLabel = new RichText();
+      infoLabel.topH = 0;
+      infoBg.addChild(infoLabel);
+  
+      infoLabel.x = 230;
+      infoLabel.fontSize = 48;
+      infoLabel.y = tmpH + 20;
+      infoLabel.width = infoBg.width * 0.8;
+      infoLabel.fontColor = '#ffe9b1'
+      infoLabel.fontName = 'Aileron-Black';
+      infoLabel.text = arr[i].dataArr[0].reason;
+
+      tmpH = infoLabel.y + infoLabel.getAreaHeight();
+
+      // const rect = lastRect.rect;
+      // rect.x -= rect.width;
+      // rect.y -= rect.height / 2;
+      // const colorRect = this.getColorRect(rect);
+      // colorRect.alpha = 0.5;
+      // // colorRect.y -= rect.height;
+      // // infoLabel.addChild(colorRect);
+
+      // const circle = new ShapeCircle();
+      // circle.setRadius(20);
+      // circle.fillColor = '#dcc077';
+      // circle.x = rect.x + rect.width;
+      // circle.y = rect.y;
+      // infoLabel.addChild(circle);
+
+      // const label = new Label();
+      // label.textAlign = 'center';
+      // label.fontColor = '#ffffff';
+      // label.fontSize = 30;
+      // label.text = (i + 1).toString()
+      // circle.addChild(label);
+
+
+    }
+
+    return tmpH
+
+    // const infoLabel = new RichText();
+    // infoBg.addChild(infoLabel);
+
+    // infoLabel.x = infoIcon.x + 50;
+    // infoLabel.fontSize = 48;
+    // infoLabel.y = 5;
+    // infoLabel.width = infoBg.width;
+    // infoLabel.fontColor = '#ffe9b1'
+    // infoLabel.fontName = 'Aileron-Black';
+    // infoLabel.text = this.getOneAiResultText(result);//  this.hotZoneArr[i].result?.info || '';
+    // this.addResultLabelCircle(infoLabel, result);
+
+  }
+
+  getOneAiResultText(result) {
+
+    const {resultTextArr} = result;
+    let curText = '';
+    for (let i=0; i<resultTextArr.length; i++) {
+      curText += resultTextArr[i].text + '';
+    }
+    console.log(' in getOneAiResultText ', resultTextArr);
+    return curText;
+  }
+
+  addResultLabelCircle(infoLabel, result) {
+    const {resultTextArr, iconData} = result;
+
+    let baseIndex = 0;
+    for (let i=0; i<resultTextArr.length; i++) {
+      const curText = resultTextArr[i].text;
+      // const rect = infoLabel.getSubTextRect(curText)?.rect;
+      const rectGroup = infoLabel.getSubTextRectGroup(curText, baseIndex);
+      if (!rectGroup) {
+        continue;
+      }
+      
+      console.log('~rectGroup: ', rectGroup);
+      
+      // const rectFirst = rectGroup[0];
+
+      if (!rectGroup ||  rectGroup.length == 0) {
+        continue;
+      }
+
+      const lastRect = rectGroup[rectGroup.length - 1];
+      const key = lastRect.index + lastRect.text.length;
+
+      console.log('~key: ', key);
+      console.log('~rect: ', lastRect);
+      console.log('iconData: ', iconData);
+      if (!iconData[key]) {
+        continue;
+      }
+
+      const rect = lastRect.rect;
+      rect.x -= rect.width;
+      rect.y -= rect.height / 2;
+      const colorRect = this.getColorRect(rect);
+      colorRect.alpha = 0.5;
+      // colorRect.y -= rect.height;
+      // infoLabel.addChild(colorRect);
+
+      const circle = new ShapeCircle();
+      circle.setRadius(20);
+      circle.fillColor = '#dcc077';
+      circle.x = rect.x + rect.width;
+      circle.y = rect.y;
+      infoLabel.addChild(circle);
+
+      const label = new Label();
+      label.fontName = 'Aileron-Bold';
+      label.textAlign = 'center';
+      label.fontColor = '#ffffff';
+      label.fontSize = 30;
+      label.text = (i + 1).toString()
+      label.y = 3;
+      circle.addChild(label);
+
+      // colorRect.addChild(circle);
+    }
+  }
+
+  getColorRect(rect) {
+
+    const colorRect = new ShapeRectNew();
+    colorRect.setSize(rect.width, rect.height, 1);
+   
+    colorRect.fillColor = Math.random() < 0.5 ? '#ff0000' : '#00ff00'
+    // const offX = 13; 
+    colorRect.x = rect.x // + offX;
+    colorRect.y = rect.y // -rect.height / 2;
+    // console.log('rect.height: ', rect.height);
+    // colorRect.alpha = 0;
+    return colorRect;
+  }
+
+
+  submitBtn;
+  btnArr = [];
+  btnDisH = 120;
+  initBtn() {
+
+    this.topArr = [];
+
+    const btnArr = [];
+    let arr =  JSON.parse(JSON.stringify(this.sentenceArr)) ;
+    arr = randomSortByArr(arr);
+    if (!arr) {
+      return;
+    }
+    const disW = 400 * this.mapScale;
+    const disH = this.btnDisH * this.mapScale;
+    const tmpLen = arr.length > 5 ? 5 : arr.length;
+    let baseX = this.canvasWidth / 2 - (tmpLen - 1) * disW / 2
+    let baseY = this.canvasHeight / 2 - 380 * this.mapScale;
+    let col = 0;
+
+    for (let i=0; i<arr.length; i++) {
+
+      const btn = new MySprite();
+      btn.init(this.images.get("btn"))
+      btn.x = baseX + disW * col;
+      btn.y = baseY;
+      btn['baseX'] = btn.x;
+      btn['baseY'] = btn.y;
+      btn.setScaleXY(this.mapScale * 0.8);
+
+      const btnBg = new MySprite();
+      btnBg.init(this.images.get("btn_bg"))
+      btnBg.x = btn.x;
+      btnBg.y = btn.y;
+      btnBg.setScaleXY(this.mapScale * 0.8);
+
+      this.renderArr.push(btnBg);
+      this.topArr.push(btn);
+
+      this.addBtnLabel(btn, arr[i].answer);
+
+      btnArr.push(btn);
+
+      col ++ ;
+      if (col % 5 == 0) {
+        col = 0;
+        baseY += disH;
+      }
+    }
+ 
+    this.btnArr = btnArr;
+
+
+    if (this.isTeacher) {
+      this.initsendBtn();
+      return;
+    }
+
+
+    const submitBtn = new MySprite();
+    submitBtn.init(this.images.get("submit"));
+    submitBtn.setScaleXY(this.mapScale);
+    submitBtn.x = this.canvasWidth / 2 + 650 * this.mapScale;
+    submitBtn.y = this.canvasHeight / 2 + 650 * this.mapScale;
+
+
+
+    const submitShadow = new MySprite();
+    submitShadow.init(this.images.get("submit_shadow"));
+    submitShadow.setScaleXY(this.mapScale);
+    submitShadow.x = submitBtn.x;
+    submitShadow.y = submitBtn.y + 20 * this.mapScale;
+
+
+
+    const submitOff = new MySprite();
+    submitOff.init(this.images.get("submit_off"));
+    submitOff.setScaleXY(this.mapScale);
+    submitOff.x = submitBtn.x;
+    submitOff.y = submitBtn.y + 5 * this.mapScale;
+
+
+    submitBtn['baseY'] = submitBtn.y;
+    submitBtn['offBtn'] = submitOff;
+    submitBtn['shadowSpr'] = submitShadow;
+    submitShadow['baseY'] = submitShadow.y;
+    
+    this.renderArr.push(submitShadow);
+    this.renderArr.push(submitBtn);
+    this.renderArr.push(submitOff);
+
+    this.submitBtn = submitBtn;
+
+    this.submitOff();
+  }
+
+  sendBtn;
+  initsendBtn() {
+
+    const panel = this.panel;
+
+    const btn = new MySprite();
+    btn.init(this.images.get("big_btn"));
+    btn.setScaleXY(this.mapScale);
+    btn.x = this.canvasWidth / 2;
+    btn.y = panel.y + ( panel.height / 2 - 120 ) * panel.scaleY;
+    this.sendBtn = btn;
+
+
+    const btnShadow = new MySprite();
+    btnShadow.init(this.images.get("big_btn_shadow"));
+    btnShadow.setScaleXY(this.mapScale);
+    btnShadow.x = btn.x;
+    btnShadow.y = btn.y + 20 * this.mapScale;
+    this.sendBtn.shadowSpr = btnShadow;
+
+    const btnLabel = new Label();
+    btnLabel.fontSize = 64;
+    btnLabel.fontName = 'Aileron-Black';
+    btnLabel.textAlign = 'center';
+    btnLabel.text = 'Send';
+    btnLabel.y = 5;
+    btnLabel.fontColor = '#ffffff';
+    btn.addChild(btnLabel, 3);
+
+    this.renderArr.push(btnShadow);
+    this.renderArr.push(btn);
+  }
+
+
+  submitOff() {
+    this.submitBtn.offBtn.visible = true;
+
+    this.submitBtn.visible = false;
+    this.submitBtn.shadowSpr.visible = false;
+  }
+
+  submitOn() {
+
+    // this.submitBtn.y = this.submitBtn.baseY;
+    // this.submitBtn.scale = this.mapScale;
+
+    // this.submitBtn.shadowSpr.alpha = 1;
+    // this.submitBtn.shadowSpr.y = this.submitBtn.shadowSpr.baseY;
+    
+
+    this.submitBtn.visible = true;
+    this.submitBtn.shadowSpr.visible = true;
+
+    this.submitBtn.offBtn.visible = false;
+  }
+
+
+  addBtnLabel(btn, text) {
+    console.log('text:  ', text);
+
+    const label = new Label();
+    label.fontSize = 64;
+    label.fontName = 'Aileron-Black';
+    label.textAlign = 'center';
+    label.text = text;
+    // label.x = this.canvasWidth / 2;
+    label.y = -2;
+    label.fontColor = '#f1e4c2';
+    btn.addChild(label);
+    btn.text = text;
+    return label;
+  }
+
+  sentenceEmptyArr;
+  initSentence() {
+
+    const row = Math.floor( (this.btnArr.length - 1) / 5 ) + 1;
+    const subH = row * this.btnDisH ;
+
+    const sv = new ScrollView();
+    // sv.setShowSize(this.panel.width, 900 - subH);
+    // sv.x = -this.panel.width / 2;
+    // sv.y = -395 + subH;
+    // sv.setBgColor('#faf7ee')
+    // // sv.setMapScale(this.mapScale);
+    // // sv.content.setScaleXY(1/this.mapScale);
+    // sv.setScrollBarSize(20 * this.mapScale, 5)
+    // // this.renderArr.push(sv);
+    // // sv.setScaleXY(this.mapScale);
+    // // sv.x = 0;
+    // // sv.y = 200;
+    // sv.setContentScale(this.mapScale);
+
+    // this.panel.addChild(sv);
+    // this.scrollView = sv;
+
+
+    
+    this.sentenceEmptyArr = [];
+    
+    const arr = this.sentenceArr;
+    // const arr = this.sentenceArr.concat(this.sentenceArr);
+    if (!arr) {
+      return;
+    }
+    const baseX = this.panel.width / 2 - 1000 ;
+    let curY = 70 // this.canvasHeight / 2 - 110 * this.mapScale;
+    const disH = 70 * this.mapScale;
+    for (let i=0; i<arr.length; i++) {
+      const richText = this.getSentenceLabel();
+      richText.x = baseX;
+      richText.y = curY;
+      richText.text = arr[i].text;
+
+      const rect = richText.getSubTextRect('____________________');
+      const colorRect = new ShapeRectNew();
+      rect.height += 30;
+      colorRect.setSize(rect.width, rect.height, 1);
+     
+      colorRect.fillColor = '#ff0000'
+      const offX = 13;
+      colorRect.x = rect.x + offX;
+      colorRect.y = -rect.height / 2;
+      console.log('rect.height: ', rect.height);
+      colorRect.alpha = 0;
+
+  
+
+      richText.addChild(colorRect);
+      this.sentenceEmptyArr.push(colorRect);
+
+      curY += richText.getAreaHeight() * this.mapScale + disH;
+
+      richText['data'] = arr[i];
+
+
+      sv.addItem(richText);
+
+      // this.renderArr.push(richText);
+      colorRect.ctx = colorRect.parent.ctx;
+
+
+    }
+    
+
+  }
+
+  getFillLabel() {
+    const label = new RichText();
+ 
+    label.fontSize = 56;
+    label.fontColor = '#000000'
+    label.fontName = 'Aileron-Bold';
+    // label.setScaleXY(this.mapScale);
+    
+    return label;
+  }
+
+  getSentenceLabel() {
+
+    const richText = new RichText();
+    richText.disH = 0;
+ 
+    richText.fontSize = 56;
+    richText.width = 2000 / this.mapScale
+    richText.fontColor = '#000000'
+    richText.fontName = 'DroidSansFallback';
+    richText.setScaleXY(this.mapScale);
+    
+    return richText;
+
+  }
+
+  changeBtnOff() {
+
+    for (let i=0; i<this.btnArr.length; i++) {
+      if (this.btnArr[i].visible) {
+        this.btnArr[i].init(this.images.get('btn_off'));
+        this.btnArr[i].isOff = true;
+      }
+    }
+    
+  }
+
+  changeBtnOn() {
+
+    for (let i=0; i<this.btnArr.length; i++) {
+      if (this.btnArr[i].visible) {
+        this.btnArr[i].init(this.images.get('btn'));
+        this.btnArr[i].isOff = false;
+      }
+    }
+    
+  }
+
+
+
+
+
+  _initTitle() {
+
+    const label = new Label();
+    label.fontSize = 42 * this.mapScale;
+    label.fontName = 'BRLNSDB';
+    label.textAlign = 'center';
+    label.setMaxSize(this.canvasWidth * 0.9);
+    label.x = this.canvasWidth / 2;
+    label.y = (this.bgRect.y - this.bgRect.height / 2 * this.bgRect.scaleY) / 2;
+    label.fontColor = '#fff143';
+    label.setShadow(0, 5, 5, 'rgba(0,0,0,0.3)');
+    this.titleLabel = label;
+    this.renderArr.push(label);
+
+    const disH = 5 * this.mapScale;
+
+
+    const starBg = new MySprite();
+    starBg.init(this.images.get('top_star_bg'));
+    starBg.setScaleXY(this.mapScale);
+
+    const disW = starBg.width / 3 * starBg.scaleX;
+
+    const num = this.picArr.length;
+    const itemW = starBg.width * starBg.scaleX;
+    const totalW = (starBg.width) * starBg.scaleX * num + disW * (num - 1);
+    const offX = this.canvasWidth / 2 - totalW / 2 + itemW / 2;
+
+    this.starBgArr = [];
+    for (let i = 0; i < num; i++) {
+      const starBg = new MySprite();
+      starBg.init(this.images.get('top_star_bg'));
+      starBg.setScaleXY(this.mapScale);
+      starBg.x = offX + (itemW + disW) * i;
+      starBg.y = label.y - starBg.height / 2 * starBg.scaleY - disH / 2;
+
+      this.renderArr.push(starBg);
+
+
+      const star = new MySprite();
+      star.init(this.images.get('top_star'));
+      star.visible = false;
+      starBg['star'] = star;
+      starBg.addChild(star);
+
+      this.starBgArr.push(starBg);
+    }
+
+
+
+    label.y += label.height / 2 * label.scaleY + disH / 2;
+
+
+    // const textBg = new MySprite();
+    // textBg.init(this.images.get('text_bg'));
+    // textBg.setScaleXY(this.mapScale);
+    // textBg.x = label.x;
+    // textBg.y = label.y - textBg.height / 2 * textBg.scaleY - disH / 2;
+    // this.renderArr.push(textBg);
+    //
+    // label.y += label.height / 2 * label.scaleY + disH / 2;
+    //
+    //
+    // const pageLabel = new Label();
+    // pageLabel.text = '0 / 0';
+    // pageLabel.fontName = 'BRLNSDB';
+    // pageLabel.fontColor = '#ffffff';
+    // pageLabel.textAlign = 'center';
+    // pageLabel.fontSize = 24;
+    // textBg.addChild(pageLabel);
+    //
+    // this.pageLabel = pageLabel;
+
+    this.refreshTitleLabel();
+    this.refreshPageLabel();
+  }
+
+
+  refreshTitleLabel(animaFlag = false) {
+    const data = this.picArr[this.curPageIndex];
+
+    if (animaFlag) {
+
+      hideItem(this.titleLabel, 0.2, () => {
+        this.titleLabel.text = data.text;
+        showItem(this.titleLabel, 0.3);
+
+        this.playAudio('tip', true);
+
+      });
+    } else {
+      this.titleLabel.text = data.text;
+
+    }
+  }
+
+
+  refreshPageLabel() {
+
+    // const label = this.pageLabel;
+    // label.text = (this.curPageIndex + 1) + ' / ' + this.picArr.length;
+
+    for (let i = 0; i < this.curPageIndex; i++) {
+
+      const starBg = this.starBgArr[i];
+      if (starBg) {
+        const star = starBg.star;
+        if (!star.visible) {
+
+          star.visible = true;
+          star.setScaleXY(3);
+          star.alpha = 0;
+
+          tweenChange(star, { scaleX: 1, scaleY: 1, alpha: 1 }, 1, () => {
+
+          }, TWEEN.Easing.Exponential.In);
+
+        }
+
+      }
+
+    }
+  }
+
+  showAudio() {
+    const data = this.picArr[this.curPageIndex];
+    const audio = this.audioObj[data.audio_url];
+    if (audio) {
+      audio.play();
+    }
+  }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  panel;
+
+  initBg() {
+
+    const colorBg = this.getMySprite("bg");
+    colorBg.setScaleXY(this.mapScaleMax);
+    this.renderArr.push(colorBg);
+    colorBg.x = this.canvasWidth / 2;
+    colorBg.y = this.canvasHeight / 2;
+
+   
+    const title = new Label();
+    title.fontSize = 76 * this.mapScale;
+    title.fontName = 'Aileron-Bold';
+    title.textAlign = 'left';
+    // title.setMaxSize(this.canvasWidth * 0.9);
+    title.x = 240;
+    title.y = 100 * this.mapScale;
+    title.fontColor = '#3e9b31';
+    title.text = this.data.contentObj.title || "";
+    title.refreshSize();
+    this.renderArr.push(title);
+
+
+
+    console.log('this.data: ', this.data);
+
+   
+
+
+    const subH = title.fontSize;
+    const url = this.data.contentObj.bgItem?.url;
+    if (url) {
+      const bg = new MySprite();
+      bg.init(this.images.get(url));
+
+      const sx = this.canvasWidth / this.canvasBaseW;
+      const sy = (this.canvasHeight - subH) / this.canvasBaseH;
+      const s = Math.min(sx, sy);
+
+      bg.setScaleXY(this.mapScale);
+      bg.x = this.canvasWidth / 2;
+      bg.y = this.canvasHeight / 2 + subH;
+      
+      bg['data'] = this.data.contentObj.bgItem;
+      this.bg = bg;
+      this.renderArr.push(bg);
+    }
+ 
+
+
+  }
+
+
+
+
+
+
+  okBtnClick() {
+    this.checkPanel.visible = false;
+    this.removeSubmit();
+    // this.appRef.tick();
+
+    this.canTouch = false;
+    setTimeout(() => {
+      // this.hideSubmit();
+      this.convertCanvasToImage();
+      // this.hideSubmit();
+      setTimeout(() => {
+        this.setAnswerData(() => {
+          this.initResultView();
+          this.canTouch = true;
+        });
+
+       
+
+      }, 500);
+    }, 500);
+ 
+  }
+
+  cancelBtnClick() {
+    this.checkPanel.visible = false;
+
+  }
+
+
+  curMoveItem = null;
+  downFlag = false;
+  mapDown(event) {
+
+
+    if (!this.canTouch) {
+      return;
+    }
+
+    if (this.videoMask && this.videoMask.visible) {
+
+      if (this.videoCloseBtn) {
+        if (this.checkClickTarget(this.videoCloseBtn)) {
+          this.videoCloseBtnClick();
+          return;
+        }
+      }
+  
+      if (this.replayBtn && this.replayBtn.visible) {
+        if (this.checkClickTarget(this.replayBtn)) {
+          this.replayBtnClick();
+          return;
+        }
+      }
+
+      return;
+    }
+
+    if (this.templateQuesType == this.TYPE_SINGLE_CHOICE) {
+      for (let i=0; i<this.optionItemArr.length; i++) {
+        if (this.checkClickTarget(this.optionItemArr[i])) {
+          this.optionItemClick(this.optionItemArr[i]);
+          return;
+        }
+      }
+    } else {
+
+      if (this.checkClickTarget(this.answerRect)) {
+        this.answerRectClick();
+        return;
+      }
+  
+    }
+   
+
+   
+    
+
+  }
+
+
+
+
+  mapMove(event) {
+
+    if (event) {
+      event.preventDefault()
+    }
+
+   
+  }
+
+
+  mapUp(event) {
+
+
+
+  }
+
+
+  answerRectClick() {
+    console.log('in answerRectClick');
+
+    const courseware = window['courseware'];
+    if (!courseware) {
+      return;
+    }
+
+    courseware.activeFormulaBoard((data) => {
+      // TODO
+      console.log('in activeFormulaBoard data: ', data);
+    });
+  }
+
+  changeBase64ToPicUrl(base64Data) {
+
+    var arr = base64Data.split(','), mime = arr[0].match(/:(.*?);/)[1],
+    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
+    while(n--){
+        u8arr[n] = bstr.charCodeAt(n);
+    }
+    var obj = new Blob([u8arr], {type:mime});
+    var fd = new FormData();
+
+    fd.append("file", obj, "jm_math.png");
+
+    // 上传截图
+    this.http.post(this.uploadUrl, fd).subscribe((res) => {
+      // 存储服务器的截图url
+      if (res['url']) {
+        console.log('res url: ', res['url']);
+        
+      } else {
+
+      }
+    });
+
+
+  }
+
+
+  replayBtnClick() {
+    if (this.curVideo) {
+      this.curVideo.element.play();
+      this.replayBtn.visible = false;
+    }
+  }
+
+  videoCloseBtnClick() {
+    console.log("in videoCloseBtnClick");
+    this.videoBg.visible = false;
+    this.videoMask.visible = false;
+    if (this.curVideo) {
+      this.curVideo.element.pause();
+    }
+    if (this.curOpItem) {
+      this.changeArea(this.curOpItem.data);
+    }
+  }
+
+  curOpItem;
+  optionItemClick(opItem) {
+    console.log('opItem: ', opItem);
+    const data = opItem.data;
+    this.curOpItem = opItem;
+    this.changeAllOpItem();
+    if (data.is_right) {
+      this.optionRight(opItem);
+    } else {
+      this.optionWrong(opItem);
+    }
+  }
+
+  optionRight(opItem) {
+    this.changeOpItemBg(opItem, 'rightPic');
+
+  }
+
+  optionWrong(opItem) {
+    this.changeOpItemBg(opItem, 'wrongPic');
+
+    this.canTouch = false;
+    setTimeout(() => {
+
+      if (opItem.data.video_url) {
+        this.showVideo(opItem);
+      } else {
+        this.callTeacher();
+      }
+
+      this.canTouch = true;
+    }, 2000);
+  }
+
+  callTeacher() {
+    this.changeArea(this.data.contentObj);
+  }
+
+  showVideo(opItem) {
+    const {data} = opItem;
+    this.videoMask.visible = true;
+    this.videoBg.visible = true;
+
+    this.initVideoPlayer(data);
+  }
+
+  hideVideo() {
+    this.videoMask.visible = false;
+    this.videoBg.visible = false;
+  }
+
+  changeAllOpItem() {
+    for (let i=0; i<this.optionItemArr.length; i++) {
+      this.changeOpItemBg(this.optionItemArr[i], 'normalPic');
+    }
+  }
+
+  changeOpItemBg(opItem, res) {
+    opItem[opItem.curRes].visible = false;
+    opItem.curRes = res;
+    opItem[opItem.curRes].visible = true;
+  }
+
+
+
+  moreBtnClick() {
+    // this.setPageData('progress', '2', false);
+    // this.setPageData('submitCount', this.submitCount);
+    this.checkShowSubTemplateOne();
+    this.isUpdateStop = true;
+
+  }
+
+  checkShowSubTemplateOne() {
+
+    // this.showSubTemplate(0);
+
+    console.log(' in checkShowSubTemplateOne')
+    const progress = this.getPageData('progress');
+
+    console.log(' progress: ', progress);
+
+
+
+    if (progress == '2') {
+      if (this.submitCount == 1) {
+        this.showSubTemplate(1);
+        this.sendSubTempIsShowMore(1, true);
+      } else {
+        this.showSubTemplate(0);
+        this.sendSubTempIsShowMore(0, true);
+      }
+
+      return;
+    }
+
+    if (progress == '3') {
+
+      let index;
+      if (this.submitCount == 1) {
+        index = 0;
+        this.hideSubTemplate(1);
+      } else {
+        index = 1;
+        this.hideSubTemplate(0);
+
+      }
+
+      this.showSubTemplate(index);
+      this.sendSubTempIsShowMore(index, false);
+
+    }
+
+  }
+
+  sendSubTempIsShowMore(index, isShowMore) {
+
+    setTimeout(() => {
+      const divArr = this.iframeContent.nativeElement.children;
+      const iframecont = divArr[index].children[0];
+      const data =  { msg: 'success', isShowMore};
+      iframecont.contentWindow.postMessage({ action: 'is_show_more', data: JSON.stringify(data) }, '*');
+      console.log(' post msg is_show_more ')
+    }, 1000);
+ 
+  }
+
+
+  btnDown(btn) {
+    this.curMoveItem = btn;
+  }
+
+  addLabelMask(target, label) {
+    const box = target.getBoundingBox();
+
+
+    // const mask = new ShapeRect();
+    // mask.setSize(400, 400);
+    // mask.fillColor = '#ff0000';
+
+    // // const mask = new MySprite();
+    // // mask.init(this.images.get("mask"))
+    // // mask.x = rect.x;
+    // // mask.y = rect.y;
+    // // const sx = rect.width / mask.width;
+    // // const sy = rect.height / mask.height;
+    // // mask.scaleX = sx;
+    // // mask.scaleY = sy;
+
+    // // this.scrollView.addItem(mask);
+
+    // // label.addMaskSpr(mask);
+    // // mask.x = 200;
+    // // mask.y = 200;
+
+
+
+    // // const label1 = new MySprite();
+    // // label1.init(this.images.get("btn_bg"))
+    // // label1.setScaleXY(2);
+
+    // const label1 = this.getFillLabel();
+    // label1.text = 'aaaaaaaa a a a a a a a a a a a a a a ';
+    // label1.x = 400;
+    // label1.y = 400;
+    // label1.width = mask.width;
+    // label1.height = mask.height;
+    // label1.textBaseline = 'top';
+    // // label1.setMaxSize(rect.width);
+
+
+    // this.renderArr.push(mask);
+
+    // this.renderArr.push(label1);
+    
+
+
+    // // mask.setMaskType("destination-in");
+    // // // label1.addMaskSpr(mask)
+    // // mask.addMaskSpr(label1);
+    // label1.addMaskSpr(mask);
+
+    label.width = box.width;
+    label.height = box.height;
+    label.x = box.width / 2;
+    label.y = box.height / 2;
+
+    const rect = new ShapeRect();
+    rect.setSize(box.width, box.heigth);
+    label.addMaskSpr(rect);
+  }
+  
+  inputFontSize = 40;
+  emptyRectDown(targetRect) {
+
+    if (!targetRect.label) {
+      const label = this.getFillLabel();
+      label.fontSize = this.inputFontSize;
+      label.disH = 0;
+      label.offW = 0;
+      label.textBaseline = 'top'
+      label.width = targetRect.width;
+      label.fontSize *= (this.bg.scaleX);
+      // targetRect.label.x = ( targetRect.width / 2  ) 
+      targetRect.label = label;
+      targetRect.addChild(targetRect.label);
+
+      this.addLabelMask(targetRect, label);
+    }
+
+    if (targetRect.label.isDefault) {
+      targetRect.label.text = '';
+      targetRect.label.fontColor = '#000000';
+      targetRect.label.isDefault = false;
+    }
+
+    if (true) {
+
+      const scale = 1 // this.mapScale;
+
+      let tmpRect = targetRect.getBoundingBox();
+
+      const svBox = this.scrollView.getBoundingBox();
+    
+      const x = svBox.x + tmpRect.x * scale;
+      const y = svBox.y + tmpRect.y * scale;
+      const width = tmpRect.width * scale;
+      const height = tmpRect.height * scale;
+      const rect = {x, y, width, height};
+
+      const style = {
+        'font-size' : this.inputFontSize / this.canvasScale * this.bg.scaleX + 'px',
+        'color' : '#000000',
+        'font-family' : 'Aileron-Bold',
+        'line-height' : this.inputFontSize / this.canvasScale * this.bg.scaleX + 'px',
+      }
+      this.showInputView(style, rect, targetRect.label.text || '', (value) => {
+        targetRect.label.text = value;
+        this.checkShowSubmit();
+      })
+  
+    }
+  }
+
+
+  checkPanel;
+  okBtn;
+  cancelBtn;
+  showCheckPanel() {
+    if (!this.checkPanel) {
+      this.checkPanel = new MySprite();
+      this.checkPanel.init(this.images.get("check_panel"));
+      this.checkPanel.setScaleXY(this.mapScale);
+      this.checkPanel.x = this.canvasWidth / 2;
+      this.checkPanel.y = this.canvasHeight / 2;
+
+      const label = new Label();
+      label.text = '本题只能提交一次,确认提交吗?'
+      label.textAlign = 'center';
+      label.fontColor = '#ffffff';
+      label.fontSize = 56 //* this.mapScale;
+      label.fontName = 'Aileron-Black';
+      label.y = -70;
+      this.checkPanel.addChild(label);
+      // label.x = this.checkPanel.width / 2;
+      // label.y = this.checkPanel.height / 2;
+  
+
+
+      const offX = 300;
+      const okBtn = new MySprite()
+      okBtn.init(this.images.get("check_ok"));
+      this.checkPanel.addChild(okBtn);
+      okBtn.x = offX;
+      okBtn.y = 130;
+      this.okBtn = okBtn;
+
+      const cancelBtn = new MySprite()
+      cancelBtn.init(this.images.get("check_cancel"));
+      this.checkPanel.addChild(cancelBtn);
+      cancelBtn.x = -offX;
+      cancelBtn.y = okBtn.y;
+      this.cancelBtn = cancelBtn;
+
+
+
+      this.renderArr.push(this.checkPanel);
+    }
+ 
+    this.checkPanel.visible = true;
+  }
+
+  submitBtnClick() {
+
+    // this.submitCount ++;
+
+
+    const btnBaseY =  this.submitBtn.y;
+    const btnTargetY = btnBaseY + 5 * this.mapScale;
+
+    const baseS = this.submitBtn.scaleX;
+    const targetS = baseS * 0.95;
+    const time = 0.07;
+
+    this.canTouch = false;
+    tweenChange(this.submitBtn, {y: btnTargetY, scaleX: targetS, scaleY: targetS}, time, ()=> {
+      // tweenChange(this.submitBtn, {y: btnBaseY, scaleX: baseS, scaleY: baseS}, time)
+
+      tweenChange(this.submitBtn, {y: btnBaseY, scaleX: baseS, scaleY: baseS}, time, () => {
+        this.canTouch = true;
+      })
+
+      this.showCheckPanel();
+
+      // this.submitOff();
+      
+      // const isAllRight = this.checkAnswer();
+
+    
+      // if (isAllRight || this.submitCount >= 2) {
+      //   this.changeBtnOff();
+      //   this.btnArr = [];
+
+    
+
+
+      //   delayCall(0.3, ()=> {
+      //     this.initResultView();
+
+      //     this.setPageData('progress', '2', false);
+      //     this.setPageData('submitCount', this.submitCount);
+      //     this.serverSendAnswer('4-1', this.resultAnswerArr);
+      //   })
+      // }
+
+        
+
+    })
+
+    const shadow = this.submitBtn.shadowSpr;
+    const shadowBaseY =  shadow.y //shadow['baseY'];
+    const shadowTargetY = shadowBaseY - 15 * this.mapScale;
+    tweenChange(shadow, {y: shadowTargetY, alpha: 0}, time, ()=> {
+      shadow.y = shadowBaseY;
+      shadow.alpha = 1;
+      tweenChange(shadow, {y: shadowBaseY, alpha: 1}, time)
+    })
+
+    // this.addResultAnswer();
+
+  }
+
+  addResultAnswer() {
+
+    console.log('this.data.contentObj.sentenceArr: ', this.data.contentObj.sentenceArr)
+    console.log('this.sentenceEmptyArr: ', this.sentenceEmptyArr);
+
+    const sentenceArr = this.data.contentObj.sentenceArr;
+    const resultAnswer = [];
+    for (let i=0; i<sentenceArr.length; i++) {
+      const label = this.sentenceEmptyArr[i].label;
+      const tmpData = {};
+      tmpData['question'] = sentenceArr[i];
+      tmpData['userAnswer'] = label.text;
+
+      resultAnswer.push(tmpData);
+    }
+
+    this.resultAnswerArr.push(resultAnswer);
+
+  }
+
+  sendResult() {
+
+    // if (this.userResultPic && this.userResultData) {
+
+      const c = window['courseware'];
+      if (!c) {
+        return;
+      }
+
+      const duration = new Date().getTime() - this.localStartTime;
+      c.sendAnswer({resultData: this.userResultData, resultPic: this.userResultPic || '', duration});
+      
+    // }
+
+
+
+
+  }
+
+
+
+
+  async setAnswerData(cb) {
+
+    let isAllRight = true;
+    for (let i=0; i<this.hotZoneArr.length; i++) {
+
+      const data = this.hotZoneArr[i].data;
+      console.log('data: ', data);
+
+      let answer = this.hotZoneArr[i].label?.text || '';
+
+      console.log('answer: ', answer);
+      console.log('i: ', i);
+
+      // answer = answer.replace(/\n/g," ");
+
+      let result;
+      if (data.selectType == 'ai') {
+        result = await this.serverTest(answer);
+        result.isAi = true;
+      } else {
+
+
+        result = await this.paperForCheck(data, answer, this.hotZoneArr[i].data.labelText)
+
+      
+         
+        // result = checkAnswer(data.selectType, answer, this.hotZoneArr[i].data.labelText || null);
+      }
+     
+     
+      console.log('result: ', JSON.parse(JSON.stringify(result)) );
+
+      this.hotZoneArr[i].result = result;
+
+
+      if (!result.right) {
+        // this.removeRectText( this.hotZoneArr[i] );
+        isAllRight = false;
+      } else {
+        this.hotZoneArr[i].isRight = true;
+        // this.showSentenceRight(this.hotZoneArr[i]);
+      }
+    }
+
+
+    cb();
+    // return isAllRight;
+   
+  }
+
+  async paperForCheck(data, answer, labelText = null) {
+
+    return new Promise((resolve, reject) => {
+
+      console.log(' in greadPapersForCheck data: ', {type:data.selectType, blankWord: answer, word: labelText});
+
+
+      window['courseware'].greadPapersForCheck({type:data.selectType, blankWord: answer, word: labelText}, res => {
+        // res 需要toJSON
+        console.log('in greadPapersForCheck res: ', res);
+        if (res) {
+
+          resolve(JSON.parse(res))
+        } else {
+          reject();
+        }
+      });
+
+       
+    })
+
+
+
+  }
+
+
+  async serverTest(text) {
+
+    return new Promise((resolve, reject) => {
+
+      const c = window['courseware'];
+      if (!c) {
+        console.error('not found window.courseware')
+        reject();
+        return;
+      }
+  
+  
+      if (text) {
+  
+        this.iconData = {};
+
+        console.log(' greadPapersForText start')
+
+        c.greadPapersForText(text, (res) => {
+
+          console.log(' greadPapersForText end')
+
+          const resData = JSON.parse(res);
+          const sentsFeedback = resData.detail?.essayFeedback?.sentsFeedback
+          let startIndex = 0;
+          if (sentsFeedback) {
+            for (let i=0; i<sentsFeedback.length; i++) {
+              const {errorPosInfos, rawSent} = sentsFeedback[i];
+              if (!errorPosInfos || !rawSent) {
+                continue;
+              }
+              const baseIndex = text.indexOf(rawSent, startIndex);
+  
+              console.log('text: ', text);
+              console.log('rawSent: ', rawSent);
+              console.log('baseIndex: ', baseIndex);
+              for (let j=0; j<errorPosInfos.length; j++) {
+                const {reason, endPos} = errorPosInfos[j];
+                this.addIconData(baseIndex + endPos, {reason});
+              }
+
+              startIndex = baseIndex;
+            }
+          }
+          console.log('res : ', res);
+          console.log('iconData : ', this.iconData);
+  
+          this.createResultTextArr(text);
+          resolve({resultTextArr:this.resultTextArr, iconData: this.iconData, detail: resData.detail});
+          // this.setComment(resData);
+        })
+      }
+
+    });
+
+  
+    
+  }
+
+  iconData;
+  addIconData(index, result) {
+    if (!this.iconData) {
+      this.iconData = {};
+    }
+  
+    const key = index.toString();
+    if (!this.iconData[key]) {
+      this.iconData[key] = {dataArr: []};
+    } 
+
+    this.iconData[key].dataArr.push(result)
+    
+  }
+
+
+  resultTextArr = [];
+  createResultTextArr(text) {
+    const iconKeys = Object.keys(this.iconData);
+    iconKeys.sort((a, b) => {
+      return Number(a) - Number(b);
+    })
+
+    this.resultTextArr = [];
+    let curStartPos = 0;
+    for (let i=0; i<iconKeys.length; i++) {
+      const textData = this.iconData[iconKeys[i]];
+      const endPos = Number(iconKeys[i]);
+      textData['text'] = text.substring(curStartPos, endPos);
+      this.resultTextArr.push(textData)
+
+      curStartPos = endPos;
+    }
+
+
+    const textData = {dataArr: []}
+    textData['text'] = text.substring(curStartPos)
+    this.resultTextArr.push(textData)
+    
+
+    console.log('text: ',  text);
+    console.log('resultTextArr: ', this.resultTextArr);
+  }
+
+
+
+  removeRectText(emptyRect) {
+
+    this.showBtnByText(emptyRect.label.text);
+
+    emptyRect.removeChild(emptyRect.label);
+    // removeItemFromArr(this.renderArr, emptyRect.label);
+    emptyRect.label = null;
+  }
+
+  showSentenceRight( emptyRect ) {
+
+    if (!emptyRect.isRight) {
+      emptyRect.isRight = true;
+      const pos = {x: emptyRect.width / 2, y: emptyRect.height / 2};
+      this.showRightParticle(emptyRect, pos)
+    }
+  
+  }
+
+  showRightParticle(emptyRect, point ) {
+    const shapeW = 25 // * this.mapScale;
+    const shapeH = 25 // * this.mapScale;
+    showShapeParticle( 'rect', shapeW, shapeH, point, emptyRect, 20, 50, 180, 0.4);
+  }
+
+
+  showBtnByText(text) {
+    for (let i=0; i<this.btnArr.length; i++) {
+      if (this.btnArr[i].text == text) {
+        this.btnArr[i].visible = true;
+        return;
+      }
+    }
+  }
+
+ 
+  checkOnTarget() {
+
+     
+    for (let i = 0; i < this.sentenceEmptyArr.length; i++) {
+      if (this.checkClickTargetSv(this.sentenceEmptyArr[i])) {
+
+        this.playAudio('click', true);
+
+        if (this.sentenceEmptyArr[i].isRight) {
+          this.moveItemBack();
+          return;
+        }
+
+        const tmpText = this.sentenceEmptyArr[i].label ? this.sentenceEmptyArr[i].label.text : '';
+
+        const btnIndex = this.btnArr.indexOf(this.curMoveItem);
+        const isBtn = btnIndex != -1;
+      
+        console.log('isBtn: ', isBtn);
+        if (isBtn && tmpText != '') {
+          this.moveItemBack();
+          return;
+        }
+
+        this.fillText(this.sentenceEmptyArr[i], this.curMoveItem.text);
+
+        if (this.curMoveItem.fillRect) {
+          this.fillText(this.curMoveItem.fillRect, tmpText);
+          this.curMoveItem.text = tmpText;
+          this.moveItemBack();
+        }
+    
+        if (isBtn) {
+          this.removeBtn(this.curMoveItem);
+        }
+
+        this.checkShowSubmit();
+        return;
+      }
+    }
+
+    this.moveItemBack();
+    
+  }
+
+  removeBtn(btn) {
+
+    this.moveItemBack();
+    btn.visible = false;
+    // removeItemFromArr(this.btnArr, btn);
+    // removeItemFromArr(this.topArr, btn);
+  }
+
+  fillText(emptyRect, text) {
+
+    const data = emptyRect.parent.data;
+
+    if (!emptyRect.label) {
+      emptyRect.label = this.getFillLabel();
+      emptyRect.label.setScaleXY(1);
+      emptyRect.label.textAlign = 'center';
+      emptyRect.addChild(emptyRect.label);
+      
+      // this.renderArr.push(emptyRect.label);
+    }
+    const label = emptyRect.label
+    label.text = text;
+    // label.width = emptyRect.width / this.mapScale;
+    // label.setScaleXY(1);
+    label.refreshSize();
+    label.anchorY = 0.5;
+
+    // label.y = emptyRect.parent.y //- label.height * label.scaleY ;
+    // label.x = emptyRect.parent.x + ( emptyRect.x + emptyRect.width / 2 - label.width / 2 ) * this.mapScale
+
+    label.y = emptyRect.height / 2;
+    label.x = ( emptyRect.width / 2  ) 
+
+
+    // const w1 = emptyRect.width / this.mapScale;
+    // const w2 = label.width;
+    // label.width = w1// w2 < w1 ? w1: w2;
+    
+
+    label['moveOffX'] = -label.width / 2 * label.scaleX;
+    label['moveOffY'] = 0// -label.height / 2 * label.scaleY;
+    label['baseX'] = label.x;
+    label['baseY'] = label.y;
+    emptyRect.label = label;
+    label.fillRect = emptyRect;
+    console.log('label: ', label.width);
+
+  }
+
+
+  checkShowSubmit() {
+
+    const arr = this.hotZoneArr;
+
+
+
+    for (let i=0; i<arr.length; i++) {
+
+     
+      if (arr[i].data.gIdx == '0') {
+        if (!arr[i].label || !arr[i].label.text || arr[i].label.isDefault) {
+          this.hideSubmit();
+          return;
+        }
+      }
+
+
+   
+    }
+    
+    this.showSubmit();
+    
+  }
+
+
+
+  moveItemBack() {
+    if (this.curMoveItem.targetLabel) {
+      this.curMoveItem.targetLabel.visible = true;
+      removeItemFromArr(this.renderArr, this.curMoveItem);
+      return;
+    }
+    
+    this.curMoveItem.x = this.curMoveItem['baseX'];
+    this.curMoveItem.y = this.curMoveItem['baseY'];
+  }
+
+
+
+
+
+
+  templateArr = [];
+  initIframe() {
+
+    this.templateArr = this.data.contentObj.templateArr;
+
+    if (this.templateArr.length <= 0) {
+      return;
+    }
+    console.log('arr: ', this.templateArr);
+
+    const c = window['courseware'];
+    if (!c) {
+      console.error('not found window.courseware')
+      return;
+    }
+
+
+
+    const addPlayUrl = (name, index) => {
+
+      c.getTemplateUrl(name, (data) => {
+        console.log('name~: ', name)
+
+        const urlData = JSON.parse(data)
+        console.log('data~~:' , data)
+        console.log('urlData~~:' , urlData)
+        console.log('i~~:' , index)
+        this.templateArr[index].playUrl = this.sanitizer.bypassSecurityTrustResourceUrl(urlData.play_url + `?key=${index+1}`);
+        this.appRef.tick();
+        
+        index ++;
+        if (index < this.templateArr.length) {
+          const {name, last_version, play_url} = this.templateArr[index].template;
+          addPlayUrl(name, index);
+        }
+      })
+    }
+
+    const {name, last_version, play_url} = this.templateArr[0].template;
+    addPlayUrl(name, 0);
+
+    // for (let i=0; i<this.templateArr.length; i++) {
+
+    //   const {name, last_version, play_url} = this.templateArr[i].template;
+
+    //   const index = i;
+    //   c.getTemplateUrl(name, (data) => {
+    //     console.log('name~: ', name)
+
+    //     const urlData = JSON.parse(data)
+    //     console.log('data~~:' , data)
+    //     console.log('urlData~~:' , urlData)
+    //     console.log('i~~:' , index)
+    //     this.templateArr[index].playUrl = this.sanitizer.bypassSecurityTrustResourceUrl(urlData.play_url + `?key=${index+1}`);
+        
+    //   })
+
+      // const playUrl = `https://staging-teach.cdn.ireadabc.com/h5template/${name}/v${last_version}/${play_url}?key=${i+1}`
+      // this.templateArr[i].playUrl = this.sanitizer.bypassSecurityTrustResourceUrl(playUrl); 
+      // console.log('playUrl: ', playUrl);
+    // }
+ 
+    // this.showSubTemplate(0);
+
+    // setTimeout(() => {
+    // this.showSubTemplate(1);
+      
+    // }, 5000);
+  }
+
+  showSubTemplate(index) {
+
+    if (this.isTeacher) {
+      return;
+    }
+
+    console.log('showSubTemplate _ ', index);
+    const arr = this.templateArr;
+    console.log('arr: ', arr);
+
+    for (let i=0; i<arr.length; i++) {
+      arr[i].leftOff = '0vw';
+    }
+    arr[index].leftOff = '100vw';
+    this.appRef.tick();
+
+    if (this.readyObj[(index + 1).toString()]) {
+      this.sendCourseIn(index);
+    }
+
+  }
+
+
+  hideSubTemplate(index) {
+
+    this.templateArr[index].isHide = true;
+    this.appRef.tick();
+
+  }
+
+
+  iframeArr;
+  readyObj;
+  initWindowListener() {
+
+
+    // const iframeArr = this.iframeArr;
+
+    // // setTimeout(() => {
+
+    //   console.log("iframeContent: ", this.iframeContent);
+    //   let divArr = this.iframeContent.nativeElement.children;
+    //   console.log('divArr: ', divArr);
+    //   for (let i=0; i<divArr.length; i++) {
+    //     const iframe = divArr[i].children[0];
+    //     console.log('iframe: ', iframe);
+    //     iframeArr.push(iframe)
+    //   }
+  
+    //   console.log('iframeArr: ', iframeArr);
+    // // }, 1);
+   
+    this.readyObj = {};
+
+    window.addEventListener('message', (e) => {
+
+
+      let msgData = e.data;
+   
+
+      if (msgData.action === "getData") {
+
+
+        console.log('msgData.urlParams: ', msgData);
+        const key = this.getQueryVariable(msgData.urlParams, 'key');
+        if (!key) {
+          return;
+        }
+
+        console.log('key: ', key);
+
+
+        const index = key - 1;
+        const divArr = this.iframeContent.nativeElement.children;
+        const iframecont = divArr[index].children[0];
+
+        // const iframecont = iframeArr[key-1]
+        const saveData = this.templateArr[index].data;
+
+
+        console.log("getData e: ", e);
+
+        
+
+        const data = { msg: 'success', data:  JSON.stringify(saveData)};
+
+        iframecont.contentWindow.postMessage({ action: 'getData', data: JSON.stringify(data) }, '*');
+        
+
+        
+        // const data2 =  { msg: 'success', data:  JSON.stringify({'isShowMore': false})};
+        // iframecont.contentWindow.postMessage({ action: 'is_show_more', data: JSON.stringify(data2) }, '*');
+        
+        // setTimeout(() => {
+        //   _this.frameLoaded = true;
+        // }, 300);
+      }
+  
+      if (msgData.action === "temp_send_result") {
+
+        console.log(' in temp_send_result');
+        console.log('msgData.urlParams: ', msgData);
+        let key = msgData.key;
+        if (!key) {
+          key = '4-3';
+        }
+
+        console.log("key: ", key);
+
+        this.serverSendAnswer(key, JSON.parse( msgData.data ));
+
+        const progress = this.getPageData('progress');
+        if (progress == '2') {
+          this.setPageData('progress', '3');
+        } else {
+          this.setPageData('progress', '4');
+        }
+        console.log('in temp_send_result __ msgData: ', msgData);
+      }
+
+      if (msgData.action === "temp_show_more") {
+
+        console.log(' in temp_show_more');
+        this.checkShowSubTemplateOne();
+      }
+
+      
+      if (msgData.action == 'course-ready') {
+        console.log(' in course-ready msgData: ', msgData)
+
+        console.log('msgData.urlParams: ', msgData);
+        const key = this.getQueryVariable(msgData.urlParams, 'key');
+        if (!key) {
+          return;
+        }
+
+        this.readyObj[key] = true;
+      }
+
+
+    });
+  }
+
+  sendCourseIn(index) {
+
+    const divArr = this.iframeContent.nativeElement.children;
+    const iframecont = divArr[index].children[0];
+    
+
+    const data = { msg: 'success', data:  ''};
+
+    iframecont.contentWindow.postMessage({ action: 'airEvents', evt: 'course-in-screen', data: JSON.stringify(data) }, '*');
+    
+  }
+
+  getQueryVariable(url, variable) {
+    var query = url.substring(1);
+    var vars = query.split("&");
+    for (var i=0;i<vars.length;i++) {
+      var pair = vars[i].split("=");
+      if(pair[0] == variable){return pair[1];}
+    }
+    return(false);
+  }
+
+
+  serverResultObj;
+  serverSendAnswer(key, data) {
+    const c = window['courseware'];
+    if (!c) {
+      return;
+    }
+
+    if (!this.serverResultObj) {
+      this.serverResultObj = {};
+    }
+
+    this.serverResultObj[key] = data;
+    this.serverResultObj.duration = new Date().getTime() - this.localStartTime;
+    
+    c.sendAnswer(this.serverResultObj);
+  }
+
+
+
+
+
+
+  isUpdateStop = false;
+  update() {
+
+    if (this.isUpdateStop) {
+      return;
+    }
+    this.animationId = window.requestAnimationFrame(this.update.bind(this));
+    // 清除画布内容
+    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
+
+    TWEEN.update();
+
+
+
+    this.updateArr(this.renderArr);
+    this.updateArr(this.topArr);
+    // if (this.curMoveItem) {
+    //   this.curMoveItem.update();
+    // }
+    this.updateItem(this.maskPic);
+
+    // this.updateArr(this.endPageArr);
+
+
+  }
+
+
+
+
+
+
+
+
+
+
+  updateItem(item) {
+    if (item) {
+      item.update();
+    }
+  }
+
+  updateArr(arr) {
+    if (!arr) {
+      return;
+    }
+    for (let i = 0; i < arr.length; i++) {
+      arr[i].update(this);
+    }
+  }
+
+
+  maskBg;
+  initHotZone() {
+
+
+    let curBgRect;
+    if (this.bg) {
+      curBgRect = this.bg.getBoundingBox();
+    }
+
+    let oldBgRect = this.data.contentObj.bgItem.rect;
+    if (!oldBgRect) {
+      oldBgRect = curBgRect;
+    }
+
+    const rate = curBgRect.width / oldBgRect.width;
+
+    this.hotZoneArr = [];
+    const arr = this.data.contentObj.hotZoneItemArr;
+    if (!arr) return;
+
+    console.log('arr: ', arr);
+
+    for (let i = 0; i < arr.length; i++) {
+
+      const data = JSON.parse(JSON.stringify(arr[i]));
+
+      data.rect.x *= rate;
+      data.rect.y *= rate;
+      data.rect.width *= rate;
+      data.rect.height *= rate;
+
+      data.rect.x += curBgRect.x;
+      data.rect.y += curBgRect.y;
+
+      const hotZone = this.getHotZoneItem(data, i);
+
+      if (hotZone != null) {
+        this.hotZoneArr.push(hotZone);
+      }
+
+    }
+
+  }
+
+
+  openBtn;
+  getHotZoneItem(data, index) {
+
+    switch(data.gIdx) {
+      case "0": // 轮播图
+        return this.setOnePic(data, index);
+
+      case "1": // 选择答题区域
+        return this.setOneArea(data);
+
+      case "2": // 简答问题
+        return this.setOneShortAnswerPic(data);
+
+      case "3": // 简答题答案区
+        return this.setOneShortAnswerRect(data);
+    }
+
+    return ;
+    
+  }
+
+  setOneShortAnswerPic(data) {
+    console.log('setOneShortAnswerPic data: ', data)
+
+    const pic = new MySprite();
+    pic.init(this.images.get(data.pic_url))
+
+    const saveRect = data.rect;
+
+    pic.x = saveRect.x + saveRect.width / 2;
+    pic.y = saveRect.y + saveRect.height / 2;
+    pic['data'] = data;
+
+    const sx = saveRect.width / pic.width;
+    const sy = saveRect.height / pic.height;
+
+    pic.scaleX = sx;
+    pic.scaleY = sy;
+    pic.visible = false;
+    // this.openBtn = pic;
+    this.renderArr.push(pic);
+
+    return pic;
+  }
+
+  answerRect;
+  setOneShortAnswerRect(data) {
+    console.log('setOneShortAnswerRect data: ', data)
+
+    const saveRect = data.rect;
+
+
+
+    const item = new MySprite();
+    item.width = saveRect.width;
+    item.height = saveRect.height;
+    item.x = saveRect.x
+    item.y = saveRect.y
+    item['data'] = data;
+    item.alpha = 0;
+    item.visible = false;
+
+
+    const answerNormal = this.getMySprite('answer_normal');
+    const sx = saveRect.width / answerNormal.width;
+    const sy = saveRect.height / answerNormal.height;
+    const s = Math.min(sx, sy);
+    answerNormal.setScaleXY(s);
+    answerNormal.x = answerNormal.width / 2 * answerNormal.scaleX;
+    answerNormal.y = answerNormal.height / 2 * answerNormal.scaleY;
+    item.addChild(answerNormal);
+    item['normalPic'] = answerNormal;
+
+    const answerRight = this.getMySprite('answer_right');
+    answerRight.setScaleXY(s);
+    answerRight.x = answerRight.width / 2 * answerRight.scaleX;
+    answerRight.y = answerRight.height / 2 * answerRight.scaleY;
+    item.addChild(answerRight);
+    item['rightPic'] = answerRight;
+    answerRight.visible = false;
+
+    const answerWrong = this.getMySprite('answer_wrong');
+    answerWrong.setScaleXY(s);
+    answerWrong.x = answerWrong.width / 2 * answerWrong.scaleX;
+    answerWrong.y = answerWrong.height / 2 * answerWrong.scaleY;
+    item.addChild(answerWrong);
+    item['wrongPic'] = answerWrong;
+    answerWrong.visible = false;
+
+    this.answerRect = answerNormal;
+
+
+    
+    // const labelPic = new MySprite();
+    // labelPic.load(url).then(img => {
+
+    // });
+
+
+    
+    // item.addChild(label);
+
+
+    // const w = item.width;
+    // const h = item.height;
+    // const subH = h / 6;
+
+    // const ques = this.getMySprite(this.data.contentObj.ques_pic_url);
+    // const sx = w / ques.width;
+    // const sy = subH * 1.5 / ques.height;
+    // ques.setScaleXY(Math.min(sx, sy));
+    // ques.x = w / 2;
+    // ques.y = subH;
+    // item.addChild(ques);
+    
+    // this.optionItemArr = [];
+
+    // const optionArr = this.data.contentObj.optionArr;
+    // const baseY = subH * 2.5;
+    // for (let i=0; i<4; i++) {
+
+    //   if (optionArr[i]) {
+    //     const opItem = this.getOneOpItem(optionArr[i]);
+    //     const sx = w / opItem.width; 
+    //     const sy = subH / opItem.height;
+    //     opItem.setScaleXY(Math.min(sx, sy));
+    //     opItem.y = baseY + i * subH;
+    //     opItem.x = w / 2;
+    //     item.addChild(opItem);
+
+    //     this.optionItemArr.push(opItem);
+    //   }
+     
+    // }
+
+
+    // this.curAreaItem = item;
+    this.renderArr.push(item);
+    return item;
+  }
+
+
+  setOnePic(data, index) {
+    console.log('setOnePic data: ', data)
+    console.log('setOnePic index: ', index)
+
+    const pic = new MySprite();
+    pic.init(this.images.get(data.pic_url))
+
+    const saveRect = data.rect;
+
+    pic.x = saveRect.x + saveRect.width / 2;
+    pic.y = saveRect.y + saveRect.height / 2;
+    pic['data'] = data;
+
+    const sx = saveRect.width / pic.width;
+    const sy = saveRect.height / pic.height;
+
+    pic.scaleX = sx;
+    pic.scaleY = sy;
+    pic.visible = false;
+    // this.openBtn = pic;
+    this.renderArr.push(pic);
+
+    return pic;
+  }
+
+
+  optionItemArr;
+  curAreaItem;
+  setOneArea(data) {
+
+    console.log('setOneBtn data: ', data)
+
+    const saveRect = data.rect;
+    // const item = new ShapeRect(this.ctx);
+    // item.setSize(saveRect.width, saveRect.height);
+    // item.fillColor = '#ff0000';
+
+    const item = new MySprite();
+    item.width = saveRect.width;
+    item.height = saveRect.height;
+    item.x = saveRect.x
+    item.y = saveRect.y
+    item['data'] = data;
+    item.alpha = 0;
+    item.visible = false;
+
+
+    const w = item.width;
+    const h = item.height;
+    const subH = h / 6;
+
+    const ques = this.getMySprite(this.data.contentObj.ques_pic_url);
+    const sx = w / ques.width;
+    const sy = subH * 1.5 / ques.height;
+    ques.setScaleXY(Math.min(sx, sy));
+    ques.x = w / 2;
+    ques.y = subH;
+    item.addChild(ques);
+    
+    this.optionItemArr = [];
+
+    const optionArr = this.data.contentObj.optionArr;
+    const baseY = subH * 2.5;
+    for (let i=0; i<4; i++) {
+
+      if (optionArr[i]) {
+        const opItem = this.getOneOpItem(optionArr[i]);
+        const sx = w / opItem.width; 
+        const sy = subH / opItem.height;
+        opItem.setScaleXY(Math.min(sx, sy));
+        opItem.y = baseY + i * subH;
+        opItem.x = w / 2;
+        item.addChild(opItem);
+
+        this.optionItemArr.push(opItem);
+      }
+     
+    }
+
+
+    this.curAreaItem = item;
+    this.renderArr.push(item);
+    return item;
+  }
+
+  changeArea(data) {
+    console.log('changeArea data: ', data)
+
+    this.showSmallTitle(data);
+
+    data.rect = this.curAreaItem.data.rect;
+
+    removeItemFromArr(this.renderArr, this.curAreaItem);
+
+    
+    const saveRect = data.rect;
+    // const item = new ShapeRect(this.ctx);
+    // item.setSize(saveRect.width, saveRect.height);
+    // item.fillColor = '#ff0000';
+
+    const item = new MySprite();
+    item.width = saveRect.width;
+    item.height = saveRect.height;
+    item.x = saveRect.x
+    item.y = saveRect.y
+    item['data'] = data;
+    // item.alpha = 0;
+    // item.visible = false;
+
+
+    const w = item.width;
+    const h = item.height;
+    const subH = h / 6;
+
+    const ques = this.getMySprite(data.ques_pic_url);
+    const sx = w / ques.width;
+    const sy = subH * 1.5 / ques.height;
+    ques.setScaleXY(Math.min(sx, sy));
+    ques.x = w / 2;
+    ques.y = subH;
+    item.addChild(ques);
+    
+    this.optionItemArr = [];
+
+    const optionArr = data.optionArr;
+
+    console.log('~optionArr: ', optionArr);
+
+    const baseY = subH * 2.5;
+    for (let i=0; i<optionArr.length; i++) {
+
+      if (optionArr[i]) {
+        const opItem = this.getOneOpItem(optionArr[i]);
+        const sx = w / opItem.width; 
+        const sy = subH / opItem.height;
+        opItem.setScaleXY(Math.min(sx, sy));
+        opItem.y = baseY + i * subH;
+        opItem.x = w / 2;
+        item.addChild(opItem);
+
+        this.optionItemArr.push(opItem);
+      }
+     
+    }
+
+
+    this.curAreaItem = item;
+    this.renderArr.push(item);
+    return item;
+  }
+
+  getOneOpItem(data) {
+
+  
+    const opItem = this.getMySprite('op_item');
+    const opItemNode = this.getMySprite('op_item');
+    opItemNode.alpha = 0;
+    opItemNode.childDepandAlpha = false;
+
+    opItemNode.width = opItem.width;
+    opItemNode.height = opItem.height;
+    opItemNode.addChild(opItem);
+    opItemNode['defaultPic'] = opItem;
+    opItemNode['curRes'] = 'defaultPic';
+
+    const opItemWrong = this.getMySprite('op_item_wrong');
+    opItemNode.addChild(opItemWrong);
+    opItemWrong.visible = false;
+    opItemNode['wrongPic'] = opItemWrong;
+    const wrongIcon = this.getMySprite('icon_wrong');
+    wrongIcon.x = opItemWrong.width / 2;
+    opItemWrong.addChild(wrongIcon)
+
+
+    const opItemRight = this.getMySprite('op_item_right');
+    opItemNode.addChild(opItemRight);
+    opItemRight.visible = false;
+    opItemNode['rightPic'] = opItemRight;
+    const rightIcon = this.getMySprite('icon_right');
+    rightIcon.x = opItemRight.width / 2;
+    opItemRight.addChild(rightIcon)
+
+    const opItemNormal = this.getMySprite('op_item_normal');
+    opItemNode.addChild(opItemNormal);
+    opItemNormal.visible = false;
+    opItemNode['normalPic'] = opItemNormal;
+
+    const label = new Label();
+    label.text = data.id;
+    label.fontColor = '#ffffff';
+    label.x = -opItemNode.width / 2 + 50;
+    opItemNode.addChild(label);
+
+
+    const subX = 150;
+    const opPic = this.getMySprite(data.pic_url);
+    const sx = (opItemNode.width - subX) / opPic.width;
+    const sy = (opItemNode.height - 20) / opPic.height;
+    opPic.setScaleXY(Math.min(sx, sy));
+    opPic.anchorX = 0;
+    opPic.x = - opItemNode.width / 2 + subX;
+    opItemNode.addChild(opPic);
+
+    console.log('op data: ', data);
+
+    opItemNode['data'] = data;
+    return opItemNode;
+  }
+
+  
+
+  setOneRect(data, index) {
+
+    const saveRect = data.rect;
+    const item = new ShapeRectNew(this.ctx);
+    item.setSize(saveRect.width, saveRect.height, 5);
+    item.setOutLine('#dcc077', 2);
+    item.fill = false;
+    item.x = saveRect.x
+    item.y = saveRect.y
+    // item.setScaleXY(this.bg.scaleX);
+    item.fillColor = '#dcc077';
+    item['data'] = data;
+    // item.alpha = 0;
+    this.scrollView.addItem(item);
+
+
+    const rectLabel = this.getFillLabel();
+    rectLabel.topH = 8;
+    rectLabel.fontSize = this.inputFontSize;
+    rectLabel.disH = 0;
+    rectLabel.offW = 0;
+    rectLabel.textBaseline = 'top'
+    rectLabel.width = item.width;
+    rectLabel.fontSize *= (this.bg.scaleX);
+    // targetRect.label.x = ( targetRect.width / 2  ) 
+    item['label'] = rectLabel;
+    item.addChild(item['label']);
+
+    this.addLabelMask(item, rectLabel);
+
+    rectLabel.text = 'Please Enter ...';
+    rectLabel.fontColor = '#cccccc'
+    rectLabel['isDefault'] = true;
+
+
+
+    // const subX = data.rect.x - this.bg.data.rect.x;
+    // const subY = data.rect.y - this.bg.data.rect.y;
+
+    // item.x = subX * this.mapScale //- this.bg.width / 2;
+    // item.y = subY * this.mapScale //- this.bg.height / 2;
+    // // item.y -= this.bg.height / 2;
+    // this.bg.addChild(item);
+
+
+    const circle = new ShapeCircle();
+    circle.setRadius(20 * this.bg.scaleX);
+    circle.x = item.x + item.width;
+    circle.y = item.y;
+    circle.fillColor = '#dcc077'
+    this.scrollView.addItem(circle);
+
+    const label = new Label();
+    label.text = index + 1;
+    label.fontColor = '#ffffff';
+    label.textAlign = 'center';
+    label.fontSize = 28 
+    label.fontName = 'Aileron-Bold';
+    label.y = 2;
+    circle.addChild(label);
+
+    // this.renderArr.push(item);
+
+    return item;
+  }
 
-      // 初始化 音频资源
-      this.initAudio();
-      // 初始化 图片资源
-      this.initImg();
-      // 开始预加载资源
-      this.load();
 
-    }, this.saveKey);
 
+  showEndPetal() {
+    this.endPageArr = [];
+    this.showPetalFlag = true;
+    this.addPetal();
   }
 
-  ngOnDestroy() {
-    window['curCtx'] = null;
-    window.cancelAnimationFrame(this.animationId);
-    this.cleanAudio();
+  stopEndPetal() {
+    this.endPageArr = [];
+    this.showPetalFlag = false;
   }
 
+  addPetal() {
 
-  cleanAudio() {
-    if (this.audioObj) {
-      for (const key in this.audioObj) {
-        this.audioObj[key].pause();
-      }
+    if (!this.showPetalFlag) {
+      return;
     }
-  }
 
+    const petal = this.getPetal();
+    this.endPageArr.push(petal);
 
-  load() {
-
-    // 预加载资源
-    this.loadResources().then(() => {
-      window["air"].hideAirClassLoading(this.saveKey, this.data);
-      this.init();
-      this.update();
+    moveItem(petal, petal.x, this.canvasHeight + petal.height * petal.scaleY, petal['time'], () => {
+      removeItemFromArr(this.endPageArr, petal);
     });
-  }
 
+    rotateItem(petal, petal['tr'], petal['time']);
 
-  init() {
+    setTimeout(() => {
+      this.addPetal();
+    }, 100);
 
-    this.initCtx();
-    this.initData();
-    this.initView();
   }
 
-  initCtx() {
-    this.canvasWidth = this.wrap.nativeElement.clientWidth;
-    this.canvasHeight = this.wrap.nativeElement.clientHeight;
-    this.canvas.nativeElement.width = this.wrap.nativeElement.clientWidth;
-    this.canvas.nativeElement.height = this.wrap.nativeElement.clientHeight;
-
 
-    this.ctx = this.canvas.nativeElement.getContext('2d');
-    this.canvas.nativeElement.width = this.canvasWidth;
-    this.canvas.nativeElement.height = this.canvasHeight;
+  getPetal() {
 
-    window['curCtx'] = this.ctx;
-    window['curImages'] = this.images;
-  }
+    const petal = new MySprite(this.ctx);
 
+    const id = Math.ceil(Math.random() * 3);
+    petal.init(this.images.get('petal_' + id));
 
+    const randomS = (Math.random() * 0.4 + 0.6) * this.mapScale;
+    petal.setScaleXY(randomS);
 
+    const randomR = Math.random() * 360;
+    petal.rotation = randomR;
 
+    const randomX = Math.random() * this.canvasWidth;
+    petal.x = randomX;
+    petal.y = -petal.height / 2 * petal.scaleY;
 
+    const randomT = 0.5 + Math.random() * 2.5;
+    petal['time'] = randomT;
 
-  updateItem(item) {
-    if (item) {
-      item.update();
-    }
-  }
+    let randomTR = 360 * Math.random(); // - 180;
+    if (Math.random() < 0.5) { randomTR *= -1; }
+    petal['tr'] = randomTR;
 
-  updateArr(arr) {
-    if (!arr) {
-      return;
-    }
-    for (let i = 0; i < arr.length; i++) {
-      arr[i].update(this);
-    }
+    return petal;
   }
 
 
 
 
 
-
-
   initListener() {
 
     this.winResizeEventStream
@@ -202,13 +3843,13 @@ export class PlayComponent implements OnInit, OnDestroy {
       if (this.canvasLeft == null) {
         setParentOffset();
       }
-      this.mx = event.touches[0].pageX - this.canvasLeft;
-      this.my = event.touches[0].pageY - this.canvasTop;
+      this.mx = event.touches[0].pageX * this.canvasScale  - this.canvasLeft;
+      this.my = event.touches[0].pageY * this.canvasScale - this.canvasTop;
     };
 
     const setMxMyByMouse = (event) => {
-      this.mx = event.offsetX;
-      this.my = event.offsetY;
+      this.mx = event.offsetX * this.canvasScale;
+      this.my = event.offsetY * this.canvasScale;
     };
     // ---------------------------------------------
 
@@ -220,6 +3861,7 @@ export class PlayComponent implements OnInit, OnDestroy {
         firstTouch = false;
         removeMouseListener();
       }
+      console.log('touch down');
       setMxMyByTouch(e);
       this.mapDown(e);
     };
@@ -237,6 +3879,7 @@ export class PlayComponent implements OnInit, OnDestroy {
         firstTouch = false;
         removeTouchListener();
       }
+      console.log('mouse down');
       setMxMyByMouse(e);
       this.mapDown(e);
     };
@@ -278,13 +3921,56 @@ export class PlayComponent implements OnInit, OnDestroy {
 
     addMouseListener();
     addTouchListener();
+
+    // element.addEventListener('mousewheel', (event) => {
+    //   setMxMyByMouse(event);
+    //   if (event.deltaY > 0) {
+    //     this.wheelDown(event);
+    //   } else {
+    //     this.wheelUp(event);
+    //   }
+    // });
+  }
+
+
+
+  wheelUp(event: any) {
+    if (this.resultSv) {
+      if (this.checkClickTarget(this.resultSv)) {
+        this.resultSv.onWheelUp(event.deltaY);
+      }
+    } else {
+      if (this.checkClickTarget(this.scrollView)) {
+        this.scrollView.onWheelUp(event.deltaY);
+      }
+    }
+ 
   }
 
 
+  wheelDown(event: any) {
+    if (this.resultSv) {
+      if (this.checkClickTarget(this.resultSv)) {
+        this.resultSv.onWheelDown(event.deltaY);
+      }
+    } else {
+      if (this.checkClickTarget(this.scrollView)) {
+        this.scrollView.onWheelDown(event.deltaY);
+      }
+    }
+
+  }
+
   playAudio(key, now = false, callback = null) {
 
     const audio = this.audioObj[key];
     if (audio) {
+
+      const audioNew = new Audio();
+      audioNew.src = audio.src;
+      audioNew.load();
+      // audioNew.play();
+
       if (now) {
         audio.pause();
         audio.currentTime = 0;
@@ -301,12 +3987,51 @@ export class PlayComponent implements OnInit, OnDestroy {
 
 
 
+
+  showArr(arr) {
+    if (!arr) {
+      return;
+    }
+    for (let i = 0; i < arr.length; i++) {
+      arr[i].visible = true;
+    }
+  }
+
+  hideArr(arr) {
+    if (!arr) {
+      return;
+    }
+    for (let i = 0; i < arr.length; i++) {
+      arr[i].visible = false;
+    }
+  }
+
+
+
+  IsPC() {
+
+    if (window['ELECTRON']) {
+      return false; // 封装客户端标记
+    }
+
+
+    if (document.body.ontouchmove !== undefined && document.body.ontouchmove !== undefined) {
+      return false;
+    } else {
+      return true;
+    }
+
+  }
+
+
   loadResources() {
     const pr = [];
     this.rawImages.forEach((value, key) => {// 预加载图片
 
       const p = this.preload(value)
         .then(img => {
+          // img['setAttribute']("crossOrigin",'Anonymous')
+
           this.images.set(key, img);
         })
         .catch(err => console.log(err));
@@ -314,7 +4039,7 @@ export class PlayComponent implements OnInit, OnDestroy {
       pr.push(p);
     });
 
-    this.rawAudios.forEach((value, key) => {// 预加载音频
+    this.rawAudios.forEach((value, key) => {// 预加载图片
 
       const a = this.preloadAudio(value)
         .then(() => {
@@ -330,6 +4055,7 @@ export class PlayComponent implements OnInit, OnDestroy {
   preload(url) {
     return new Promise((resolve, reject) => {
       const img = new Image();
+      img['crossOrigin'] = "Anonymous";
       // img.crossOrigin = "anonymous";
       img.onload = () => resolve(img);
       img.onerror = reject;
@@ -340,8 +4066,9 @@ export class PlayComponent implements OnInit, OnDestroy {
   preloadAudio(url) {
     return new Promise((resolve, reject) => {
       const audio = new Audio();
+      resolve({})
       audio.oncanplay = (a) => {
-        resolve();
+        //resolve();
       };
       audio.onerror = () => {
         reject();
@@ -358,14 +4085,60 @@ export class PlayComponent implements OnInit, OnDestroy {
     this.init();
   }
 
+  checkClickTargetSv(target) {
+
+    // console.log('target: ', target);
+
+    const scale = 1 // this.mapScale;
+
+    let tmpRect = target.getBoundingBox();
+
+    const svBox = this.scrollView.getBoundingBox();
+
+   
+    const x = svBox.x +  tmpRect.x * scale;
+    const y = svBox.y + tmpRect.y * scale;
+    const width = tmpRect.width * scale;
+    const height = tmpRect.height * scale;
+    const rect = {x, y, width, height};
+    
+    
+    // const contentBox = this.scrollView.content.getBoundingBox();
+
+    // rect.x += contentBox.x + svBox.x;
+    // rect.y += contentBox.y + svBox.y;
+
+    // console.log('rect: ', rect);
+    // console.log('this.mx: ', this.mx);
+    // console.log('this.my: ', this.my);
 
+    // const shape = new ShapeRectNew();
+    // shape.x = rect.x;
+    // shape.y = rect.y;
+    // shape.fillColor = '#0000ff'
+    // shape.alpha = 0.5;
+    // shape.setSize(rect.width, rect.height, 1);
+    // this.renderArr.push(shape);
 
 
+    if (this.checkPointInRect(this.mx, this.my, rect)) {
+      return true;
+    }
+    return false;
+  }
 
   checkClickTarget(target) {
 
     const rect = target.getBoundingBox();
 
+    // rect.x /= 2;
+    // rect.y /= 2;
+    // rect.width /= 2;
+    // rect.height /= 2;
+
+
+    // console.log('rect: ', rect);
+
     if (this.checkPointInRect(this.mx, this.my, rect)) {
       return true;
     }
@@ -386,6 +4159,7 @@ export class PlayComponent implements OnInit, OnDestroy {
   }
 
   checkPointInRect(x, y, rect) {
+
     if (x >= rect.x && x <= rect.x + rect.width) {
       if (y >= rect.y && y <= rect.y + rect.height) {
         return true;
@@ -394,297 +4168,96 @@ export class PlayComponent implements OnInit, OnDestroy {
     return false;
   }
 
+  getPosByAngle(angle, len) {
 
+    const radian = angle * Math.PI / 180;
+    const x = Math.sin(radian) * len;
+    const y = Math.cos(radian) * len;
 
+    return { x, y };
 
-
-  addUrlToAudioObj(key, url = null, vlomue = 1, loop = false, callback = null) {
-
-    const audioObj = this.audioObj;
-
-    if (url == null) {
-      url = key;
-    }
-
-    this.rawAudios.set(key, url);
-
-    const audio = new Audio();
-    audio.src = url;
-    audio.load();
-    audio.loop = loop;
-    audio.volume = vlomue;
-
-    audioObj[key] = audio;
-
-    if (callback) {
-      audio.onended = () => {
-        callback();
-      };
-    }
-  }
-
-  addUrlToImages(url) {
-    this.rawImages.set(url, url);
-  }
-
-
-
-
-
-
-  // ======================================================编写区域==========================================================================
-
-
-
-
-
-  /**
-   * 添加默认数据 便于无数据时的展示
-   */
-  initDefaultData() {
-
-    if (!this.data.pic_url) {
-      this.data.pic_url = 'assets/play/default/pic.jpg';
-      this.data.pic_url_2 = 'assets/play/default/pic.jpg';
-    }
-  }
-
-
-  /**
-   * 添加预加载图片
-   */
-  initImg() {
-
-    this.addUrlToImages(this.data.pic_url);
-    this.addUrlToImages(this.data.pic_url_2);
-
-  }
-
-  /**
-   * 添加预加载音频
-   */
-  initAudio() {
-
-    // 音频资源
-    this.addUrlToAudioObj(this.data.audio_url);
-    this.addUrlToAudioObj(this.data.audio_url_2);
-
-    // 音效
-    this.addUrlToAudioObj('click', this.rawAudios.get('click'), 0.3);
-
-  }
-
-
-
-  /**
-   * 初始化数据
-   */
-  initData() {
-
-    const sx = this.canvasWidth / this.canvasBaseW;
-    const sy = this.canvasHeight / this.canvasBaseH;
-    const s = Math.min(sx, sy);
-    this.mapScale = s;
-
-    // this.mapScale = sx;
-    // this.mapScale = sy;
-
-
-    this.renderArr = [];
-
-
-
-  }
-
-
-
-  /**
-   * 初始化试图
-   */
-  initView() {
-
-
-    this.initPic();
-
-    this.initBottomPart();
-
-  }
-
-  initBottomPart() {
-
-    const btnLeft = new MySprite();
-    btnLeft.init(this.images.get('btn_left'));
-    btnLeft.x = this.canvasWidth - 150 * this.mapScale;
-    btnLeft.y = this.canvasHeight - 100 * this.mapScale;
-
-    btnLeft.setScaleXY(this.mapScale);
-
-    this.renderArr.push(btnLeft);
-
-    this.btnLeft = btnLeft;
-
-
-
-    const btnRight = new MySprite();
-    btnRight.init(this.images.get('btn_right'));
-    btnRight.x = this.canvasWidth - 50 * this.mapScale;
-    btnRight.y = this.canvasHeight - 100 * this.mapScale;
-    btnRight.setScaleXY(this.mapScale);
-
-    this.renderArr.push(btnRight);
-
-    this.btnRight = btnRight;
-  }
-
-  initPic() {
-
-    const maxW = this.canvasWidth * 0.7;
-
-    const pic1 = new MySprite();
-    pic1.init(this.images.get(this.data.pic_url));
-    pic1.x = this.canvasWidth / 2;
-    pic1.y = this.canvasHeight / 2;
-    pic1.setScaleXY(maxW / pic1.width);
-
-    this.renderArr.push(pic1);
-    this.pic1 = pic1;
-
-
-    const label1 = new Label();
-    label1.text = this.data.text;
-    label1.textAlign = 'center';
-    label1.fontSize = 50;
-    label1.fontName = 'BRLNSDB';
-    label1.fontColor = '#ffffff';
-
-    pic1.addChild(label1);
-
-
-
-
-
-    const pic2 = new MySprite();
-    pic2.init(this.images.get(this.data.pic_url_2));
-    pic2.x = this.canvasWidth / 2 + this.canvasWidth;
-    pic2.y = this.canvasHeight / 2;
-    pic2.setScaleXY(maxW / pic2.width);
-
-    this.renderArr.push(pic2);
-    this.pic2 = pic2;
-
-    this.curPic = pic1;
-  }
-
-
-  btnLeftClicked() {
-
-    this.lastPage();
-  }
-
-  btnRightClicked() {
-
-    this.nextPage();
   }
 
-  lastPage() {
-
-    if (this.curPic == this.pic1) {
-      return;
-    }
-
-    this.canTouch = false;
+  getPosDistance(sx, sy, ex, ey) {
 
-    const moveLen = this.canvasWidth;
-    tweenChange(this.pic1, {x: this.pic1.x + moveLen}, 1);
-    tweenChange(this.pic2, {x: this.pic2.x + moveLen}, 1, () => {
-      this.canTouch = true;
-      this.curPic = this.pic1;
-    });
+    const _x = ex - sx;
+    const _y = ey - sy;
+    const len = Math.sqrt(Math.pow(_x, 2) + Math.pow(_y, 2));
+    return len;
   }
 
-  nextPage() {
+  b64ToUint8Array(b64Image) {
 
-    if (this.curPic == this.pic2) {
-      return;
+    var img = atob(b64Image.split(',')[1]);
+    var img_buffer = [];
+    var i = 0;
+    
+    while (i < img.length) {
+      img_buffer.push(img.charCodeAt(i));
+      i++;
     }
-
-    this.canTouch = false;
-
-    const moveLen = this.canvasWidth;
-    tweenChange(this.pic1, {x: this.pic1.x - moveLen}, 1);
-    tweenChange(this.pic2, {x: this.pic2.x - moveLen}, 1, () => {
-      this.canTouch = true;
-      this.curPic = this.pic2;
-    });
-  }
-
-  pic1Clicked() {
-    this.playAudio(this.data.audio_url);
-  }
-
-  pic2Clicked() {
-    this.playAudio(this.data.audio_url_2);
+    
+    return new Uint8Array(img_buffer);
   }
 
+  convertCanvasToImage() {
 
+    console.log('1');
 
-
-
-  mapDown(event) {
-
-    if (!this.canTouch) {
-      return;
-    }
-
-    if ( this.checkClickTarget(this.btnLeft) ) {
-      this.btnLeftClicked();
-      return;
-    }
-
-    if ( this.checkClickTarget(this.btnRight) ) {
-      this.btnRightClicked();
-      return;
+    if (!this.uploadUrl) {
+      // this.userResultPic = ''//true
+      this.sendResult();
+      return
     }
+    console.log('2 uploadUrl: ', this.uploadUrl.toString());
 
-    if ( this.checkClickTarget(this.pic1) ) {
-      this.pic1Clicked();
-      return;
-    }
+    const src = this.canvas.nativeElement.toDataURL("image/png");
 
-    if ( this.checkClickTarget(this.pic2) ) {
-      this.pic2Clicked();
-      return;
+    const arr = src.split(','),
+    mime = arr[0].match(/:(.*?);/)[1],
+    bstr = atob(arr[1]);
+    let n = bstr.length;
+    const u8arr = new Uint8Array(n);
+    while ( n -- ) {
+      u8arr[n] = bstr.charCodeAt(n);
     }
+    const obj = new Blob([u8arr], {type: mime});
+    const fd = new FormData();
+    fd.append("file", obj, "cw_cover.png");
+
+    // 上传截图
+    this.http.post(this.uploadUrl, fd).subscribe((res) => {
+      // 存储服务器的截图url
+      if (res['url']) {
+        console.log('res url: ', res['url']);
+        this.userResultPic = res['url'];
+        this.sendResult();
+      }
+    });
 
-  }
-
-  mapMove(event) {
-
-  }
-
-  mapUp(event) {
 
   }
 
+  httpServer(type, url, options, cb) {
 
+    this.http[type](url, options)
+      .subscribe( val =>  {
 
-  update() {
-
-    // ----------------------------------------------------------
-    this.animationId = window.requestAnimationFrame(this.update.bind(this));
-    // 清除画布内容
-    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
-    // tween 更新动画
-    TWEEN.update();
-    // ----------------------------------------------------------
-
+        console.log("Post call successful value returned in body", val);
 
+        cb(val);
+      
+      }, error => {
+        console.log("Post call in error", error);
+        cb(error);
 
-    this.updateArr(this.renderArr);
+      }, () => {
+        console.log("The Post observable is now completed.");
+        cb(null);
 
+      }
+    );
 
   }
 
-
-
 }
diff --git a/src/app/play/resources.js b/src/app/play/resources.js
index d9b584c92bc24d0c04374cb1568478fdbefba9c7..2ed46fa351f0adac209643f369c8c4f86ef2ae38 100644
--- a/src/app/play/resources.js
+++ b/src/app/play/resources.js
@@ -1,9 +1,21 @@
 const res = [
 
-  // ['bg', "assets/play/bg.jpg"],
-  ['btn_left', "assets/play/btn_left.png"],
-  ['btn_right', "assets/play/btn_right.png"],
-  // ['text_bg', "assets/play/text_bg.png"],
+  ['op_item', "assets/play/op_item.png"],
+  ['op_item_wrong', "assets/play/op_item_wrong.png"],
+  ['op_item_right', "assets/play/op_item_right.png"],
+  ['op_item_normal', "assets/play/op_item_normal.png"],
+  ['btn_close', "assets/play/btn_close.png"],
+  ['bg', "assets/play/bg.jpg"],
+  ['video_bg', "assets/play/video_bg.jpg"],
+  ['btn_replay', "assets/play/btn_replay.png"],
+
+  ['icon_right', "assets/play/icon_right.png"],
+  ['icon_wrong', "assets/play/icon_wrong.png"],
+
+  ['answer_normal', "assets/play/answer_normal.png"],
+  ['answer_right', "assets/play/answer_right.png"],
+  ['answer_wrong', "assets/play/answer_wrong.png"],
+
 
 ];
 
@@ -12,7 +24,14 @@ const res = [
 const resAudio = [
 
   ['click', "assets/play/music/click.mp3"],
+  ['submit', "assets/play/music/submit.mp3"],
+  ['more', "assets/play/music/more.mp3"],
+
+
 
 ];
 
 export {res, resAudio};
+
+
+
diff --git a/src/app/play/words.ts b/src/app/play/words.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b28d508982d6244193a2e96c360af5573190c82
--- /dev/null
+++ b/src/app/play/words.ts
@@ -0,0 +1,18 @@
+export const data = {
+  "device": ["vacuum cleaner", "electric fan", "air conditioner", "hair dryerstyler", "electric cooker", "desk lamp", "electric shaver", "micro wave oven", "frige", "washing machine", "electric heater", "fluorescent lamp", "dictaphone, dictating machine", "tape recorder", "television", "electric iron", "electric foot warmer", "electric vacuum cleaner", "bulb", "electronic oven", "radio", "loud-speaker", "refrigerator", "air conditioning", "microwave oven", "dry cell", "tap", "broiler", "defroster", "dicer", "dishwasher", "dryer", "eggbeater", "fan", "air-condition", "vertical disinfection cabinetffice", "horizontal disinfection cabinet", "pasteurize cupboard", "disinfectant tank", "household gas stove", "smoke exhauster", "air exhauster", "soya-bean milk grinder", "soya-bean milking", "food blender", "juice extractor", "filter purifier", "water dispenser", "can opener", "compactor", "freezer", "furnace", "humidifier", "iron", "juicer", "oven ", "percolator", "range hood", "rotisserie", "shaver", "stove", "toaster", "absorber", "ac accumulator", "ac azinuth comprator", "active loudspeaker", "acu automatic calling unit", "electric rice cooker", "electric kettle", "coffee maker", "blender", "toast oven", "exhaust fan", "disinfecting cabinet", "towel dispenser", "lcd tv", "electric fireplace", "wine cabinet", "cool freezer", "massager", "hair dryer", "electric toolbrush", "depilation equipment", "hair crimper", "compactordefroster", "lithium battery/cell", "lcd(liquid crystal displaytv", "electric fireplaceelectric fan", "electric toolbrushelectric iron", "tv", "computer", "drive", "monitor", "speaker", "air-conditioning", "flashlight", "calculator", "electric light", "electric calculator", "tube", "dictaphone,dictating machine", "electric shaverelectric cooker electric heaterelectric vacuum cleanerbulb", "microphone", "air conditioning microwave ovendry cell", "airconditioning", "electricfootwarmer", "electricshaver", "electriccooker", "electricheater", "electricvacuumcleaner", "electronicoven", "loudspeaker", "microwaveoven", "drycell", "boiler", "aircondition", "householdgasstove", "smokeexhauster", "airexhauster", "soyabeanmilkgrinder", "foodblender", "juiceextractor", "filterpurifier", "waterdispenser", "oven", "rangehood", "vacuumcleaner", "electriccalculator", "taperecorder", "projection lamp", "large screen splicing", "professional display", "industry display", "plasma panel display", "digital signage", "led display", "touch one machine", "touch screen", "recreational machine", "psp nds handheld game machine", "recreational machine ", "laser printer", "bubble jet printer", "multifunctional integrated machine", "large format printer", "scanner", "the fax machine", "copier", "copying machine", "dye sublimation printer", "projection booth", "projection screen", "electronic whiteboard", "automobile electric productsthe", "education electric products", "cd", "camera", "digital video (dv)", "camera lens", "mp3", "mp4", "u disk", "e-book", "electric book", "digital voice recorder", "earphone", "electric dictionary", "digital phone frameusb", "repeater", "point reading machine", "multimedia hard disk recoding", "portable source", "digital companion", "pick-up head", "desktop computer", "all-in-one desktops", "workstation", "jotter", "notebook", "netbooks", "iphone", "ipad", "panel computer", "computer components", "mouse", "liquid crystal display", "loudspeaker box", "voice box", "cell phone", "mobile phone", "phone", "gps", "walkie-talkie", "intercom", "telephone", "teleconference", "dial conference", "conference call", "do mobile phone", "electric toothbrush", "bath heater", "soap dispenser", "television set", "washer", "mosquito killer", "electric mosquito swatter", "electric mosquito repellent", "electro-thermal mosquito-repellent incense", "gas water heater", "electric water heater", "solar heater", "air source heat gong water heater", "water heater", "bracker fan", "desk fan", "floor fan", "ceiling fan", "wall fan", "air conditioning fan", "warm fan", "hand warmer", "heat insulation plate", "insulating pad", "insulation chests", "incubator", "hair removal device", "depilator", "shaving device", "electric eyebrow shaving device ", "electric eyelash curler", "beauty device", "crimping iron", "curler", "straighener", "pore cleaner", "nose hair trimmer", "electric manicure", "eyelash pen", "heating apparatus", "electric blanket", "foot bath", "ear cleaner", "hair cap", "electric massager", "barber", "the fat meter", "deaf-aid", "hemomanometer", "sphy gmomanometer", "pedometer", "bathroom scale", "weight balance", "weight scale", "glucometer", "moisture extractor", "humidity regulator", "perfuming machine", "perfuming device", "steam clean machine", "electronic trash", "air purifier", "air-cleaner", "water purifying machine", "vacuum cleaner,", "sweeping machine", "oxygen machine", "oxygen bar", "bread machine", "egg boiler", "steamed egg", "sandwich machine", "electric baking pan", "soybean milk machine", "ice cream machine", "coffee machine", "popcorn machine", "mill", "winepress,juicer", "fruit and vegetable cleaning machine", "clothes drier", "(electric) kettle", "electric cup", "fridge", "shoes dryer", "shoe polisher", "hand dryer", "electronic thermometer", "electronic timer", "reminder", "hair ball trim", "spin drier", "dehydrator", "agitator", "amalgamator", "cooking machine", "dish-washing machine", "food waste processor", "disinfection cabinet", "electric oven", "cheese furnace", "decoction vessel", "block shaving machine", "ice crusher", "fry ice machine", "hotplate,gas cooker", "casserole", "electromagnetic oven", "induction cooker", "electric (rice) cooker", "electric pressure cooker", "electric chafing dish", "the convection oven", "electric frying pan", "electric steamer", "the soup pot", "slow cooker ", "juicer ", "toaster ", "grill/barbeque ", "micro oven ", "toaster over", "coffer maker", "kettle", "ice cream maker", "break maker", "pasta maker", "mixer/blader", "food process", "deep fryer", "dishwasher ", "water pump", "compressed water pump", "Refrigerators", "freezers", "wine cabinets", "ice makers", "air conditioners", "washing machines", "dryers", "televisions", "home theaters", "projectors", "DVD players", "combination audio", "recorders", "tablets", "video game consoles", "Walkmans", "PDAs", "electronic dictionaries", "learning machines", "mobile memory", "digital cameras Electric paper books", "walkie-talkies", "car refrigerators", "mahjong machines", "fumes", "cooktops", "dishwashers", "disinfection cupboards", "gas water heaters", "electric hot water taps", "electric water heaters", "heat pump water heaters", "body cleaners", "hair dryers", "bathers", "dry mobile phones", "integrated ceiling", "soy milk machine", "rice cooker", "electric cake", "electric stew pot", "noodle machine", "yogurt machine", "fruit and vegetable detoxifier Egg-boiling machine", "egg-laying machine", "bean sprout machine", "oil press", "kitchen treasure", "garbage disposal machine", "solar water heater", "solar lawn lamp", "courtyard lamp", "solar toys", "solar panels", "0401 water treatment appliances: water dispensers", "water purifiers", "pure water machines", "soft water machines", "pipeline machines", "straight drinker", "defying vacuum cleaner", "steam mop", "sweeper", "floor waxing machine", "ironing machine", "shoe shine machine", "electric mosquito racket", "toilet deodorant", "cold fan", "dehumidifier", "negative oxygen ion occurrence devices", "small oxygen generators", "electric heaters", "electric blankets", "shavers", "curlers", "electric toothbrushes", "beauty devices", "electric breast pumps", "foot baths", "massage chairs", "massagers", "blood pressure meters", "thermometers", "body fat meters", "smart home systems", "Smart lighting control smart home central control system", "smart home ecological control", "smart shade", "smart door and window control", "smart socket", "security", "monitoring", "access control", "visual intercom", "set-top box", "smart switch", "information appliances", "home network system", "system integration product communication network and component sensor controller", "smart bracelet", "smart watch", "smart toys", "smart drop device", "router", "electrodrill", "electric drill", "electrical drill", "electron telescope", "electronic telescope"],
+  "side": [
+    "east",
+    "south",
+    "west",
+    "north",
+    "northeast",
+    "northwest",
+    "southeast",
+    "southwest",
+    "middle"
+  ],
+  "work": ["Social workers", "personnelmanagers", "translators", "human mediators", "librarians", "journalists", "newspaper and magazine editors", "screenwriters", "book editors", "stone carvers", "carpenters", "cartoonists", "dancers", "instrument players", "construction and engineering management", "steel structure design and management personnel", "microcomputers", "electronic newspaper/e-magazine editors", "information managers", "kindergarten education teachers", "chain store managers", "marketing planning", "telecommunications engineers", "web designers", "Construction machinery operators", "electrical engineers", "mechanical cartographers", "architectural cartographers", "electronic processing data system operators", "photography staff", "merchant ship staff", "civil aviation transport pilots", "turbine personnel", "flight controllers", "dietitians", "eyewear professionals", "medical records managers", "insurance support personnel", "salesmen", "securities dealers", "sales agents", "real estate agents", "procurement personnel", "import and export clerks", "futures brokers", "accountants", "commercial art designers", "interior designers", "TV presenter", "advertising AE", "international trade practitioner", "international trade English clerk", "Chinese typist", "secretary of affairs", "information login", "assistant accountant", "quality control assistant", "warehouse manager", "production planning assistant", "distribution staff", "postman", "postal staff", "customs clearance Staff", "guest management", "bank clerks", "financial tellers", "foreign exchange traders", "travel agents", "tour guides", "flight attendants", "cultural relics narrator", "chef", "bartender", "Western food chef", "meal attendant", "nanny", "beauty barber", "beautician", "preservation Staff", "police", "fire workers", "fashion models", "merchandise salesmen", "pet beauticians", "horticultural crop growers", "nursery workers", "florists", "builders", "plumbers", "industrial plumbers", "industrial wiring operators", "indoor wiring workers", "electricians", "Sanitary plumbers", "magnetic brick pavers", "wiring workers", "painters", "automotive board goldworkers", "welders", "general board goldworkers", "special welders", "foundries", "metal mold makers", "fitters", "car repairmen", "automotive electricians", "Transaction machine repairer", "heavy mechanical repairman", "car chassis worker", "precision grinder", "milling machine worker", "numerical control lathe operator", "refrigeration air conditioner", "refrigeration air conditioner decorator", "audio-visual electronics worker", "industrial electronic worker", "bread baker", "motor repairer", "electrical repair Workers", "electronic instrument repairers", "jewelry and precious metals manufacturers", "precision gage manufacturers", "instrument tuners", "precision machinery", "ceramics workers", "printers", "printing design and plate makers", "bakers", "food and beverage technicians", "carpentry", "furniture carpentry", "Sewing", "weaver", "suit worker", "national clothing seamstress", "shoemaker", "garment design and production staff", "women's fitter", "garment worker", "heat treatment worker", "metal modeling", "metal surface treatment worker", "plate maker", "cold work", "planer", "metal plating Workers", "plastic mold manufacturers", "plastic products manufacturers", "rubber products manufacturers", "paper products manufacturers", "photo plate makers", "dairy manufacturers", "electronic assembly personnel", "automobile drivers", "fishing boat crews", "lathe workers", "oil and gas pressure automatic control personnel", "cleaning service personnel", "professional athletes teachers worker", "Teachers", "workers", "actors", "cooks", "doctors", "nurses", "drivers", "military personnel", "lawyers", "businessmen", "shop assistants", "cashiers", "writers", "models", "singers", "tailors", "judges", "security guards", "florists Waiter", "Cleaning workers", "architects", "hairdressers", "buyers", "designers", "firefighters", "mechanics", "magicians", "postmen", "lifeguards", "athletes", "engineers", "pilots", "administrators", "brokers", "auditors", "Cartoonist", "gardener", "scientist", "host", "make-up artist", "music festival", "artist", "pastry chef", "dessertr", "athlete", "diplomat", "dancer", "archery", "actor", "skater", "piano player", "guzheng hand", "designer", "bar owner", "CEO", "Amusement park owner", "captain", "journalist", "racer", "lawyer", "barber", "taekwondo coach", "veterinarian", "special police", "masseuse Java software engineer", "ERP software development engineer", "WAP development engineer", "software test engineer", "document engineer", "Internet software development engineer", "search engine engineer", "game development engineer", "web designer", "network engineer", "network administrator", "website editor", "embedded software development engineer", "electronic technology research and development engineer", "electronic engineer", "communication technology engineer", "mobile communication engineer", "3G software engineer", "mobile phone application development engineer", "urban planning designer", "civil engineer", "water supply and drainage engineer", "HVAC engineer", "engineering budgeter", "contract engineer", "property management specialist", "product process engineer", "Quality Management Specialist", "Supply Chain Specialist", "Automotive Electronics Engineer", "Automotive Technical Support Engineer", "Electrical Development Engineer", "Digital Product Development Engineer", "Mold Engineer", "Mechanical Engineer", "Pre-Sales Technical Engineer", "Aftermarket Technical Engineer", "Bioengineering Technician", "Chemical Technology Engineer", "Materials Engineer", "Water Treatment Engineer", "Strong Electrical Engineer", "Weak Electrical Engineer", "Electrical Engineer", "Automation Engineer", "Electrical and Mechanical Engineer", "Mining Engineer", "Geoengineer", "Physician", "Surgeon", "Pharmacist", "Pharmaceutical technology developer", "drug registrar", "bench worker", "drawing worker", "field worker", "forestry worker", "lathe worker", "lifting worker", "harbor worker", "iron worker", "Technical Worker", "accountant", "actress", "airlinerepresentative", "anchor", "announcer", "architect", "associateprofessor", "astronaut.", "attendant", "auditor", "automechanic", "baker", "baseballplayer", "bellboy", "bellhop", "binman", "blacksmith", "boxer", "broker(agent)", "budgeteer", "busdriver", "butcher", "buyer", "carpenter", "cashier", "chemist", "clerk", "clown", "cobbler", "computerprogrammer", "constructionworker", "cook", "cowboy", "customsofficer", "dentist", "deskclerk", "detective", "doctor", "door-to-doorsalesman", "driver", "dustman", "editor", "electrician", "engineer", "farmer", "fashiondesigner", "fireman(firefighter)", "fisherman", "florist", "flyer", "Foreignminister", "gasstationattendant", "geologist", "guard", "guide", "hiredresseer", "housekeeper", "housewife", "interpreter", "janitor", "judge", "librarian.", "lifeguard", "magician", "masseur", "masseuse", "mathematician", "mechanic", "miner", "model", "monk", "moviedirector", "moviestar", "musician", "nun", "nurse", "officeclerk", "officestaff", "operator", "parachutist.", "photographer", "pilot", "planner", "policeman", "postalclerk", "President", "priest", "processfor", "realestateagent", "receptionist", "repairman", "reporter", "sailor", "salesman/selespeople/salesperson", "seamstress", "secretary", "singer", "soldiery", "statistician", "surveyor", "tailor", "taxidriver", "teacher", "technician", "tourguide", "trafficwarden", "translator", "TVproducer", "typist", "vet", "waiter", "waitress", "welder", "writer", "MarketingandSales", "Vice-PresidentofSales", "SeniorCustomerManager", "SalesManager", "RegionalSalesManager", "MerchandisingManager", "SalesAssistant", "WholesaleBuyer", "Tele-Interviewer", "RealEstateAppraiser", "MarketingConsultant", "MarketingandSalesDirector", "MarketResearchAnalyst", "Manufacturer\\'sRepresentative", "DirectorofSubsidiaryRights", "SalesRepresentative", "AssistantCustomerExecutive", "MarketingIntern", "MarketingDirector", "InsuranceAgent", "CustomerManager", "Vice-PresidentofMarketing", "RegionalCustomerManager", "SalesAdministrator", "TelemarketingDirector", "AdvertisingManager", "TravelAgent", "Salesperson", "Telemarketer", "SalesExecutive", "MarketingAssistant", "RetailBuyer", "RealEstateManager", "RealEstateBroker", "PurchasingAgent", "ProductDeveloper", "MarketingManager", "AdvertisingCoordinator", "AdvertisingAssistant", "AdCopywriter(DirectMail)", "CustomerRepresentative", "ComputersandMathematics()", "ManagerofNetworkAdministration", "MISManager", "ProjectManager", "TechnicalEngineer", "DevelopmentalEngineer", "SystemsProgrammer", "Administrator", "OperationsAnalyst", "ComputerOperator", "ProductSupportManager", "ComputerOperationsSupervisor", "DirectorofInformationServices", "SystemsEngineer", "HardwareEngineer", "ApplicationsProgrammer", "InformationAnalyst", "LANSystemsAnalyst", "HumanResources", "DirectorofHumanResources", "AssistantPersonnelOfficer", "CompensationManager", "EmploymentConsultant", "FacilityManager", "JobPlacementOfficer", "LaborRelationsSpecialistRecruiter", "TrainingSpecialist", "Vice-PresidentofHumanResources", "AssistantVice-PresidentofHumanResources", "PersonnelManager", "BenefitsCoordinator", "EmployerRelationsRepresentative", "PersonnelConsultant", "TrainingCoordinator", "ExecutiveandManagerial", "ChiefExecutiveOfficer(CEO)", "DirectorofOperations", "Vice-President", "BranchManager", "RetailStoreManager", "HMOProductManager", "OperationsManager", "AssistantVice-President", "FieldAssuranceCoordinator", "ManagementConsultant", "DistrictManager", "HospitalAdministrator", "Import/ExportManager", "InsuranceClaimsController", "ProgramManager", "InsuranceCoordinator", "InventoryControlManager", "RegionalManager", "ChiefOperationsOfficer(COO)", "GeneralManager", "ExecutiveMarketingDirector", "Controller(International)", "FoodServiceManager", "ProductionManager", "PropertyManager", "ClaimsExaminer", "Controller(General)", "ServiceManager", "ManufacturingManager", "VendingManager", "TelecommunicationsManager", "TransportationManager", "WarehouseManager", "AssistantStoreManager", "Manager(Non-ProfitandCharities)", "simultaneous", "publisher", "graphicdesigner", "deliveryboy", "director", "talent", "producer", "scholar", "novelist", "playwright", "linguist", "botanist", "economist", "philosopher", "politician", "physicist", "astropologist", "archaeologist", "expertonfolklore", "biologist", "zoologist", "physiologist", "futurologist", "artists", "painter", "composer", "sculptor", "fashioncoordinator", "dressmaker", "cutter", "sewer", "ballerina", "firefigher", "chiefofpolice", "mailman", "newspaperboy", "bootblack", "poet", "copywriter", "newscaster", "milkman", "merchant", "greengrocer", "fish-monger", "shoe-maker", "saleswoman", "stewardess", "conductor", "stationagent", "porter", "carmechanic", "civilplanner", "civilengineer", "druggist", "oilsupplier", "(public)healthnurse", "supervisor", "forman", "keypuncher", "stenographer", "telephoneoperator", "programmer", "systemanalyst", "shorthandtypist", "officegirl", "publicservants", "nationalpublicservant", "localpublicserviceemployee", "nationrailroadman", "tracer", "illustrator", "acountant", "businessman", "tradesman", "pedlar", "meson", "spinner", "dyer", "temporaryworker", "probationer", "lecturer", "headmaster", "headmistress", "personnel", "administrative director", "file clerk", "executive assistant", "office manager", "executive secretary", "general office clerk", "inventory control analyst", "staff assistant", "mail room supervisor", "order entry clerk", "telephone operator", "shipping/receiving expediter", "ticket agent", "vice-president of administration", "daycare worker", "esl teacher", "developmental educator '", "head teacher", "foreign language teacher", "librarian", "guidance counselor", "music teacher", "library technician", "physical education teacher", "principal", "school psychologist", "special needs educator", "teacher aide", "art instructor", "computer teacher", "college professor", "coach", "assistant dean of students", "archivist", "vocational counselor", "tutor", "police officer", "fire fighter", "police sergeant", "assistant attorney general", "law clerk", "contracts manager", "law student", "ombudsman", "paralegal", "security manager", "patent agent", "legal assistant", "court officer", "legal secretary", "court reporter", "attorney", "clinical director", "cardiologist", "dental hygienist", "chiropractor", "dental technician", "dental assistant", "dietary technician", "emergency medical technician", "dietician", "fitness instructor", "home health aide", "health services coordinator", "lab technician", "hospital supervisor", "medical records clerk", "nursing aide", "medical technologist", "nursing supervisor", "nursing administrator", "nutritionist", "nursing home manager", "optician", "occupational therapist", "orthodontist", "veterinary assistant", "pharmacy technician", "psychiatrist", "physical therapist", "physician's assistant", "respiratory therapist", "pediatrician", "speech pathologist", "sanitation inspector", "wait person", "customer service manager", "customer service representative", "caterer", "fast food worker", "health club manager", "cosmetologist", "hotel concierge", "hotel manager'", "food inspector", "hotel clerk", "restaurant manager", "hairstylist", "flight attendant", "technical", "technical llustrator", "landscape architect", "research and development technician", "aircraft mechanic", "quality control inspector", "qatest technician", "precision inspector", "drafter", "technical support specialist", "building inspector", "engineering technician", "electronic equipment repairer", "aircraft pilot", "deputy gencral managc", "economic rescarch assistan", "electrical enginccr", "enginccring technician", "english instructor/tcachcr", "export salcs managc", "export salcs staf", "financial controllcrfinancial reportcr", "f.x. (foreign exchangc)clcrk", "f.x. settlcmcnt clcrk", "fund managcr", "gcncral auditor", "gcncral managcr", "gcncral managcr assistant", "gcncral managcr's sccrctary", "hardwarc enginccr )", "accounting assistant", "accounting clcrk", "accounting managcraccounting stall ", "accounting supervisor", "administration managcr", "administration staff", "administrativc assistant ", "administrativc clerk ", "advcrtising staff ", "airlincs sales rcprcscntativc", "airlincs staff ", "application enginccr", "assistant managcr", "bond analyst ", "bond trader", "business controllcr", "business managcr", "buycr ", "cashicr ", "chcmical enginccr", "civil enginccr", "clcrk receptionist", "clerk typist & secrctary", "computcr data input operator", "computcr enginccr", "computcr proccssing operator", "computer systcm managcr", "deputy gcncral managc", "gencral managcrl prcsident", "gcneral managcr assistant.", "gcneral managcr's secrctary", "hardwarc enginecr)", "lmport liaison staff", "import managcr", "insurancc actuary", "intcrnational salcs staff", "intcrprctcr ", "lcgal adviscr", "linc supcrvisor", "maintenancc enginccr", "managcmcnt consultant ", "managcr", "managcr for public rclation", "manufacturing enginccr", "manufacturing workcr", "markct analyst", "markct devclopmcnt managcr", "markcting managcr", "markcting staff", "markcting assistant", "markcting exccutivc", "markcting rcprcscntativc", "markcting rcprcscntativc managcr", "mechanical enginccr", "mining enginccr", "naval architect", "officc assistant", "officc clerk ", "opcrational managcr", "packagc designcr", "passenger rcservation staff", "pcrsonnel clcrk ", "pcrsonncl managcr", "plant/ factory managcr", "postal clerk", "privatc secrctary", "product managcr", "production enginecr", "profcssional staff", "project staff ", "promotional managcr", "proof-rcader", "purchasing agcnt ", "quality control enginccr", "rcal estatc staff", "rccruitment co-ordinator", "rcgional mangcr", "rcscarch&.devclopment enginccr", "rcstaurant managcr", "salcs and planning staff", "salcs assistant ", "salcs clerk ", "salcs coordinator", "salcs enginccr", "salcs exccutivc", "salcs managcr", "scller rprcscntativc", "salcs supcrvisor", "school rcgistrar", "secrctarial assistant", "securitics custody clcrk", "security officcr", "scnior accountant", "senior consultant/adviscr", "scnior employcc", "senior sccrctary", "scrvicc managcr", "simultancous interprctcr", "softwarc enginccr", "systcms adviscr", "systems enginccr", "systems operator ", "tcchnical editor", "technical translator", "tcchnical workcr", "tclecommunication executivc()", "tclcphonist / opcrator", "tourist guidc", "tradc financc exccutivc", "trainec managcr", "translation checkcr", "'translator", "trust banking exccutivc", "wordproccssor operator", "actrcss", "airlinc reprcscntativc ", "announccr", "architcct", "associatc profcssor", "astronaut", "attcndant", "auto mechanic ", "bakcr", "barbcr", "bascball playcr", "bcll boy", "binman,", "boxcr", "broker (agcnt) ", "budgctecr", "bus driver", "taxi driver", "subway driver", "train driver", "butchcr,", "buycr", "carpentcr", "cashicr", "chcmist ", "clerk ", "clown ", "cobblcr", "computcr programmcr ", "construction workcr ", "cowboy ", "customs officcr", "danccr ", "designcr", "dcsk clcrk", "detcctivc", "door-to-door salesmen"],
+  "adj": [
+    "big", "small", "red", "blue", "good", "bad"
+  ]
+}
\ No newline at end of file
diff --git a/src/assets/font/BRLNSDB_1.TTF b/src/assets/font/BRLNSDB_1.TTF
new file mode 100755
index 0000000000000000000000000000000000000000..e5f34b2f342ffcd820a8a0956af3ab36bb1f9e1f
Binary files /dev/null and b/src/assets/font/BRLNSDB_1.TTF differ
diff --git a/src/assets/libs/tmp_air.js b/src/assets/libs/tmp_air.js
new file mode 100644
index 0000000000000000000000000000000000000000..991663d203b0cfd4d5f258618d8d75146b3b0f20
--- /dev/null
+++ b/src/assets/libs/tmp_air.js
@@ -0,0 +1,430 @@
+// 刷新CDN路径
+// https://staging-teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js
+// https://teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js
+
+const commonPostMessage = function (messageObj) {
+  const obj = {...messageObj, urlParams: window.location.search}
+  window.parent.postMessage(obj, '*');
+};
+
+const commonPostMessageWithCallback = function (messageObj, callback) {
+  const onMessage = (e) => {
+    let msgData = e.data;
+    if(msgData && msgData.action === messageObj.action){
+      window.removeEventListener("message", onMessage);
+      callback && callback(msgData.data);
+    }
+  };
+  window.addEventListener("message", onMessage);
+  commonPostMessage(messageObj);
+};
+
+const realAir = {
+  uploadUrl: null,
+  uploadData: null,
+  getDataCallback: null,
+  setDataCallback: null,
+  getUploadCallback: null,
+  pageLoaded: false,
+  isCourseInScreen: false,
+  hideAirClassLoading: function(templateName,loadData){
+    // 隐藏页面加载时候的loading
+    if (deleteHistory && typeof (deleteHistory) == 'function') {
+      deleteHistory();
+    }
+    window.parent.postMessage({ action: "course-ready", data: {template:templateName,loadData:loadData,urlParams: window.location.search}, urlParams: window.location.search}, '*');
+    window.air.pageLoaded = true; 
+    window.courseware.next(); 
+
+    var cc = window.cc;
+
+    if (cc && cc.director && cc.director._scene) {
+      try {
+        const canvas = cc.find("Canvas");
+        canvas.on("mousemove", function () {});
+        cc.director._scene.on("mousemove", function () {
+          window.parent.postMessage({ action: "mousemove" }, "*");
+        });
+        cc.director._scene.on("touchmove", function () {
+          window.parent.postMessage({ action: "mousemove" }, "*");
+        });
+
+        if (cc.systemEvent && cc.SystemEvent) {
+          cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, (event) => {
+            switch (event.keyCode) {
+              case cc.KEY.left:
+                window.parent.postMessage({ action: "ArrowLeft" }, "*");
+                break;
+              case cc.KEY.right:
+                window.parent.postMessage({ action: "ArrowRight" }, "*");
+                break;
+            }
+          });
+        }
+      } catch (e) {
+        console.log("====cc.director._scene绑定事件====");
+      }
+    }
+  },
+};
+
+const uploadCallbackQueue = [];
+
+try{
+  window.air = new Proxy(realAir, {
+    set: function (target, key, value, receiver) {
+      if (key=="getUploadCallback") {
+				uploadCallbackQueue.push(value);
+			}
+      return Reflect.set(target, key, value, receiver); 
+    },
+    get: function (target, key, receiver) {
+			return Reflect.get(target, key, receiver);
+    },
+    deleteProperty: function(target, key){
+			return Reflect.deleteProperty(target, key);
+		}
+  });
+}catch(e){
+  console.error("浏览器不支持ES6新特性Proxy/Reflect,请使用谷歌浏览器!");
+}
+
+function deleteHistory() {
+  const disableBack = () => {
+    window.history.pushState(null, "", document.URL);
+    window.addEventListener("popstate", () => {
+      window.history.pushState(null, "", document.URL);
+    });
+  }
+  disableBack();
+  window.addEventListener("load", disableBack);
+}
+deleteHistory();
+
+if (window.self !== window.top) {
+  window.addEventListener("message", function (e) {
+    let msgData = e.data;
+    // console.log("子页面接收到了消息", msgData);
+    if(msgData.type === "webpackWarnings" || msgData.type === "webpackOk") {
+      return;
+    }
+    if(msgData.action==="airEvents"){
+      return;
+    } 
+    if (msgData.action === "getUpload" && msgData.uploadUrl) {
+      window.air.uploadUrl = msgData.uploadUrl;
+      window.air.uploadData = msgData.uploadData;
+      for (let i = 0; i < uploadCallbackQueue.length; i++) {
+        uploadCallbackQueue[i](msgData.uploadUrl, msgData.uploadData);
+      }
+      return;
+    }
+    if (msgData.action === "pauseCocos") {
+      let cc = window.cc;
+      if (cc && cc.game) {
+        cc.game.pause();
+        console.log('pause了');
+      }
+      return;
+    }
+    if (msgData.action === "resumeCocos") {
+      let cc = window.cc;
+      if (cc && cc.game) {
+        cc.game.resume();
+        console.log('resume了');
+      }
+      return;
+    }
+    if (msgData.action === "restartCocos") {
+      let cc = window.cc;
+      if (cc && cc.game) {
+        cc.audioEngine.stopAll();
+        // cc.game.restart();
+        console.log('restart了');
+      }
+      return;
+    }
+    if (msgData.action === "setData") {
+      window.air.setDataCallback && window.air.setDataCallback();
+      return;
+    }
+    if (msgData.action === "getData") {
+      try {
+        const res = JSON.parse(msgData.data);
+        if (res.msg !== "success") {
+          console.log('数据加载失败!');
+          return;
+        }
+        if (res.data && res.data != 'null') {
+          window.air.callData = JSON.parse(res.data);
+        }
+        window.air.callDataFlag = true;
+        return;
+      } catch (e) {
+        console.log('数据加载失败!');
+      }
+    }
+  });
+  window.parent.postMessage({ action: "getUpload" }, '*');
+
+  document.onmousemove = function(){
+    window.parent.postMessage({ action: "mousemove" }, '*');
+  };
+  document.ontouchmove = function(){
+    window.parent.postMessage({ action: "mousemove" }, '*');
+  };
+
+  document.onkeydown = (event) => {
+    switch(event.key) {
+      case "ArrowLeft":
+        window.parent.postMessage({ action: "ArrowLeft" }, '*');
+        break;
+      case "ArrowRight": 
+        window.parent.postMessage({ action: "ArrowRight" }, '*');
+        break;
+    }
+  }
+
+  document.oncontextmenu = function(){
+    return false;
+  }
+}
+
+
+window.courseware = (function () {
+  let obj = {
+    airEvents: {},
+    eventQueue: [],
+    eventLock: false,
+    next: () => {
+      let exe = obj.eventQueue.splice(0,1); 
+      if(exe.length>0){
+        obj.eventLock = true;
+        let evtName = exe[0].evtName;
+        let data = exe[0].data; 
+        if(obj.airEvents[evtName]){
+          if(evtName === "course-in-screen"){
+            if(window.air.isCourseInScreen){
+              return;
+            }
+            window.air.isCourseInScreen = true;
+          }
+          console.log(`evtName==${evtName}的方法被执行`);
+          obj.airEvents[evtName](data, obj.next);
+        } else {
+          console.warn(`airclass教室交互事件 ${evtName} 没有被捕获,请确认监听事件的先后顺序2`);
+          obj.next();
+        }
+      }else{
+        obj.eventLock = false;
+      } 
+    }
+  };
+  if (window.self !== window.top) {
+    window.addEventListener("message", function (e) {
+      let msgData = e.data;
+      if(msgData.action!=="airEvents"){
+        return;
+      } 
+      let evtName = msgData.evt; 
+      let res = msgData.data;
+      if (evtName === "course-out-screen"){
+        console.log(`evtName==${evtName}的方法被执行`);
+        window.location.reload();
+        return;
+      }
+      if (res&&evtName!='userchange') {
+        //userchange事件传过来的值不需要转换
+        res = JSON.parse(res); 
+      }
+      if(!window.air.pageLoaded){
+        //如果页面还没有加载完成
+        obj.eventQueue.push({"evtName": evtName, data: res});
+      }else{ 
+        if (obj.eventQueue.length === 0 && !obj.eventLock) {
+          //如果没有消息积压并且事件锁未锁定
+          obj.eventLock = true; 
+          if(obj.airEvents[evtName]){
+            if(evtName === "course-in-screen"){
+              if(window.air.isCourseInScreen){
+                return;
+              }
+              window.air.isCourseInScreen = true;
+            }
+            console.log(`evtName==${evtName}的方法被执行`);
+            obj.airEvents[evtName](res, obj.next); 
+          } else {
+            console.warn(`airclass教室交互事件 ${evtName} 没有被捕获,请确认监听事件的先后顺序1`);
+            obj.next();
+          }
+        } else {
+          obj.eventQueue.push({"evtName": evtName, data: res});
+        }
+      } 
+    }); 
+    obj.getData = function (callback, key = '') {
+      window.parent.postMessage({ action: "getData", data: window.location.search, urlParams: window.location.search }, '*');
+      window.air.callDataFlag = false;
+      
+      const liuintval = setInterval(()=>{
+        if(window.air.callDataFlag){
+          console.log("========我进来了=========");
+          clearInterval(liuintval);
+          setTimeout(() => {
+            console.log("执行回调,回调数据为:");
+            console.log(window.air.callData);
+            callback(window.air.callData);
+          }, 100);
+        }
+      }, 100);
+    };
+    obj.setData = function (data, callback, key = '') {
+      let str = JSON.stringify(data);
+      console.log("==setData==", str);
+      window.parent.postMessage({ action: "setData", data: str, urlParams: window.location.search }, '*');
+      window.air.setDataCallback = callback;
+    };
+    obj.uploadUrl = function () {
+      // return net.getUploadFileURL();
+      return window.air.uploadUrl;
+    };
+    obj.uploadData = function () {
+      // return net.getAjaxData("uploadFile","");
+      return window.air.uploadData;
+    };
+    obj.beganRecording = function(){
+      commonPostMessage({ action: "beganRecording" }); 
+    };
+    obj.endRecording = function(callback){
+      commonPostMessageWithCallback({ action: "endRecording" }, callback);
+    };
+    obj.speakPoints = function(audioUrl, evalText, callback){
+      const obj = {audioUrl, evalText};
+      commonPostMessageWithCallback({ action: "speakPoints", data: JSON.stringify(obj) }, callback);
+    };
+    obj.startRecord = function(testText){
+      commonPostMessage({ action: "startRecord", data: testText  });
+    };
+    obj.stopRecord = function(callback){
+      commonPostMessageWithCallback({ action: "stopRecord" }, callback);
+    };
+    obj.startTest = function(testText){
+      commonPostMessage({ action: "startTest", data: testText });
+    };
+    obj.stopTest = function(callback){
+      commonPostMessageWithCallback({ action: "stopTest" }, callback);
+    };
+    obj.getTemplates = function(callback){
+      commonPostMessageWithCallback({ action: "getTemplates" }, callback);
+    };
+    obj.getTemplateUrl = function(templateName, callback){
+      commonPostMessageWithCallback({ action: "getTemplateUrl", data: templateName}, callback);
+    };
+    obj.sendEvent = function(evtName,data,key = ''){
+      if(evtName==="reconnect"){
+        console.error("reconnect是内置的断线重连事件名称,请勿使用此名称作为事件名");
+        return;
+      }
+      if(evtName==="userchange"){
+        console.error("userchange是内置的用户变更事件名称,请勿使用此名称作为事件名");
+        return;
+      }
+      let str = null;
+      if(data){
+        str = JSON.stringify(data);
+      } 
+      window.parent.postMessage({ action: "airEvents",evt: evtName, data: str, urlParams: window.location.search }, '*'); 
+    };
+    obj.onEvent = function(evtName,callback){  
+      obj.airEvents[evtName] = callback; 
+    };
+    obj.removeEvent = function(evtName){
+      if(obj.airEvents[evtName]){ 
+        delete obj.airEvents[evtName];
+      }  
+    };
+    obj.sendErrorLog = function (error) { 
+      // todo 这块预留后面采集模板的报错信息
+      // window.parent.postMessage({ action: "errorlog", data: error.stack }, '*'); 
+    };
+  } else {
+    obj.getData = function (callback, key = '') {
+      let data = localStorage.getItem("courseware_data_" + key);
+      if (data) {
+        data = JSON.parse(data);
+      }
+      callback && callback(data);
+    };
+    obj.setData = function (data, callback, key = '') {
+      console.log("******local********");
+      localStorage.setItem("courseware_data_" + key, JSON.stringify(data));
+      callback && callback();
+    };
+    obj.uploadUrl = function () {
+      var protocolStr = document.location.protocol;
+      return `${protocolStr}//staging-teach.ireadabc.com/fileUpload`;
+    };
+    obj.uploadData = function () {
+      return {};
+    };
+    obj.beganRecording = function(){
+      console.log("******beganRecording********");
+    };
+    obj.endRecording = function(callback){
+      console.log("******endRecording********");
+      callback&&callback("");
+    };
+    obj.speakPoints = function(audioUrl, evalText, callback){
+      console.log("******speakPoints********");
+      callback&&callback("");
+    };
+    obj.startRecord = function(){
+      console.log("******startRecord********");
+    };
+    obj.stopRecord = function(callback){
+      console.log("******stopRecord********");
+      callback&&callback("");
+    };
+    obj.startTest = function(testText){
+      console.log("******startTest********");
+    };
+    obj.stopTest = function(callback){
+      console.log("******stopTest********");
+      callback&&callback("");
+    };
+    obj.getTemplates = function(callback){
+      callback&&callback("");
+    };
+    obj.getTemplateUrl = function(templateName, callback){
+      const obj = {
+        play_url: "",
+        form_url: "",
+      };
+      callback&&callback(JSON.stringify(obj));
+    };
+    obj.sendEvent = function(evtName, data, key = ''){ 
+      if(evtName==="reconnect"){
+        console.error("reconnect是内置的断线重连事件名称,请勿使用此名称作为事件名");
+        return;
+      }
+      if(evtName==="userchange"){
+        console.error("userchange是内置的用户变更事件名称,请勿使用此名称作为事件名");
+        return;
+      }
+      return;
+    };
+    obj.onEvent = function(evtName,callback){
+      console.warn("==本地测试用,onEvent的监听事件会直接触发==", "事件名:"+ evtName);
+      callback && callback("", function(){});
+    };
+    obj.removeEvent = function(evtName){
+      
+    };
+    obj.sendErrorLog = function (error) { 
+
+    };
+  }
+
+  return obj;
+})();
+
diff --git a/src/assets/play/answer_normal.png b/src/assets/play/answer_normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4de36f26eeb45bab61e6cc28a8c09050810d63b
Binary files /dev/null and b/src/assets/play/answer_normal.png differ
diff --git a/src/assets/play/answer_right.png b/src/assets/play/answer_right.png
new file mode 100644
index 0000000000000000000000000000000000000000..44c392a7ce3971e3d5f8dd24f3afc82138b63b1d
Binary files /dev/null and b/src/assets/play/answer_right.png differ
diff --git a/src/assets/play/answer_wrong.png b/src/assets/play/answer_wrong.png
new file mode 100644
index 0000000000000000000000000000000000000000..d7240b3380e276d2ac945a93431c813547aeff98
Binary files /dev/null and b/src/assets/play/answer_wrong.png differ
diff --git a/src/assets/play/bg.jpg b/src/assets/play/bg.jpg
old mode 100755
new mode 100644
index 701c49c4c716d9e48412993f87f47d0fdcfbbe44..ab4db4b3ee3153a9669cfa83f511d0d368460ae0
Binary files a/src/assets/play/bg.jpg and b/src/assets/play/bg.jpg differ
diff --git a/src/assets/play/btn_close.png b/src/assets/play/btn_close.png
new file mode 100644
index 0000000000000000000000000000000000000000..a0822fcaab4587aa205e37d93eba3d63b4349f5b
Binary files /dev/null and b/src/assets/play/btn_close.png differ
diff --git a/src/assets/play/btn_left.png b/src/assets/play/btn_left.png
deleted file mode 100755
index e3428fd5bcda4bad311e87c5aa5669fdb7e96a60..0000000000000000000000000000000000000000
Binary files a/src/assets/play/btn_left.png and /dev/null differ
diff --git a/src/assets/play/btn_replay.png b/src/assets/play/btn_replay.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa1722c1689141b025d11256be4faa5c84c028ca
Binary files /dev/null and b/src/assets/play/btn_replay.png differ
diff --git a/src/assets/play/btn_right.png b/src/assets/play/btn_right.png
deleted file mode 100755
index db0f274024b4ad41d5219acf3936ec2b2f79459c..0000000000000000000000000000000000000000
Binary files a/src/assets/play/btn_right.png and /dev/null differ
diff --git a/src/assets/play/default/audio.mp3 b/src/assets/play/default/audio.mp3
deleted file mode 100755
index 66313dcf084aa98da69908cb6ee9da2d0d4a451e..0000000000000000000000000000000000000000
Binary files a/src/assets/play/default/audio.mp3 and /dev/null differ
diff --git a/src/assets/play/default/pic.jpg b/src/assets/play/default/pic.jpg
deleted file mode 100755
index 1da3c1135cc324bf3ec67cc0c7b4c19caeef08f1..0000000000000000000000000000000000000000
Binary files a/src/assets/play/default/pic.jpg and /dev/null differ
diff --git a/src/assets/play/font/Aileron-Black.ttf b/src/assets/play/font/Aileron-Black.ttf
new file mode 100755
index 0000000000000000000000000000000000000000..eea596e5445f32ef11373ae18e4a021fdf1f2232
Binary files /dev/null and b/src/assets/play/font/Aileron-Black.ttf differ
diff --git a/src/assets/play/font/Aileron-Bold.ttf b/src/assets/play/font/Aileron-Bold.ttf
new file mode 100755
index 0000000000000000000000000000000000000000..c35d2ddadad0984b7405e4467b013695bcd0e915
Binary files /dev/null and b/src/assets/play/font/Aileron-Bold.ttf differ
diff --git a/src/assets/play/font/BRLNSDB_1.TTF b/src/assets/play/font/BRLNSDB_1.TTF
new file mode 100755
index 0000000000000000000000000000000000000000..e5f34b2f342ffcd820a8a0956af3ab36bb1f9e1f
Binary files /dev/null and b/src/assets/play/font/BRLNSDB_1.TTF differ
diff --git a/src/assets/play/font/DroidSansFallback.ttf b/src/assets/play/font/DroidSansFallback.ttf
new file mode 100755
index 0000000000000000000000000000000000000000..6f928809672fbad6a6c66f57aad46f865b2a5800
Binary files /dev/null and b/src/assets/play/font/DroidSansFallback.ttf differ
diff --git a/src/assets/play/icon_right.png b/src/assets/play/icon_right.png
new file mode 100644
index 0000000000000000000000000000000000000000..16cee929a6eaabbfa68d00fdbe1b603becc50e8a
Binary files /dev/null and b/src/assets/play/icon_right.png differ
diff --git a/src/assets/play/icon_wrong.png b/src/assets/play/icon_wrong.png
new file mode 100644
index 0000000000000000000000000000000000000000..77571c2b625e38bf00b66e5e6adceec024b95c29
Binary files /dev/null and b/src/assets/play/icon_wrong.png differ
diff --git a/src/assets/play/music/click.mp3 b/src/assets/play/music/click.mp3
index f551dc50e89af2d63b79892af8bf1d61ae92f7a5..c3b69b21ac958fc11c3db2c263933a3464e78d3c 100644
Binary files a/src/assets/play/music/click.mp3 and b/src/assets/play/music/click.mp3 differ
diff --git a/src/assets/play/music/more.mp3 b/src/assets/play/music/more.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..716d38357af4556779878e421541ac7a84dcc150
Binary files /dev/null and b/src/assets/play/music/more.mp3 differ
diff --git a/src/assets/play/music/submit.mp3 b/src/assets/play/music/submit.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..a7988179aef59223734c49ca098323f55b04b93f
Binary files /dev/null and b/src/assets/play/music/submit.mp3 differ
diff --git a/src/assets/play/op_item.png b/src/assets/play/op_item.png
new file mode 100644
index 0000000000000000000000000000000000000000..84bd5d9558ff527aded89de6e3648c3e9509c987
Binary files /dev/null and b/src/assets/play/op_item.png differ
diff --git a/src/assets/play/op_item_normal.png b/src/assets/play/op_item_normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..d087a3b0a97de671afaa78bc3b4d5b275ced662a
Binary files /dev/null and b/src/assets/play/op_item_normal.png differ
diff --git a/src/assets/play/op_item_right.png b/src/assets/play/op_item_right.png
new file mode 100644
index 0000000000000000000000000000000000000000..060d0df2dbbdf0bcb336966ae913cdb609809c4e
Binary files /dev/null and b/src/assets/play/op_item_right.png differ
diff --git a/src/assets/play/op_item_wrong.png b/src/assets/play/op_item_wrong.png
new file mode 100644
index 0000000000000000000000000000000000000000..dc7f4363e8722e390862ec26a2c8fc91ef3845ca
Binary files /dev/null and b/src/assets/play/op_item_wrong.png differ
diff --git a/src/assets/play/text_bg.png b/src/assets/play/text_bg.png
deleted file mode 100755
index 6084daeadcb2cec320328e7e1f362d104b7ffbaa..0000000000000000000000000000000000000000
Binary files a/src/assets/play/text_bg.png and /dev/null differ
diff --git a/src/assets/play/video_bg.jpg b/src/assets/play/video_bg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e266ea2c96d2f90a6bca0e872fde74e05ce7476d
Binary files /dev/null and b/src/assets/play/video_bg.jpg differ
diff --git a/src/index.html b/src/index.html
index e55641675b96cbe7dae938f19f7d6ba8773e4c42..c7c0f218195d3729294cd01c5d0833413212bfb0 100644
--- a/src/index.html
+++ b/src/index.html
@@ -9,6 +9,12 @@
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <script type="text/javascript" src="https://staging-teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script>
 
+  <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.9.0/vconsole.min.js"></script>
+    <script>
+        // init vConsole
+        // var vConsole = new VConsole();
+        // console.log('Hello world');
+    </script>
 </head>
 <body>
 <app-root></app-root>