# ng-template-generator

angularjs技术框架下的H5互动模板框架脚手架，基于NG-ZORRO实现快速开发基于绘玩云的H5互动课件。

# 使用简介

## 前期准备

* git下载  https://git-scm.com/downloads 
* nodejs下载  https://nodejs.org/zh-cn/download/
* 谷歌浏览器下载  https://www.google.cn/chrome/

都下载最新版就行，然后默认安装就可以


## 生成项目

* 登录绘玩云课件开发平台：http://staging-teach.ireadabc.com/ 
* 点击“登录账号，查看我的课件”
* 输入测试的用户名/密码：developers/12345678
* 在右上角“个人中心”的下拉菜单里，点击“我的模板” 菜单，然后点击“新建模板”, 填写必要的信息，在“技术选型”一项上选择“Angular”
* 点击“确定”后，列表页就会出现一个新生成的模板项目
* 在项目的卡片下找到“开发”按钮，则会弹出相对应的git地址

## 获取并启动项目

```
// xxx 是上面项目对应的Git地址
git clone xxx
cd 项目名称/
npm install -g yarn
yarn install
npm start

//启动成功后打开浏览器，输入：http://localhost:4200 则可以看到这个项目的初始化效果了

```

## 项目结构

|-- bin

|-- dist

|-- e2e

|-- node_modules

|-- publish

|-- src

| |-- app

| | |-- common

| | |-- form

| | |-- pipes

| | |-- play

| | |-- services

| | |-- style

| | |-- app.component.html

| | |-- app.component.scss

| | |-- app.component.ts

| | |-- app.module.ts

| |-- assets

| |-- environments

| |-- services

| |-- index.html

| |-- main.ts

| |--.......



* 其中 play 文件夹是H5模板展示页面，form文件夹是模板的配置页面
* common文件夹是以往做过的模板积累的一些比较常用的 AngularJs 的组件
* assets文件夹存放模板样式的静态资源，例如小图标、背景图片、字体、样式等等
* 其他文件夹开发者可以不做任何更改

## 开始开发

本脚手架的页面类UI框架是基于  [NG-ZORRO](https://ng.ant.design/docs/introduce/zh "With a Title") 框架实现的，在配置页面（form文件夹），一些表单输入，上传图片等组件也可以使用 NG-ZORRO 现成的组件

### 开发配置页面（form/）

配置页面主要作用是为了呈现多样化模板，提高H5模板的高复用性、灵活性提供的一个配置工具，根据H5模板的设计规划或特性，会规定某些元素是可以配置的，比如背景图片、音频、选项、标题等等等等。(课件制作者) 通过配置页面，就可以灵活的更换这些，从而形成多种多样的课件。


配置页面的开发要求很简单，我们不关心布局、样式、交互等所有实现细节与效果，只需调用两个内置的全局接口即可：

* setData 接口，用于将配置的数据存储到云端。

  此方法是一个异步的方法，接收三个必要参数

  obj: 一个json对象

  callback: 回调函数

  t_name: 模板名称

  一般使用例子如下：

  ```
  // ts语法，不能直接使用js的window对象自定义方法
  (<any> window).courseware.setData(obj, callback, t_name);
  ```

* getData 接口，用于当点击修改的时候将配置的数据从云端获取下来。

  此方法是一个异步的方法，接收两个必要参数 

  > callback: 回调函数, 回调函数里会得到云端保存的数据

  > t_name: 模板名称

  一般使用例子如下：

  ```
  // ts语法，不能直接使用js的window对象自定义方法
  const getData = (<any> window).courseware.getData;
  getData((data) => {
    //data 就是云端获取的数据，也就是setData方法存到云端的那个obj
    //模板开发者需要根据数据，依次填写到表单的相应位置，方便用户二次修改编辑
  });
  ```

### 开发展示页面（play/）

展示页面主要是呈现模板，通过H5技术，实现多样化的交互与展示方式。


展示页面更简单，开发者可以运用各种技术，实现Web端可以实现的任何效果，包括2D、3D、动画、游戏等各种场景，把表单配置的数据获取到，用于渲染相对应的页面即可：
 
* getData 接口，用于当点击修改的时候将配置的数据从云端获取下来。

  此方法是一个异步的方法，接收两个必要参数 

  > callback: 回调函数, 回调函数里会得到云端保存的数据

  > t_name: 模板名称

  一般使用例子如下：

  ```
  // ts语法，不能直接使用js的window对象自定义方法
  const getData = (<any> window).courseware.getData;
  getData((data) => {
    //data 就是云端获取的数据，也就是setData方法存到云端的那个obj
    //模板开发者需要根据数据来渲染或控制展示页面的呈现，达到模板的互动目的 
  });
  ```

  ### 互动课件

  通过H5的交互方式，我们可以很轻松的制作在线的互动课件，使老师端与学生端实现联动，使在线课堂更具有交互性。

  * onEvent 定义事件：用于自定义同步事件，配合多端互动使用，与sendEvent方法配合使用，用于监听多端发送的同步事件

  参数

  > evtName : 自定义事件的名称

  > callback : 回调函数，回调函数里会得到传过来的数据

  使用例子：

  ```
  const cw = (<any> window).courseware;
  //订阅一个事件
  cw.onEvent('testEvent', (data,next) => {
    console.log(data); 
    //处理事件的同步逻辑

    //在逻辑处理完之后一定要执行next方法,用于解锁事件队列
    next();
  }); 
  ```

  * sendEvent 发送事件：用于自定义同步事件，配合多端互动使用，与onEvent方法配合使用，用于对端发送同步事件

  参数

  > evtName : 自定义事件的名称

  > data : 传递的参数 

  使用例子：

  ```
  const cw = (<any> window).courseware;
  //发送事件
  //这样，所有端（教师、学生） 都会触发 testEvent 方法
  cw.sendEvent('testEvent', 'Hello world'); 

  ```

  * storeAspect 存储切面数据： 课件的切面状态数据；互动课件需要每一个开发者设计切面状态，用于学生进入已经进行的课件，恢复课件同步。例如老师讲图片轮播到第四页，这个时候学生进入教师，要恢复到第四页，才能跟老师同步上课。

  参数

  > data : 保存的数据，一个JSON对象

  使用例子：

  ```
  const cw = (<any> window).courseware; 
  //存储切面数据，用于学生进入教室，同步课堂信息等
  cw.storeAspect({page: 5}); 
  ```

  * getAspect 获取切面数据： 课件的切面状态数据；互动课件需要每一个开发者设计切面状态，用于学生进入已经进行的课件，恢复课件同步。例如老师讲图片轮播到第四页，这个时候学生进入教师，要恢复到第四页，才能跟老师同步上课。

  参数

  > callback : 获取切面数据的回调函数

  使用例子：

  ```
  const cw = (<any> window).courseware; 
  //存储切面数据，用于学生进入教室，同步课堂信息等
  cw.getAspect(function(aspect){
    //恢复切面状态的逻辑
  }); 
  ```

  * 补充项

  1)  有时候我们在实现模板效果的时候，需要一些基本的信息，比如教室信息、当前用户信息等等，例如，老师会显示某个按钮，而学生不显示，我们可以用如下方式获取教室信息；后续大部分静态信息都会完善到这个对象里

  ```
  //获取教室信息，值得注意的是获取这个信息一定要再 getData方法之后，或者确保页面完成之后
  getData((data, aspect) => {
    let airClassInfo = window["air"].airClassInfo;
    console.log(airClassInfo);
  }); 
  ```

  2)  互动课件的 getData 方法的回调函数里多返回了一个切面参数，类似如下：

  ```
  const getData = (<any> window).courseware.getData;
  getData((data, aspect) => {
    //data 就是云端获取的数据，也就是setData方法存到云端的那个obj
    //aspect 切面参数，用于恢复页面的状态
    //模板开发者需要根据数据来渲染或控制展示页面的呈现，达到模板的互动目的 
  });
  ``` 

  3)  一些必要的内置事件，通过监听这些事件，我们可以实现一些相关的功能

      * userchange 事件，用来监听教室内部的人员变动情况，例如某人进入，某人退出都会触发此事件

        示例：

        ```
        const cw = (<any> window).courseware;
        //订阅一个事件
        cw.onEvent('userchange', (data,next) => {
          //data的结构如下
          /*{
           *  id: '变动用户的ID',
           *  connected: true/false,  true: 连接的，false:d断开的
           *  status: 'connect/reconnect',  connect：新建连接，reconnect:重新连接；这个字段在断开连接的事件里是缺失的
           *  all_user: []  这个是现有的用户列表 
           *}
           */
          console.log(data); 
          //处理事件的同步逻辑

          //在逻辑处理完之后一定要执行next方法,用于解锁事件队列
          next();
        }); 
        
        ```

  4)  测试互动课件

      我们在本地开发中，无法模拟线上老师和学生互动的交互场景，所以提供了一套开发者测试的账号，如下：

      测试地址：http://staging-ac.ireadabc.com/ 

      老师用户名/密码：devtea / 1

      学生用户名/密码： 学生1(13877777711 / 1)    学生2(13877777712 / 1)


      测试方法： 

      1、首先开发者发布完模板之后，在制作课件的菜单下用这个模板制作一个课件

      2、在本地打开两个浏览器窗口  一个登陆老师用户，另一个登陆学生用户

      3、老师用户进去之后，就会有很多课件，这个时候双击自己制作的课件，学生的窗口就会同步的打开课件了

      4、测试自己的交互事件，看看老师端和学生端是否是自己想要的展示效果



  ### 补充方法

  在模板的编辑或展示过程中，还会经常遇到如下两个场景，特提供针对性的解决办法

  1)  表单录入往往需要上传图片、音视频等多媒体文件，所以脚手架内置如下方法，用于获取一个上传到云端的接口方法

  ```
  const uploadUrl = (<any> window).courseware.uploadUrl;
  const uploadData = (<any> window).courseware.uploadData;

  //例如用于NG-ZORRO的上传组件则如下写法：
  <nz-upload class="p-image-uploader" [nzDisabled]="disableUpload"
    [nzShowUploadList]="false"
    nzAccept = "image/*"
    [nzAction]="uploadUrl"    <!-- 注意这里 -->
    [nzData]="uploadData"     <!-- 注意这里 -->
    (nzChange)="handleChange($event)">
  </nz-upload>

  ```

  2)  在展示页面加载完成的时候，需要调用一下下面的方法，用于释放切换课件的遮罩层

  ```
  //此方法为固定写法，
  //其中t_name为模板名称，obj是云端存储的配置数据
  window["air"].hideAirClassLoading(t_name,obj);
  ```


  ## 打包发布

  模板开发完成之后，要推送到云平台上使用则需要进行打包发布操作

  ```
  npm run publish
  ```
  在项目根目录下执行上述命令，则在 ./publish 目录下生产一个 .zip的压缩包，此时打开云平台
  http://staging-teach.ireadabc.com/ 
  点击“模板管理” 菜单，找到对应的模板卡片，点击“发布”按钮，在弹出的对话框中选中压缩包，然后点击“确定”，上传完成后，则发布就成功了


  ## 常见问题汇总

  ### 静态资源引用路径写法

  css文件里面要带 ..  , eg:
  ```
  background-image: url("../../assets/playground-house/bg.jpg");
  ```

  其他的文件(html、ts、resources.js)里路径要开头不带斜杠， eg:
  ```
  src="assets/guess-read/btn.png"
  ```
  
  ### no such file or directory,

  出现类似如：
  ```
  Error: ENOENT: no such file or directory, open 'C:/publish/resources/xxxx/v1/index.html'
  ```
  这样的代码时， 请确认新建模板的时候表单填写的“配置页面URL”与“展示页面URL” 填写的路径是否正确

  ### 已经发布的模板，制作课件时看不到

  如果模板已经确认发布成功了，制作课件的时候却看不到这个模板，请编辑模板，查看模板类型是否选择为“课中模板”

  ### 有些模板上传图片或视频时，存在偶尔上传不上去的问题

  此问题是因为有些异步的情况下 uploadUrl 获取不到导致的
  在 constructor 或者 ngOnInit 方法里，一直轮询，直到获取到 uploadUrl
  ```
    this.uploadUrl = (<any> window).courseware.uploadUrl();
    this.uploadData = (<any> window).courseware.uploadData();

    const fun = () => {
      setTimeout(() => {
        this.uploadUrl = (<any> window).courseware.uploadUrl();
        this.uploadData = (<any> window).courseware.uploadData();
        if(!this.uploadUrl){
          fun();
        }
      }, 1000);
    }
    if(!this.uploadUrl){
      fun();
    }
  ```

  ### 模板编译CI不通过，报类似如下错误

  ```
    ERROR in ./src/styles.scss Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js): ModuleBuildError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js): Error: Node Sass version 5.0.0 is incompatible with ^4.0.0. at getSassImplementation 
  ```

  此情况属于老的模板遗留问题，需要在package.json里面锁一下node-sass的版本

  ```
  "node-sass": "4.0.0"
  ```

  