Browse Source

Merge branch 'master' of http://git.fmode.cn:3000/bin/edu-textbook

ryanemax 7 months ago
parent
commit
55ee31a54a

+ 6 - 3
projects/textbook/src/app/textbook/textbook.component.html

@@ -22,15 +22,18 @@
     #tableData
     [nzData]="textbookList"
     [nzTotal]="count"
-    [nzPageSize]="limit"
+    [(nzPageSize)]="limit"
     [nzPageIndex]="pageIndex"
     style="margin: 10px 0"
     [nzLoading]="loading"
     nzSize="middle"
     [nzFrontPagination]="false"
-    [nzScroll]="{ x: (maxWidth || '1200') + 'px' }"
+    [nzScroll]="{ x: (maxWidth || '1200') + 'px',y: '580px' }"
     nzTableLayout="fixed"
+    [nzPageSizeOptions]="[ 10, 20, 30, 40, 50 ]"
+    nzShowSizeChanger
     (nzPageIndexChange)="pageIndexChange($event)"
+    (nzPageSizeChange)="onPageSizeChange($event)"
   >
     @if(filterObj?.showMore){
     <thead>
@@ -715,7 +718,7 @@
   }
 </div>
 <!-- 全选操作:批量操作 -->
-@if (filterObj.isCheck && setOfCheckedId.size) {
+@if (filterObj.isCheck && setOfCheckedId.size && !filterObj?.showText) {
 <div class="batch-toolbar-modal" [style.left]="'calc(50% - ' + calc + 'px)'">
   <div class="batch-toolbar">
     <div class="styles_counter__18S08">

+ 3 - 0
projects/textbook/src/app/textbook/textbook.component.scss

@@ -87,4 +87,7 @@
   align-items: center;
   justify-content: center;
   background: rgb(0 0 0 / 30%);
+}
+::ng-deep .nz-table-hide-scrollbar{
+  scrollbar-color: auto;
 }

+ 34 - 17
projects/textbook/src/app/textbook/textbook.component.ts

@@ -57,6 +57,11 @@ export class TextbookComponent implements OnInit {
     isCheck: true,
     noStared: false, //非推荐
     team: false, //只获取当前部门或下级部门所属用户的教材,且不限制是否提交
+    showText:false, //选择后不展示定位btns,只展示相关教材名称列表
+    notContained:[],//不包含教材(查询时自动排除)
+    eduReivew:{ //评审相关
+
+    },
     btns: {
       edit: false, //编辑
       isDelete: false, //删除权限
@@ -156,7 +161,7 @@ export class TextbookComponent implements OnInit {
   };
   loading: boolean = false;
   checkedAll: boolean = false; //全选
-  setOfCheckedId = new Set<string>();
+  @Input('setOfCheckedId') setOfCheckedId = new Set<string>();
   searchValue: string = '';
   manage: boolean = false;
   calc: number = 150;
@@ -215,7 +220,7 @@ export class TextbookComponent implements OnInit {
       }
     });
   }
-  async getTextbook(val?: string, exported?: boolean): Promise<any[] | void> {
+  async getTextbook(val?: string, exported?:{excel:boolean, id:boolean}): Promise<any[] | void> {
     this.loading = true;
     let queryParams:any = {
       where: {
@@ -302,6 +307,7 @@ export class TextbookComponent implements OnInit {
     this.recommend && query.equalTo('recommend', true);
     this.filterObj?.noStared && query.notEqualTo('recommend', true);
     this.uid && query.equalTo('user', this.uid);
+    this.filterObj?.notContained && query.notContainedIn('objectId', this.filterObj.notContained);
     query.descending('createdAt');
     query.notEqualTo('isDeleted', true);
     if (!this.uid && !this.filterObj.team) {
@@ -315,7 +321,11 @@ export class TextbookComponent implements OnInit {
     } else {
       query.notEqualTo('discard', true);
     }
-    if (exported) {
+    if (exported?.excel) {
+      query.limit(1000);
+      if(exported?.id){
+        query.select('objectId')
+      }
       let r = await query.find();
       this.loading = false;
       return r;
@@ -374,6 +384,13 @@ export class TextbookComponent implements OnInit {
     this.pageIndex = e;
     this.getTextbook(this.searchValue);
   }
+  //切换分页条数
+  onPageSizeChange($event:any): void {
+    console.log(this.limit);
+    this.onAllChecked(false)
+    this.pageIndex = 1
+    this.getTextbook()
+  }
   //全选
   onAllChecked(checked: boolean) {
     console.log(checked);
@@ -759,25 +776,25 @@ export class TextbookComponent implements OnInit {
       //   this.tbookSer.profile.user?.department?.objectId
       // );
       await this.tbookSer.tbookExportReport({ bookList: bookList });
+      // Parse.Cloud.run('tbookExportReport', { bookList: bookList }).then(
+      //   (data) => {
+      //     console.log(data);
+      //     let url = data.zipUrl;
+      //     url = url.replaceAll("http://","https://");
+      //     const a = document.createElement('a'); // 创建一个&lt;a&gt;元素
+      //     a.href = url; // 设置链接的href属性为要下载的文件的URL
+      //     a.download = '报送流程'; // 设置下载文件的名称
+      //     document.body.appendChild(a); // 将&lt;a&gt;元素添加到文档中
+      //     a.click(); // 模拟点击&lt;a&gt;元素
+      //     document.body.removeChild(a); // 下载后移除&lt;a&gt;元素
+      //   }
+      // );
       this.showLoading = false;
     } catch (err) {
       this.message.error('导出超时,请稍后重试');
       console.log(err);
       this.showLoading = false;
     }
-    // Parse.Cloud.run('tbookExportReport', { bookList: bookList }).then(
-    //   (data) => {
-    //     console.log(data);
-    //     let url = data.zipUrl;
-    //     url = url.replaceAll("http://","https://");
-    //     const a = document.createElement('a'); // 创建一个&lt;a&gt;元素
-    //     a.href = url; // 设置链接的href属性为要下载的文件的URL
-    //     a.download = '报送流程'; // 设置下载文件的名称
-    //     document.body.appendChild(a); // 将&lt;a&gt;元素添加到文档中
-    //     a.click(); // 模拟点击&lt;a&gt;元素
-    //     document.body.removeChild(a); // 下载后移除&lt;a&gt;元素
-    //   }
-    // );
   }
   toUrl(url: string, param?: Object) {
     console.log(url);
@@ -835,7 +852,7 @@ export class TextbookComponent implements OnInit {
       query.include('profileSubmitted', 'profileSubmitted.user');
       query.select('name', 'profileSubmitted');
       let r = await query.first();
-      let data: any = await this.getTextbook('', true);
+      let data: any = await this.getTextbook('',{excel:true, id:false});
       let table = `<table border="1px" cellspacing="0" cellpadding="0">
           <thead>
           <tr>

+ 77 - 3
projects/textbook/src/modules/nav-province-contact/activity/activity.component.html

@@ -4,18 +4,92 @@
       <span nz-icon nzType="left" nzTheme="outline"></span>返回
     </div>
   </nz-breadcrumb>
-  <nz-page-header-title
-    >{{ eduProcess?.get("name") }}评委活动
+  <nz-page-header-title>{{activity?.get('name')?activity?.get('name'):'创建评委活动'}}
   </nz-page-header-title>
 </nz-page-header>
 <div class="edit-content">
   <nz-tabset [(nzSelectedIndex)]="active">
     <nz-tab nzTitle="活动规则设置">
       @if (active == 0) {
+      <div class="basic">
+        <div class="title">基本信息</div>
+        <ul nz-row nzJustify="space-between">
+          <li nz-col nzSpan="11">
+            <p>评审活动名称<span>*</span></p>
+            <input nz-input placeholder="请输入评审活动名称" [(ngModel)]="name" type="text" />
+          </li>
+          <li nz-col nzSpan="11">
+            <p>开始时间<span></span></p>
+            <nz-date-picker nzShowTime nzFormat="yyyy-MM-dd HH:mm:ss" [(ngModel)]="startDate"></nz-date-picker>
+          </li>
+          <li nz-col nzSpan="11">
+            <p>结束时间<span></span></p>
+            <nz-date-picker nzShowTime nzFormat="yyyy-MM-dd HH:mm:ss" [(ngModel)]="deadline"></nz-date-picker>
+          </li>
+        </ul>
+
+        <button (click)="save()" class="btn save" nz-button nzType="primary">保存</button>
+        <button (click)="reset()" class="btn replay" nz-button nzType="primary">重置</button>
+        <div class="title">评审规则</div>
+        <ul nz-row nzJustify="space-between">
+          <li nz-col nzSpan="11">
+            <p>总分值计算方式<span>*</span></p>
+            <nz-radio-group [(ngModel)]="calculation" (ngModelChange)="changeCalculation()">
+              <label nz-radio nzValue="mean">平均数</label>
+              <label nz-radio nzValue="truncatedMean">截尾平均数</label>
+            </nz-radio-group>
+          </li>
+          <li nz-col nzSpan="11">
+            <p>评审细则</p>
+            @if(reviewDetails?.url){
+            <a target="_blank" [href]="reviewDetails?.url">
+              <span nz-icon nzType="file" nzTheme="outline"></span>
+              {{reviewDetails?.name}}
+            </a>
+            }
+
+            <div class="uploadBtn">
+              <app-comp-upload [type]="'pdf'" (change)="upload($event)" title="上传评审细则文件"></app-comp-upload>
+            </div>
+
+          </li>
+        </ul>
+        <div class="title">
+          评审组
+          <button (click)="creatReviewGroup()" class="btn replay" nz-button nzType="primary">创建评审组</button>
+        </div>
+        @if (expertGroupList.length>0) {
+        <ul class="reviewGroup" >
+          @for (item of expertGroupList; track $index) {
+          <li>
+            <div>
+              <div>{{item?.get('name')||'未命名'}}</div>
+              <p>更新时间:{{item?.get('updatedAt')|date:'yyyy-MM-dd HH:mm:ss'}}</p>
+            </div>
+            <div>
+              <button class="btn replay" nz-button nzType="primary">编辑</button><br>
+              <button (click)="deleteGroup($index)" nz-button nzType="text" nzDanger>删除</button>
+            </div>
+          </li>
+          }
+
+        </ul>
+        }@else {
+        <nz-empty class="empty" nzNotFoundImage="simple" nzNotFoundContent="还没有评审组"></nz-empty>
+        }
+
+
+      </div>
+
+
       }
     </nz-tab>
     <nz-tab nzTitle="评审明细">
-      
+
     </nz-tab>
   </nz-tabset>
+</div>
+
+<div class="loading" [hidden]="!saveLoading">
+  <nz-spin nzSimple [nzSize]="'large'"></nz-spin>
 </div>

+ 103 - 7
projects/textbook/src/modules/nav-province-contact/activity/activity.component.scss

@@ -6,31 +6,127 @@
   text-align: left;
   cursor: pointer;
 }
+
 .edit-content {
   margin: 0 0 20px;
   padding: 0 24px;
   height: calc(100vh - 250px);
-  .title {
-    font-family: PingFang SC;
-    font-size: 20px;
-    font-weight: 500;
-    line-height: 32px;
-    text-align: left;
-    margin-bottom: 16px;
+
+  .basic {
+    .title {
+      width: 100%;
+      font-family: PingFang SC;
+      font-size: 20px;
+      font-weight: bold;
+      line-height: 32px;
+      text-align: left;
+      margin-top: 30px;
+      margin-bottom: 20px;
+      display: flex;
+      justify-content: space-between;
+    }
+
+    ul {
+      margin: 0;
+      padding: 0;
+      li {
+        list-style: none;
+        margin: 10px 0;
+        p {
+          color: rgb(109, 108, 108);
+
+          span {
+            color: red;
+            margin-left: 5px;
+          }
+        }
+        .uploadBtn{
+          width: 150px;
+          text-wrap: nowrap;
+          padding: 5px 10px;
+          border-radius: 5px;
+          background: #EAE6E6;
+          color: gray;
+        }
+      }
+    }
+    .btn{
+      margin-right: 15px;
+      margin-top: 20px;
+      background: #3E49B3;
+      border: none;
+    }
+    .replay{
+      background:#EAE6E6;
+      color: black;
+    }
+    .empty{
+      color: #86909C; 
+      background: whitesmoke;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+    }
+    .reviewGroup{
+      background: whitesmoke;
+      padding: 20px;
+      margin-bottom: 20px;
+      border-radius: 2px;
+      >li{
+      border-radius: 2px;
+        background: white;
+        padding: 20px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        >div>div{
+          font-size: 18px;
+          font-weight: bold;
+        }
+        p{
+          margin: 10px 0;
+        }
+      }
+    }
   }
+
 }
+.loading{
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  text-align: center;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgb(0 0 0 / 30%);
+  z-index: 99;
+}
+
+
+
+
+
+
 ::ng-deep .ant-page-header-heading-title {
   white-space: normal;
 }
+
 ::ng-deep .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
   color: #c6233f;
 }
+
 ::ng-deep .ant-tabs-ink-bar {
   background: #c6233f;
 }
+
 ::ng-deep .ant-tabs-tab:hover {
   color: #e97488;
 }
+
 ::ng-deep .ant-tabs-tab-btn:active {
   color: #e97488;
 }

+ 174 - 2
projects/textbook/src/modules/nav-province-contact/activity/activity.component.ts

@@ -7,6 +7,23 @@ import { ActivatedRoute, Router } from '@angular/router';
 import Parse from 'parse';
 import { TextbookComponent } from '../../../app/textbook/textbook.component';
 import { textbookServer } from '../../../services/textbook';
+
+import { NzInputModule } from 'ng-zorro-antd/input';
+import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzGridModule } from 'ng-zorro-antd/grid';
+import { NzMessageModule } from 'ng-zorro-antd/message';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+import { NzEmptyModule } from 'ng-zorro-antd/empty';
+import { CompUploadComponent } from '../../../app/comp-upload/comp-upload.component';
+import { MatDialog, MatDialogModule } from '@angular/material/dialog';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { FormsModule } from '@angular/forms';
+import { NzSpinModule } from 'ng-zorro-antd/spin';
+import { MatDialogRef } from '@angular/material/dialog';
+import { DatePipe } from '@angular/common';
+
 @Component({
   selector: 'app-activity',
   templateUrl: './activity.component.html',
@@ -17,18 +34,33 @@ import { textbookServer } from '../../../services/textbook';
     CommonCompModule,
     NzTabsModule,
     TextbookComponent,
+    NzInputModule,
+    NzDatePickerModule,
+    NzButtonModule,
+    NzGridModule,
+    NzMessageModule,
+    NzRadioModule,
+    NzEmptyModule,
+    CompUploadComponent,
+    MatDialogModule,
+    NzSpinModule,
   ],
+  providers: [DatePipe],
+
   standalone: true,
 })
-export class ActivityComponent  implements OnInit {
+export class ActivityComponent implements OnInit {
   active: number = 0;
   eduProcess: Parse.Object | undefined;
   constructor(
     private activeRoute: ActivatedRoute,
     public tbookSer: textbookServer,
-    private router: Router
+    private router: Router,
+    private msg: NzMessageService,
+    public dialog: MatDialog
   ) { }
 
+  saveLoading = true
   ngOnInit() {
     this.activeRoute.paramMap.subscribe(async (params) => {
       let id = params.get('id');
@@ -44,6 +76,8 @@ export class ActivityComponent  implements OnInit {
           );
         }
         this.eduProcess = res;
+        this.refersh()
+
       }
     });
   }
@@ -53,4 +87,142 @@ export class ActivityComponent  implements OnInit {
     //   queryParams: { page: this.activeRoute.snapshot.queryParamMap.get('page') },
     // });
   }
+
+
+  async refersh() {
+    this.getActivity()
+    this.getExpertGroup()
+  }
+  company: string = localStorage.getItem('company')!
+  /**评审活动 */
+  activity?: Parse.Object
+  /**获取评审活动 */
+  async getActivity() {
+    console.log(this.eduProcess?.id, this.company)
+    let query = new Parse.Query('Activity')
+    query.notEqualTo('isDeleted', true)
+    query.equalTo('eduProcess', this.eduProcess?.id)
+    query.equalTo('company', this.company)
+    this.activity = await query.first()
+    console.log(this.activity)
+    if (this.activity?.id) {
+      this.name = this.activity?.get('name') || ''
+      this.startDate = this.activity?.get('startDate') || new Date()
+      this.deadline = this.activity?.get('deadline') || null
+      this.calculation = this.activity?.get('calculation') || 'mean'
+      this.reviewDetails = this.activity?.get('reviewDetails') || {}
+      this.saveLoading = false
+    } else {
+      let Activity = Parse.Object.extend('Activity')
+      this.activity = new Activity()
+    }
+  }
+  /**活动名称 */
+  name: string = ''
+  startDate?: any
+  deadline?: any
+  reset() {
+    this.name = ''
+    this.startDate = null
+    this.deadline = null
+  }
+  /**保存活动 */
+  async save() {
+    if (this.name == '' && !this.name) {
+      this.msg.create('warning', '评审活动名称不能为空')
+      return
+    }
+    if (!this.activity?.id) {
+      this.activity?.set('company', { __type: 'Pointer', className: 'Company', objectId: this.company })
+      this.activity?.set('eduProcess', { __type: 'Pointer', className: 'EduProcess', objectId: this.eduProcess?.id })
+    }
+    this.activity?.set('name', this.name)
+    this.activity?.set('startDate', this.startDate)
+    this.activity?.set('deadline', this.deadline)
+    this.activity?.set('calculation', this.calculation || 'mean')
+    this.activity?.set('reviewDetails', this.reviewDetails || {})
+    this.activity = await this.activity?.save()
+    this.msg.create('success', '保存成功')
+
+  }
+  /**计算方式 mean:平均数 truncatedMean:截尾平均数 */
+  calculation: 'truncatedMean' | 'mean' = 'mean'
+  async changeCalculation() {
+    console.log(this.calculation)
+    this.save()
+  }
+  /**评审细则 */
+  reviewDetails: any
+  async upload(e: any) {
+    console.log(e)
+    let file = e[0]
+    this.reviewDetails = file
+    console.log(this.reviewDetails)
+    if (this.reviewDetails?.url) {
+      this.save()
+    }
+  }
+
+  /**评审组 */
+  expertGroupList: Array<Parse.Object> = []
+  async getExpertGroup() {
+    let query = new Parse.Query('ExpertGroup')
+    query.equalTo('eduProcess', this.eduProcess?.id)
+    query.notEqualTo('isDeleted', true)
+    query.descending('createdAt')//大到小
+    this.expertGroupList = await query.find()
+    console.log(this.expertGroupList)
+  }
+  async deleteGroup(index: number) {
+    this.expertGroupList[index].set('isDeleted', true)
+    await this.expertGroupList[index].save()
+    this.expertGroupList.splice(index, 1)
+    
+  }
+  /**创建评审组弹框 */
+  creatReviewGroup() {
+    const dialogRef = this.dialog.open(CreateReviewGroupContent, {
+      width: '400px'
+    });
+    let that = this
+    dialogRef.afterClosed().subscribe(async (result) => {
+      let name = result
+      if (result = '' || !result) {
+        that.msg.create('warning', '评审组名称未填写')
+        return
+      }
+      console.log(name)
+      let ExpertGroup = Parse.Object.extend('ExpertGroup')
+      let expertGroup = new ExpertGroup()
+      expertGroup.set('eduProcess', { __type: 'Pointer', className: 'EduProcess', objectId: that.eduProcess?.id })
+      expertGroup.set('name', name)
+      await expertGroup.save()
+      that.getExpertGroup()
+      that.msg.create('success', '创建成功')
+
+    });
+  }
+}
+
+
+
+@Component({
+  selector: 'create_review',
+  templateUrl: 'create_review.html',
+  standalone: true,
+  imports: [
+    MatDialogModule,
+    NzIconModule,
+    CommonModule,
+    FormsModule,
+    NzInputModule,
+    NzButtonModule,
+  ],
+})
+export class CreateReviewGroupContent {
+  constructor(private dialogRef: MatDialogRef<CreateReviewGroupContent>) { }
+  name: string = ''
+  onConfirm() {
+    this.dialogRef.close(this.name)
+  }
 }

+ 24 - 0
projects/textbook/src/modules/nav-province-contact/activity/create_review.html

@@ -0,0 +1,24 @@
+<div style="height: 250px;background: white;">
+    <div style="padding: 20px;display: flex;justify-content: space-between;border-bottom: 1px solid rgb(221, 221, 221);">
+        <span style="font-family: PingFang SC;font-size: 18px;font-weight: bold;">创建评审组</span>
+        <span mat-dialog-close nz-icon nzType="close" nzTheme="outline"></span>
+    </div>
+    <ul style="margin: 0;padding: 15px;">
+        <li style="list-style: none;margin: 0;padding: 0;">
+            <p style="color: gray;">评审组名称<span style="color: red;padding-left: 10px;">*</span></p>
+            <input nz-input placeholder="请输入评审组的名称" [(ngModel)]="name" type="text" />
+        </li>
+    </ul>
+    <div style="width: 100%;display: flex;justify-content: end;">
+        <button style="margin-right: 15px;
+        margin-top: 20px;
+        background: #EAE6E6;
+        border: none;color: black;" mat-dialog-close nz-button nzType="primary">取消</button>
+        <button style="margin-right: 15px;
+        margin-top: 20px;
+        background: #3E49B3;
+        border: none;" nz-button nzType="primary"
+        (click)="onConfirm()">创建</button>
+    </div>
+   
+</div>

+ 167 - 0
projects/textbook/src/modules/nav-province-contact/activity/review-edit/review-edit.component.html

@@ -0,0 +1,167 @@
+<nz-page-header>
+  <nz-breadcrumb nz-page-header-breadcrumb>
+    <div class="back" (click)="back()">
+      <span nz-icon nzType="left" nzTheme="outline"></span>返回
+    </div>
+  </nz-breadcrumb>
+  <nz-page-header-title>{{expertGroup?.get('name') || '-'}} </nz-page-header-title>
+</nz-page-header>
+<div class="edit-content">
+  <div class="edit-comp">
+    <div class="title">设置评审教材</div>
+    <nz-radio-group
+      [ngModel]="radio"
+      (ngModelChange)="onchangeTextbook($event)"
+    >
+      <label nz-radio nzValue="all">全部教材</label>
+      <label nz-radio nzValue="free">手动选择</label>
+    </nz-radio-group>
+    <div class="textbook">
+      @if (showTextbook) {
+      <app-textbook
+        [hidden]="radio == 'all'"
+        [filterObj]="filterObj"
+        [eduProcess]="eduProcess"
+        [setOfCheckedId]="setOfCheckedTextbookAll"
+        #textbook
+      ></app-textbook>
+      }
+    </div>
+    <div class="fonter">
+      <button
+        style="background: #3e49b3; border: 1px #3e49b3; margin-right: 50px"
+        nz-button
+        nzType="primary"
+        (click)="onSave('eduTextbook')"
+      >
+        保存
+      </button>
+      <button
+        style="
+          background: #eae6e6;
+          border: 1px #eae6e6;
+          margin-right: 50px;
+          color: #000;
+        "
+        nz-button
+        nzType="primary"
+        (click)="reset()"
+      >
+        重置
+      </button>
+    </div>
+  </div>
+  <div class="edit-comp" style="margin-top: 30px">
+    <div class="title">设置评审专家</div>
+    <nz-radio-group
+      [ngModel]="radioReview"
+      (ngModelChange)="onchangeReview($event)"
+    >
+      <label nz-radio nzValue="all">全部专家</label>
+      <label nz-radio nzValue="free">指定专家</label>
+    </nz-radio-group>
+    @if (radioReview == 'free') {
+    <div class="dep-comp">
+      <div class="dep-left">
+        <div class="row">
+          <div class="value">
+            <nz-input-group [nzPrefix]="prefixTemplateUser">
+              <input
+                nz-input
+                placeholder="可输入评审专家用户姓名/手机号/邮箱搜索"
+                [(ngModel)]="modalValue"
+                (ngModelChange)="getProfile()"
+                type="text"
+              />
+            </nz-input-group>
+            <ng-template #prefixTemplateUser
+              ><span nz-icon nzType="search"></span
+            ></ng-template>
+          </div>
+        </div>
+        <div class="select">
+          @if (profilList.length > 0) { @for (item of profilList; track $index)
+          {
+          <div class="li" (click)="updateAllChecked(item)">
+            <div class="lable">
+              <label
+                nz-checkbox
+                [ngModel]="this.setOfCheckedProfileAll.has(item.id)"
+                >{{
+                  item?.get("user")?.get("name") ||
+                    item?.get("user")?.get("username")
+                }}</label
+              >
+              <div class="detail">
+                {{ item?.get("user")?.get("phone") || "-" }} |
+                {{ item?.get("email") || "-" }}
+              </div>
+            </div>
+          </div>
+          } } @else {
+          <nz-empty
+            style="margin-top: 10px"
+            nzNotFoundContent="暂无找到相关评审专家"
+          ></nz-empty>
+          }
+        </div>
+      </div>
+      <div class="dep-right">
+        <div class="select">
+          <div class="tool">
+            <div class="">已选:{{ checkProfileListLeng }}</div>
+            <div class="clrea">清除</div>
+          </div>
+          @if (checkProfileListLeng > 0) { @for (item of profilList; track
+          $index) { @if ( setOfCheckedProfileAll.has(item.id) ) {
+          <div class="li">
+            <div class="lable">
+              {{
+                item?.get("user")?.get("name") ||
+                  item?.get("user")?.get("username")
+              }}
+              <div class="detail" style="margin-left: 0">
+                {{ item?.get("user")?.get("phone") || "-" }} |
+                {{ item?.get("email") || "-" }}
+              </div>
+            </div>
+            <div class="check" (click)="setOfCheckedProfileAll.delete(item.id)">
+              <span nz-icon nzType="close" nzTheme="outline"></span>
+            </div>
+          </div>
+          } } } @else {
+          <nz-empty
+            style="margin-top: 10px"
+            nzNotFoundContent="暂无选择相关评审专家"
+          ></nz-empty>
+          }
+        </div>
+      </div>
+    </div>
+    }
+
+    <div class="fonter">
+      <button
+        style="background: #3e49b3; border: 1px #3e49b3; margin-right: 50px"
+        nz-button
+        nzType="primary"
+        (click)="onSave('profile')"
+      >
+        保存
+      </button>
+      <button
+        style="
+          background: #eae6e6;
+          border: 1px #eae6e6;
+          margin-right: 50px;
+          color: #000;
+        "
+        nz-button
+        nzType="primary"
+        (click)="reset()"
+      >
+        重置
+      </button>
+    </div>
+  </div>
+</div>

+ 126 - 0
projects/textbook/src/modules/nav-province-contact/activity/review-edit/review-edit.component.scss

@@ -0,0 +1,126 @@
+.back {
+  font-family: PingFang SC;
+  font-size  : 14px;
+  font-weight: 400;
+  line-height: 22px;
+  text-align : left;
+  cursor     : pointer;
+}
+
+.edit-content {
+  margin   : 0 0 20px;
+  padding  : 0 24px;
+  // height   : calc(100% - 250px);
+  min-width: 1000px;
+
+  .title {
+    font-family  : PingFang SC;
+    font-size    : 20px;
+    font-weight  : 500;
+    line-height  : 32px;
+    text-align   : left;
+    margin-bottom: 16px;
+  }
+
+  .textbook {
+    // height: 200px;
+    margin       : 10px auto;
+    // background: aliceblue;
+  }
+
+  .dep-comp {
+    margin         : 10px auto;
+    display        : flex;
+    justify-content: space-between;
+    background     : #f2f3f5;
+    margin         : 10px auto;
+    padding        : 10px;
+
+    .dep-left,
+    .dep-right {
+      width        : 48%;
+      background   : white;
+      padding      : 10px;
+      border-radius: 10px;
+    }
+
+    .dep-right {
+      .tool {
+        display        : flex;
+        justify-content: space-between;
+
+        .clrea {
+          font-family: PingFang SC;
+          font-size  : 14px;
+          font-weight: 400;
+          line-height: 22px;
+          text-align : right;
+          color      : #B21A39;
+          cursor: pointer;
+        }
+      }
+    }
+
+    .select {
+      height    : 240px;
+      overflow-y: scroll;
+
+      .li {
+        margin-top     : 6px;
+        padding        : 4px 6px;
+        width          : 100%;
+        cursor         : pointer;
+        font-family    : PingFang SC;
+        font-size      : 14px;
+        font-weight    : 400;
+        line-height    : 22px;
+        text-align     : left;
+        display        : flex;
+        justify-content: space-between;
+        align-items    : center;
+
+        .detail {
+          margin-left: 24px;
+          font-family: PingFang SC;
+          font-size  : 12px;
+          font-weight: 400;
+          line-height: 20px;
+          text-align : left;
+          color      : #86909C;
+        }
+      }
+
+      .li:hover {
+        background: #f2f3f5;
+      }
+
+      .li:hover .check {
+        background: white;
+      }
+    }
+  }
+
+  .fonter {
+    margin-top: 10px;
+  }
+}
+
+::ng-deep .ant-page-header-heading-title {
+  white-space: normal;
+}
+
+::ng-deep .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
+  color: #c6233f;
+}
+
+::ng-deep .ant-tabs-ink-bar {
+  background: #c6233f;
+}
+
+::ng-deep .ant-tabs-tab:hover {
+  color: #e97488;
+}
+
+::ng-deep .ant-tabs-tab-btn:active {
+  color: #e97488;
+}

+ 24 - 0
projects/textbook/src/modules/nav-province-contact/activity/review-edit/review-edit.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { ReviewEditComponent } from './review-edit.component';
+
+describe('ReviewEditComponent', () => {
+  let component: ReviewEditComponent;
+  let fixture: ComponentFixture<ReviewEditComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ReviewEditComponent ],
+      imports: [IonicModule.forRoot()]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ReviewEditComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 230 - 0
projects/textbook/src/modules/nav-province-contact/activity/review-edit/review-edit.component.ts

@@ -0,0 +1,230 @@
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { NzSpaceModule } from 'ng-zorro-antd/space';
+import { CommonCompModule } from '../../../../services/common.modules';
+import { NzTabsModule } from 'ng-zorro-antd/tabs';
+import { ActivatedRoute, Router } from '@angular/router';
+import Parse from 'parse';
+import { TextbookComponent } from '../../../../app/textbook/textbook.component';
+import { textbookServer } from '../../../../services/textbook';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { NzMessageService } from 'ng-zorro-antd/message';
+@Component({
+  selector: 'app-review-edit',
+  templateUrl: './review-edit.component.html',
+  styleUrls: ['./review-edit.component.scss'],
+  imports: [
+    CommonModule,
+    NzSpaceModule,
+    CommonCompModule,
+    NzTabsModule,
+    TextbookComponent,
+    NzRadioModule,
+    NzCheckboxModule,
+  ],
+  standalone: true,
+})
+export class ReviewEditComponent implements OnInit {
+  @ViewChild('textbook') textbook: TextbookComponent | any;
+  id: string | null = ''; //评审组id
+  eduProcess?: Parse.Object;
+  expertGroup?: Parse.Object;
+  setOfCheckedTextbookAll = new Set<string>(); //所有已选择教材
+  setOfCheckedProfileAll = new Set<string>(); //所有已选择教材
+  get checkProfileListLeng(): number {
+    return Array.from(this.setOfCheckedProfileAll).length;
+  }
+  radio: string = 'all';
+  radioReview: string = 'all';
+
+  filterObj: any = {
+    showMore: true, //显示更多字段
+    isCheck: true,
+    noStared: true,
+    status: ['200'],
+    notContained: [], //排除id
+    showText: true,
+    btns: {
+      // reject: true, //退回教材
+      // star: true, //移除推荐
+      // export: true,
+    },
+  };
+  modalValue: string = '';
+  profilList: Array<Parse.Object> = [];
+  loading = false;
+  showTextbook:boolean = false
+
+  constructor(
+    private activeRoute: ActivatedRoute,
+    public tbookSer: textbookServer,
+    private message: NzMessageService,
+    private modal: NzModalService,
+    private router: Router
+  ) {}
+
+  ngOnInit() {
+    this.activeRoute.paramMap.subscribe((params) => {
+      this.id = params.get('id');
+      this.refresh();
+    });
+  }
+  async refresh() {
+    let query = new Parse.Query('ExpertGroup');
+    query.include('eduProcess');
+    query.equalTo('objectId', this.id);
+    query.notEqualTo('isDeleted', true);
+    let res = await query.first();
+    if (res?.id) {
+      this.expertGroup = res;
+      this.eduProcess = res?.get('eduProcess');
+
+      this.expertGroup.get('textbookList')?.forEach((item: any) => {
+        this.setOfCheckedTextbookAll.add(item.id || item.objectId);
+      });
+      this.expertGroup.get('reviewList')?.forEach((item: any) => {
+        this.setOfCheckedProfileAll.add(item.id || item.objectId);
+      });
+      await this.getExpertGroup()
+      this.radio = res?.get('checkTextbook') ?? 'all';
+      this.getProfile()
+      this.radioReview = res?.get('checkReview') ?? 'all';
+      this.showTextbook = true
+    }
+  }
+  //重置
+  reset() {
+    this.refresh();
+  }
+  back() {
+    history.back();
+  }
+  async onchangeTextbook($event: string) {
+    console.log($event);
+    // if ($event == 'free') {
+    //   await this.getExpertGroup()
+    // }
+    this.radio = $event;
+  }
+
+  //获取所有已被其他评审组选择过教材
+  async getExpertGroup(){
+    let query = new Parse.Query('ExpertGroup');
+    query.include('eduProcess');
+    query.notEqualTo('isDeleted', true);
+    query.notEqualTo('objectId', this.expertGroup?.id);
+    query.select('textbookList');
+    let res = await query.find();
+    res.forEach((item) => {
+      item.get('textbookList')?.forEach((obj: any) => {
+        this.filterObj.notContained.push(obj.id || obj.objectId);
+      });
+    });
+  }
+  async onchangeReview($event: any) {
+    console.log($event);
+    if ($event == 'free') {
+      this.getProfile();
+    }
+    this.radioReview = $event;
+  }
+  /* 获取评委专家 */
+  async getProfile() {
+    this.profilList = [];
+    this.loading = true;
+    let queryParams: any = {
+      where: {
+        $or: [
+          {
+            user: {
+              $inQuery: {
+                where: {
+                  $or: [
+                    {
+                      name: { $regex: `.*${this.modalValue}.*` },
+                    },
+                    {
+                      username: { $regex: `.*${this.modalValue}.*` },
+                    },
+                    {
+                      phone: { $regex: `.*${this.modalValue}.*` },
+                    },
+                    {
+                      email: { $regex: `.*${this.modalValue}.*` },
+                    },
+                  ],
+                },
+                className: '_User',
+              },
+            },
+          },
+        ],
+      },
+    };
+    let childrens = await this.tbookSer.getChild(
+      this.tbookSer.profile.user.department?.objectId
+    );
+    queryParams['where']['$or'][0]['user']['$inQuery']['where']['department'] =
+      {
+        $in: childrens,
+      };
+    let query = Parse.Query.fromJSON('Profile', queryParams);
+    query.include('user');
+    query.notEqualTo('isDeleted', true);
+    query.notEqualTo('identity', '国家级管理员');
+    query.descending('createdAt');
+    query.containedIn('identity', ['评审专家']);
+    let r = await query.find();
+    this.profilList = r;
+    console.log(this.profilList);
+    this.loading = false;
+  }
+  updateAllChecked($event: any): void {
+    console.log($event);
+    this.setOfCheckedProfileAll.has($event.id)
+      ? this.setOfCheckedProfileAll.delete($event.id)
+      : this.setOfCheckedProfileAll.add($event.id);
+  }
+  /* 保存 */
+  async onSave(type: string) {
+    if (type == 'eduTextbook') {
+      let fileds: any = [];
+      if(this.radio == 'all'){
+       let list = await this.textbook.getTextbook('',{excel:true,id:true})
+       list.forEach((item:any) => {
+        fileds.push({
+          __type: 'Pointer',
+          className: 'EduTextbook',
+          objectId: item.id,
+        });
+       });
+      }else{
+        Array.from(this.textbook.setOfCheckedId).forEach((item) => {
+          fileds.push({
+            __type: 'Pointer',
+            className: 'EduTextbook',
+            objectId: item,
+          });
+        });
+      }
+      this.expertGroup?.set('textbookList', fileds);
+      this.expertGroup?.set('checkTextbook', this.radio);
+    } else {
+      let fileds: any = [];
+      Array.from(this.setOfCheckedProfileAll).forEach((item) => {
+        fileds.push({
+          __type: 'Pointer',
+          className: 'Profile',
+          objectId: item,
+        });
+      });
+      this.expertGroup?.set('reviewList', fileds);
+      this.expertGroup?.set('checkReview', this.radioReview);
+    }
+    await this.expertGroup?.save();
+    this.message.success('设置成功');
+    // this.refresh()
+  }
+}

+ 5 - 1
projects/textbook/src/modules/nav-province-contact/modules.routes.ts

@@ -9,6 +9,7 @@ import { PageTextbookComponent } from "./page-textbook/page-textbook.component";
 import { SubmittedComponent } from "./submitted/submitted.component";
 import { AuthGuard } from "./auth.guard";
 import { ActivityComponent } from "./activity/activity.component";
+import { ReviewEditComponent } from "./activity/review-edit/review-edit.component";
 // import { PageTextbookComponent } from "./page-textbook/page-textbook.component";
 
 const routes: Routes = [
@@ -53,7 +54,10 @@ const routes: Routes = [
         path: 'activity/:id', //评审活动
         component: ActivityComponent,
       },
-      
+      {
+        path: 'review-edit/:id', //评审活动
+        component: ReviewEditComponent,
+      },
     ]
   }
 ];

+ 2 - 2
projects/textbook/src/modules/nav-province-contact/page-process/process-list/process-list.component.html

@@ -220,7 +220,7 @@
           <div class="lable">评审数量:</div>
           <div class="val">未设置</div>
         </div>
-        <!-- <div class="examine-fonter">
+        <div class="examine-fonter">
           <button
             style="background: #3e49b3; border: 1px #3e49b3; margin-right: 50px"
             nz-button
@@ -245,7 +245,7 @@
           >
             计算平均分
           </button>
-        </div> -->
+        </div>
       </div>
     </nz-tab>
   </nz-tabset>

+ 6 - 3
server/db/index.js

@@ -8,8 +8,10 @@ import {_Session} from "./schemas/_Session"
 import {Submitted} from "./schemas/Submitted"
 import {EduProcess} from "./schemas/EduProcess"
 import { Department } from "./schemas/Department"
-import { Review } from "./schemas/Review"
+import { EduReview } from "./schemas/EduReview"
 import { Activity } from "./schemas/Activity"
+import { ExpertGroup } from "./schemas/ExpertGroup"
+
 export const EduSchemas = [
     _User,
     _Role,
@@ -21,7 +23,8 @@ export const EduSchemas = [
     Submitted,
     EduProcess,
     Department,
-    Review,
-    Activity
+    EduReview,
+    Activity,
+    ExpertGroup
 ]
 module.exports.EduSchemas = EduSchemas

+ 3 - 3
server/db/schemas/Review.js → server/db/schemas/EduReview.js

@@ -1,6 +1,6 @@
 /* 评审表 */
-export const Review = {
-  "className": "Review",
+export const EduReview = {
+  "className": "EduReview",
   "fields": {
     "eduTextbook": { //评审教材
       "type": "Pointer",
@@ -54,4 +54,4 @@ export const Review = {
     }
   }
 }
-module.exports.Review = Review
+module.exports.EduReview = EduReview

+ 58 - 0
server/db/schemas/ExpertGroup.js

@@ -0,0 +1,58 @@
+/* 评审表 */
+export const ExpertGroup = {
+  "className": "ExpertGroup",
+  "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
+    },
+  },
+  "classLevelPermissions": {
+    "find": {
+      "*": true
+    },
+    "get": {
+      "*": true
+    },
+    "count": {
+      "*": true
+    },
+    "create": {
+      "*": true
+    },
+    "update": {
+      "*": true
+    },
+    "delete": {
+      "*": true
+    },
+    "addField": {
+      "*": true
+    },
+    "protectedFields": {
+      "*": []
+    }
+  }
+}
+module.exports.ExpertGroup = ExpertGroup