Forráskód Böngészése

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

ryanemax 1 hónapja
szülő
commit
2ef0e73bd5
25 módosított fájl, 1650 hozzáadás és 51 törlés
  1. 7 0
      projects/textbook/src/app/comp-manage/comp-manage.component.ts
  2. 268 0
      projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.html
  3. 102 0
      projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.scss
  4. 24 0
      projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.spec.ts
  5. 238 0
      projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.ts
  6. 34 11
      projects/textbook/src/modules/nav-admin/page-process/page-process.component.html
  7. 5 21
      projects/textbook/src/modules/nav-admin/page-process/page-process.component.ts
  8. 79 6
      projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.html
  9. 6 1
      projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.scss
  10. 75 5
      projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.ts
  11. 1 1
      projects/textbook/src/modules/nav-author/components/attachment/attachment.component.html
  12. 198 0
      projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.html
  13. 71 0
      projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.scss
  14. 24 0
      projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.spec.ts
  15. 272 0
      projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.ts
  16. 1 1
      projects/textbook/src/modules/nav-province-contact/components/review-details/review-details.component.html
  17. 2 2
      projects/textbook/src/modules/nav-province-contact/components/review-details/review-details.component.ts
  18. 93 0
      projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.html
  19. 14 0
      projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.scss
  20. 24 0
      projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.spec.ts
  21. 95 0
      projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.ts
  22. 6 1
      projects/textbook/src/modules/nav-province-contact/modules.routes.ts
  23. 1 1
      projects/textbook/src/modules/nav-province-contact/page-textbook/page-textbook.component.html
  24. 1 1
      projects/textbook/src/modules/nav-review/approve/approve.component.ts
  25. 9 0
      server/db/schemas/EduProcess.js

+ 7 - 0
projects/textbook/src/app/comp-manage/comp-manage.component.ts

@@ -177,6 +177,13 @@ export class CompManageComponent implements OnInit {
 
   ngOnInit() {
     console.log(this.tbookSer.profile.identity);
+    if (this.tbookSer.profile.identity == '工作联系人') {
+      this.optionsMap['工作联系人'][0].child.push({
+        name: '本社教材源文件',
+        path: '/nav-province-contact/manage/collect',
+        id: '1-3',
+      });
+    }
     if (localStorage.getItem('active')) return;
     if (this.optionsMap[this.tbookSer.profile.identity][0]?.child) {
       this.active = '1-1';

+ 268 - 0
projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.html

@@ -0,0 +1,268 @@
+<div class="textbook-table">
+  <div class="tool">
+    <div class="search">
+      <nz-input-group style="width: 280px" [nzPrefix]="prefixTemplateUser">
+        <input
+          type="text"
+          nz-input
+          placeholder="输入教材名称 / ISBN / 申报编号"
+          [(ngModel)]="searchValue"
+          (ngModelChange)="onSearch($event)"
+        />
+      </nz-input-group>
+      <ng-template #prefixTemplateUser
+        ><span nz-icon nzType="search"></span
+      ></ng-template>
+    </div>
+    <!-- <div class="sbt" (click)="export()">导出评审明细</div> -->
+  </div>
+  <nz-table
+    #tableData
+    [nzData]="textbookList"
+    [nzTotal]="count"
+    [(nzPageSize)]="limit"
+    [nzPageIndex]="pageIndex"
+    style="margin: 10px 0"
+    [nzLoading]="loading"
+    nzSize="middle"
+    [nzFrontPagination]="false"
+    [nzScroll]="{ x: (maxWidth || '800') + 'px', y: '580px' }"
+    nzTableLayout="fixed"
+    [nzPageSizeOptions]="[10, 20, 30, 40, 50]"
+    nzShowSizeChanger
+    (nzPageIndexChange)="pageIndexChange($event)"
+    (nzPageSizeChange)="onPageSizeChange($event)"
+  >
+    <thead>
+      <tr>
+        <th nzEllipsis nzWidth="120px" nzLeft>教材名称</th>
+        <th nzEllipsis nzWidth="120px">申报编号</th>
+        <th nzEllipsis nzWidth="120px">ISBN</th>
+        <th nzEllipsis nzWidth="120px">出版单位</th>
+        <th nzEllipsis nzWidth="120px">出版单位联系人</th>
+        <th nzEllipsis nzWidth="80px" nzAlign="center">文件状态</th>
+        <th nzEllipsis nzWidth="80px" nzAlign="center" nzRight>操作</th>
+      </tr>
+    </thead>
+    @if (textbookList.length > 0) {
+    <tbody>
+      @for (data of textbookList; track data.id) {
+      <tr>
+        <td
+          nzEllipsis
+          class="activeTd"
+          (click)="
+            toUrl('/common/textbook/details/' + data?.get('eduTextbook')?.id)
+          "
+          nz-popover
+          [nzPopoverContent]="contentTemplatetitle"
+        >
+          {{ data?.get("title") || "-" }}
+          <ng-template #contentTemplatetitle>
+            <div style="max-width: 400px">
+              {{ data?.get("title") || "-" }}
+            </div>
+          </ng-template>
+        </td>
+        <td nzEllipsis>
+          {{ data?.get("code") ?? "-" }}
+        </td>
+        <td
+          nzEllipsis
+          nz-popover
+          [nzPopoverContent]="contentTemplateISBN"
+          #ISBN
+        >
+          {{ fromatFiled(data?.get("childrens"), "ISBN") }}
+          <ng-template #contentTemplateISBN>
+            <div style="max-width: 400px">
+              {{ ISBN.innerText }}
+            </div>
+          </ng-template>
+        </td>
+        <td
+          nzEllipsis
+          nz-popover
+          [nzPopoverContent]="contentTemplateeditionUnit"
+          #editionUnit
+        >
+          {{ fromatFiled(data?.get("childrens"), "editionUnit") }}
+          <ng-template #contentTemplateeditionUnit>
+            <div style="max-width: 400px">
+              {{ editionUnit.innerText }}
+            </div>
+          </ng-template>
+        </td>
+
+        <td
+          nzEllipsis
+          nz-popover
+          [nzPopoverContent]="contentTemplateChild"
+          #childrens
+        >
+          @for (n of formatMapProfile(data?.get('childrens')); track n) {
+          {{ n }}
+          }
+          <ng-template #contentTemplateChild>
+            <div style="max-width: 400px">
+              {{ childrens.innerText }}
+            </div>
+          </ng-template>
+        </td>
+        <td nzEllipsis nzAlign="center">未上传</td>
+        <td nzEllipsis nzRight nzAlign="center">
+          <a nz-button nzType="link">查看</a>
+        </td>
+      </tr>
+      }
+    </tbody>
+    }
+  </nz-table>
+  @if (textbookList.length == 0) {
+  <nz-empty nzNotFoundImage="/img/group-empty.png"></nz-empty>
+  }
+
+  <div class="loading" [hidden]="!showLoading">
+    <nz-spin nzSimple [nzSize]="'large'"></nz-spin>
+  </div>
+</div>
+
+<nz-modal
+  [(nzVisible)]="showModal"
+  nzTitle="收集教材源文件"
+  (nzOnCancel)="handleCancel()"
+  nzWidth="600px"
+>
+  <ng-container *nzModalContent>
+    <div nz-row class="depart-modal">
+      <div nz-col nzSpan="24">
+        <div class="row">
+          <div class="label">
+            开始时间 <span style="color: #e8353e">*</span>
+          </div>
+          <div class="value">
+            <nz-date-picker
+              nzFormat="yyyy-MM-dd HH:mm:ss"
+              [(ngModel)]="collectStartData"
+              [nzShowTime]="{ nzDefaultOpenValue: timeDefaultValue }"
+            ></nz-date-picker>
+          </div>
+        </div>
+        <div class="row">
+          <div class="label">
+            截止时间 <span style="color: #e8353e">*</span>
+          </div>
+          <div class="value">
+            <nz-date-picker
+              nzFormat="yyyy-MM-dd HH:mm:ss"
+              [(ngModel)]="collectEndData"
+              [nzStatus]="collectEndData < collectStartData ? 'error' : ''"
+              [nzShowTime]="{ nzDefaultOpenValue: timeDefaultValue }"
+            ></nz-date-picker>
+          </div>
+        </div>
+        <div class="row" style="align-items: start">
+          <div class="label">收集教材</div>
+          <div class="value">
+            <nz-table
+              #tableData
+              [nzData]="textbookList"
+              [nzTotal]="count"
+              [(nzPageSize)]="limit"
+              [nzPageIndex]="pageIndex"
+              [nzLoading]="loading"
+              nzSize="middle"
+              [nzFrontPagination]="false"
+              [nzScroll]="{ x: (maxWidth || '400') + 'px', y: '580px' }"
+              nzTableLayout="fixed"
+              [nzPageSizeOptions]="[10, 20, 30, 40, 50]"
+              nzShowSizeChanger
+              (nzPageIndexChange)="pageIndexChange($event)"
+              (nzPageSizeChange)="onPageSizeChange($event)"
+              nzBordered
+            >
+              <thead>
+                <tr>
+                  <th nzEllipsis nzWidth="120px" nzLeft>教材名称</th>
+                  <th nzEllipsis nzWidth="120px">申报编号</th>
+                  <th nzEllipsis nzWidth="80px">ISBN</th>
+                  <th nzEllipsis nzWidth="120px">出版单位</th>
+                </tr>
+              </thead>
+              @if (textbookList.length > 0) {
+              <tbody>
+                @for (data of textbookList; track data.id) {
+                <tr>
+                  <td
+                    nzEllipsis
+                    class="activeTd"
+                    (click)="
+                      toUrl(
+                        '/common/textbook/details/' +
+                          data?.get('eduTextbook')?.id
+                      )
+                    "
+                    nz-popover
+                    [nzPopoverContent]="contentTemplatetitle"
+                  >
+                    {{ data?.get("title") || "-" }}
+                    <ng-template #contentTemplatetitle>
+                      <div style="max-width: 400px">
+                        {{ data?.get("title") || "-" }}
+                      </div>
+                    </ng-template>
+                  </td>
+                  <td nzEllipsis>
+                    {{ data?.get("code") ?? "-" }}
+                  </td>
+                  <td
+                    nzEllipsis
+                    nz-popover
+                    [nzPopoverContent]="contentTemplateISBN"
+                    #ISBN
+                  >
+                    {{ fromatFiled(data?.get("childrens"), "ISBN") }}
+                    <ng-template #contentTemplateISBN>
+                      <div style="max-width: 400px">
+                        {{ ISBN.innerText }}
+                      </div>
+                    </ng-template>
+                  </td>
+                  <td
+                    nzEllipsis
+                    nz-popover
+                    [nzPopoverContent]="contentTemplateeditionUnit"
+                    #editionUnit
+                  >
+                    {{ fromatFiled(data?.get("childrens"), "editionUnit") }}
+                    <ng-template #contentTemplateeditionUnit>
+                      <div style="max-width: 400px">
+                        {{ editionUnit.innerText }}
+                      </div>
+                    </ng-template>
+                  </td>
+                </tr>
+                }
+              </tbody>
+              }
+            </nz-table>
+            @if (textbookList.length == 0) {
+            <nz-empty nzNotFoundImage="/img/group-empty.png"></nz-empty>
+            }
+          </div>
+        </div>
+      </div>
+    </div>
+  </ng-container>
+  <div *nzModalFooter>
+    <button nz-button nzType="default" (click)="handleCancel()">取消</button>
+    <button
+      nz-button
+      nzType="primary"
+      [disabled]="!this.collectStartData || !this.collectEndData"
+      (click)="editCollect()"
+    >
+      收集
+    </button>
+  </div>
+</nz-modal>

+ 102 - 0
projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.scss

@@ -0,0 +1,102 @@
+.activeTd{
+  cursor: pointer;
+}
+.tool{
+  display: flex;
+  justify-content: space-between;
+  .sbt{
+    padding: 6px 10px;
+    background: #E8F5FC;
+    font-family: PingFang SC;
+    font-size: 14px;
+    font-weight: 400;
+    /* line-height: 20px; */
+    text-align: center;
+    color: #0054C3;
+    border-radius: 4px;
+    cursor: pointer;
+  }
+}
+.activeTd{
+  color: #c6233f;
+}
+// 选中,批量操作区域
+.batch-toolbar-modal{
+  position: fixed;
+  display: flex;
+  justify-content: center;
+  bottom: 80px;
+  // left: calc(50% - 134px);
+  left: calc(50% - 328px);
+  -webkit-transform: translate(0);
+  transform: translate(0);
+}
+.batch-toolbar {
+  display: flex;
+  align-items: center;
+  height: 56px;
+  // min-width: 600px;
+  background: #fff;
+  border: 1px solid #e5e6eb;
+  box-sizing: border-box;
+  box-shadow: 0 16px 32px -10px rgba(4, 24, 115, .1);
+  border-radius: 4px;
+  button{
+      color:#545968;
+  }
+}
+.batch-toolbar .styles_counter__18S08 {
+  color: #fff;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  background: #C6233F;
+  width: 100px;
+  font-size: 12px;
+  border-top-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+.batch-toolbar .batch-toolbar-actions {
+  display: flex;
+  margin: auto;
+  padding: 0 20px;
+}
+.batch-toolbar .styles_cancel__AARoT {
+  font-size: 16px;
+  border-left: 1px solid #a9aeb8;
+  // padding-left: 20px;
+  display: flex;
+  justify-content: center;
+  width: 128px;
+}
+.batch-toolbar .styles_counter__18S08 .styles_num__178Wa {
+  font-size: 24px;
+  margin-left: 8px;
+}
+
+.loading{
+  position: fixed;
+  z-index: 99;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  text-align: center;
+  height: 100vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgb(0 0 0 / 30%);
+}
+::ng-deep .nz-table-hide-scrollbar{
+  scrollbar-color: auto;
+}
+.row{
+  width: 100%;
+  margin-bottom: 20px;
+  display: flex;
+  align-items: center;
+  .label{
+    width: 100px;
+  }
+}

+ 24 - 0
projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.spec.ts

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

+ 238 - 0
projects/textbook/src/modules/nav-admin/components/collect-textbook/collect-textbook.component.ts

@@ -0,0 +1,238 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { NzSpaceModule } from 'ng-zorro-antd/space';
+import { CommonCompModule } from '../../../../services/common.modules';
+import { ActivatedRoute, Router } from '@angular/router';
+import { NzMessageModule } from 'ng-zorro-antd/message';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import Parse from 'parse';
+// import { textbookServer } from '../../../../services/textbook';
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { MatDialog } from '@angular/material/dialog';
+import { NzEmptyModule } from 'ng-zorro-antd/empty';
+import { DatePipe } from '@angular/common';
+import { NzPopoverModule } from 'ng-zorro-antd/popover';
+import { setHours } from 'date-fns';
+import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
+@Component({
+  selector: 'app-collect-textbook',
+  templateUrl: './collect-textbook.component.html',
+  styleUrls: ['./collect-textbook.component.scss'],
+  imports: [
+    CommonModule,
+    NzSpaceModule,
+    CommonCompModule,
+    NzMessageModule,
+    NzEmptyModule,
+    NzPopoverModule,
+    NzDatePickerModule
+  ],
+  providers: [DatePipe],
+  standalone: true,
+})
+export class CollectTextbookComponent implements OnInit {
+  textbookList: Array<Parse.Object> = [];
+  count: number = 0;
+  timeDefaultValue = setHours(new Date(), 0);
+
+  @Input('limit') limit: number = 10;
+  pageIndex: number = 1;
+  loading: boolean = false;
+  @Input('maxWidth') maxWidth: any; //最大宽度
+  @Input('eduProcess') eduProcess?: Parse.Object; //流程id
+
+  // @Input('filterObj') filterObj: any = {
+  //   contained: [],
+  // };
+
+  showModal: boolean = false;
+  textBookList: Array<Parse.Object> = []; //流程教材列表(推荐)
+  collectStartData: any;
+  collectEndData: any;
+
+  searchValue: string = '';
+  time: any;
+  showLoading: boolean = false; //全局
+  /* 格式化拓展表字段 */
+  fromatFiled(list: Array<Parse.Object>, filed: string): string {
+    let arr: Array<string | null> = [];
+    let isDate = false;
+    list.forEach((item: Parse.Object) => {
+      if (
+        isDate ||
+        Object.prototype.toString.call(item.get(filed)).indexOf('Date') != -1
+      ) {
+        arr.push(this.datePipe.transform(item.get(filed), 'yyyy-MM'));
+        isDate = true;
+      } else {
+        arr.push(item.get(filed));
+      }
+    });
+    let j = Array.from(arr).join(',');
+    if (!isDate) {
+      j = Array.from(new Set(arr)).join(' ');
+    }
+    return j || '-';
+  }
+  manageProfiles:any = {}
+
+  formatMapProfile(list:Array<Parse.Object>):Array<string>{
+    let arr:Array<string> = []
+    list.forEach(item=>{
+      arr.push(this.manageProfiles[item?.get('editionUnit')]) 
+    })
+    return [...new Set(arr)]
+  }
+
+  constructor(
+    // private activeRoute: ActivatedRoute,
+    // public tbookSer: textbookServer,
+    private msg: NzMessageService,
+    public dialog: MatDialog,
+    private route: Router,
+    private datePipe: DatePipe,
+    private modal: NzModalService
+  ) {}
+
+  ngOnInit() {
+    this.getTextbook();
+  }
+  async getTextbook(val?: string, review?: boolean): Promise<any[] | void> {
+    if (this.loading) return;
+    this.loading = true;
+    try {
+      let queryParams: any = {
+        where: {
+          $or: [
+            {
+              title: { $regex: `.*${val || ''}.*` },
+            },
+            {
+              code: { $regex: `.*${val || ''}.*` },
+            },
+            {
+              childrens: {
+                $inQuery: {
+                  where: {
+                    $or: [
+                      {
+                        ISBN: { $regex: `.*${val || ''}.*` },
+                      },
+                      // {
+                      //   author: { $regex: `.*${val || ''}.*` },
+                      // },
+                    ],
+                  },
+                  className: 'EduTextbookVolume',
+                },
+              },
+            },
+          ],
+        },
+      };
+      let query = Parse.Query.fromJSON('EduTextbook', queryParams);
+      query.equalTo('eduProcess', this.eduProcess?.id);
+      query.descending('updatedAt');
+      query.notEqualTo('isDeleted', true);
+      query.equalTo('status', '400');
+      query.equalTo('recommend', true);
+      query.notEqualTo('discard', true);
+      // query.exists('score');
+      // query.equalTo('verify', true);
+      query.include('childrens');
+      this.count = await query.count();
+      query.limit(this.limit);
+      query.skip(this.limit * (this.pageIndex - 1));
+      // if (exported) {
+      //   query.limit(1000);
+      //   let r = await query.find();
+      //   this.loading = false;
+      //   return r;
+      // }
+      this.textbookList = await query.find();
+      //获取对应出版单位管理员
+      if(!review && this.textbookList.length > 0){
+        let contains:Array<string> = []
+        this.textbookList.forEach((childs:any)=>{
+          childs.get('childrens').forEach((item:Parse.Object)=> contains.push(item.get('editionUnit')))
+        })
+        let queryProcess = new Parse.Query('EduProcess')
+        queryProcess.notEqualTo('isDeleted',true)
+        queryProcess.containedIn('name',[...new Set(contains)])
+        queryProcess.include('profileSubmitted.user')
+        queryProcess.select('profileSubmitted.user.name','name')
+        let processList = await queryProcess.find()
+        console.log(processList);
+        processList.forEach(i=>{
+          this.manageProfiles[i?.get('name')] = i?.get('profileSubmitted')?.get('user')?.get('name')
+        })
+      }
+      console.log(this.textbookList);
+      this.loading = false;
+    } catch (err) {
+      console.warn(err);
+      this.msg.error('获取超时');
+    }
+    this.loading = false;
+  }
+  onSearch(e: string) {
+    this.pageIndex = 1;
+    console.log(e);
+    if (this.time) clearTimeout(this.time);
+    this.time = setTimeout(() => {
+      this.getTextbook(e);
+    }, 500);
+  }
+  //分页切换
+  pageIndexChange(e: any) {
+    console.log(e);
+    this.pageIndex = e;
+    this.getTextbook(this.searchValue);
+  }
+  //切换分页条数
+  onPageSizeChange($event: any): void {
+    console.log(this.limit);
+    // this.onAllChecked(false)
+    this.pageIndex = 1;
+    this.getTextbook(this.searchValue);
+  }
+
+  toUrl(url: string, param?: Object) {
+    console.log(url);
+    if (param) {
+      this.route.navigate([url, param]);
+      return;
+    }
+    this.route.navigate([url]);
+  }
+
+  //打开编辑收集文件弹窗
+  async openEditCollect() {
+    this.collectStartData = this.eduProcess?.get('collectStartData');
+    this.collectEndData = this.eduProcess?.get('collectEndData');
+    this.pageIndex = 1;
+    this.searchValue = ''
+    this.getTextbook();
+
+    this.showModal = true;
+  }
+  handleCancel(): void {
+    this.showModal = false
+  }
+  //保存收集文件设置
+  async editCollect() {
+    if (
+      !this.collectStartData ||
+      !this.collectEndData ||
+      this.collectStartData > this.collectEndData
+    ) {
+      this.msg.warning('请设置正确的开始和截止时间');
+      return;
+    }
+    this.eduProcess?.set('collectStartData', this.collectStartData);
+    this.eduProcess?.set('collectEndData', this.collectEndData);
+    await this.eduProcess?.save();
+    this.msg.success('设置成功');
+    this.showModal = false;
+  }
+}

+ 34 - 11
projects/textbook/src/modules/nav-admin/page-process/page-process.component.html

@@ -1,15 +1,34 @@
 <nz-page-header>
   <nz-breadcrumb nz-page-header-breadcrumb>
     <nz-breadcrumb-item>申报流程</nz-breadcrumb-item>
-    <nz-breadcrumb-item><a>{{eduProcess?.get('department')?.get('name') || '未选择申报单位'}}</a></nz-breadcrumb-item>
+    <nz-breadcrumb-item
+      ><a>{{
+        eduProcess?.get("department")?.get("name") || "未选择申报单位"
+      }}</a></nz-breadcrumb-item
+    >
   </nz-breadcrumb>
   <nz-page-header-title
-    >{{eduProcess?.get('department')?.get('name') || '未选择申报单位'}}
+    >{{ eduProcess?.get("department")?.get("name") || "未选择申报单位" }}
     <br />
     <div class="subtitle">
       在流程中邀请作者、高校联系人、评审员登录系统,创建并提交教材,由工作联系人评审、提交推荐教材完成流程工作
     </div>
   </nz-page-header-title>
+  @if (active == 2) {
+  <nz-page-header-extra>
+    <nz-space>
+      <button
+        style="background: #3e49b3; border: 1px #3e49b3"
+        *nzSpaceItem
+        nz-button
+        nzType="primary"
+        (click)="collect.openEditCollect()"
+      >
+        收集教材源文件
+      </button>
+    </nz-space>
+  </nz-page-header-extra>
+  }
 </nz-page-header>
 <div class="edit-content">
   <nz-tabset [(nzSelectedIndex)]="active">
@@ -20,15 +39,19 @@
     </nz-tab>
     <nz-tab nzTitle="教材列表">
       @if (active == 1) {
-      <!-- <comp-table-list
-        #list
-        [schema]="EduTextbook"
-        *ngIf="className && fieldsArray"
-        [className]="className"
-        [fieldsArray]="fieldsArray"
-        [queryParams]="queryParams"
-      ></comp-table-list> -->
-      <app-textbook [filterObj]="filterObj" [eduProcess]="eduProcess"></app-textbook>
+      <app-textbook
+        [filterObj]="filterObj"
+        [eduProcess]="eduProcess"
+      ></app-textbook>
+      }
+    </nz-tab>
+
+    <nz-tab nzTitle="教材文件">
+      @if (active == 2) {
+      <app-collect-textbook
+        #collect
+        [eduProcess]="eduProcess"
+      ></app-collect-textbook>
       }
     </nz-tab>
   </nz-tabset>

+ 5 - 21
projects/textbook/src/modules/nav-admin/page-process/page-process.component.ts

@@ -1,15 +1,14 @@
-import { Component, Input, OnInit } from '@angular/core';
+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 { EduTextbook } from '../../../schemas/EduTextbook';
 import { CompTableListComponent } from '../../../app/comp-table/comp-table-list/comp-table-list.component';
 import { ProcessCreateComponent } from './process-create/process-create.component';
 import { TextbookComponent } from '../../../app/textbook/textbook.component';
-
+import { CollectTextbookComponent } from '../components/collect-textbook/collect-textbook.component';
 @Component({
   selector: 'app-page-process',
   templateUrl: './page-process.component.html',
@@ -22,18 +21,17 @@ import { TextbookComponent } from '../../../app/textbook/textbook.component';
     ProcessCreateComponent,
     CompTableListComponent,
     TextbookComponent,
+    CollectTextbookComponent
   ],
   standalone: true,
 })
 export class PageProcessComponent  implements OnInit {
+  @ViewChild('collect') collect: CollectTextbookComponent | any;
+
   active: number = 0;
   eduProcess: Parse.Object | undefined;
 
-  EduTextbook = EduTextbook;
   user: Parse.User | undefined;
-  className: string | undefined;
-  queryParams: any | undefined;
-  fieldsArray: Array<any> | undefined;
   filterObj: any = {
     showMore: true, //显示更多字段
     isCheck: false,
@@ -45,21 +43,11 @@ export class PageProcessComponent  implements OnInit {
     private router: Router,
   ) {
     this.user = Parse.User.current();
-    this.className = this.EduTextbook.className;
-    this.fieldsArray = this.EduTextbook.fieldsArray;
-    this.queryParams = {
-      where: {
-        isDeleted: { $ne: true },
-        render: true,
-        status: '200',
-      },
-    };
   }
 
   ngOnInit() {
     this.activeRoute.paramMap.subscribe(async (params) => {
       let id = params.get('id');
-      this.queryParams.where['department'] = id
       if (id) {
         let query = new Parse.Query('EduProcess');
         query.include('branch', 'department');
@@ -68,8 +56,4 @@ export class PageProcessComponent  implements OnInit {
       }
     })
   }
-
-  onCreateProcess(){
-    this.router.navigate(['/nav-admin/manage/process/create',{cid:this.eduProcess?.id}])
-  }
 }

+ 79 - 6
projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.html

@@ -1,5 +1,4 @@
 <nz-page-header>
-  <!--title-->
   <nz-page-header-title
     >申报流程
     <br />
@@ -7,8 +6,6 @@
       统一管理各类教材推荐流程和限额,设置各个流程开始和结束时间、查看各流程工作进度
     </div>
   </nz-page-header-title>
-
-  <!--extra-->
   <nz-page-header-extra>
     <nz-space>
       <button
@@ -161,13 +158,17 @@
             <td nzEllipsis nz-popover [nzPopoverContent]="contentTemplate">
               {{ data?.get("desc") || "-" }}
               <ng-template #contentTemplate>
-                <div style="max-width: 400px;">
-                  {{data?.get('desc')}}
+                <div style="max-width: 400px">
+                  {{ data?.get("desc") }}
                 </div>
               </ng-template>
             </td>
             <td nzEllipsis>
-              {{ data?.get("num") || data?.get("num") == 0 ? data?.get("num") : "-" }}
+              {{
+                data?.get("num") || data?.get("num") == 0
+                  ? data?.get("num")
+                  : "-"
+              }}
             </td>
             <td nzEllipsis>
               {{
@@ -222,6 +223,22 @@
                       >暂停流程
                     </button>
                   </li>
+                  }@if (statusMap[data.id].collect){
+                  <li nz-menu-item>
+                    <button
+                      nz-button
+                      nzType="link"
+                      style="color: #231c1f"
+                      (click)="openEditCollect(data)"
+                    >
+                      <span
+                        nz-icon
+                        nzType="plus-circle"
+                        nzTheme="outline"
+                      ></span
+                      >收集源文件
+                    </button>
+                  </li>
                   } @if (statusMap[data.id].end){
                   <li nz-menu-item>
                     <button
@@ -365,3 +382,59 @@
     </button>
   </div>
 </nz-modal>
+
+<nz-modal
+  [(nzVisible)]="showModal"
+  nzTitle="收集教材源文件"
+  (nzOnCancel)="handleCancel()"
+  nzWidth="600px"
+>
+  <ng-container *nzModalContent>
+    <div nz-row class="depart-modal">
+      <div nz-col nzSpan="24">
+        <div class="row">
+          <div class="label">
+            开始时间 <span style="color: #e8353e">*</span>
+          </div>
+          <div class="value">
+            <nz-date-picker
+              nzFormat="yyyy-MM-dd HH:mm:ss"
+              [(ngModel)]="collectStartData"
+              [nzShowTime]="{ nzDefaultOpenValue: timeDefaultValue }"
+            ></nz-date-picker>
+          </div>
+        </div>
+        <div class="row">
+          <div class="label">截止时间 <span style="color: #e8353e">*</span></div>
+          <div class="value">
+            <nz-date-picker
+              nzFormat="yyyy-MM-dd HH:mm:ss"
+              [(ngModel)]="collectEndData"
+              [nzStatus]="collectEndData < collectStartData ? 'error' : ''"
+              [nzShowTime]="{ nzDefaultOpenValue: timeDefaultValue }"
+            ></nz-date-picker>
+          </div>
+        </div>
+        <div class="row" style="align-items: start;">
+          <div class="label">收集教材</div>
+          <div class="value">
+            @for (item of textBookList; track $index) {
+              <span>{{item?.get('title')}}; </span>
+            }
+          </div>
+        </div>
+      </div>
+    </div>
+  </ng-container>
+  <div *nzModalFooter>
+    <button nz-button nzType="default" (click)="handleCancel()">取消</button>
+    <button
+      nz-button
+      nzType="primary"
+      [disabled]="!this.collectStartData || !this.collectEndData"
+      (click)="editCollect()"
+    >
+      收集
+    </button>
+  </div>
+</nz-modal>

+ 6 - 1
projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.scss

@@ -75,8 +75,13 @@
 }
 .depart-modal{
   .row{
-    width: 90%;
+    width: 100%;
     margin-bottom: 20px;
+    display: flex;
+    align-items: center;
+    .label{
+      width: 100px;
+    }
   }
   .tree{
     height: 180px;

+ 75 - 5
projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.ts

@@ -1,6 +1,6 @@
 import { Component, OnInit, ViewChild } from '@angular/core';
 import { ActivatedRoute, RouterOutlet, Router } from '@angular/router';
-import { CompTableListComponent } from '../../../../app/comp-table/comp-table-list/comp-table-list.component';
+// import { CompTableListComponent } from '../../../../app/comp-table/comp-table-list/comp-table-list.component';
 import _Role from '../../../../schemas/_Role';
 // import { TranslateService } from '@ngx-translate/core';
 import * as Parse from 'parse';
@@ -26,7 +26,8 @@ import { NzRadioModule } from 'ng-zorro-antd/radio';
 import { NzMessageService } from 'ng-zorro-antd/message';
 import { NzModalService } from 'ng-zorro-antd/modal';
 import { NzPopoverModule } from 'ng-zorro-antd/popover';
-
+import { setHours } from 'date-fns';
+import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
 interface nodes {
   title: string;
   key: string;
@@ -50,7 +51,7 @@ interface depart {
     CommonModule,
     CommonCompModule,
     RouterOutlet,
-    CompTableListComponent,
+    // CompTableListComponent,
     NzSpaceModule,
     NzPageHeaderModule,
     NzBreadCrumbModule,
@@ -59,12 +60,14 @@ interface depart {
     NzEmptyModule,
     NzModalModule,
     NzRadioModule,
-    NzPopoverModule
+    NzPopoverModule,
+    NzDatePickerModule
   ],
   standalone: true,
 })
 export class ProcessListComponent implements OnInit {
-  @ViewChild(CompTableListComponent) list: CompTableListComponent | undefined;
+  // @ViewChild(CompTableListComponent) list: CompTableListComponent | undefined;
+  timeDefaultValue = setHours(new Date(), 0);
 
   // _Role = _Role
   Department = Department;
@@ -126,6 +129,30 @@ export class ProcessListComponent implements OnInit {
         stop: false,
         end: false,
         del: true,
+        collect:true
+      };
+    }
+    if(e?.get('collectStartData') && new Date() < new Date(e?.get('collectStartData'))){
+      return {
+        title: '待收集',
+        color: 'default',
+        strat: false,
+        stop: false,
+        end: false,
+        del: false,
+        collect:true
+      };
+    }
+    if(e?.get('collectStartData') && new Date() > new Date(e?.get('collectStartData')) 
+    && new Date() < e?.get('collectEndData')){
+      return {
+        title: '收集中',
+        color: 'orange',
+        strat: false,
+        stop: false,
+        end: false,
+        del: false,
+        collect:true
       };
     }
     if (e?.get('status') == '400') {
@@ -136,6 +163,7 @@ export class ProcessListComponent implements OnInit {
         stop: false,
         end: true,
         del: false,
+        collect:true
       };
     }
     if (!e?.get('startDate') || new Date() < new Date(e.get('startDate'))) {
@@ -200,6 +228,12 @@ export class ProcessListComponent implements OnInit {
     code: '',
     desc: '',
   };
+  showModal:boolean = false
+  eduProcess?:Parse.Object //当前编辑流程
+  textBookList:Array<Parse.Object> = [] //流程教材列表(推荐)
+  collectStartData:any
+  collectEndData:any
+
   constructor(
     private route: Router,
     private activeRoute: ActivatedRoute,
@@ -420,6 +454,12 @@ export class ProcessListComponent implements OnInit {
     this.isVisible = false;
     this.activatedNode = undefined;
     this.parentMap = [];
+
+    this.showModal = false
+    this.collectStartData = undefined
+    this.collectEndData = undefined
+    this.eduProcess = undefined
+    this.textBookList = []
   }
 
   statusSelected(type: string) {
@@ -502,4 +542,34 @@ export class ProcessListComponent implements OnInit {
       this.getEduProcess();
     }
   }
+
+  //打开编辑收集文件弹窗
+  async openEditCollect(data:Parse.Object){
+    this.eduProcess = data
+    this.collectStartData = this.eduProcess?.get('collectStartData')
+    this.collectEndData = this.eduProcess?.get('collectEndData')
+    let query = new Parse.Query('EduTextbook')
+    query.equalTo('eduProcess',this.eduProcess?.id)
+    query.notEqualTo('isDeleted',true)
+    query.equalTo('status','400')
+    query.equalTo('recommend', true);
+    query.notEqualTo('discard', true);
+    query.select('title')
+    let r = await query.find()
+    this.textBookList = r
+    this.showModal = true
+  }
+  //保存收集文件设置
+  async editCollect(){
+    if(!this.collectStartData || !this.collectEndData || this.collectStartData > this.collectEndData){
+      this.message.warning('请设置正确的开始和截止时间')
+      return
+    }
+    this.eduProcess?.set('collectStartData',this.collectStartData)
+    this.eduProcess?.set('collectEndData',this.collectEndData)
+    await this.eduProcess?.save()
+    this.message.success('设置成功')
+    this.getEduProcess()
+    this.showModal = false
+  }
 }

+ 1 - 1
projects/textbook/src/modules/nav-author/components/attachment/attachment.component.html

@@ -127,7 +127,7 @@
   <div class="author-content">
     <div class="title">2.图书编校质量自查结果记录表(必须提供)</div>
     <div class="text">
-      教材出版单位对申报教材的编校质量自查后,按要求提供图书编校质量自查结果记录表,并加盖出版社公章。全册教材的不同分册以不同文件分别上传。<a
+      教材出版单位对申报教材的编校质量自查后,按要求提供图书编校质量自查结果记录表,并加盖出版社公章。全册教材的不同分册文件按分册顺序排序后上传。<a
         (click)="downloadFile('图书编校质量自查结果记录表.docx')"
         >下载格式要求</a
       >

+ 198 - 0
projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.html

@@ -0,0 +1,198 @@
+<nz-page-header>
+  <nz-page-header-title
+    >本社教材源文件
+    <br />
+    <div class="subtitle">
+      将本社出版社的教材源文件PDF或数字资源链接提交至申报系统,以供评审使用
+    </div>
+  </nz-page-header-title>
+</nz-page-header>
+<div class="textbook-table">
+  <div class="tool">
+    <div class="search">
+      <nz-input-group style="width: 280px" [nzPrefix]="prefixTemplateUser">
+        <input
+          type="text"
+          nz-input
+          placeholder="输入教材名称 / ISBN / 申报编号"
+          [(ngModel)]="searchValue"
+          (ngModelChange)="onSearch($event)"
+        />
+      </nz-input-group>
+      <ng-template #prefixTemplateUser
+        ><span nz-icon nzType="search"></span
+      ></ng-template>
+    </div>
+  </div>
+  <nz-table
+    #tableData
+    [nzData]="textbookList"
+    [nzTotal]="count"
+    [(nzPageSize)]="limit"
+    [nzPageIndex]="pageIndex"
+    style="margin: 10px 0"
+    [nzLoading]="loading"
+    nzSize="middle"
+    [nzFrontPagination]="false"
+    [nzScroll]="{ x: (maxWidth || '800') + 'px', y: '580px' }"
+    nzTableLayout="fixed"
+    [nzPageSizeOptions]="[10, 20, 30, 40, 50]"
+    nzShowSizeChanger
+    (nzPageIndexChange)="pageIndexChange($event)"
+    (nzPageSizeChange)="onPageSizeChange()"
+  >
+    <thead>
+      <tr>
+        <th nzEllipsis nzWidth="120px" nzLeft>教材名称</th>
+        <th nzEllipsis nzWidth="120px">申报编号</th>
+        <th nzEllipsis nzWidth="120px">ISBN</th>
+        <th nzEllipsis nzWidth="120px">推荐单位</th>
+        <th nzEllipsis nzWidth="120px">推荐单位联系人</th>
+        <th nzEllipsis nzWidth="80px" nzAlign="center">任务状态</th>
+        <th nzEllipsis nzWidth="80px" nzAlign="center" nzRight>操作</th>
+      </tr>
+    </thead>
+    @if (textbookList.length > 0) {
+    <tbody>
+      @for (data of textbookList; track data.id) {
+      <tr>
+        <td
+          nzEllipsis
+          class="activeTd"
+          (click)="
+            toUrl('/common/textbook/details/' + data?.get('eduTextbook')?.id)
+          "
+          nz-popover
+          [nzPopoverContent]="contentTemplatetitle"
+        >
+          {{ data?.get("title") || "-" }}
+          <ng-template #contentTemplatetitle>
+            <div style="max-width: 400px">
+              {{ data?.get("title") || "-" }}
+            </div>
+          </ng-template>
+        </td>
+        <td nzEllipsis>
+          {{ data?.get("code") ?? "-" }}
+        </td>
+        <td
+          nzEllipsis
+          nz-popover
+          [nzPopoverContent]="contentTemplateISBN"
+          #ISBN
+        >
+          {{ fromatFiled(data?.get("childrens"), "ISBN") }}
+          <ng-template #contentTemplateISBN>
+            <div style="max-width: 400px">
+              {{ ISBN.innerText }}
+            </div>
+          </ng-template>
+        </td>
+        <td nzEllipsis>
+          {{ data?.get("department")?.get("name") }}
+        </td>
+        <td nzEllipsis>
+          {{
+            data
+              ?.get("eduProcess")
+              ?.get("profileSubmitted")
+              ?.get("user")
+              ?.get("name")
+          }}
+        </td>
+        <td nzEllipsis nzAlign="center">
+          <span [style.color]="statusMap[data.id].color">{{
+            statusMap[data.id].status
+          }}</span>
+        </td>
+        <td nzEllipsis nzRight nzAlign="center">
+          @if (statusMap[data.id].btn) {
+          <a nz-button nzType="link" (click)="onEditModal(data)">{{
+            statusMap[data.id].btn
+          }}</a>
+          }@else { - }
+        </td>
+      </tr>
+      }
+    </tbody>
+    }
+  </nz-table>
+  @if (textbookList.length == 0) {
+  <nz-empty nzNotFoundImage="/img/group-empty.png"></nz-empty>
+  }
+
+  <div class="loading" [hidden]="!showLoading">
+    <nz-spin nzSimple [nzSize]="'large'"></nz-spin>
+  </div>
+</div>
+
+<nz-modal
+  [(nzVisible)]="isVisible"
+  nzTitle="上传教材源文件"
+  (nzOnCancel)="handleCancel()"
+  nzWidth="600px"
+>
+  <ng-container *nzModalContent>
+    <div nz-row class="modal-content">
+      @if (currentTextbook?.get('type')=='全册') {
+      <nz-collapse [nzBordered]="false">
+        @for (panel of currentTextbook?.get('childrens'); track panel) { @if
+        (panel?.get('editionUnit') == this.eduProcess?.get('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>
+        } }
+      </nz-collapse>
+      } @else {
+      <app-upload-collect
+        #children
+        [eduTextbookVolume]="currentTextbook?.get('childrens')[0]"
+      ></app-upload-collect>
+      }
+    </div>
+  </ng-container>
+  <div *nzModalFooter>
+    <button nz-button nzType="default" (click)="handleCancel()">取消</button>
+    <button nz-button nzType="default" (click)="saveCollect('sbmit')">
+      提交
+    </button>
+    <button
+      nz-button
+      nzType="primary"
+      [disabled]="false"
+      (click)="saveCollect('save')"
+    >
+      保存
+    </button>
+  </div>
+</nz-modal>

+ 71 - 0
projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.scss

@@ -0,0 +1,71 @@
+.subtitle {
+  margin-right: 12px;
+  color: #00000073;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.5715;
+  // overflow: hidden;
+  // white-space: nowrap;
+  // text-overflow: ellipsis;
+}
+.textbook-table {
+  margin: 0 0 20px;
+  padding: 0 24px;
+  height: calc(100vh - 192px);
+  min-width: 800px;
+  .activeTd {
+    cursor: pointer;
+  }
+  .tool {
+    display: flex;
+    justify-content: space-between;
+    .sbt {
+      padding: 6px 10px;
+      background: #e8f5fc;
+      font-family: PingFang SC;
+      font-size: 14px;
+      font-weight: 400;
+      /* line-height: 20px; */
+      text-align: center;
+      color: #0054c3;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+  }
+  .activeTd {
+    color: #c6233f;
+  }
+}
+.loading {
+  position: fixed;
+  z-index: 99;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  text-align: center;
+  height: 100vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgb(0 0 0 / 30%);
+}
+::ng-deep .nz-table-hide-scrollbar {
+  scrollbar-color: auto;
+}
+// .row {
+//   width: 100%;
+//   margin-bottom: 20px;
+//   // display: flex;
+//   // align-items: center;
+//   .label {
+//     width: 100px;
+//   }
+//   .desc{
+//     color: rgba(0, 0, 0, 0.4509803922);
+//     font-size: 14px;
+//   }
+// }
+.modal-content{
+  max-height: 500px;
+  overflow-y: scroll;
+}

+ 24 - 0
projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.spec.ts

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

+ 272 - 0
projects/textbook/src/modules/nav-province-contact/collect-file/collect-file.component.ts

@@ -0,0 +1,272 @@
+import {
+  Component,
+  Input,
+  OnInit,
+  QueryList,
+  ViewChildren,
+} from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { NzSpaceModule } from 'ng-zorro-antd/space';
+import { CommonCompModule } from '../../../services/common.modules';
+import { ActivatedRoute, Router } from '@angular/router';
+import { NzMessageModule } from 'ng-zorro-antd/message';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import Parse from 'parse';
+import { textbookServer } from '../../../services/textbook';
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { MatDialog } from '@angular/material/dialog';
+import { NzEmptyModule } from 'ng-zorro-antd/empty';
+import { DatePipe } from '@angular/common';
+import { NzPopoverModule } from 'ng-zorro-antd/popover';
+import { UploadCollectComponent } from '../components/upload-collect/upload-collect.component';
+import { NzCollapseModule } from 'ng-zorro-antd/collapse';
+
+@Component({
+  selector: 'app-collect-file',
+  templateUrl: './collect-file.component.html',
+  styleUrls: ['./collect-file.component.scss'],
+  imports: [
+    CommonModule,
+    NzSpaceModule,
+    CommonCompModule,
+    NzMessageModule,
+    NzEmptyModule,
+    NzPopoverModule,
+    UploadCollectComponent,
+    NzCollapseModule,
+  ],
+  providers: [DatePipe],
+  standalone: true,
+})
+export class CollectFileComponent implements OnInit {
+  textbookList: Array<Parse.Object> = [];
+  eduProcess?: Parse.Object; //流程
+  count: number = 0;
+
+  limit: number = 10;
+  pageIndex: number = 1;
+  loading: boolean = false;
+  @Input('maxWidth') maxWidth: any; //最大宽度
+  @ViewChildren(UploadCollectComponent) children:
+    | QueryList<UploadCollectComponent>
+    | any;
+
+  searchValue: string = '';
+  time: any;
+  showLoading: boolean = false; //全局
+  /* 格式化拓展表字段 */
+  fromatFiled(list: Array<Parse.Object>, filed: string): string {
+    let arr: Array<string | null> = [];
+    let isDate = false;
+    list.forEach((item: Parse.Object) => {
+      if (
+        isDate ||
+        Object.prototype.toString.call(item.get(filed)).indexOf('Date') != -1
+      ) {
+        arr.push(this.datePipe.transform(item.get(filed), 'yyyy-MM'));
+        isDate = true;
+      } else {
+        arr.push(item.get(filed));
+      }
+    });
+    let j = Array.from(arr).join(',');
+    if (!isDate) {
+      j = Array.from(new Set(arr)).join(' ');
+    }
+    return j || '-';
+  }
+
+  isVisible: boolean = false;
+  currentTextbook?: Parse.Object; //当前编辑教材
+
+  statusMap: any = {}; //任务状态
+
+  constructor(
+    private msg: NzMessageService,
+    public tbookSer: textbookServer,
+    public dialog: MatDialog,
+    private route: Router,
+    private datePipe: DatePipe,
+    private modal: NzModalService
+  ) {}
+
+  async ngOnInit() {
+    await this.getEduProcess();
+    this.getTextbook();
+  }
+  async getEduProcess() {
+    let query = new Parse.Query('EduProcess');
+    query.notEqualTo('isDeleted', true);
+    query.equalTo('profileSubmitted', this.tbookSer.profile.objectId);
+    query.containedIn('status', ['400']);
+    let r = await query.first();
+    console.log(r);
+    this.eduProcess = r;
+  }
+
+  async getTextbook(val?: string): Promise<any[] | void> {
+    if (this.loading) return;
+    this.loading = true;
+    try {
+      let queryParams: any = {
+        where: {
+          $or: [
+            {
+              title: { $regex: `.*${val || ''}.*` },
+            },
+            {
+              code: { $regex: `.*${val || ''}.*` },
+            },
+            {
+              childrens: {
+                $inQuery: {
+                  where: {
+                    $or: [
+                      {
+                        ISBN: { $regex: `.*${val || ''}.*` },
+                      },
+                    ],
+                  },
+                  className: 'EduTextbookVolume',
+                },
+              },
+            },
+          ],
+          childrens: {
+            $inQuery: {
+              where: {
+                editionUnit: this.eduProcess?.get('name'),
+                // editionUnit: '山东大学出版社',
+              },
+              className: 'EduTextbookVolume',
+            },
+          },
+        },
+      };
+      let query = Parse.Query.fromJSON('EduTextbook', queryParams);
+      // query.equalTo('eduProcess', this.eduProcess?.id);
+      query.descending('updatedAt');
+      query.notEqualTo('isDeleted', true);
+      query.equalTo('status', '400');
+      query.equalTo('recommend', true);
+      query.notEqualTo('discard', true);
+      query.include(
+        'childrens',
+        'eduProcess.profileSubmitted.user',
+        'department'
+      );
+      this.count = await query.count();
+      query.limit(this.limit);
+      query.skip(this.limit * (this.pageIndex - 1));
+      this.textbookList = await query.find();
+      //获取对应出版单位管理员
+      this.textbookList.forEach((item) => {
+        this.statusMap[item.id] = {
+          status: '待上传',
+          color: '#1890ff',
+          btn: '上传',
+        };
+        //是否保存
+        let isSave = item?.get('childrens').some((child: Parse.Object) => {
+          return (
+            this.eduProcess?.get('name') == child?.get('editionUnit') &&
+            child?.get('collectStatus') == '100'
+          );
+        });
+        if (isSave) {
+          this.statusMap[item.id] = {
+            status: '已保存',
+            color: '#20c94e',
+            btn: '编辑',
+          };
+          return;
+        }
+        //是否提交
+        let isSbmit = item?.get('childrens').every((child: Parse.Object) => {
+          return (
+            this.eduProcess?.get('name') != child?.get('editionUnit') ||
+            (this.eduProcess?.get('name') == child?.get('editionUnit') &&
+              child?.get('collectStatus') == '200')
+          );
+        });
+        console.log(isSbmit);
+        if (isSbmit) {
+          this.statusMap[item.id] = {
+            status: '已提交',
+            color: '#7a7a7a',
+            btn: false,
+          };
+        }
+      });
+      console.log(this.textbookList);
+      this.loading = false;
+    } catch (err) {
+      console.warn(err);
+      this.msg.error('获取超时');
+    }
+    this.loading = false;
+  }
+  onSearch(e: string) {
+    this.pageIndex = 1;
+    console.log(e);
+    if (this.time) clearTimeout(this.time);
+    this.time = setTimeout(() => {
+      this.getTextbook(e);
+    }, 500);
+  }
+  //分页切换
+  pageIndexChange(e: any) {
+    console.log(e);
+    this.pageIndex = e;
+    this.getTextbook(this.searchValue);
+  }
+  //切换分页条数
+  onPageSizeChange(): void {
+    console.log(this.limit);
+    this.pageIndex = 1;
+    this.getTextbook(this.searchValue);
+  }
+
+  toUrl(url: string, param?: Object) {
+    console.log(url);
+    if (param) {
+      this.route.navigate([url, param]);
+      return;
+    }
+    this.route.navigate([url]);
+  }
+  //打开上传弹窗
+  onEditModal(data: Parse.Object) {
+    this.currentTextbook = data;
+    this.isVisible = true;
+  }
+  handleCancel(): void {
+    this.isVisible = false;
+    this.currentTextbook = undefined;
+  }
+  async saveCollect(type: string): Promise<any> {
+    console.log(type);
+    let isVrifly = await this.saveEduTextbookVolume(type);
+    if (!isVrifly) return this.msg.warning('已保存,填写信息不完整');
+    this.msg.success(type == 'save' ? '保存成功' : '提交成功');
+    this.isVisible = false;
+    this.getTextbook();
+  }
+
+  /**上传分册数据 */
+  async saveEduTextbookVolume(type: string): Promise<any> {
+    let isVrifly = true; //默认都通过,若一项填写未完成,则不通过
+    return Promise.all(
+      this.children.map(async (comp: any) => {
+        let complete = await comp.submitForm(type);
+        if (!complete) {
+          isVrifly = false;
+        }
+        return isVrifly;
+      })
+    ).then((data) => {
+      console.log(isVrifly);
+      return isVrifly;
+    });
+  }
+}

+ 1 - 1
projects/textbook/src/modules/nav-province-contact/components/review-details/review-details.component.html

@@ -31,7 +31,7 @@
     [nzPageSizeOptions]="[10, 20, 30, 40, 50]"
     nzShowSizeChanger
     (nzPageIndexChange)="pageIndexChange($event)"
-    (nzPageSizeChange)="onPageSizeChange($event)"
+    (nzPageSizeChange)="onPageSizeChange()"
   >
     <thead>
       <tr>

+ 2 - 2
projects/textbook/src/modules/nav-province-contact/components/review-details/review-details.component.ts

@@ -216,11 +216,11 @@ export class ReviewDetailsComponent implements OnInit {
     this.getTextbook(this.searchValue);
   }
   //切换分页条数
-  onPageSizeChange($event: any): void {
+  onPageSizeChange(): void {
     console.log(this.limit);
     // this.onAllChecked(false)
     this.pageIndex = 1;
-    this.getTextbook();
+    this.getTextbook(this.searchValue);
   }
 
   toUrl(url: string, param?: Object) {

+ 93 - 0
projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.html

@@ -0,0 +1,93 @@
+<div nz-row class="depart-modal">
+  <div nz-col nzSpan="24">
+    @if (eduTextbookVolume?.get('carrierShape') == '纸质教材'
+    ||eduTextbookVolume?.get('carrierShape') == '纸质教材附带电子资源') {
+    <div class="row">
+      <div class="title-name">纸质教材PDF文件</div>
+      <div class="desc">单个文件不超过500M</div>
+      <div>
+        <app-comp-upload
+          [files]="
+            eduTextbookVolume?.get('collectFiles')
+              ? eduTextbookVolume?.get('collectFiles')
+              : []
+          "
+          [type]="'pdf'"
+          [width]="500"
+          (change)="upload($event, 'collectFiles')"
+          title="上传文件"
+          [size]="512000"
+          [maxlenght]="10"
+        ></app-comp-upload>
+      </div>
+    </div>
+    } @if (eduTextbookVolume?.get('carrierShape') == '数字教材'
+    ||eduTextbookVolume?.get('carrierShape') == '纸质教材附带电子资源') {
+    <div class="row">
+      <div class="title-name">数字教材</div>
+      <div class="desc">
+        可单选上次文件、链接、链接和账号密码三种格式,上传文件总大小不可超过500M
+      </div>
+      <div class="value">
+        <nz-radio-group [(ngModel)]="radioValue">
+          <label nz-radio nzValue="上传文件">上传文件</label>
+          <label nz-radio nzValue="链接">链接</label>
+          <label nz-radio nzValue="链接和账号密码">链接和账号密码</label>
+        </nz-radio-group>
+      </div>
+      @if (radioValue == '链接') {
+      <div class="desc">请填写可以直接访问数字教材的链接</div>
+      <input
+        style="margin-bottom: 10px"
+        nz-input
+        placeholder="请填写可以直接访问数字教材的链接"
+        [(ngModel)]="collectLink.url"
+        type="text"
+      />
+      }@else if (radioValue == '链接和账号密码') {
+      <div class="desc">请填写可以访问数字教材的链接</div>
+      <input
+        style="margin-bottom: 10px"
+        nz-input
+        placeholder="请填写可以访问数字教材的链接"
+        [(ngModel)]="collectLink.url"
+        type="text"
+      />
+      <div class="desc">请填写可以访问数字教材的账号</div>
+      <input
+        style="margin-bottom: 10px"
+        nz-input
+        placeholder="请填写可以访问数字教材的账号"
+        [(ngModel)]="collectLink.username"
+        type="text"
+      />
+      <div class="desc">请填写可以访问数字教材的密码</div>
+      <input
+        style="margin-bottom: 10px"
+        nz-input
+        placeholder="请填写可以访问数字教材的密码"
+        [(ngModel)]="collectLink.password"
+        type="text"
+      />
+      } @else {
+      <div class="desc">批量上传数字文件,支持PDF格式,总大小不可超过500M</div>
+      <div>
+        <app-comp-upload
+          [files]="
+            eduTextbookVolume?.get('collectFiles')
+              ? eduTextbookVolume?.get('collectFiles')
+              : []
+          "
+          [type]="'pdf'"
+          [width]="500"
+          (change)="upload($event, 'collectDigitFiles')"
+          title="上传文件"
+          [size]="512000"
+          [maxlenght]="10"
+        ></app-comp-upload>
+      </div>
+      }
+    </div>
+    }
+  </div>
+</div>

+ 14 - 0
projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.scss

@@ -0,0 +1,14 @@
+.row {
+  width: 100%;
+  margin-bottom: 20px;
+  // display: flex;
+  // align-items: center;
+  .label {
+    width: 100px;
+  }
+  .desc{
+    color: rgba(0, 0, 0, 0.4509803922);
+    font-size: 14px;
+    margin-bottom: 10px;
+  }
+}

+ 24 - 0
projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.spec.ts

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

+ 95 - 0
projects/textbook/src/modules/nav-province-contact/components/upload-collect/upload-collect.component.ts

@@ -0,0 +1,95 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import Parse from 'parse';
+import { CompUploadComponent } from '../../../../app/comp-upload/comp-upload.component';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+import { CommonCompModule } from '../../../../services/common.modules';
+import { NzMessageService } from 'ng-zorro-antd/message';
+interface link {
+  url: string;
+  username?: string;
+  password?: string;
+}
+@Component({
+  selector: 'app-upload-collect',
+  templateUrl: './upload-collect.component.html',
+  styleUrls: ['./upload-collect.component.scss'],
+  standalone: true,
+  imports: [CommonCompModule, CommonModule, CompUploadComponent, NzRadioModule],
+})
+export class UploadCollectComponent implements OnInit {
+  @Input('eduTextbookVolume') eduTextbookVolume: Parse.Object | undefined;
+  @Output() save: EventEmitter<any> = new EventEmitter<any>();
+
+  collectFiles: Array<any> = []; //纸质教材PDF文件列表
+  collectDigitFiles: Array<any> = []; //数字资源PDF文件列表
+  collectLink: link = {
+    url: '',
+    username: '',
+    password: '',
+  }; //链接、账号密码
+
+  radioValue: string = '上传文件';
+
+  constructor(private msg: NzMessageService) {}
+
+  ngOnInit() {
+    this.collectFiles = this.eduTextbookVolume?.get('collectFiles');
+    this.collectDigitFiles = this.eduTextbookVolume?.get('collectDigitFiles');
+    this.collectLink = this.eduTextbookVolume?.get('collectLink');
+    this.radioValue = this.eduTextbookVolume?.get('collectCheck');
+  }
+  upload(e: any, type: string) {
+    console.log('上传材料发生改变');
+    console.log(e);
+    if (type == 'collectFiles' || type == 'collectDigitFiles') {
+      this[type] = e;
+    }
+  }
+  async submitForm(type: string) {
+    if (
+      this.eduTextbookVolume?.get('carrierShape') == '纸质教材' ||
+      this.eduTextbookVolume?.get('carrierShape') == '纸质教材附带电子资源'
+    ) {
+      let upload = this.collectFiles?.some((item) => item.url);
+      console.log(upload);
+      if (!upload && type == 'sbmit') {
+        // this.msg.warning('请上传纸质教材PDF文件');
+        return;
+      }
+      this.eduTextbookVolume?.set('collectFiles', this.collectFiles);
+    }
+    if (
+      this.eduTextbookVolume?.get('carrierShape') == '数字教材' ||
+      this.eduTextbookVolume?.get('carrierShape') == '纸质教材附带电子资源'
+    ) {
+      let upload = this.collectDigitFiles?.some((item) => item.url);
+      console.log(upload);
+      if (this.radioValue == '上传文件' && !upload && type == 'sbmit') {
+        // this.msg.warning('请上传数字文件PDF');
+        return;
+      }
+      this.eduTextbookVolume?.set('collectDigitFiles', this.collectDigitFiles);
+      this.eduTextbookVolume?.set('collectCheck', this.radioValue);
+      if (
+        type == 'sbmit' &&
+        ((this.radioValue == '链接' && !this.collectLink?.url) ||
+          (this.radioValue == '链接和账号密码' &&
+            (!this.collectLink?.url ||
+              !this.collectLink?.username ||
+              !this.collectLink?.password)))
+      ) {
+        // this.msg.warning('请填写完整信息');
+        return;
+      }
+      this.eduTextbookVolume?.set('collectLink', this.collectLink);
+    }
+    /* 100:已上传 200:已提交*/
+    this.eduTextbookVolume?.set(
+      'collectStatus',
+      type == 'sbmit' ? '200' : '100'
+    );
+    await this.eduTextbookVolume?.save();
+    return true;
+  }
+}

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

@@ -10,7 +10,7 @@ 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";
+import { CollectFileComponent } from "./collect-file/collect-file.component";
 
 const routes: Routes = [
   {
@@ -58,6 +58,11 @@ const routes: Routes = [
         path: 'review-edit/:id', //评审活动
         component: ReviewEditComponent,
       },
+      
+      {
+        path: 'collect', //本社教材源文件
+        component: CollectFileComponent,
+      },
     ]
   }
 ];

+ 1 - 1
projects/textbook/src/modules/nav-province-contact/page-textbook/page-textbook.component.html

@@ -1,7 +1,7 @@
 @if (!editLoading && eduProcess?.id) {
   <nz-page-header>
     <nz-page-header-title
-      >测试评审活动
+      >全部教材列表
       <br />
       <div class="subtitle">
         查看全部申报流程所提交的教材推荐表

+ 1 - 1
projects/textbook/src/modules/nav-review/approve/approve.component.ts

@@ -57,7 +57,7 @@ export class ApproveComponent implements OnInit {
   afterFilterObj: any = {
     showMore: true, //显示更多字段
     isCheck: false,
-    status: ['200'],
+    status: ['200','201','300','400'],
     contained: [],
     btns: {
       eduReivew: true,

+ 9 - 0
server/db/schemas/EduProcess.js

@@ -60,6 +60,15 @@ export const EduProcess = {
       "type": "Date",
       "required": false
     },
+
+    "collectStartData": {//收集文件开始时间
+      "type": "Date",
+      "required": false
+    },
+    "collectEndData": {//收集文件结束时间
+      "type": "Date",
+      "required": false
+    },
   },
   "classLevelPermissions": {
     "find": {