Browse Source

update admin

warrior 7 months ago
parent
commit
82fa3d53e2
21 changed files with 806 additions and 250 deletions
  1. 27 5
      projects/textbook/src/app/comp-manage/comp-manage.component.ts
  2. 1 1
      projects/textbook/src/app/comp-nav/comp-nav.component.html
  3. 2 1
      projects/textbook/src/app/comp-nav/comp-nav.component.ts
  4. 5 2
      projects/textbook/src/app/textbook/textbook.component.ts
  5. 1 1
      projects/textbook/src/modules/nav-admin/modules.routes.ts
  6. 5 5
      projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.html
  7. 1 0
      projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.scss
  8. 18 13
      projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.ts
  9. 8 4
      projects/textbook/src/modules/nav-admin/page-role/page-role.component.ts
  10. 5 2
      projects/textbook/src/modules/nav-admin/page-user/user-create/user-create.component.html
  11. 5 2
      projects/textbook/src/modules/nav-admin/page-user/user-create/user-create.component.ts
  12. 2 2
      projects/textbook/src/modules/nav-author/modules.routes.ts
  13. 2 1
      projects/textbook/src/modules/nav-author/recycle/recycle.component.html
  14. 3 4
      projects/textbook/src/modules/nav-author/recycle/recycle.component.ts
  15. 1 1
      projects/textbook/src/modules/nav-province-contact/modules.routes.ts
  16. 50 22
      projects/textbook/src/modules/nav-province-contact/page-process/process-list/process-list.component.html
  17. 2 1
      projects/textbook/src/modules/nav-province-contact/page-process/process-list/process-list.component.scss
  18. 137 75
      projects/textbook/src/modules/nav-province-contact/page-process/process-list/process-list.component.ts
  19. 172 23
      projects/textbook/src/modules/nav-province-contact/page-role/page-role.component.html
  20. 345 84
      projects/textbook/src/modules/nav-province-contact/page-role/page-role.component.ts
  21. 14 1
      projects/textbook/src/modules/nav-province-school-contact/modules.routes.ts

+ 27 - 5
projects/textbook/src/app/comp-manage/comp-manage.component.ts

@@ -51,7 +51,7 @@ export class CompManageComponent implements OnInit {
             id:'2-1',
           },
           {
-            name:'组织管理',
+            name:'申报单位管理',
             path:"/nav-admin/manage/role",
             id:'2-3',
           },
@@ -113,19 +113,41 @@ export class CompManageComponent implements OnInit {
     "高校联系人":[
       {
         id:'1',
-        name:'全部教材',
-        path:'/nav-province-school-contact/manage/textbook',
+        name:'教材申报',
+        child:[
+          {
+            id:'1-1',
+            name:'全部教材',
+            path:'/nav-province-school-contact/manage/textbook',
+          }
+        ]
       },
       {
-        name:'申报单位管理',
-        path:"/nav-province-school-contact/manage/role",
         id:'2',
+        name:'用户过来',
+        child:[
+          {
+            id:'2-1',
+            name:'用户列表',
+            path:'/nav-province-school-contact/manage/user',
+          },
+          {
+            name:'申报单位管理',
+            path:"/nav-province-school-contact/manage/role",
+            id:'2-2',
+          },
+        ]
       },
       {
         name:'个人空间',
         id:'3',
         path: '/nav-province-school-contact/manage/space',
       },
+      {
+        name:'回收站',
+        id:'4',
+        path: '/nav-province-school-contact/manage/recycle',
+      },
     ],
     "评审专家":[
       {

+ 1 - 1
projects/textbook/src/app/comp-nav/comp-nav.component.html

@@ -6,7 +6,7 @@
   ></nz-avatar>
   @if(!viewCollapsed){
   <div class="user">
-    <div class="name">{{ tbookSer.profile?.user?.name || "未认证用户" }}</div>
+    <div class="name">{{ tbookSer.profile?.user?.name || "未认证用户" }}<nz-tag [nzColor]="'red'">{{tbookSer.profile?.identity}}</nz-tag></div>
     <div class="email">
       {{ tbookSer.profile?.user?.email || "test@edu.com" }}
     </div>

+ 2 - 1
projects/textbook/src/app/comp-nav/comp-nav.component.ts

@@ -6,12 +6,13 @@ import { MatIconModule } from '@angular/material/icon';
 import { RouterModule,Router } from '@angular/router';
 import { AuthServr } from '../../services/auth.service'
 import { NzPopoverModule } from 'ng-zorro-antd/popover';
+import { NzTagModule } from 'ng-zorro-antd/tag';
 @Component({
   selector: 'app-comp-nav',
   standalone: true,
   imports: [
     RouterModule,
-    NzAvatarModule,
+    NzAvatarModule,NzTagModule,
     MatButtonModule,MatIconModule,NzPopoverModule
   ],
   templateUrl: './comp-nav.component.html',

+ 5 - 2
projects/textbook/src/app/textbook/textbook.component.ts

@@ -34,7 +34,7 @@ export class TextbookComponent implements OnInit {
   @Input('uid') uid: string = ''; //个人空间
   @Input('path') path: string = '/nav-admin/manage/textbook/details';
   @Input('power') power: Boolean = false; //权限
-  @Input('isSelf') isSelf: Boolean = false; //是否自己创建
+  @Input('discard') discard: Boolean = false; //是否删除
 
   loading: boolean = false;
   checkedAll: boolean = false; //全选
@@ -86,10 +86,13 @@ export class TextbookComponent implements OnInit {
     this.recommend && query.equalTo('recommend', true);
     this.uid && query.equalTo('user', this.uid);
     query.notEqualTo('isDeleted', true);
-    if(!this.isSelf){
+    if(!this.uid){
       query.equalTo('render',true)
       query.containedIn('status',['200','400'])
     }
+    if(this.discard){
+      query.equalTo('discard',true)
+    }
     this.textbookList = await query.find();
     console.log(this.textbookList);
     this.loading = false;

+ 1 - 1
projects/textbook/src/modules/nav-admin/modules.routes.ts

@@ -15,7 +15,7 @@ import { UserCreateComponent } from './page-user/user-create/user-create.compone
 const routes: Routes = [
   {
     path: '',
-    redirectTo:'manage/user',
+    redirectTo:'manage/process',
     pathMatch: "full",
   },
   {

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

@@ -26,7 +26,7 @@
 <div class="edit-content">
   <div class="tool">
     <div class="tool-left">
-      <div class="search">
+      <!-- <div class="search">
         <nz-input-group style="width: 210px" [nzPrefix]="prefixTemplateUser">
           <input
             type="text"
@@ -51,7 +51,7 @@
         </button>
         <nz-dropdown-menu #menutep="nzDropdownMenu">
           <ul nz-menu>
-            <!-- <li nz-menu-item>
+            <li nz-menu-item>
               <button
                 nz-button
                 nzType="link"
@@ -60,7 +60,7 @@
               >
                 <span nz-icon nzType="plus" nzTheme="outline"></span>添加部门
               </button>
-            </li> -->
+            </li>
             <li nz-menu-item>
               <button
                 nz-button
@@ -74,9 +74,9 @@
             </li>
           </ul>
         </nz-dropdown-menu>
-      </div>
+      </div> -->
       <div class="">
-        <nz-input-group style="width: 210px" [nzPrefix]="prefixTemplateUser">
+        <nz-input-group style="width: 280px" [nzPrefix]="prefixTemplateUser">
           <input
             type="text"
             nz-input

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

@@ -103,6 +103,7 @@
   left: calc(50% - 376px);
   -webkit-transform: translate(0);
   transform: translate(0);
+  z-index: 99;
 }
 .batch-toolbar {
   display: flex;

+ 18 - 13
projects/textbook/src/modules/nav-admin/page-process/process-list/process-list.component.ts

@@ -266,6 +266,7 @@ export class ProcessListComponent implements OnInit {
     this.eduProcessLength = await query.count();
     query.skip(this.pageSize * (this.pageIndex - 1));
     query.limit(this.pageSize);
+    query.descending('createdAt');
     let r = await query.find();
     // let list: any[] = [];
     r.forEach((item) => {
@@ -292,6 +293,9 @@ export class ProcessListComponent implements OnInit {
         this.setOfCheckedId.delete(item.id);
       }
     });
+    if (!checked) {
+      this.setOfCheckedId = new Set<string>();
+    }
     this.checkedAll = checked;
   }
   onItemChecked(id: string, e: boolean) {
@@ -373,16 +377,16 @@ export class ProcessListComponent implements OnInit {
     };
     this.isVisible = true;
   }
-  async handleOk():Promise<void> {
-    if(!this.branchObj?.name){
+  async handleOk(): Promise<void> {
+    if (!this.branchObj?.name) {
       this.message.warning('组织名称不能为空');
     }
-    let obj = Parse.Object.extend('Department')
-    let depart = new obj()
-    depart.set('name',this.branchObj?.name)
-    depart.set('desc',this.branchObj?.desc)
-    depart.set('code',this.branchObj?.code)
-    await depart.save()
+    let obj = Parse.Object.extend('Department');
+    let depart = new obj();
+    depart.set('name', this.branchObj?.name);
+    depart.set('desc', this.branchObj?.desc);
+    depart.set('code', this.branchObj?.code);
+    await depart.save();
     this.isVisible = false;
     this.message.success('新建成功');
     this.nodes = await this.getDepart();
@@ -447,10 +451,10 @@ export class ProcessListComponent implements OnInit {
         if (data?.get('status') == '100') {
           data?.set('status', null);
         }
-        if (data?.get('startDate') > new Date()) {
+        if (!data?.get('startDate') || data?.get('startDate') > new Date()) {
           data?.set('startDate', new Date());
         }
-        if (data?.get('deadline') < new Date()) {
+        if (!data?.get('deadline') || data?.get('deadline') < new Date()) {
           data?.set(
             'deadline',
             new Date(new Date().getTime() + 60 * 1000 * 60 * 24 * 7)
@@ -476,10 +480,11 @@ export class ProcessListComponent implements OnInit {
   }
 
   /* 批量预设(临时) */
-  // async saveProcess(){
-  //   let count = 0
+  // async saveProcess() {
+  //   let count = 0;
   //   let query = new Parse.Query('Department')
   //   query.equalTo('parent',null)
+  //   query.equalTo('name','省级教育行政部门')
   //   let r = await query.find()
   //   for (let index = 0; index < r.length; index++) {
   //     const element = r[index];
@@ -508,7 +513,7 @@ export class ProcessListComponent implements OnInit {
   //       });
   //       eduProcess?.set('name', item.get('name'));
   //       eduProcess?.set('desc', item.get('name') + '流程');
-  //       eduProcess?.set('code', item.get('code'));
+  //       eduProcess?.set('code', item.get('code') || item.id);
   //       if(element.get('name') == '全国出版单位') {
   //         eduProcess?.set('startDate', new Date('2024-07-20 18:00'));
   //         eduProcess?.set('deadline', new Date('2024-09-20 18:00'));

+ 8 - 4
projects/textbook/src/modules/nav-admin/page-role/page-role.component.ts

@@ -152,7 +152,7 @@ export class PageRoleComponent implements OnInit {
     }
     searchValue && query.contains('name', searchValue);
     query.notEqualTo('isDeleted', true);
-    query.select('code', 'name', 'branch', 'parent', 'type');
+    query.select('code', 'name', 'branch', 'parent', 'type','hasChildren');
     query.descending('createdAt');
     query.limit(2000);
     if (this.activeDepart) query.notEqualTo('objectId', this.activeDepart?.id);
@@ -163,7 +163,7 @@ export class PageRoleComponent implements OnInit {
         key: item.id,
         children: [],
         branch: item.get('branch'),
-        isLeaf: item.get('type') == '单位' ? true : false, //是否是最下级
+        isLeaf: !item.get('hasChildren'), //是否是最下级
       });
     });
     return nodes;
@@ -496,10 +496,14 @@ export class PageRoleComponent implements OnInit {
       this.message.error("请填写正确手机号");
       return;
     }
+    if(!this.account?.email || !this.account?.phone){
+      this.message.error("邮箱或手机号必须填写一项");
+      return;
+    }
     try{
       let obj = Parse.Object.extend('_User');
       let user = new obj()
-      user?.set('username', this.account?.email);
+      user?.set('username', this.account?.email || this.account?.phone);
       user?.set('name', this.account?.name);
       user?.set('phone', this.account?.phone);
       user?.set('email', this.account?.email);
@@ -530,7 +534,7 @@ export class PageRoleComponent implements OnInit {
     }
     catch(err:any){
       console.warn('添加用户错误',err);
-      this.message.error(err?.Error || '错误:请检查用户邮箱是否已存在');
+      this.message.error(err?.Error || '错误:请检查用户邮箱或手机号是否已存在');
       return;
     }
   }

+ 5 - 2
projects/textbook/src/modules/nav-admin/page-user/user-create/user-create.component.html

@@ -387,7 +387,6 @@
     <div nz-row class="depart-modal">
       <div nz-col nzSpan="24">
         <div class="row">
-          <div class="label">选择部门</div>
           <div class="value">
             <input
               nz-input
@@ -404,7 +403,11 @@
             @for (data of parentMap; track data.title) {
             <span style="margin: 0 10px">/</span>
             @if($index == 0){
-            <a (click)="onCheck(data)">{{ data.title }}</a>
+              @if (tbookSer.profile.identity != '国家级管理员') {
+                <a style="color: #86909c">{{ data.title }}</a>
+              }@else {
+                <a (click)="onCheck(data)">{{ data.title }}</a>
+              }
             }@else {
             <a style="color: #86909c">{{ data.title }}</a>
             } }

+ 5 - 2
projects/textbook/src/modules/nav-admin/page-user/user-create/user-create.component.ts

@@ -121,10 +121,13 @@ export class UserCreateComponent implements OnInit {
   //根据所选单位类型获取对应单位
   async provinceChange(id?: string, val?: string) {
     let query = new Parse.Query('Department');
-    query.equalTo('parent', id ? id : null);
     query.select('name', 'branch', 'parent');
     if (this.tbookSer.profile.identity != '国家级管理员') {
-      query.equalTo('branch', this.tbookSer.profile.companyType);
+      // query.equalTo('branch', this.tbookSer.profile.companyType);
+      query.equalTo('objectId', this.tbookSer.profile?.user.department?.objectId);
+    }
+    if(this.tbookSer.profile.identity == '国家级管理员' || id){
+      query.equalTo('parent', id ? id : null);
     }
     query.limit(100);
     val && query.contains('name', val);

+ 2 - 2
projects/textbook/src/modules/nav-author/modules.routes.ts

@@ -16,11 +16,11 @@ const routes: Routes = [
     path: 'manage',
     children: [
       {
-        path: 'space',//创建教材
+        path: 'space',//个人空间
         component: SpaceComponent,
       },
       {
-        path: 'apply',//创建教材
+        path: 'apply',//填写材料
         component: ApplyComponent,
       },
       {

+ 2 - 1
projects/textbook/src/modules/nav-author/recycle/recycle.component.html

@@ -12,6 +12,7 @@
     </div>
   </div>
   <div class="space-content">
-    <app-page-textbook [discard]="true"></app-page-textbook>
+    <!-- <app-page-textbook [discard]="true"></app-page-textbook> -->
+    <app-textbook [uid]="user?.id"></app-textbook>
   </div>
 </div>

+ 3 - 4
projects/textbook/src/modules/nav-author/recycle/recycle.component.ts

@@ -3,18 +3,17 @@ import { CommonCompModule } from '../../../services/common.modules';
 import { Router } from '@angular/router';
 import * as Parse from 'parse';
 import { PageTextbookComponent } from '../components/page-textbook/page-textbook.component';
+import { TextbookComponent } from '../../../app/textbook/textbook.component';
 
 @Component({
   selector: 'app-recycle',
   standalone: true,
-  imports: [CommonCompModule,PageTextbookComponent],
+  imports: [CommonCompModule,PageTextbookComponent,TextbookComponent],
   templateUrl: './recycle.component.html',
   styleUrls: ['./recycle.component.scss'],
 })
 export class RecycleComponent  implements OnInit {
-
-  user: Parse.Object = Parse.User.current()!;
-
+  user: Parse.Object | any = Parse.User.current();
   constructor(private router: Router) {}
 
   ngOnInit() {}

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

@@ -10,7 +10,7 @@ import { PageTextbookComponent } from "./page-textbook/page-textbook.component";
 const routes: Routes = [
   {
     path: '',
-    redirectTo:'manage',
+    redirectTo:'manage/process',
     pathMatch: "full",
   },
   {

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

@@ -57,13 +57,16 @@
       <nz-table
         #tableData
         [nzData]="eduProcessList"
-        [nzTotal]="eduProcessList.length"
-        [nzPageSize]="10"
+        [nzTotal]="eduProcessLength"
+        [nzPageSize]="pageSize"
+        [nzPageIndex]="pageIndex"
         style="margin: 10px 0"
         [nzLoading]="loading"
         nzSize="middle"
         [nzNoResult]="emptyResult"
         nzTableLayout="fixed"
+        [nzFrontPagination]="false"
+        (nzPageIndexChange)="pageIndexChange($event)"
       >
         <thead>
           <tr>
@@ -86,40 +89,42 @@
           </tr>
         </thead>
         <tbody>
-          @for (data of eduProcessList; track data.objectId) {
+          @for (data of tableData.data; track data.id) {
           <tr>
             <td
               nzEllipsis
               nzLeft
-              [nzChecked]="data.checked"
-              (nzCheckedChange)="onItemChecked(data.objectId, $event)"
+              [nzChecked]="setOfCheckedId.has(data.id)"
+              (nzCheckedChange)="onItemChecked(data.id, $event)"
             ></td>
             <td
               nzEllipsis
               (click)="
                 toUrl('/nav-province-contact/manage/process/page', {
-                  id: data?.objectId
+                  id: data?.id
                 })
               "
               class="activeTd"
             >
-              {{ data?.name || "-" }}
+              {{ data?.get("name") || "-" }}
             </td>
             <td nzEllipsis>
-              {{ data?.code || "-" }}
+              {{ data?.get("code") || "-" }}
             </td>
             <td nzEllipsis>
-              {{ data?.desc || "-" }}
+              {{ data?.get("desc") || "-" }}
             </td>
             <td nzEllipsis>
-              {{ data?.num || "-" }}
+              {{ data?.get("num") || "-" }}
             </td>
             <td nzEllipsis>
-              {{ data?.profileSubmitted?.user.name || "-" }}
+              {{
+                data?.get("profileSubmitted")?.get("user").get("name") || "-"
+              }}
             </td>
             <td nzEllipsis>
-              <nz-tag [nzColor]="data?.state.color">
-                {{ data?.state.title }}</nz-tag
+              <nz-tag [nzColor]="statusMap[data.id].color">
+                {{ statusMap[data.id].title }}</nz-tag
               >
             </td>
             <td nzEllipsis nzRight>
@@ -133,13 +138,13 @@
               </button>
               <nz-dropdown-menu #menu="nzDropdownMenu">
                 <ul nz-menu>
-                  @if (data.state?.strat){
+                  @if (statusMap[data.id].strat){
                   <li nz-menu-item>
                     <button
                       nz-button
-                      [disabled]="user?.get('isDeleted')"
                       nzType="link"
                       style="color: #231c1f"
+                      (click)="onStatusChange(data, 'strat', true)"
                     >
                       <span
                         nz-icon
@@ -149,13 +154,13 @@
                       >开始流程
                     </button>
                   </li>
-                  } @if (data.state?.stop){
+                  } @if (statusMap[data.id].stop){
                   <li nz-menu-item>
                     <button
                       nz-button
-                      [disabled]="user?.get('isDeleted')"
                       nzType="link"
                       style="color: #231c1f"
+                      (click)="onStatusChange(data, 'stop', true)"
                     >
                       <span
                         nz-icon
@@ -165,21 +170,26 @@
                       >暂停流程
                     </button>
                   </li>
-                  } @if (data.state?.end){
+                  } @if (statusMap[data.id].end){
                   <li nz-menu-item>
                     <button
                       nz-button
-                      [disabled]="user?.get('isDeleted')"
                       nzType="link"
                       style="color: #231c1f"
+                      (click)="onStatusChange(data, 'end', true)"
                     >
                       <span nz-icon nzType="stop" nzTheme="outline"></span
                       >结束流程
                     </button>
                   </li>
-                  } @if (data.state?.del){
+                  } @if (statusMap[data.id].del){
                   <li nz-menu-item>
-                    <button nz-button nzType="link" style="color: #231c1f">
+                    <button
+                      nz-button
+                      nzType="link"
+                      (click)="onStatusChange(data, 'del', true)"
+                      style="color: #231c1f"
+                    >
                       <span nz-icon nzType="delete" nzTheme="outline"></span
                       >删除流程
                     </button>
@@ -208,13 +218,31 @@
     <div class="batch-toolbar-actions">
       <div class="ant-space ant-space-horizontal ant-space-align-center">
         <div class="ant-space-item" style="margin-right: 16px">
-          <button nz-button nzType="text" (click)="deleteSelected()">
+          <button nz-button nzType="text" (click)="statusSelected('del')">
             <span nz-icon nzType="delete"></span>
             删除
           </button>
         </div>
       </div>
     </div>
+    <div class="styles_cancel__AARoT">
+      <button nz-button nzType="text" (click)="statusSelected('strat')">
+        <span nz-icon nzType="caret-right" nzTheme="outline"></span>
+        批量开始
+      </button>
+    </div>
+    <div class="styles_cancel__AARoT">
+      <button nz-button nzType="text" (click)="statusSelected('stop')">
+        <span nz-icon nzType="pause-circle" nzTheme="outline"></span>
+        批量暂停
+      </button>
+    </div>
+    <div class="styles_cancel__AARoT">
+      <button nz-button nzType="text" (click)="statusSelected('end')">
+        <span nz-icon nzType="stop" nzTheme="outline"></span>
+        批量结束
+      </button>
+    </div>
     <div class="styles_cancel__AARoT">
       <button nz-button nzType="text" (click)="onAllChecked(false)">
         取消选中

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

@@ -100,9 +100,10 @@
   display: flex;
   justify-content: center;
   bottom: 80px;
-  left: calc(50% - 134px);
+  left: calc(50% - 376px);
   -webkit-transform: translate(0);
   transform: translate(0);
+  z-index: 99;
 }
 .batch-toolbar {
   display: flex;

+ 137 - 75
projects/textbook/src/modules/nav-province-contact/page-process/process-list/process-list.component.ts

@@ -72,6 +72,11 @@ export class ProcessListComponent implements OnInit {
   currentDepart: nodes | any = null;
 
   eduProcessList: Array<any> = [];
+  statusMap: any = {};
+  eduProcessLength: number = 0;
+  pageSize: number = 10;
+  pageIndex: number = 1;
+
   checkedShowFilter: boolean = false;
 
   checkedAll: boolean = false; //全选
@@ -91,69 +96,77 @@ export class ProcessListComponent implements OnInit {
   };
   parentMap: Array<any> = [];
   setOfCheckedId = new Set<string>();
-  formatStatus = (e:any)=>{
-    if(e.status == '100'){
+  formatStatus = (e: any) => {
+    if (e.get('status') == '100') {
       return {
-        title:'已暂停',
-        color:'lime',
-        strat:true,
-        stop:true,
-        end:false,
-        del:false,
-      }
+        title: '已暂停',
+        color: 'lime',
+        strat: true,
+        stop: true,
+        end: false,
+        del: false,
+      };
     }
-    if(e?.deadline.iso && new Date() > new Date(e.deadline.iso) && e.status == '200'){
+    if (
+      e?.get('deadline') &&
+      new Date() > new Date(e?.get('deadline')) &&
+      e?.get('status') == '200'
+    ) {
       return {
-        title:'已结束',
-        color:'gold',
-        strat:false,
-        stop:false,
-        end:false,
-        del:true,
-      }
+        title: '已结束',
+        color: 'gold',
+        strat: false,
+        stop: false,
+        end: false,
+        del: true,
+      };
     }
-    if(e.status == '200'){
+    if (e?.get('status') == '200') {
       return {
-        title:'已完成',
-        color:'green',
-        strat:false,
-        stop:false,
-        end:true,
-        del:false,
-      }
+        title: '已完成',
+        color: 'green',
+        strat: false,
+        stop: false,
+        end: true,
+        del: false,
+      };
     }
-    if(!e?.startDate.iso || (new Date() < new Date(e.startDate.iso))){
+    if (!e?.get('startDate') || new Date() < new Date(e.get('startDate'))) {
       return {
-        title:'未开始',
-        color:'grey',
-        strat:true,
-        stop:false,
-        end:false,
-        del:true,
-      }
+        title: '未开始',
+        color: 'grey',
+        strat: true,
+        stop: false,
+        end: false,
+        del: true,
+      };
     }
-    if(!e.statuse && e.startDate.iso && new Date() >= new Date(e.startDate.iso) ){
+    if (
+      !e.get('status') &&
+      e?.get('startDate') &&
+      new Date() >= new Date(e?.get('startDate'))
+    ) {
       return {
-        title:'遴选中',
-        color:'blue',
-        strat:false,
-        stop:true,
-        end:true,
-        del:false,
-      }
+        title: '遴选中',
+        color: 'blue',
+        strat: false,
+        stop: true,
+        end: true,
+        del: false,
+      };
     }
-    if(e?.deadline.iso && (new Date() > new Date(e.deadline.iso))){
+    if (e?.get('deadline') && new Date() > new Date(e?.get('deadline'))) {
       return {
-        title:'已逾期',
-        color:'red',
-        strat:true,
-        stop:false,
-        end:false,
-        del:true,
-      }
+        title: '已逾期',
+        color: 'red',
+        strat: true,
+        stop: false,
+        end: false,
+        del: true,
+      };
     }
-    return
-  }
+    return;
+  };
   constructor(
     public tbookSer: textbookServer,
     private route: Router,
@@ -236,71 +249,120 @@ export class ProcessListComponent implements OnInit {
     query.include('profileSubmitted', 'profileSubmitted.user');
     query.notEqualTo('isDeleted', true);
     query.equalTo('department',this.tbookSer.profile.user.department?.objectId)
+    query.descending('createdAt');
     let r = await query.find();
-    let list: any[] = [];
+    // let list: any[] = [];
     r.forEach((item) => {
-      let _item = item.toJSON();
-      _item['checked'] = false;
-      _item['state'] = this.formatStatus(_item);
-      list.push(_item);
+      // let _item = item.toJSON();
+      // _item['checked'] = false;
+      // _item['state'] = this.formatStatus(_item);
+      this.statusMap[item.id] = this.formatStatus(item);
     });
-    this.eduProcessList = list;
+    this.eduProcessList = r;
     this.loading = false;
   }
-
+  //分页切换
+  pageIndexChange(e: any) {
+    console.log(e);
+    this.pageIndex = e;
+    this.getEduProcess();
+  }
   onAllChecked(checked: boolean): void {
     console.log(checked);
-    this.eduProcessList = this.eduProcessList.map((item) => {
-      item.checked = checked;
+    this.eduProcessList.forEach((item) => {
       if (checked) {
-        this.setOfCheckedId.add(item.user.objectId);
+        this.setOfCheckedId.add(item.id);
       } else {
-        this.setOfCheckedId.delete(item.user.objectId);
+        this.setOfCheckedId.delete(item.id);
       }
-      return item;
     });
     this.checkedAll = checked;
   }
   onItemChecked(id: string, e: boolean) {
-    let checkedAll = true;
-    this.eduProcessList = this.eduProcessList.map((item) => {
-      if (id == item.objectId) item.checked = e;
-      if (!item.checked) checkedAll = false;
-      return item;
-    });
     if (e) {
       this.setOfCheckedId.add(id);
     } else {
       this.setOfCheckedId.delete(id);
     }
-    this.checkedAll = checkedAll;
+    this.checkedAll = this.eduProcessList.every((item) =>
+      this.setOfCheckedId.has(item.id)
+    );
   }
 
-  deleteSelected() {
+  statusSelected(type: string) {
+    let map: any = {
+      strat: '开始',
+      stop: '暂停',
+      end: '结束',
+    };
     this.modal.confirm({
       nzTitle: '批量删除',
-      nzContent: `删除后数据不可恢复,请谨慎操作`,
+      nzContent:
+        type == 'del'
+          ? `删除后数据不可恢复,请谨慎操作`
+          : `确认批量${map[type]}吗`,
       nzOkText: '确认',
       nzOkType: 'primary',
-      nzOkDanger: true,
+      nzOkDanger: map[type] ? true : false,
       nzOnOk: async () => {
         let selectedList = this.eduProcessList.filter((item: any) =>
           this.setOfCheckedId.has(item?.id)
         );
         let deletePromiseList = selectedList.map((item: any) => {
           return new Promise((resolve) => {
-            item.set('isDeleted', true);
-            item.save();
+            resolve(this.onStatusChange(item, type));
           });
         });
         try {
           await Promise.all(deletePromiseList);
+          this.getEduProcess();
         } catch (err) {}
       },
       nzCancelText: '取消',
       nzOnCancel: () => console.log('Cancel'),
     });
   }
+
+    //暂停流程
+    async onStatusChange(
+      data: Parse.Object,
+      type: string,
+      end?: boolean
+    ): Promise<void> {
+      console.log(data, type);
+      switch (type) {
+        case 'strat':
+          if (data?.get('status') == '100') {
+            data?.set('status', null);
+          }
+          if (data?.get('startDate') > new Date()) {
+            data?.set('startDate', new Date());
+          }
+          if (data?.get('deadline') < new Date()) {
+            data?.set(
+              'deadline',
+              new Date(new Date().getTime() + 60 * 1000 * 60 * 24 * 7)
+            );
+            console.warn('结束时间延长一周之后');
+          }
+          break;
+        case 'stop':
+          data?.set('status', '100');
+          break;
+        case 'end':
+          data?.set('status', '200');
+          data?.set('deadline', new Date());
+          break;
+        case 'del':
+          data?.set('isDeleted', true);
+          break;
+      }
+      await data.save();
+      if (end) {
+        this.getEduProcess();
+      }
+    }
+
   toUrl(url: string, params?: object) {
     if (params) {
       this.route.navigate([url, params]);

+ 172 - 23
projects/textbook/src/modules/nav-province-contact/page-role/page-role.component.html

@@ -57,7 +57,7 @@
                 <span nz-icon nzType="plus" nzTheme="outline"></span>添加部门
               </button>
             </li>
-            <li nz-menu-item>
+            <!-- <li nz-menu-item>
               <button
                 nz-button
                 nzType="link"
@@ -67,7 +67,7 @@
                 <span nz-icon nzType="merge" nzTheme="outline"></span>
                 新建组织
               </button>
-            </li>
+            </li> -->
           </ul>
         </nz-dropdown-menu>
       </div>
@@ -109,8 +109,10 @@
       <nz-dropdown-menu #menu="nzDropdownMenu">
         <ul nz-menu>
           <li nz-menu-item (click)="showModalDepart('add')">添加下级部门</li>
-          <li nz-menu-item (click)="showModalDepart('edit')">编辑部门</li>
-          <li nz-menu-item (click)="onDelDepart()">删除部门</li>
+          @if (activatedNode?.parentNode) {
+            <li nz-menu-item (click)="showModalDepart('edit')">编辑部门</li>
+            <li nz-menu-item (click)="onDelDepart()">删除部门</li>
+          }
         </ul>
       </nz-dropdown-menu>
     </div>
@@ -128,14 +130,14 @@
         <thead>
           <tr>
             <th
-              nzWidth="120px"
+              nzWidth="50px"
               nzLeft
               [nzChecked]="checkedAll"
               [nzIndeterminate]="indeterminate"
               nzLabel="Select all"
               (nzCheckedChange)="onAllChecked($event)"
             ></th>
-            <th nzWidth="120px" nzLeft>用户</th>
+            <th nzWidth="120px">用户</th>
             <th nzWidth="120px">手机号</th>
             <th nzWidth="120px">邮箱</th>
             <th nzWidth="120px">人员类型</th>
@@ -152,7 +154,6 @@
               (nzCheckedChange)="onItemChecked(data.objectId, $event)"
             ></td>
             <td
-              nzLeft
               nzEllipsis
               (click)="goDateil(data.user?.objectId)"
               class="activeTd"
@@ -173,7 +174,9 @@
               {{ data?.user.departmentName }}
             </td>
             <td nzRight>
-              @if(data?.user?.objectId == user?.id || data?.identity == '工作联系人'){ - } @else {
+              @if(data?.user?.objectId == user?.id || data?.identity ==
+              '工作联系人' || data?.identity ==
+              '高校联系人'){ - } @else {
               <button
                 nz-button
                 nz-dropdown
@@ -215,7 +218,9 @@
     <div nz-row class="depart-modal">
       <div nz-col nzSpan="12">
         <div class="row">
-          <div class="label">部门名称</div>
+          <div class="label">
+            部门名称 <span style="color: #e8353e">*</span>
+          </div>
           <div class="value">
             <input
               nz-input
@@ -250,41 +255,45 @@
       </div>
       <div nz-col nzSpan="12">
         <div class="row">
-          <div class="label">上级部门</div>
+          <div class="label">
+            上级部门 <span style="color: #e8353e">*</span>
+          </div>
           <div class="value">
             <input
               nz-input
-              placeholder="请选择上级"
+              placeholder="可击下方选择所属上级"
               [ngModel]="editObject.parent?.title"
+              (ngModelChange)="onSearchNodes($event, true)"
               type="text"
             />
           </div>
         </div>
         <div class="select">
           <div class="bar">
-            <a style="color: #86909c" (click)="onPre(nodes[0].key)">{{
-              nodes[0].title
-            }}</a>
+            <a style="color: #86909c" (click)="onPre()">教材遴选</a>
             @for (data of parentMap; track data.title) {
-            <span style="margin: 0 10px">/</span>
-            <a (click)="onPre(data.key, $index)">{{ data.title }}</a>
+              /
+              @if ($index !=0) {
+                <a (click)="onPre(data, $index)">{{ data.title }}</a>
+              }@else {
+                <a style="color: #86909c">{{ data.title }}</a>
+              }
             }
           </div>
           <div class="tree">
-            @if (parentList.length > 0) { @for (data of parentList; track
-            $index) {
-            <div class="li" (click)="onCheckedDepart(data)">
+            @for (data of parentList; track $index) {
+            <div class="li" (click)="onCheckedDepart('branch', data)">
               <label
                 nz-radio
-                ngModel
+                [ngModel]="data.key == radio"
                 [nzValue]="data.key"
-                (click)="onCheckedDepart(data, true)"
+                (click)="onCheckedDepart('branch', data, true)"
                 >{{ data.title }}</label
               >
+              @if (!data.isLeaf) {
               <span nz-icon nzType="right" nzTheme="outline"></span>
+              }
             </div>
-            } } @else {
-            <nz-empty nzNotFoundContent="暂无部门"></nz-empty>
             }
           </div>
         </div>
@@ -303,3 +312,143 @@
     </button>
   </div>
 </nz-modal>
+
+<nz-modal
+  [(nzVisible)]="accountIsVisible"
+  nzTitle="添加账号(手机号及邮箱请至少填写一个,将发送登录账号)"
+  (nzOnCancel)="handleCancel()"
+  nzWidth="800px"
+  nzCentered
+>
+  <ng-container *nzModalContent>
+    <div nz-row class="depart-modal">
+      <div nz-col nzSpan="12">
+        <div class="row">
+          <div class="label">姓名 <span style="color: #e8353e">*</span></div>
+          <div class="value">
+            <input
+              nz-input
+              placeholder="请输入姓名"
+              [(ngModel)]="account.name"
+              type="text"
+            />
+          </div>
+        </div>
+        <div class="row">
+          <div class="label">手机号</div>
+          <div class="value">
+            <input
+              nz-input
+              maxlength="12"
+              placeholder="请输入手机号"
+              [(ngModel)]="account.phone"
+              type="text"
+            />
+          </div>
+        </div>
+        <div class="row">
+          <div class="label">邮箱</div>
+          <div class="value">
+            <input
+              nz-input
+              placeholder="请输入邮箱"
+              [(ngModel)]="account.email"
+              type="email"
+            />
+          </div>
+        </div>
+        <div class="row">
+          <div class="label">密码 <span style="color: #e8353e">*</span></div>
+          <div class="value">
+            <nz-input-group nzSearch [nzAddOnAfter]="suffixIconButton">
+              <input type="password" [(ngModel)]="account.password" nz-input placeholder="请输入密码" />
+            </nz-input-group>
+            <ng-template #suffixIconButton>
+              <button nz-button (click)="randomPassword()" nzType="primary" nzSearch>自动生成密码</button>
+            </ng-template>
+          </div>
+        </div>
+        <div class="row">
+          <div class="label">人员类型</div>
+          <div class="value">
+            <!-- <input
+              nz-input
+              placeholder="请选择人员类型"
+              [(ngModel)]="account.identity"
+              type="text"
+            /> -->
+            <nz-select
+              style="width: 100%"
+              nzShowSearch
+              nzAllowClear
+              [(ngModel)]="account.identity"
+              nzPlaceHolder="请选择所属的身份类型"
+            >
+              @for(item of userType; track item;let index = $index){
+              <nz-option nzCustomContent [nzValue]="item" [nzLabel]="item">{{
+                item
+              }}</nz-option>
+              }
+            </nz-select>
+          </div>
+        </div>
+      </div>
+      <div nz-col nzSpan="12">
+        <div class="row">
+          <div class="label">
+            上级部门 <span style="color: #e8353e">*</span>
+          </div>
+          <div class="value">
+            <input
+              nz-input
+              placeholder="请选择所属部门"
+              [ngModel]="account.department?.title"
+              (ngModelChange)="onSearchNodes($event, true)"
+              type="text"
+            />
+          </div>
+        </div>
+        <div class="select">
+          <div class="bar">
+            <a style="color: #86909c" (click)="onPre()">教材遴选</a>
+            @for (data of parentMap; track data.title) {
+            <span style="margin: 0 10px">/</span>
+            @if ($index !=0) {
+              <a (click)="onPre(data, $index)">{{ data.title }}</a>
+            }@else {
+              <a style="color: #86909c">{{ data.title }}</a>
+            }
+            }
+          </div>
+          <div class="tree">
+            @for (data of parentList; track $index) {
+            <div class="li" (click)="onCheckedDepart('account', data)">
+              <label
+                nz-radio
+                [ngModel]="data.key == radio"
+                [nzValue]="data.key"
+                (click)="onCheckedDepart('account', data, true)"
+                >{{ data.title }}</label
+              >
+              @if (!data.isLeaf) {
+              <span nz-icon nzType="right" nzTheme="outline"></span>
+              }
+            </div>
+            }
+          </div>
+        </div>
+      </div>
+    </div>
+  </ng-container>
+  <div *nzModalFooter>
+    <button nz-button nzType="default" (click)="handleCancel()">取消</button>
+    <button
+      nz-button
+      nzType="primary"
+      style="background: #3e49b3; border: 1px #3e49b3"
+      (click)="accountComplete()"
+    >
+      确定
+    </button>
+  </div>
+</nz-modal>

+ 345 - 84
projects/textbook/src/modules/nav-province-contact/page-role/page-role.component.ts

@@ -10,7 +10,7 @@ import { NzSpaceModule } from 'ng-zorro-antd/space';
 import { NzPageHeaderModule } from 'ng-zorro-antd/page-header';
 import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb';
 import { CommonCompModule } from '../../../services/common.modules';
-import { NzModalModule } from 'ng-zorro-antd/modal';
+import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';
 import {
   NzFormatEmitEvent,
   NzTreeModule,
@@ -25,6 +25,7 @@ import { NzEmptyModule } from 'ng-zorro-antd/empty';
 import { NzRadioModule } from 'ng-zorro-antd/radio';
 import { NzMessageService } from 'ng-zorro-antd/message';
 import { textbookServer } from '../../../services/textbook';
+import { NzSelectModule } from 'ng-zorro-antd/select';
 interface nodes {
   title: string;
   key: string;
@@ -57,6 +58,7 @@ interface depart {
     NzEmptyModule,
     NzModalModule,
     NzRadioModule,
+    NzSelectModule,
   ],
   standalone: true,
 })
@@ -93,13 +95,28 @@ export class PageRoleComponent implements OnInit {
   };
   parentMap: Array<any> = [];
   parentList: Array<any> = [];
+  radio: string = '';
+
+  /* 添加账号 */
+  accountIsVisible: boolean = false;
+  account: any = {
+    name: '',
+    phone: '',
+    email: '',
+    password: '',
+    identity: '',
+    department: {},
+    companyType: '',
+  };
+  userType: Array<string> = ['个人'];
 
   constructor(
     public tbookSer: textbookServer,
     private route: Router,
+    private modal: NzModalService,
     private activeRoute: ActivatedRoute,
     private nzContextMenuService: NzContextMenuService,
-    private message: NzMessageService,
+    private message: NzMessageService
   ) {
     this.user = Parse.User.current();
     this.className = this.Department.className;
@@ -121,57 +138,96 @@ export class PageRoleComponent implements OnInit {
       //   this.queryParams.where['isDeleted'] = { $ne: true };
       // }
       // this.list?.ngOnInit();
-      if(!this.tbookSer.profile.user.department?.objectId){
-        this.message.warning('权限不足')
-        history.back()
-        return
+      if (!this.tbookSer.profile.user.department?.objectId) {
+        this.message.warning('权限不足');
+        history.back();
+        return;
       }
-      this.nodes = [{
-        title: this.tbookSer.profile.user.department.name,
-        key: this.tbookSer.profile.user.department?.objectId,
-        children: [...await this.getDepart(this.tbookSer.profile.user.department?.objectId)],
-        isParent: false,
-        isLeaf: false,
-      }];
+      let query = new Parse.Query('Department')
+      query.equalTo('objectId', this.tbookSer.profile.user.department?.objectId)
+      query.select('hasChildren','branch')
+      let r = await query.first()
+
+      this.nodes = [
+        {
+          title: this.tbookSer.profile.user.department.name,
+          key: this.tbookSer.profile.user.department?.objectId,
+          children: [
+            ...(await this.getDepart(
+              this.tbookSer.profile.user.department?.objectId
+            )),
+          ],
+          isParent: !r?.get('hasChildren'),
+          isLeaf: !r?.get('hasChildren'),
+          branch:r?.get('branch')
+        },
+      ];
     });
   }
   async getDepart(
     parent?: string,
-    searchValue?: string
+    searchValue?: string,
+    filter?: boolean
   ): Promise<Array<nodes>> {
     let nodes: any = [];
     let query = new Parse.Query('Department');
-    query.equalTo('parent', parent ? parent : undefined);
+    if (!filter) {
+      query.equalTo('parent', parent ? parent : undefined);
+    }
     searchValue && query.contains('name', searchValue);
     query.notEqualTo('isDeleted', true);
-    query.select('code', 'name', 'branch', 'parent','type');
+    query.select('code', 'name', 'branch', 'parent', 'type','hasChildren');
     query.descending('createdAt');
-    query.limit(2000);
+    query.equalTo('branch',this.tbookSer.profile.companyType)
+    console.log(searchValue);
+    if(searchValue && searchValue != undefined){
+      query.equalTo('parent',this.tbookSer.profile.user.department?.objectId)
+    }
+    query.limit(100);
+    if (this.activeDepart) query.notEqualTo('objectId', this.activeDepart?.id);
     let res = await query.find();
     res.forEach((item) => {
       nodes.push({
         title: item.get('name'),
         key: item.id,
         children: [],
-        isParent: item.get('type') =='单位' ? true : false, //是否是最下级
-        isLeaf: false,
+        branch: item.get('branch'),
+        isLeaf: !item.get('hasChildren') ? true : false, //是否是最下级
       });
     });
     return nodes;
   }
+  //搜索
+  async onSearchNodes(e: string, modal?: boolean) {
+    if (modal) {
+      this.parentList = await this.getDepart('', e, true);
+      return;
+    }
+    this.nodes = await this.getDepart('', e, e ? true : false);
+  }
   //添加成员
   addMember() {
-    this.message.warning('权限灰度中')
+    this.parentList = this.nodes;
+    this.account = {
+      name: '',
+      phone: '',
+      email: '',
+      password: '',
+      identity: '',
+      department: {},
+      companyType: '',
+    };
+    this.accountIsVisible = true;
   }
   //展开/合并
   async nzEvent(event: NzFormatEmitEvent): Promise<void> {
     console.log(event);
     let node: any = event.node;
     if (event.eventName === 'expand') {
-      if (node.origin.isParent) {
-        node.addChildren([]);
-        return;
-      }
+      // if (node.origin.isParent) {
+      //   node.addChildren([]);
+      //   return;
+      // }
       if (node?._children.length <= 0) {
         let data = await this.getDepart(node.key);
         node.addChildren(data);
@@ -179,8 +235,8 @@ export class PageRoleComponent implements OnInit {
       console.log(this.nodes);
     } else {
       // if (node.origin.isParent) {
-        this.currentDepart = node.origin;
-        this.getProfile();
+      this.currentDepart = node.origin;
+      this.getProfile();
       // }
     }
   }
@@ -188,23 +244,26 @@ export class PageRoleComponent implements OnInit {
     this.profiles = [];
     this.loading = true;
     let queryParams = {
-      where : {
-        "$or": [{
-          "user": {
-            "$inQuery": {
-              "where": {
-                "$or": [{
-                    "department": { "$eq": this.currentDepart.key}
-                  },
-                ]
+      where: {
+        $or: [
+          {
+            user: {
+              $inQuery: {
+                where: {
+                  $or: [
+                    {
+                      department: { $eq: this.currentDepart.key },
+                    },
+                  ],
+                },
+                className: '_User',
               },
-              "className": "_User"
-            }
-          }
-        }]
-      }
-    }
-    let query = Parse.Query.fromJSON('Profile',queryParams);
+            },
+          },
+        ],
+      },
+    };
+    let query = Parse.Query.fromJSON('Profile', queryParams);
     query.include('user');
     query.notEqualTo('identity', '国家级管理员');
     let r = await query.find();
@@ -232,7 +291,7 @@ export class PageRoleComponent implements OnInit {
   }
   //删除部门
   onDelDepart() {
-    this.message.warning('权限灰度中')
+    this.message.warning('权限灰度中');
   }
 
   onAllChecked(checked: boolean): void {
@@ -254,82 +313,284 @@ export class PageRoleComponent implements OnInit {
   }
   //新建打开弹窗
   async showModalDepart(type: string) {
+    this.parentList = this.nodes;
+    this.editObject = {
+      name: '',
+      code: '',
+      desc: '',
+      parent: '',
+      branch: '',
+    };
     if (type == 'edit') {
       let query = new Parse.Query('Department');
       let r = await query.get(this.activatedNode?.key);
       this.activeDepart = r;
-      this.parentMap = this.formatNode(this.activatedNode);
-      if(this.activatedNode?.parentNode?.origin.key){
-        this.parentList = await this.getDepart(this.activatedNode.parentNode.origin.key);
+      this.editObject = {
+        name: this.activeDepart.get('name'),
+        code: this.activeDepart.get('code'),
+        desc: this.activeDepart.get('desc'),
+        parent: {
+          title: this.activeDepart.get('parent')?.get('name'),
+          id: this.activeDepart.get('parent')?.id,
+        },
+        branch: this.activeDepart.get('branch'),
+      };
+      this.parentMap = await this.formatNode(
+        this.activeDepart.get('parent')?.id
+      );
+      if (this.activatedNode?.parentNode?.origin?.parentNode?.origin.key) {
+        this.parentList = await this.getDepart(
+          this.activatedNode?.parentNode?.origin?.parentNode?.origin.key
+        );
+      }
+    } else if (type == 'add' && this.activatedNode) {
+      console.log(this.activatedNode);
+      this.editObject.parent = {
+        title: this.activatedNode.origin.title,
+        id: this.activatedNode.origin.key,
+      };
+      this.editObject.branch =
+        this.activatedNode.origin.branch || this.activatedNode.origin.title;
+      this.parentMap = await this.formatNode(this.activatedNode.origin.key);
+      if (this.activatedNode?.parentNode?.origin.key) {
+        this.parentList = await this.getDepart(
+          this.activatedNode.parentNode.origin.key
+        );
       }
-    }else{
-      this.parentList = await this.getDepart(this.tbookSer.profile.user.department?.objectId)
     }
+    console.log(this.parentMap);
     this.editType = type;
     this.isVisible = true;
   }
   //格式化链
-  formatNode(node: NzTreeNode): Array<any> {
-    let arr = [];
-    if (node.parentNode?.origin.title) {
-      arr.push({
-        title: node.parentNode?.origin.title,
-        key: node.parentNode?.origin.key,
-      });
-      arr.unshift(...this.formatNode(node.parentNode));
+  async formatNode(id: string): Promise<Array<any>> {
+    let query = new Parse.Query('Department');
+    query.select('name', 'parent','hasChildren');
+    let r = await query.get(id);
+    let arr = [
+      {
+        title: r.get('name'),
+        key: r.id,
+        hasChildren: r.get('hasChildren'), //是否是最下级
+      },
+    ];
+    if (r?.get('parent')) {
+      arr.unshift(...(await this.formatNode(r?.get('parent').id)));
     }
     return arr;
   }
-  async onPre(key?:any,index?:number){
-    if(!key){
+  async onPre(data?: any, index?: number) {
+    if (!data?.key || !data.hasChildren) {
       // this.parentList = await this.getDepart();
       // this.parentMap = []
-      return
+      return;
     }
     // if(index == this.parentMap.length-1) return
-    if(index !== 0 && !index) this.parentMap.splice(index || 0+1)
-    this.parentList = await this.getDepart(key);
+    if (index !== 0 && !index) this.parentMap.splice(index || 0 + 1);
+    this.parentList = await this.getDepart(data?.key);
   }
   //选择所属类别下级列表
-  async onCheckedDepart(e: any, checked?: boolean) {
+  async onCheckedDepart(type: string, e: any, checked?: boolean) {
+    this.radio = e.key;
     console.log(e);
+    this.parentMap = await this.formatNode(e.key);
     if (checked) {
-      this.editObject = {
-        name: e.title,
-        code: '',
-        desc: '',
-        parent: e,
-        branch: '',
-      };
+      // this.editObject.name = e.title
+      if (type == 'account') {
+        this.account.department = { title: e.title, id: e.key };
+        this.account.companyType = e.branch || e.title;
+      } else {
+        this.editObject.parent = {
+          title: e.title,
+          id: e.key,
+        };
+        this.editObject.branch = e.branch || e.title;
+      }
       return;
     }
-    this.parentMap.push({
-      title: e.title,
-      key: e.key,
-    });
+    if (e.isLeaf) {
+      return;
+    }
+    // this.parentMap.push({
+    //   title: e.title,
+    //   key: e.key,
+    // });
     this.parentList = await this.getDepart(e?.key);
   }
-  handleOk(): void {
-    this.message.warning('权限灰度中')
+  async handleOk(): Promise<void> {
+    if (!this.editObject?.name || !this.editObject.parent?.id) {
+      this.message.error('请填写完整信息');
+      return;
+    }
+    if (this.activeDepart?.id && this.editType == 'edit') {
+      this.activeDepart.set('name', this.editObject?.name);
+      this.activeDepart.set('code', this.editObject?.code);
+      this.activeDepart.set('desc', this.editObject.desc);
+      this.activeDepart.set('parent', {
+        __type: 'Pointer',
+        className: 'Department',
+        objectId: this.editObject.parent?.id,
+      });
+      this.activeDepart.set('branch', this.editObject.branch);
+    } else {
+      let obj = Parse.Object.extend('Department');
+      this.activeDepart = new obj();
+      this.activeDepart?.set('name', this.editObject?.name);
+      this.activeDepart?.set('code', this.editObject?.code);
+      this.activeDepart?.set('desc', this.editObject.desc);
+      this.activeDepart?.set('parent', {
+        __type: 'Pointer',
+        className: 'Department',
+        objectId: this.editObject.parent?.id,
+      });
+      this.activeDepart?.set('branch', this.editObject.branch);
+    }
+    await this.activeDepart?.save();
+    await this.updateChildren()
     this.isVisible = false;
+    this.message.success(this.editType == 'edit' ? '保存' : '添加' + '成功');
+    this.activeDepart = undefined;
+    this.ngOnInit()
+  }
+  //更新上级children字段
+  async updateChildren(){
+    let query = new Parse.Query('Department')
+    query.equalTo('objectId', this.editObject.parent?.id)
+    let r = await query.first()
+    if(r?.id && !r.get('hasChildren')){
+      r?.set('hasChildren', true)
+      await r.save()
+    }
+    return
   }
-
   handleCancel(): void {
     console.log('Button cancel clicked!');
     this.isVisible = false;
     this.activatedNode = undefined;
     this.parentMap = [];
-    this.activeDepart = undefined
+
+    this.accountIsVisible = false;
+    this.account = null;
   }
 
   /* 组织 */
-  showModalOrganize(){
-    this.message.warning('权限灰度中')
+  showModalOrganize() {
+    this.message.warning('权限灰度中');
+  }
+  goDateil(id: string) {
+    this.route.navigate(['/nav-admin/manage/user/edit', { id: id }]);
+  }
+  randomPassword() {
+    let sCode =
+      'A,B,C,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,W,X,Y,Z,1,2,3,4,5,6,7,8,9,0,q,w,e,r,t,y,u,i,o,p,a,s,d,f,g,h,j,k,l,z,x,c,v,b,n,m';
+    let aCode = sCode.split(',');
+    let aLength = aCode.length; //获取到数组的长度
+    let str = [];
+    for (let i = 0; i <= 16; i++) {
+      let j = Math.floor(Math.random() * aLength); //获取到随机的索引值
+      let txt = aCode[j]; //得到随机的一个内容
+      str.push(txt);
+    }
+    this.account.password = str.join('');
+    console.log(this.account.password);
+  }
+  /* 添加账号 */
+  async accountComplete() {
+    if (
+      !this.account?.name.trim() ||
+      !this.account.department?.id ||
+      !this.account.password.trim()
+    ) {
+      this.message.warning('请填写必填项');
+      return;
+    }
+    if (!this.account.identity) {
+      this.message.error('请选择人员类型');
+      return;
+    }
+    let a = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/;
+    if (this.account.phone && !String(this.account.phone).match(a)) {
+      this.message.error('请填写正确手机号');
+      return;
+    }
+    try {
+      let obj = Parse.Object.extend('_User');
+      let user = new obj();
+      user?.set('username', this.account?.email);
+      user?.set('name', this.account?.name);
+      user?.set('phone', this.account?.phone);
+      user?.set('email', this.account?.email);
+      user?.set('password', this.account.password);
+      user?.set('accountState', '已认证');
+      user?.set('department', {
+        __type: 'Pointer',
+        className: 'Department',
+        objectId: this.account.department?.id,
+      });
+      let u = await user.save();
+      let p = Parse.Object.extend('Profile');
+      let profile = new p();
+      profile?.set('user', u?.toPointer());
+      profile?.set('companyType', this.account.companyType);
+      profile?.set('email', this.account.email);
+      profile?.set('identity', this.account.identity);
+      let res = await profile?.save();
+      this.accountIsVisible = false;
+      this.account = null;
+      this.modal.success({
+        nzTitle: '添加成功',
+        nzContent: '',
+        nzOnOk: () => {
+          this.currentDepart && this.getProfile();
+        },
+      });
+    } catch (err: any) {
+      console.warn('添加用户错误', err);
+      this.message.error(err?.Error || '错误:请检查用户邮箱是否已存在');
+      return;
+    }
+  }
+  setOfCheckedId = new Set<string>();
+  //移除部门
+  async removeBranch(data: Parse.Object) {
+    if (data?.get('user')?.id) {
+      data?.get('user')?.set('department', null);
+      await data?.get('user')?.save();
+      this.message.error('移除成功');
+      this.getProfile();
+    }
+  }
+  removeBranchAll() {
+    this.modal.confirm({
+      nzTitle: '批量移除',
+      nzContent: `请谨慎操作`,
+      nzOkText: '确认',
+      nzOkType: 'primary',
+      nzOkDanger: true,
+      nzOnOk: async () => {
+        let selectedList = this.profiles.filter((item: any) =>
+          this.setOfCheckedId.has(item?.id)
+        );
+        let romovePromiseList = selectedList.map((item: any) => {
+          return new Promise(async (resolve) => {
+            item?.get('user')?.set('department', null);
+            await item?.get('user')?.save();
+            resolve(true);
+          });
+        });
+        try {
+          await Promise.all(romovePromiseList);
+          this.message.error('移除成功');
+          this.getProfile();
+          this.resetChange();
+        } catch (err) {}
+      },
+      nzCancelText: '取消',
+      nzOnCancel: () => console.log('Cancel'),
+    });
   }
-  goDateil(id:string){
-    this.route.navigate([
-      '/nav-admin/manage/user/edit',
-      { id:id },
-    ])
+  resetChange() {
+    this.setOfCheckedId = new Set<string>();
+    this.checkedAll = false;
   }
 }

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

@@ -2,13 +2,14 @@ import { NgModule } from "@angular/core";
 import { RouterModule, Routes } from "@angular/router";
 import { PageUserComponent } from "../nav-admin/page-user/page-user.component";
 import { UserEditComponent } from "../nav-admin/user-edit/user-edit.component";
+import { RecycleComponent } from "../nav-author/recycle/recycle.component";
 import { SpaceComponent } from "../nav-author/space/space.component";
 import { PageRoleComponent } from "../nav-province-contact/page-role/page-role.component";
 import { PageTextbookComponent } from "../nav-province-contact/page-textbook/page-textbook.component";
 const routes: Routes = [
   {
     path: '',
-    redirectTo:'manage',
+    redirectTo:'manage/textbook',
     pathMatch: "full",
   },
   {
@@ -18,6 +19,14 @@ const routes: Routes = [
         path: 'textbook', //全部教材
         component: PageTextbookComponent,
       },
+      {
+        path: 'user', //用户列表
+        component: PageUserComponent,
+      },
+      {
+        path: 'user/edit', //用户管理&编辑
+        component: UserEditComponent,
+      },
       {
         path: 'role', //组织列表
         component: PageRoleComponent,
@@ -26,6 +35,10 @@ const routes: Routes = [
         path: 'space',//个人空间
         component: SpaceComponent,
       },
+      {
+        path: 'recycle',//回收站
+        component: RecycleComponent,
+      },
     ]
   }
 ];