# 145教材系统技术文档

## 一、技术语言和框架
#### 数据库和数据表
- 数据库：[PostgreSQL](https://www.postgresql.org/ "PostgreSQL")
- 数据表
  - _User【用户】
    ```js
    "fields": {
        "avatar": {
            "type": "String"
        },
        "name": {
            "type": "String"
        },
        "mobile": {
            "type": "String"
        },
        // Authing 提供信息
        "phone": {
            "type": "String"
        },
        "lastIP":{
            "type":"String"
        },
        "lastLogin": {
            "type": "Date"
        },
        "userSourceType":{
            "type": "String"
        },
        "loginsCount":{
            "type": "Number"
        }
    }
    ```
  - Profile 【用户角色】
    ```js
    "fields": {
      "user": {
        "type": "Pointer",
        "targetClass": "_User",
        "required": false
      },
      "phone": {
        "type": "String"
      },
      "email": {
        "type": "String"
      },
      "telephone": {
        "type": "String"
      },
      "province": {
        "type": "String"
      },
      "companyType": {
        "type": "String"
      },
      "postName": {
        "type": "String"
      },
      "idcard": {
        "type": "String"
      },
      "identity": {
        "type": "String"
      },
      "identityFile": {
        "type": "String"
      },
      "majorSubject": {
        "type": "String"
      }
    },
    ```
  - Department 【单位部门】
    ```js
    "fields": {
      "parent": {//上级
        "type": "Pointer",
        "targetClass": "Department",
        "required": false
      },
      "name": {//单位、组织名称
        "type": "String",
        "required": false
      },
      "code": {//标识码
        "type": "String",
        "required": false
      },
      "branch": {//主管部门
        "type": "String",
        "required": false
      },
      "address": {//所在地
        "type": "String",
        "required": false
      },
      "type": { // 单位类型
        "type": "String",
        "required": false
      },
      "hasChildren": {//办学层次
        "type": "Boolean",
        "required": false
      },
    },
    ```
  - EduTextbook 【教材】
    ```js
    "fields": {
      "status": {
          /*状态 
          101:待完善资料  
          102:可提交状态 
          103:已提交至高校管理员
          200:已提交遴选中 
          201：公示中 
          300:退回
          400:已报送
          */
          "type": "String",
          "required": false
      },
      "childrens":{//指向所有分册
          "type": "String",
          "required": false
      },
      "user": {
          "type": "Pointer",
          "targetClass":"_User",
          "required": false
      },
      "department": { //所属流程
          "type": "Pointer",
          "targetClass":"Department",
          "required": false
      },
      "render": { //是否提交
          "type": "Boolean",
          "required": false
      },
      "recommend": { //是否推荐
          "type": "Boolean",
          "required": false
      },
      "complete": { //是否填写完整
          "type": "Boolean",
          "required": false
      },
      "isDeleted": {
          "type": "Boolean",
          "required": false
      },
      "discard": { //删除至回收站
          "type": "Boolean",
          "required": false
      },
      "title": {
          "type": "String",
          "required": false
      },
      "ISBN": {
          "type": "String",
          "required": false
      },
      "author": {
          "type": "String",
          "required": false
      },
      "unit": {
          "type": "String",
          "required": false
      },
      "type": {
          "type": "String",
          "required": false
      },
      "typeNumber": {//全册次数
          "type": "Number",
          "required": false
      },
      "majorPoniter": {
          "type": "String",
          "required": false
      },
      "lang": {
          "type": "String",
          "required": false
      },
      "authors": {
          "type": "String",
          "required": false
      },
      "editor": {
          "type": "String",
          "required": false
      },
      "approval": {//是否为重点立项教材
          "type": "String",
          "required": false
      },
      "edition": {
          "type": "String",
          "required": false
      },
      "editionFirst": {
          "type": "Date",
          "required": false
      },
      "carrierShape": {
          "type": "String",
          "required": false
      },
      "editionDate": {
          "type": "Date",
          "required": false
      },
      "editionNumber": {//出版版次
          "type": "Number",
          "required": false
      },
      "printDate": {
          "type": "Date",
          "required": false
      },
      "printNumber": {
          "type": "Number",
          "required": false
      },
      "printSum": {
          "type": "Number",
          "required": false
      },
      "importantProject": {//重点项目
          "type": "Array",
          "required": false
      },
      "importantProjectOther": {//重点项目-其他
          "type": "String",
          "required": false
      },
      "copyright": {
          "type": "Array",
          "required": false
      },
      "CIP": {
          "type": "Array",
          "required": false
      },
      'majors':{//适用专业代码（六位）及名称
          "type":'Number',
          "required": false  
      },
      "period": {
          "type": "Number",
          "required": false
      },
      "lessons": {
          "type": "Array",
          "required": false
      },
      "characteristic": {
          "type": "Array",
          "required": false
      },
      "authorList": {
          "type": "Array",
          "required": false
      },
      "authorDetails": {
          "type": "String",
          "required": false
      },
      "achievementOptions": {
          "type": "Array",
          "required": false
      },
      "courses": {
          "type": "Array",
          "required": false
      },
      "innovateExplain": {
          "type": "String",
          "required": false
      },
      "influence": {
          "type": "String",
          "required": false
      },
      "textbookFiles": {
          "type": "Array",
          "required": false
      },
      "links": {
          "type": "Array",
          "required": false
      },
      "opinions": {
          "type": "Array",
          "required": false
      },
      "selfResults": {
          "type": "Object",
          "required": false
      },
      "expertOpinion": {
          "type": "Object",
          "required": false
      },
      "evidence": {
          "type": "Object",
          "required": false
      },
      "moreMaterial": {
          "type": "Array",
          "required": false
      },
      "authorSign": {
          "type": "Array",
          "required": false
      },
      "unitMaterial": {
          "type": "Object",
          "required": false
      },
      "accept": {//填写承诺书
          "type": "String",
          "required": false
      },
      "approvedImgUrl":{//重点立项教材获批截图            
          "type":'String',
          "required": false
      },
      "eduProcess": {//所属流程
          "type": "Pointer",
          "targetClass":"EduProcess",
          "required": false
      },
      "code":{//编号
          "type":'String',
          "required": false
      },
      'editionUnit':{
          "type":'String',
          "required": false
      },
      'copyrightImgUrl':{
          "type":'String',
          "required": false  
      },
      'CIPImgurl':{
          "type":'String',
          "required": false  
      },
      'examine':{//作者政治审核表
          "type":'Array',
          "required": false  
      },
      'cipProveFile':{//cip相关截图
          "type":'Array',
          "required": false  
      },
      'inviteUnit':{//推荐单位
          "type":'Array',
          "required": false  
      },
      'unitType':{//推荐单位类型
          "type":'Array',
          "required": false  
      },
      'score':{//平均分
          "type":'Number',
          "required": false  
      },
    },
    ```
  - EduTextbookVolume 【分册】
    ```js
    "fields": {
      "user": {
          "type": "Pointer",
          "targetClass":"_User",
          "required": false
      },
      "isDeleted": {
          "type": "Boolean",
          "required": false
      },
      "title": {
          "type": "String",
          "required": false
      },
      "ISBN": {
          "type": "String",
          "required": false
      },
      "author": {
          "type": "String",
          "required": false
      },
      "unit": {
          "type": "String",
          "required": false
      },
      "lang": {
          "type": "String",
          "required": false
      },
      "authors": {
          "type": "String",
          "required": false
      },
      "editor": {
          "type": "String",
          "required": false
      },
      "editionFirst": {
          "type": "Date",
          "required": false
      },
      "carrierShape": {
          "type": "String",
          "required": false
      },
      "editionDate": {
          "type": "Date",
          "required": false
      },
      "editionNumber": {//出版版次
          "type": "Number",
          "required": false
      },
      "printDate": {
          "type": "Date",
          "required": false
      },
      "printNumber": {
          "type": "Number",
          "required": false
      },
      "printSum": {
          "type": "Number",
          "required": false
      },
      "importantProject": {//重点项目
          "type": "Array",
          "required": false
      },
      "importantProjectOther": {//重点项目-其他
          "type": "String",
          "required": false
      },
      "authorList": {
          "type": "Array",
          "required": false
      },
      "authorDetails": {
          "type": "String",
          "required": false
      },
      "achievementOptions": {
          "type": "Array",
          "required": false
      },
      "courses": {
          "type": "Array",
          "required": false
      },
      'editionUnit':{
          "type":'String',
          "required": false
      },
      'unitType':{//推荐单位类型
          "type":'Array',
          "required": false  
      },
      "collectFIles": {//纸质教材文件
          "type": "Object",
          "required": false
      },
      "collectDigitFiles":{//纸质数字文件
          "type": "Object",
          "required": false
      },
      "collectCheck":{//教材文件上传类型
          "type": "Object",
          "required": false
      },
      "collectStatus":{//教材文件上传状态
          "type": "Object",
          "required": false
      },
    },
    ```
  - Activity 【评审活动】
    ```js
    "fields": {
      "name":{//活动名称
        "type": "String",
        "required": false
      },
      "eduProcess": { //评审教材
        "type": "Pointer",
        "targetClass": "EduProcess",
        "required": false
      },
      "startDate": {//开始时间
        "type": "Date",
        "required": false
      },
      "deadline": {//截止时间
        "type": "Date",
        "required": false
      },
      "status":{//是否开始
        "type": "Number",
        "required": false
      },
      "desc":{//详情说明
        "type": "String",
        "required": false
      },
    },
    ```
  - Company 【公司】
    ```js
    "fields": {
      "title": {
          "type": "String"
      },
    },
    ```
  - EduProcess 【流程】
    ```js
    "fields": {
        "eduCollection": { //指向报送合集
          "type": "Pointer",
          "targetClass": "EduCollection",
          "required": false
        },
        "branch": { //类别
          "type": "Pointer",
          "targetClass": "Department",
          "required": false
        },
        "department": { //部门/单位
          "type": "Pointer",
          "targetClass": "Department",
          "required": false
        },
        "profileSubmitted": {//流程管理员
          "type": "Pointer",
          "targetClass":"Profile",
          "required": false
        },
        "name": {//流程名称
          "type": "String",
          "required": false
        },
        "num": {//报送名额
          "type": "Number",
          "required": false
        },
        "code": {//流程唯一标识
          "type": "String",
          "required": false
        },
        "startDate": {//开始时间
          "type": "Date",
          "required": false
        },
        "deadline": {//截止时间
          "type": "Date",
          "required": false
        },
        /**@状态
         * 100:暂停
        * 200：已开始
        * 201：评审中
        * 300:已公示
        * 400:已提交（完成）
        */
        "status": {
          "type": "String",
          "required": false
        },
        "releaseDate": {//公示时间
          "type": "Date",
          "required": false
        },

        "collectStartData": {//收集文件开始时间
          "type": "Date",
          "required": false
        },
        "collectEndData": {//收集文件结束时间
          "type": "Date",
          "required": false
        },
    },
    ```
  - EduReview 【评审记录】
    ```js
    "fields": {
      "eduTextbook": { //评审教材
        "type": "Pointer",
        "targetClass": "EduTextbook",
        "required": false
      },
      "user": {
        "type": "Pointer",
        "targetClass": "_User",
        "required": false
      },
      "profile": { //评审用户
        "type": "Pointer",
        "targetClass": "Profile",
        "required": false
      },
      "score": {//最终分数
        "type": "Number"
      },
      "num":{//预估分
        "type": "Number"
      },
      "verify":{//是否完成
        "type": "Boolean"
      }
    },
    ```
  - ExpertGroup 【评审组】
    ```js
    "fields": {
      "name":{//活动名称
        "type": "String",
        "required": false
      },
      "eduProcess": { //评审流程
        "type": "Pointer",
        "targetClass": "EduProcess",
        "required": false
      },
      "textbookList": {//评审教材列表
        "type": "Array",
        "required": false
      },
      "reviewList": {//评审教师列表
        "type": "Array",
        "required": false
      },
      "checkTextbook": {//选择教材类型
        "type": "String",
        "required": false
      },
      "checkReview": {//选择评审专家类型
        "type": "String",
        "required": false
      },
    },
    ```

### 后端语言及框架
- 编写语言：JavaScript
- 技术框架：[Node.js](https://bcn4fkmpxbdo.feishu.cn/wiki/N10SwNRbmi2lfCktmAacehxInzf#share-W6CqdCNP7oyKTMxFjKTcxSIgnYd "Node.js")、[Parse Server](https://parseplatform.org/parse-server/api/7.3.0/ "Parse")
### 前端语言及框架
- 编写语言：TypeScript、HTML、SCSS
- 技术框架：Angular18、Vite、NG-ZORRO

## 二、客户端前端
### 用户操作逻辑介绍
graph TB
    Start(开始) --> User[用户登录页]
    User --> Register[注册]
    User --> Login[用户登录]
    Register --> Regcomplete[注册成功]
    Regcomplete --> User
    Login --> Manage[登录成功进入后台]
    Manage --> LogOut[退出登录]
    LogOut --> End(结束)

### 身份权限
- 登录成功时存储本地缓存profile;
- 通过profile.identity身份判断角色验证
- 全局采用路由守卫 [auth.guard.ts](projects\textbook\src\services\auth.guard.ts "Parse")
### 页面路由
- 入口路由文件：projects\textbook\src\app\app.routes.ts
- 各个身份子路由
  - user/login //默认路由
  - nav-admin //国家级管理员管理平台
  - nav-province-contact //工作联系人：中央部门所属高校联系人、部省合建高校联系人、出版单位联系人、省属高校流程管理员
  - nav-province-school-contact //高校联系人
  - nav-review //教材评审组成员
  - nav-author //作者 / 教师 / 主编
### 主组件
- app-comp-manage【全局组件】：登录成功后跳转后页面显示；包括左侧menu，通过optionsMap身份对应显示身份功能栏
  - 附件下载功能函数：openFile、downloadFile
- app-comp-nav 【个人信息组件】：页面左下角个人信息，包括退出登录功能
- app-comp-upload 【文件上传组件】
  - provider-oss-aliyun 引入阿里OSS处理函数等，含公开转私有、私有转公开、临时链接等
  - 重点传入参数
    - @files：回显文件
    - @acl 上传类型，是否加密
    - @type 可上传文件类型
    ```js
    //映射关系处理
    get accept() {
      let type;
      switch (this.type) {
        case 'image':
          type = 'image/*';
          break;
        case 'pdf':
          type = 'application/pdf';
          break;
        case 'audio':
          type = 'audio/*';
          break;
        case 'video':
          type = 'video/*';
          break;
        default:
          type = 'file';
          break;
      }
      return type;
    }
    ```
  - 上传成功、失败、删除文件触发回调
    - 函数`change`，返回fileList
- app-textbook 【教材列表组件】
  - 重点传入参数
    - @filterObj 相关权限配置
    - @uid 对应用户
    - @eduProcess 流程id，verify存在时需要
    - @discard 是否删除
    - @recommend 是否推荐
  - 重要函数解释
    - `getTextbook`：获取教材列表重要函数，利用Parse.Query.fromJSON多条件查询，通过设定参数传入接口查询获取教材，满足全平台90%以上页面教材列表管理
    - `getDepartment`：获取所有单位部门
    - `reject`： 退回教材
    - `del`：删除教材
    - `updateStatus`：加入/移除推荐
    - `submit`：提交教材
    - `beforSubmit`：高校联系人提交教材至工作联系人
    - `restore`：恢复教材
    - `exportProcess`：导出教材；功能业务：导出推荐汇总表
    - `submitted`：提交报送跳转，非直接请求报送教材接口
### 各身份模块
##### 用户登录
- parse-authing.ts 【登录服务类】 应用authing登录回调，返回获取登录结果
  - initLoginModal 初始化登录模块
- app-login 登录页
  - new ParseAuthing实例化authing登录服务，通过login成功后回调调用AuthServr服务类，profileVerify处理登录成功后函数
  ```js
    let parseAuthing = new ParseAuthing({
      // 监听事件:登陆成功后,返回用户信息
      login:(user,authClient)=>{
        console.log(user)
        console.log(Parse.User.current());
        Parse.User.current()?.id && this.authServr.profileVerify(this.modal)
      },
      beforeChangeModule:(data:any)=>{
        if(data=='register'){
          this.router.navigate(['/user/account_info'])
        }
      }
    });
    parseAuthing.initLoginModal();
    ```
- app-account-info 工作联系人注册
  - 功能说明
    - 访客用户可在登录页点击“账户申请”进入工作联系人账户申请，国家级管理员可查看资料根据情况是否通过，通过条件会自动校验是否存在，如果流程存在该管理员会自动替换
  - 主要函数方法
    - `validateForm`：基础填写字段
    - `identity`：注册身份
    - `identityFile`：单位联系人认证文件
  - 主要参数说明
    - `getUnitTypes`：获取可选择注册单位
    - `provinceChange`：根据所选单位类型获取对应单位
    - `submitForm`：提交注册信息
    - `profileSave`：提交角色信息
    - `authUser`：注册基础字段信息校验，判断手机号、邮箱是否合规
##### 作者/教师/主编
- 路由模块：`NavAuthorRoutingModule`
- 组件(教材创建)
  - app-basic 【填写基本信息】
    - 必填参数
      - @eduTextbook 教材
    - 主要函数方法
      - `getEduTextbookVolumeList`：获取分册表
      - `upload`：上传附件回调
      - `saveEduTextbook`：保存教材信息
        - 保存接口中判断是否填写完成
        - 判断各分册填写及获取分册id代码
        - 部分主要代码
          ```js
          let eduTextbookVolumes = await this.saveEduTextbookVolume(this.eduTextbookId);
          console.log(eduTextbookVolumes?.isVrifly);
          console.log(eduTextbookVolumes.list);
          this.eduTextbook?.set('childrens', eduTextbookVolumes.list);
          this.eduTextbook?.set('typeNumber', eduTextbookVolumes.list.length);
          isComplete = isComplete && eduTextbookVolumes?.isVrifly;
          //如果填写未完整，仅保存，状态修改待完善101
          if (this.eduTextbook.get('status') == '102' && !isComplete) {
            this.eduTextbook?.set('status', '101');
            this.eduTextbook.set('complete', false);
          } else if (!this.eduTextbook.get('status')) {
            this.eduTextbook?.set('status', '101');
          }
          if(isComplete){
            this.eduTextbook.set('complete', true)
          }
      - `saveEduTextbookVolume`：上传分册数据
    - 子组件调用
      - 上传获批截图  
        ```html
        <app-comp-upload
          [type]="'pdf'"
          (change)="upload($event, 'approvedImgUrl')"
          title="上传教材获批截图"
        ></app-comp-upload>
      - 分册信息
        ```html
        <app-one #children
          [eduTextbookId]="eduTextbookId"
          [eduTextbookVolumeId]="panel?.id"
        ></app-one>
  - app-textbook-pertain【填写教材适用情况】
    - 必填参数
      - @eduTextbook 教材
    - 主要函数方法
      - `saveEduTextbook`：保存教材信息
      - `saveEduTextbookVolume`：上传分册数据
    - 子组件调用
      - 分册信息
      ```html
      <app-author
        #children
        [eduTextbook]="eduTextbook?.id"
        [eduTextbookVolume]="panel"
      ></app-author>
  - app-textbook-content【填写教材内容】
    - 必填参数
      - @eduTextbook 教材
    - 主要函数方法
      - `saveEduTextbook`：保存教材信息
      - `saveEduTextbookVolume`：上传分册数据
    - 子组件调用
      - 分册信息
      ```html
      <app-three
        #children
        [eduTextbook]="eduTextbook?.id"
        [eduTextbookVolume]="panel"
      ></app-three>
      ```
  - app-faith【填写教材内容】
    - 必填参数
      - @eduTextbook 教材
    - 主要函数方法
      - `saveEduTextbook`：保存教材信息
      - `saveEduTextbookVolume`：上传分册数据     
      - `verify`：校验填写字段是否匹配
  - app-attachment【上传附件材料】
    - 必填参数
      - @eduTextbook 教材
    - 主要函数方法
      - `saveEduTextbook`：保存教材信息
      - `saveEduTextbookVolume`：上传分册数据
      - `examineNull`：检查本页必填是否存在空项
      - `isIgnoreFiledNull`：判断教材所有字段是否必填
      - `upload`：上传附件回调
    - 子组件调用
      - app-comp-upload 附件上传组件
      - 分册信息
        ```html
        <app-author-file
          #children
          [eduTextbook]="eduTextbook?.id"
          [eduTextbookVolume]="panel"
        ></app-author-file>
        ```
- ###### 个人空间：app-space
  - 调用组件：app-textbook
  - 主要函数：`getEduProcess` 获取教师所属单位的流程
- 创建教材：app-apply
  - 组件调用
    - app-basic
    - app-textbook-pertain
    - app-textbook-content
    - app-faith
    - app-attachment
  - 应用代码
  ```html
   @switch (state) { @case (0) {
    <app-basic
      [eduTextbook]="textBook"
      (state)="changeState($event)"
      (save)="save()"
    ></app-basic>
    } @case (1) {
    <app-textbook-pertain
      [eduTextbook]="textBook"
      (state)="changeState($event)"
      (save)="save()"
      (maxWidth)="(submitComp.style.width)"
    ></app-textbook-pertain>
    } @case (2) {
    <app-textbook-content
      [eduTextbook]="textBook"
      (state)="changeState($event)"
      (save)="save()"
      (maxWidth)="(submitComp.style.width)"
    ></app-textbook-content>
    }@case (3) {
      <app-faith
        [eduTextbook]="textBook"
        (state)="changeState($event)"
        (save)="save()"
        (maxWidth)="(submitComp.style.width)"
      ></app-faith>
      } 
    @case (4) {
    <app-attachment
      [eduTextbook]="textBook"
      (state)="changeState($event)"
      (save)="save()"
      (maxWidth)="(submitComp.style.width)"
    ></app-attachment>
    } }
  ```
- ###### 回收站：app-recycle
    - 调用组件：app-textbook
    ```html
    <app-textbook
    [filterObj]="filterObj"
      [uid]="user?.id"
      path="/nav-author/manage/details"
      [discard]="true"
    ></app-textbook>
    ```
##### 国家级管理员管理平台
- 路由模块：`NavAdminRoutingModule`
- 组件
  - app-collect-textbook 【教材文件收集列表】
    - 必填参数
      - @eduProcess 流程id
    - 主要函数方法
      - `getTextbook`：获取教材
      - `onEmitMsg`：短信提醒，提交状态置为100
      - `onReject`：退回教材给出版社
  - app-profile 【工作联系人】
    - 必填参数
      - depart：单位，用于查询本单位下注册联系人身份用户列表
    - 主要函数方法
      - `getProfile`：获取用户角色列表
- 全部教材：app-page-textbook
  - 功能说明
    - 批量查看已提交至国家级管理员教材、教材文件上传管理
  - 调用组件
    - app-textbook
    - app-collect-textbook
- 申报流程：app-process-list
  - 功能说明
    - 进入页面通过`getDepart`方法获取所有一级节点部门
    - 点击左侧流程调用`getDepart`获取对应一级节点下全部流程，右侧显示流程table
    - table操作栏可对流程进行操作，可操作按钮通过statusMap状态判断
    - 可通过`toUrl('/nav-admin/manage/process/create')`跳转创建流程页
  - 主要参数说明
    - `activeDepart`：当前编辑部门
    - `eduProcessList`：流程列表
    - `formatStatus`：格式化流程状态
    - `formatFileStatus`：教材上传状态
  - 主要函数方法
    - `getDepart`：获取部门单位节点
    - `getEduProcess`：获取部门列表
    - `formatNode`：格式化链
    - `statusSelected`：操作流程，开始、暂停、结束、删除
    - `onStatusChange`：暂停流程
    - `openEditCollect`：保存收集文件设置
    - `sendNoticeMSG`:短信通知
- 流程详情：app-page-process
  - 功能说明
    - 默认进入报送流程tab栏，可修改流程名称、限额、工作联系人等相关配置信息
    - 教材列表可查看已提交到国家级管理员的所有教材
    - 教材文件功能查看上传教材的状态
  - 调用组件
    - app-process-create：流程管理
    - app-textbook
    - app-collect-textbook
- ###### 用户列表：app-page-user
  - 功能说明
    - 本页权限通过`tbookSer.profile.identity`判断给与相应权限，包含国家级管理员、高校联系人管理权限区分；国家级管理员可管理平台所有身份用户，工作联系人可管理所属部门节点及下级节点的部门用户，不包含“工作联系人”身份的用户
    - 统一管理平台所有用户，可创建、删除、修改用户
    - 创建用户：点击`addMember`弹出创建用户弹窗，填写用户基本信息后点击确定完成请求创建用户，人员类型校验必须选择部门后才能选择
    - 在操作通过认证时，如果该工作联系人对应流程未被绑定，则会自动校验绑定至该单位的流程作为流程管理员（注：每一个流程只能对应一个流程管理员），诺已绑定，则会取消本次绑定事件，但会继续通过身份认证。部分代码逻辑
    ```js
    //更新工作联系人textbook.ts
    async updateProfileSubmitted(
      pid: string,
      type: string,
      dpid?: string,
      msg?: NzMessageService
    ): Promise<boolean | undefined> {
      console.log(pid, type, dpid);
      let query = new Parse.Query('EduProcess');
      if (!dpid) {
        query.equalTo('profileSubmitted', pid);
      } else {
        query.equalTo('department', dpid);
      }
      query.include('profileSubmitted');
      query.select('objectId', 'profileSubmitted');
      query.notEqualTo('isDeleted', true);
      let res = await query.first();
      if (!res?.id) {
        msg?.warning('所属单位不存在');
        return false;
      }
      if (type == 'del') {
        //删除工作联系人
        res.set('profileSubmitted', null);
        await res.save();
        return true;
      } else {
        //添加工作联系人
        if (res?.get('profileSubmitted')?.get('user')) {
          msg?.warning('该单位已有部门联系人，请先移除后再操作');
          return false;
        }
        res?.set('profileSubmitted', {
          __type: 'Pointer',
          className: 'Profile',
          objectId: pid,
        });
        await res.save();
        return true;
      }
      return false;
    }
    ```
  - 主要参数说明
    - `profiles`：用户列表
    - `parentList`：部门选择
    - `filters`:筛选条件，用于列表搜索或列表条件查询等
  - 主要函数方法
    - `getProfile`：获取用户列表
    - `updateUrlPageIndex`：更新页面，路由返回功能回显作用
    - `updateUser`：更新用户信息，通过认证或删除操作
    - `addMember`：添加用户初始化，初始化后通过`accountIsVisible`设置弹窗弹出用户信息弹出，对其设置
    - `getDepart`：获取部门信息
    - `onCheckedDepart`:选择所属类别下级列表
    - `accountComplete`：添加账号，需通过`authVrifly`表单信息校验
    - `authVrifly`：用户信息表单校验，含邮箱格式正则、手机号正则等
- 用户管理&编辑：app-user-edit
  - 功能说明
    - 路由须携带用户ID
    - 编辑、查看用户信息，含扩展字段，省份、职务等
    - 修改密码：判断是否具有修改权限，如果有可直接在弹出中修改，修改后会直接通过邮箱或短信通知
    - 用户教材：可查看该用户所创建的所有教材，含未提交的，组件传入参数
    ```js
    submitTextBook = {
      status:['102','103','200','201','300','400'],
      btns:{
        review:true,//查阅
      }
    }
    ```
  - 调用组件
   - app-textbook
   ```html
    <app-textbook
      [filterObj]="submitTextBook"
      [uid]="user?.id"
    ></app-textbook>
    ```
  - 主要参数说明
    - `user`：用户信息
    - `profileJson`：身份编辑数据
    - `userJson`：user编辑数据，用于编辑未保存提示
    - `edit`：是否可编辑权限
    - `profile`：用户角色信息
  - 主要函数方法
    - `updateUser`：操作用户删除、变更用户状态
    - `handleOk`：变更密码
    - `showModalDepart`：选择部门
    - `getDepart`：根据所选单位类型获取对应单位
    - `onCheckedDepart`：选择部门
    - `completeChange`：选择部门
    - `submitForm`：修改账户所属部门单位
    - `updateUserJson`：更新用户数据
    - `moduleChange`：判断编辑数据变化
    - `updateCanDeActivate`：保存提示
- ###### 申报单位管理：app-page-role
  - 功能说明
    - 通过权限判断获取单位部门`getDepart`,存储nodes节点，部分代码
    ```js
    nodes.push({
      title: item.get('name'),
      key: item.id,
      children: [],
      branch: item.get('branch'),
      parent: item.get('parent')?.id, //上级
      isLeaf: !item.get('hasChildren'), //是否是最下级
      type: item.get('type'),
    });
    ```
    - 点击部门获取用户列表，在左侧用户显示，可对其管理
    - 添加成员：`addMember`添加选中部门的用户角色，工作联系人管理状态下默认同节点
    - 添加部门：国家级管理员可添加任意节点部门、工作联系人可添加当前单位下的部门
    - 使用`nz-tree`组件，通过`contextmenu`函数调用鼠标左击直接操作选中单位可编辑的权限
  - 主要参数说明
    - `nodes`：部门节点
    - `profileLength`：用户列表
    - `activatedNode`：当前选中节点
    - `activeDepart`：当前编辑部门
    - `editObject`：编辑部门字段，单项赋值绑定
  - 主要函数方法
    - `getDepart`：获取部门单位
    - `getProfile`：获取用户列表
    - `contextMenu`：鼠标右击事件
    - `onDelDepart`：删除部门
    - `showModalDepart`：初始化用户
    - `handleOk`：保存编辑&新增部门
    - `showProfile`：变更用户部门
    - `accountComplete`：添加账号
    - `updateProfile`：保存更新部门
##### 工作联系人：中央部门所属高校联系人、部省合建高校联系人、出版单位联系人、省属高校流程管理员
- 路由模块：`NavProContactRoutingModule`
- 组件
  - app-review-details 【评审教材列表】
    - 必填参数
      - @eduProcess 流程id
      - @filterObj 评审组条件
    - 主要函数方法
      - `getTextbook`：获取教材
      - `export`：导出表格
  - app-upload-collect 【教材上传列表】
    - 必填参数
      - @eduTextbookVolume 分册信息
    - 主要函数方法
      - `upload`：上传回调参数处理
      - `submitForm`：提交教材文件
- 全部教材：app-page-textbook
  - 功能说明
    - 批量查看已提交至国家级管理员教材、教材文件上传管理
    - 通过`textbook?.export()`调用`app-textbook`组件直接下载汇总表
    ```html
    <app-textbook
      hidden
      #textbook
      [recommend]="true"
      [filterObj]="filterObj"
      [eduProcess]="eduProcess"
    ></app-textbook>
    ```
  - 调用组件
    - app-textbook
  - 主要参数说明
    - `beforeFilterObj`：待审核参数
    ```js
      beforeFilterObj: any = {
        showMore: true, //显示更多字段
        isCheck: true,
        noStared: true,
        status: ['200'],
        btns: {
          export: true,
        },
      };
    ```
    - `afterFilterObj`：已加入推荐
    ```js
    afterFilterObj: any = {
      showMore: true, //显示更多字段
      isCheck: true,
      status: ['200', '201', '400'],
      btns: {
        // remove: true, //移除推荐
        export: true,
      },
    };
    ```
  - 主要函数方法
    - `getProcess`：获取流程
    - `submitted`：报送
- 申报流程：app-process-list
  - 功能说明
   - 查看本单位申报流程的流程，可点击教材名称进入详情查看
  - 主要参数说明
   - formatStatus：格式化流程状态，收集状态
  - 主要函数方法
   - getEduProcess：获取流程
- 流程详情：app-page-process
  - 功能说明
    - 流程管理可以查看报送限额、开始及结束时间
    - 评审活动显示评审活动时间
    - 显示所有的已提交的教材列表;
  - 调用组件
    - app-process-create
    - app-textbook
    ```html
     <div class="title">待评审教材列表</div>
      <app-textbook
        [filterObj]="beforeFilterObj"
        [eduProcess]="eduProcess"
      ></app-textbook>
      <br />
      <div class="title">推荐教材列表</div>
      <app-textbook
        [filterObj]="afterFilterObj"
        [recommend]="true"
        [eduProcess]="eduProcess"
      ></app-textbook>
    ```
    ```js
    //待审核
    beforeFilterObj: any = {
      showMore: true, //显示更多字段
      isCheck: true,
      noStared: true,
      status: ['200'],
      btns: {
        export: true,
      },
    };
    //已加入推荐
    afterFilterObj: any = {
      showMore: true, //显示更多字段
      isCheck: true,
      status: ['200', '201', '400'],
      btns: {
        export: true,
      },
    };
    ```
  - 主要参数说明
    - `beforeFilterObj`：待审核
    - `afterFilterObj`：已加入推荐
    - `activity`：评审活动
  - 主要函数方法
    - `getActivity`：获取评审活动
    - `getExpertGroup`：获取评审组
    - `startActivity`：开始活动
    - `onCompute`：计算平均分
    - `getEduReview`：获取评审详情列表
- 创建&编辑评委活动：app-activity
  - 功能说明
    - 编辑评审活动，name、startDate、deadline；评审端评选打分教材以此时间为准，评审端通过条件判断是否在评审时间段内为评审条件之一
    - 评审规则：平均数mean、截尾平均数truncatedMean用于计算时评审计平均分分
    - 评审组：设置教材可评审的评审人员，教材与评审组关系：n:1
  - 调用组件
    - app-comp-upload
    ```html
    //上传教材评审文件，在评审端评审时可在弹出的打分中看到该文件
    <app-comp-upload
      [type]="'pdf'"
      (change)="upload($event)"
      title="上传评审细则文件"
    ></app-comp-upload>
    ```
    - app-review-details
    ```html
    <app-review-details
      [listOfFilter]="listOfFilter"
      [filterObj]="filterObj"
    ></app-review-details>
    ```
  - 主要参数说明
    - `filterObj`：评审明细子组件介绍参数条件；showGroup：显示评审组名称，contained：指定评审组查询，bookMap：教材对应评审组结构{booid:评审组名称}
  - 主要函数方法
    - `getActivity`：获取评审活动
    - `getExpertGroup`：评审组
    - `deleteGroup`：删除评审组
- 创建&编辑评委组：app-review-edit
  - 功能说明
    - 默认评审教材为全部，可以onchangeTextbook切换手动选中教材，选中手动选中教材后，显示教材列表app-textbook组件，可勾选后确定，在app-textbook组件的回调中获取勾选的教材赋值变量在保存请求
    - 默认评审专家为全部，可手动onchangeReview切换手动选择专家，选择的专家会在右侧
    - 所有设置均有回显类名class="dep-right"元素显示，也会在当前类class="dep-comp"显示选中样式
    - 教材评审明细显示已打分的各个评审专家分数
  - 调用组件
    - app-review-details
      ```html
      <app-review-details [filterObj]="reviewFilterObj"></app-review-details>
      ```
    - app-textbook
      ``` html
      //setOfCheckedTextbookAll传入已选择的id
      //updateCheck是组件选中后确认的回调方法，通过@ViewChild获取组件元素服务，直接调用组件方法赋值给页面
      <app-textbook
        [hidden]="radio == 'all'"
        [filterObj]="filterObj"
        [eduProcess]="eduProcess"
        [setOfCheckedId]="setOfCheckedTextbookAll"
        (updateCheck)="updateCheck()"
        #textbook
      >
      ```
  - 主要参数说明
    - `eduProcess`：申报流程
    - `expertGroup`：评审组对象
    - `checkProfileListLeng`：已选择评审专家人数
    - `reviewFilterObj`：传入评审组件显示列表参数
  - 主要函数方法
    - `refresh`：初始化评审组，回显、赋值可编辑信息
    - `getTextName`：获取展示已选择教材
    - `getExpertGroup`：获取所有已被其他评审组选择过教材
    - `getProfile`：获取评委专家
    - `updateAllChecked`：选中专家改变事件
    - `onSave`：保存评审设置，评审教材、评审专家
- 本社教材源文件：app-collect-file
  - 功能说明
   - 本路由权限只有出版社部门节点下的联系人有权限进入，在app-comp-manage初始化组件中做逻辑menu权限判断
    - comp-manage.component.ts相关代码
      ```js
      if (this.tbookSer.profile.identity == '工作联系人' && this.tbookSer.profile.user?.department?.branch == '出版单位') {
      this.optionsMap['工作联系人'][0].child.push({
          name: '本社教材源文件',
          path: '/nav-province-contact/manage/collect',
          id: '1-3',
        });
      }
      ```
   - 根据教材分册所选出版社，editionUnit字段是否等于当前出版社单位筛选条件；查询对应教材是否在上传期间条件：EduTextbook.eduProcess.status流程状态需要是已提交至国家级管理员
   - 因为单本教材可能涉及到多个分册，每个分册可能不同出版单位，我们在教材获取下来后在分册中做了本次教材校验，且在上传中只能对应选中本出版单位本册可操作、查看，以下部分代码，通过判断渲染可编辑权限教材
      ```html
      @for (panel of currentTextbook?.get('childrens'); track panel) { @if
            (panel?.get('editionUnit') == tbookSer.profile.user?.department?.name) {
          <nz-collapse-panel
            #p
            [nzHeader]="title"
            [nzActive]="false"
            nzExpandedIcon="caret-right"
            style="
              background: #f7f7f7;
              border-radius: 4px;
              margin-bottom: 24px;
              border: 0px;
            "
          >
            <ng-template #title>
              <span class="panel-title">{{
                "分册" + ($index + 1) + "-" + currentTextbook?.get("title")
              }}</span>
            </ng-template>
            <div class="">
              <app-upload-collect
                #children
                [eduTextbookVolume]="panel"
              ></app-upload-collect>
            </div>
            <ng-template #expandedIcon let-active>
              {{ active }}
              <span
                nz-icon
                nzType="caret-right"
                class="ant-collapse-arrow"
                [nzRotate]="p.nzActive ? 90 : -90"
              ></span>
            </ng-template>
          </nz-collapse-panel>
          } }
      ```
   - 管理员可点击列表编辑打开弹出后，进行教材上传
  - 调用组件
    - app-upload-collect
  - 主要参数说明
    - `textbookList`：教材列表
    - `statusMap`：任务状态，映射关系：{教材id：{状态，状态色、可操作按钮}}
  - 主要函数方法
    - `fromatFiled`：格式化拓展表字段
    - `getTextbook`：获取教材
    - `getEduProcess`：获取流程
    - `onEditModal`：上传教材打开上传弹窗
    - `saveCollect`：保存上传教材
    - `saveEduTextbookVolume`：保存事件推送上传教材
- 用户列表：app-page-user 功能同国家级管理员[用户列表](#用户列表app-page-user)
- 申报单位管理: app-page-role 功能同国家级管理员[申报单位管理](#申报单位管理app-page-roler)
##### 高校联系人
- 路由模块：`NavProSchoolRoutingModule`
- 全部教材：app-page-textbook
  - 功能说明
    - 高校联系人只有省级教育行政部门、有关部门（单位）教育司（局）单位下级省局单位下高校有登录权限，当教师属于该单位下时，默认提交到高校联系人，由高校联系人审核后提交至工作联系人
    - 在本页高校联系人可查看对应高校及以下单位的所有教师教材，含未提交
    - 待提交教材可点击提交至工作联系人，代评审教材为未被专家评审或未提交报送的教材
  - 调用组件
    - app-textbook
  - 主要参数说明
    - `draftFilterObj`：获取草稿传参子组件调教
    - `beforeFilterObj`：获取待提交教材子组件调教
    - `afterFilterObj`：获取待评审教材子组件调教
  - 主要函数方法
    - `getProcess`：获取流程
- 用户列表：app-page-user 功能同国家级管理员[用户列表](#用户列表app-page-user)
- 申报单位管理: app-page-role 功能同国家级管理员[申报单位管理](#申报单位管理app-page-roler)
- 个人空间：app-space 功能同教师[个人空间](#个人空间app-space)
- 回收站：app-recycle 功能同教师[回收站](#回收站app-recycle)
##### 教材评审专家
- 路由模块：`NavReviewRoutingModule`
- 教材打分：app-approve
  - 功能说明
    - 根据评审组件获取该评审专家对应可打分评审的教材，首先需先确定该流程下的评审活动已开启，是否在评审期间以活动时间为准，以下为权限及获取打分教材部分代码
      ```js
      //判断是否在获取期间
      get isStart(): boolean {
        return (
          !this.activity?.get('startDate') ||
          this.activity?.get('startDate') > new Date()
        );
      }
      ```
      ```html
      <!-- 获取教材列表组件 -->
      <app-textbook
        (markScore)="onMarkScore($event)"
        [filterObj]="beforeFilterObj"
        [scoreMap]="scoreMap"
        #textbook
      ></app-textbook>
      ```
      ```js
      //待评分
      beforeFilterObj: any = {
        showMore: true, //显示更多字段
        isCheck: true,
        status: ['200'],
        contained: [],
        btns: {
          eduReivew: true,
          mark: true,
        },
      };
      //已评审
      afterFilterObj: any = {
        showMore: true, //显示更多字段
        isCheck: false,
        status: ['200','201','300','400'],
        contained: [],
        btns: {
          eduReivew: true,
        },
      };
      ```
    - 点击打分可在可允许打分条件下弹窗，若已过或未开始评审时间，则打分弹窗会被阻止；评审专家可在打分弹窗中打分，可先保存`saveScore('save')`或提交`saveScore('submit')`
  - 调用组件
    - app-textbook
  - 主要参数说明
    - `eduProcess`：流程
    - `editTextbook`：评分教材
    - `eduReview`：评审记录
    - `scoreMap`：教材评审状态
  - 主要函数方法
    - `authCompute`：校验是否在评审期间
    - `isStart`：校验评审是否开始
    - `isEnd`：校验评审是否结束
    - `getEduReview`：获取评审记录
    - `getExpertGroup`：获取评审组相关教材，评审专家可对应对各评审组，需把所有评审组中教材获取后传递给渲染组件获取可评审教材
    ```js
    //部分代码逻辑
    r.forEach((item) => {
      item.get('textbookList')?.forEach((obj: any) => {
        this.beforeFilterObj.contained.push(obj.id || obj.objectId);
      });
    });
    ```
    - `getActivity`：获取评审活动
    - `onMarkScore`：打开评审弹窗
    - `saveScore`：提交打分
##### 其他公共模块
- 教材详情：app-textbook-details
  - 功能说明
    - 展示教材所有基础信息，含附件
    - 联系人或国家级管理员身份可查看评审分数
  - 主要参数说明
    - `textBook`：教材对象
  - 主要函数方法
    - `getExpertGroup`：获取评审组
    - `getEduReview`：获取本教程评分
- 补充上传教材作者诚信承诺：app-upload-pdf
  - 功能说明
    - 创建时未上传作者签名文件等，可链接本页面填写添加补全
  - 主要参数说明
    - `authorSignPDF`：上传签名页
    - `unitMaterial`：申报单位承诺意见
  - 主要函数方法
    - `saveEduTextbook`：获取教材签名文件是否上传
    - `upload`：上传文件回调
### 项目公共服务类services
- auth.guard.ts：路由守卫
- auth.service.ts：用户身份服务
- compute.ts：科学计算
- majors.map.ts：教材所属专业类map（6位）
- majors4.map.ts：教材所属专业类map（4位）
- shell.ts：定制化导出脚本
- textbook.ts：教材common服务