warrior 7 месяцев назад
Родитель
Сommit
62c53fafd0

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

@@ -182,6 +182,7 @@ export class CompManageComponent implements OnInit {
 
   constructor(
     public router: Router,
+    public tbookSer: textbookServer,
     public textbook:textbookServer,
     iconRegistry: MatIconRegistry, 
     sanitizer: DomSanitizer
@@ -190,6 +191,12 @@ export class CompManageComponent implements OnInit {
    }
 
   ngOnInit() {
+    if(localStorage.getItem('active')) return
+    if(this.optionsMap[this.tbookSer.profile.identity][0]?.child){
+      this.active = '1-1'
+    }else{
+      this.active = '1'
+    }
   }
   toggleCollapsed(): void {
     this.isCollapsed = !this.isCollapsed;

+ 3 - 2
projects/textbook/src/modules/login/account-info/account-info.component.ts

@@ -131,9 +131,10 @@ export class AccountInfoComponent implements OnInit {
     if (isChange) this.companyId = '';
     this.companys = [];
     let parent = this.unitTypes.find(item=>item.name == this.companyType)
-    console.log(parent);
+    // console.log(parent);
+    if(!parent?.id) return
     let query = new Parse.Query('Department');
-    query.equalTo('parent', parent.id);
+    query.equalTo('parent', parent?.id);
     query.select('name', 'branch');
     query.limit(100);
     val && query.contains('name', val);

+ 34 - 16
projects/textbook/src/modules/nav-admin/page-role/page-role.component.html

@@ -167,19 +167,19 @@
               class="activeTd"
             >
               <nz-avatar nzIcon="user"></nz-avatar>
-              {{ data?.get('user')?.get('name') }}
+              {{ data?.get("user")?.get("name") }}
             </td>
             <td>
-              {{ data?.get('user')?.get('phone') }}
+              {{ data?.get("user")?.get("phone") }}
             </td>
             <td>
-              {{ data?.get('user')?.get('email')  || data?.get('email') }}
+              {{ data?.get("user")?.get("email") || data?.get("email") }}
             </td>
             <td>
-              {{ data?.get('identity') }}
+              {{ data?.get("identity") }}
             </td>
             <td>
-              {{ data?.get('user').get('departmentName') }}
+              {{ data?.get("user").get("departmentName") }}
             </td>
             <td nzRight>
               <button
@@ -193,7 +193,12 @@
               <nz-dropdown-menu #menu="nzDropdownMenu">
                 <ul nz-menu>
                   <li nz-menu-item>
-                    <button (click)="removeBranch(data)" nz-button style="color: #231c1f" nzType="link">
+                    <button
+                      (click)="removeBranch(data)"
+                      nz-button
+                      style="color: #231c1f"
+                      nzType="link"
+                    >
                       <span nz-icon nzType="stop" nzTheme="outline"></span
                       >移除部门
                     </button>
@@ -281,7 +286,8 @@
             }
           </div>
           <div class="tree">
-            @for (data of parentList; track $index) {
+            @if(parentList.length > 0){ @for (data of parentList; track $index)
+            {
             <div class="li" (click)="onCheckedDepart('branch', data)">
               <label
                 nz-radio
@@ -294,6 +300,8 @@
               <span nz-icon nzType="right" nzTheme="outline"></span>
               }
             </div>
+            }}@else {
+            <nz-empty nzNotFoundContent="暂无下级部门"></nz-empty>
             }
           </div>
         </div>
@@ -361,24 +369,31 @@
           <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="请输入密码" />
+              <input
+                type="password"
+                [(ngModel)]="account.password"
+                nz-input
+                placeholder="请输入密码"
+              />
             </nz-input-group>
             <ng-template #suffixIconButton>
-              <button nz-button (click)="randomPassword()" nzType="primary" nzSearch>自动生成密码</button>
+              <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%"
+              [disabled]="!this.account.department?.id"
               nzShowSearch
               nzAllowClear
               [(ngModel)]="account.identity"
@@ -417,7 +432,8 @@
             }
           </div>
           <div class="tree">
-            @for (data of parentList; track $index) {
+            @if(parentList.length > 0){ @for (data of parentList; track $index)
+            {
             <div class="li" (click)="onCheckedDepart('account', data)">
               <label
                 nz-radio
@@ -430,6 +446,8 @@
               <span nz-icon nzType="right" nzTheme="outline"></span>
               }
             </div>
+            }}@else {
+            <nz-empty nzNotFoundContent="暂无下级部门"></nz-empty>
             }
           </div>
         </div>

+ 19 - 22
projects/textbook/src/modules/nav-admin/page-role/page-role.component.ts

@@ -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 { NzSelectModule } from 'ng-zorro-antd/select';
+import { textbookServer } from '../../../services/textbook';
 interface nodes {
   title: string;
   key: string;
@@ -109,11 +110,13 @@ export class PageRoleComponent implements OnInit {
     companyType: '',
   };
   userType: Array<string> = ['工作联系人', '评审专家', '高校联系人', '个人'];
+  parents: Array<any> = []; //所有一级列表
 
   constructor(
     private route: Router,
     private activeRoute: ActivatedRoute,
     private nzContextMenuService: NzContextMenuService,
+    public tbookSer: textbookServer,
     private modal: NzModalService,
     private message: NzMessageService
   ) {
@@ -130,14 +133,8 @@ export class PageRoleComponent implements OnInit {
 
   ngOnInit(): void {
     this.activeRoute.paramMap.subscribe(async (params) => {
-      // let isDeleted = params.get('isDeleted');
-      // if (isDeleted) {
-      //   this.queryParams.where['isDeleted'] = { $eq: true };
-      // } else {
-      //   this.queryParams.where['isDeleted'] = { $ne: true };
-      // }
-      // this.list?.ngOnInit();
       this.nodes = await this.getDepart();
+      this.parents = await this.getDepart();
     });
   }
   async getDepart(
@@ -163,7 +160,9 @@ export class PageRoleComponent implements OnInit {
         key: item.id,
         children: [],
         branch: item.get('branch'),
+        parent: item.get('parent')?.id, //上级
         isLeaf: !item.get('hasChildren'), //是否是最下级
+        type: item.get('type'),
       });
     });
     return nodes;
@@ -383,19 +382,25 @@ export class PageRoleComponent implements OnInit {
       return;
     }
     if (index == this.parentMap.length - 1) return;
-    this.parentMap.splice(index || 0 + 1);
+    this.parentMap.splice((index || 0) + 1);
     this.parentList = await this.getDepart(data?.key);
+    this.radio = '';
   }
   //选择所属类别下级列表
   async onCheckedDepart(type: string, e: any, checked?: boolean) {
     this.radio = e.key;
-    console.log(this.radio);
+    if (type == 'account') this.account.identity = '';
     this.parentMap = await this.formatNode(e.key);
-    if (checked) {
+    if (checked && e.parent) {
       // this.editObject.name = e.title
       if (type == 'account') {
         this.account.department = { title: e.title, id: e.key };
         this.account.companyType = e.branch || e.title;
+        this.userType = this.parents.some((item) => e.parent == item.key)
+        ? !e.type ? ['工作联系人', '评审专家', '个人'] : ['评审专家', '工作联系人', '个人']
+        : e.type
+        ? ['评审专家', '高校联系人', '个人']
+        : ['评审专家', '个人'];
       } else {
         this.editObject.parent = {
           title: e.title,
@@ -468,17 +473,7 @@ export class PageRoleComponent implements OnInit {
   }
 
   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('')
+    this.account.password = this.tbookSer.randomPassword()
     console.log(this.account.password);
   }
   /* 添加账号 */
@@ -496,6 +491,8 @@ export class PageRoleComponent implements OnInit {
       this.message.error("请填写正确手机号");
       return;
     }
+    this.account.email = this.account?.email.trim()
+    this.account.phone = this.account?.phone.trim()
     if(!this.account?.email && !this.account?.phone){
       this.message.error("邮箱或手机号必须填写一项");
       return;
@@ -506,7 +503,7 @@ export class PageRoleComponent implements OnInit {
       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);
+      this.account?.email && user?.set('email', this.account?.email);
       user?.set('password', this.account.password);
       user?.set('accountState', '已认证');
       user?.set('department', {

+ 185 - 28
projects/textbook/src/modules/nav-admin/page-user/page-user.component.html

@@ -13,7 +13,7 @@
         *nzSpaceItem
         nz-button
         nzType="primary"
-        (click)="createUser()"
+        (click)="addMember()"
       >
         创建用户
       </button>
@@ -21,22 +21,6 @@
   </nz-page-header-extra>
 </nz-page-header>
 <div class="content">
-  <!-- <comp-table-list
-  #list
-  [schema]="_User"
-  *ngIf="className && fieldsArray"
-  [className]="className"
-  [fieldsArray]="fieldsArray"
-  [queryParams]="queryParams"
-  ></comp-table-list> -->
-  <!-- <comp-table-list
-    #list
-    [schema]="ProfileList"
-    *ngIf="className && fieldsArray"
-    [className]="className"
-    [fieldsArray]="fieldsArray"
-    [queryParams]="queryParams"
-  ></comp-table-list> -->
   <div class="tool">
     <div class="tool-left">
       <div class="search">
@@ -107,30 +91,36 @@
         <td
           nzLeft
           [nzChecked]="setOfCheckedId.has(data.id)"
-          (nzCheckedChange)="
-            onItemChecked(data.id,$event)
-          "
+          (nzCheckedChange)="onItemChecked(data.id, $event)"
         ></td>
-        <td nzEllipsis (click)="goDateil(data.get('user')?.id)" class="activeTd">
+        <td
+          nzEllipsis
+          (click)="goDateil(data.get('user')?.id)"
+          class="activeTd"
+        >
           <nz-avatar nzIcon="user"></nz-avatar>
-          {{ data?.get('user')?.get('name') || '-'  }}
+          {{ data?.get("user")?.get("name") || "-" }}
         </td>
         <td>
-          {{ data?.get('user')?.get('phone') || '-' }}
+          {{ data?.get("user")?.get("phone") || "-" }}
         </td>
         <td nzEllipsis>
           <div class="email">
             <span
               class="state"
               [style.background]="
-                data?.get('user')?.get('accountState') == '已认证' ? '#1EB76D' : '#C9CDD4'
+                data?.get('user')?.get('accountState') == '已认证'
+                  ? '#1EB76D'
+                  : '#C9CDD4'
               "
             ></span>
-            <div class="text">{{ data?.get('user')?.get('email') || data?.get('email') }}</div>
+            <div class="text">
+              {{ data?.get("user")?.get("email") || data?.get("email") }}
+            </div>
           </div>
         </td>
         <td>
-          {{ data?.get('identity') }}
+          {{ data?.get("identity") }}
         </td>
         <td>
           @switch (data?.get('user')?.get('accountState')) { @case ('待认证') {
@@ -142,7 +132,7 @@
           } }
         </td>
         <td nzEllipsis>
-          {{ data?.get('companyType') }}
+          {{ data?.get("companyType") }}
         </td>
         <td nzRight>
           <button
@@ -171,7 +161,7 @@
                   >通过认证
                 </button>
               </li>
-              } 
+              }
               <!-- @if (data?.user.accountState != '已禁用') {
               <li nz-menu-item>
                 <button
@@ -221,6 +211,173 @@
     <nz-empty nzNotFoundImage="/img/group-empty.png"></nz-empty>
   </ng-template>
 </div>
+<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.username"
+              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">
+            <nz-select
+              style="width: 100%"
+              nzShowSearch
+              nzAllowClear
+              [disabled]="!this.account.department?.id"
+              [(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)"
+              type="text"
+            />
+          </div>
+        </div>
+        <div class="select">
+          <div class="bar">
+            @if (this.tbookSer.profile.identity == '国家级管理员') {
+              <a style="color: #86909c" (click)="onPre()">教材遴选</a>
+            }@else {
+              <a style="color: #86909c">教材遴选</a>
+            }
+            @for (data of parentMap; track data.title) {
+              <span style="margin: 0 10px">/</span>
+              @if (this.tbookSer.profile.identity == '国家级管理员' ||
+              data?.verify) {
+                <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)">
+                  <label
+                    nz-radio
+                    [ngModel]="data.key == radio"
+                    [nzValue]="data.key"
+                    (click)="onCheckedDepart(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>
+      </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>
 
 <!-- 全选操作:批量操作 -->
 <div class="batch-toolbar-modal" *ngIf="setOfCheckedId?.size">

+ 28 - 1
projects/textbook/src/modules/nav-admin/page-user/page-user.component.scss

@@ -66,7 +66,27 @@
   border: 1px #3e49b3;
   color: white;
 }
-
+.depart-modal{
+  .row{
+    width: 90%;
+    margin-bottom: 20px;
+  }
+  .tree{
+    height: 320px;
+    overflow-y: scroll;
+    .li{
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 4px;
+      span{
+        flex-shrink: 0;
+      }
+    }
+    .li:hover{
+      background-color: #f9f9f9;
+    }
+  }
+}
 // 选中,批量操作区域
 .batch-toolbar-modal{
   position: fixed;
@@ -127,6 +147,13 @@
 .ant-space {
   display: inline-flex;
 }
+input:-webkit-autofill,
+input:-webkit-autofill:hover,
+input:-webkit-autofill:focus,
+input:-webkit-autofill:active {
+  -webkit-transition-delay: 99999s;
+  -webkit-transition: color 99999s ease-out, background-color 99999s ease-out;
+}
 ::ng-deep .ant-page-header-heading-title{
   white-space: normal;
 }

+ 261 - 21
projects/textbook/src/modules/nav-admin/page-user/page-user.component.ts

@@ -13,6 +13,9 @@ import { NzEmptyModule } from 'ng-zorro-antd/empty';
 import { NzModalService } from 'ng-zorro-antd/modal';
 import { textbookServer } from '../../../services/textbook';
 import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+
 @Component({
   selector: 'app-page-user',
   templateUrl: './page-user.component.html',
@@ -25,7 +28,9 @@ import { NzMessageService } from 'ng-zorro-antd/message';
     CompTableListComponent,
     NzBreadCrumbModule,
     CommonCompModule,
+    NzRadioModule,
     NzEmptyModule,
+    NzSelectModule,
   ],
   standalone: true,
 })
@@ -47,6 +52,24 @@ export class PageUserComponent implements OnInit {
   loading = false;
   searchValue: string = ''; //搜索内容
   setOfCheckedId = new Set<string>();
+  userType: Array<string> = ['工作联系人', '评审专家', '高校联系人', '个人'];
+  parentMap: Array<any> = [];
+  parentList: Array<any> = [];
+  radio: string = '';
+  loadingParent: boolean = false;
+  /* 添加账号 */
+  accountIsVisible: boolean = false;
+  account: any = {
+    name: '',
+    username: '',
+    phone: '',
+    email: '',
+    password: '',
+    identity: '',
+    department: {},
+    companyType: '',
+  };
+  parents: Array<any> = []; //所有一级列表
 
   constructor(
     public tbookSer: textbookServer,
@@ -64,11 +87,15 @@ export class PageUserComponent implements OnInit {
     //     isDeleted: { $ne: true },
     //   },
     // };
+    if (this.tbookSer.profile.identity != '国家级管理员') {
+      this.userType = ['评审专家', '高校联系人', '个人'];
+    }
   }
 
   ngOnInit(): void {
     this.activeRoute.paramMap.subscribe(async (params) => {
       this.getProfile();
+      this.parents = await this.getDepart();
     });
   }
   async getProfile() {
@@ -99,13 +126,11 @@ export class PageUserComponent implements OnInit {
                 className: '_User',
               },
             },
-          }
+          },
         ],
       },
     };
-    if (
-      this.tbookSer.profile.identity != '国家级管理员'
-    ) {
+    if (this.tbookSer.profile.identity != '国家级管理员') {
       this.tbookSer.profile.user.department;
       queryParams['where']['$or'][0]['user']['$inQuery']['where'][
         'department'
@@ -116,11 +141,11 @@ export class PageUserComponent implements OnInit {
     let query = Parse.Query.fromJSON('Profile', queryParams);
     query.include('user');
     query.notEqualTo('identity', '国家级管理员');
-    query.descending('createdAt')
-    if(this.tbookSer.profile.identity == '工作联系人'){
-      query.containedIn('identity', ['个人', '评审专家','高校联系人']);
-    }else if(this.tbookSer.profile.identity == '高校联系人'){
-      query.containedIn('identity', ['个人','评审专家']);
+    query.descending('createdAt');
+    if (this.tbookSer.profile.identity == '工作联系人') {
+      query.containedIn('identity', ['个人', '评审专家', '高校联系人']);
+    } else if (this.tbookSer.profile.identity == '高校联系人') {
+      query.containedIn('identity', ['个人', '评审专家']);
     }
     this.profileLength = await query.count();
     query.limit(this.pageSize);
@@ -136,7 +161,7 @@ export class PageUserComponent implements OnInit {
     this.getProfile();
   }
   createUser() {
-    this.route.navigate(['/nav-admin/manage/user/create'])
+    this.route.navigate(['/nav-admin/manage/user/create']);
   }
   onItemChecked(id: string, e: boolean) {
     if (e) {
@@ -158,17 +183,19 @@ export class PageUserComponent implements OnInit {
     });
     this.checkedAll = checked;
   }
-  resetChange(){
+  resetChange() {
     this.setOfCheckedId = new Set<string>();
     this.checkedAll = false;
   }
   async updateUser(data: Parse.Object, type: string) {
     console.log(type);
-    if(this.tbookSer.profile.identity != '国家级管理员'
-     && (data?.get('identity') == '工作联系人' || data?.get('identity') == '高校联系人')
-     ){
-      this.message.warning('暂无权限')
-      return
+    if (
+      this.tbookSer.profile.identity != '国家级管理员' &&
+      (data?.get('identity') == '工作联系人' ||
+        data?.get('identity') == '高校联系人')
+    ) {
+      this.message.warning('暂无权限');
+      return;
     }
     this.modal.confirm({
       nzTitle: '操作提示',
@@ -184,7 +211,7 @@ export class PageUserComponent implements OnInit {
           switch (type) {
             case '通过认证':
               r?.set('accountState', '已认证');
-               await r?.save();
+              await r?.save();
               break;
             case '禁用':
               r?.set('accountState', '已禁用');
@@ -192,10 +219,10 @@ export class PageUserComponent implements OnInit {
               break;
             case '删除':
               // r?.set('isDeleted', true);
-              await data.destroy()
-              await r.destroy()
+              await data.destroy();
+              await r.destroy();
               break;
-            }
+          }
         }
         this.getProfile();
       },
@@ -219,7 +246,7 @@ export class PageUserComponent implements OnInit {
           return new Promise((resolve) => {
             // item.set('isDeleted', true);
             // item.save();
-            item.destroy().then(()=> resolve)
+            item.destroy().then(() => resolve);
           });
         });
         try {
@@ -234,4 +261,217 @@ export class PageUserComponent implements OnInit {
   goDateil(id: string) {
     this.route.navigate(['/nav-admin/manage/user/edit', { id: id }]);
   }
+
+  /* 添加用户 */
+  async addMember() {
+    this.radio = '';
+    this.account = {
+      name: '',
+      username: '',
+      phone: '',
+      email: '',
+      password: '',
+      identity: '',
+      department: {},
+      companyType: '',
+    };
+    if (this.tbookSer.profile.identity != '国家级管理员') {
+      // let query = new Parse.Query('Department');
+      // query.select('code', 'name', 'branch', 'parent', 'type', 'hasChildren');
+      // let res = await query.get(
+      //   this.tbookSer.profile.user.department?.objectId
+      // );
+      // this.parentList = [
+      //   {
+      //     title: res.get('name'),
+      //     key: res.id,
+      //     branch: res.get('branch'),
+      //     parent: res.get('parent')?.id, //上级
+      //     isLeaf: !res.get('hasChildren'), //是否是最下级
+      //     type: res.get('type'),
+      //   },
+      // ];
+      this.parentList = await this.getDepart(this.tbookSer.profile.user.department?.objectId);
+      this.parentMap = await this.formatNode(this.tbookSer.profile.user.department?.objectId);
+    } else {
+      this.parentList = await this.getDepart();
+    }
+    this.accountIsVisible = true;
+  }
+  async getDepart(parent?: string, searchValue?: string): Promise<Array<any>> {
+    let nodes: any = [];
+    let query = new Parse.Query('Department');
+    query.equalTo(
+      'parent',
+      searchValue
+        ? this.parentMap[this.parentMap.length - 1]?.key
+        : parent
+        ? parent
+        : this.tbookSer.profile.identity != '国家级管理员'
+        ? this.tbookSer.profile.user.department?.objectId
+        : null
+    );
+    searchValue && query.contains('name', searchValue);
+    query.notEqualTo('isDeleted', true);
+    query.select('code', 'name', 'branch', 'parent', 'type', 'hasChildren');
+    query.descending('createdAt');
+    query.limit(2000);
+    let res = await query.find();
+    res.forEach((item) => {
+      nodes.push({
+        title: item.get('name'),
+        key: item.id,
+        branch: item.get('branch'),
+        parent: item.get('parent')?.id, //上级
+        isLeaf: !item.get('hasChildren'), //是否是最下级
+        type: item.get('type'),
+      });
+    });
+    return nodes;
+  }
+
+  //搜索
+  async onSearchNodes(e: string) {
+    this.parentList = await this.getDepart('', e);
+    return;
+  }
+  async onPre(data?: any, index?: number) {
+    if (!data) {
+      this.parentList = await this.getDepart();
+      this.parentMap = [];
+      return;
+    }
+    if (index == this.parentMap.length - 1){
+      if(this.parentList.length == 0 ) this.parentList = await this.getDepart(data?.key);
+      return;
+    } 
+    this.parentMap.splice((index || 0) + 1);
+    this.parentList = await this.getDepart(data?.key);
+  }
+  //选择所属类别下级列表
+  async onCheckedDepart(e: any, checked?: boolean) {
+    this.radio = e.key;
+    this.account.identity = '';
+    this.parentMap = await this.formatNode(e.key);
+    if (this.tbookSer.profile.identity != '国家级管理员') {
+      let index = this.parentMap.findIndex(
+        (item) => this.tbookSer.profile.user.department?.objectId == item.key
+      );
+      if (index != -1) {
+        this.parentMap.forEach((item, i) => {
+          if (i >= index) {
+            this.parentMap[i].verify = true;
+          }
+        });
+      }
+    }
+    if (checked && e.parent) {
+      this.account.department = { title: e.title, id: e.key };
+      this.account.companyType = e.branch || e.title;
+      this.userType = this.parents.some((item) => e.parent == item.key)
+        ? !e.type ? ['工作联系人', '评审专家', '个人'] : ['评审专家', '工作联系人', '个人']
+        : e.type
+        ? ['评审专家', '高校联系人', '个人']
+        : ['评审专家', '个人'];
+      return;
+    }
+    if (e.isLeaf) {
+      return;
+    }
+    this.parentList = await this.getDepart(e?.key);
+  }
+  //格式化链
+  async formatNode(id: string): Promise<Array<any>> {
+    let query = new Parse.Query('Department');
+    query.select('name', 'parent');
+    let r = await query.get(id);
+    let arr = [
+      {
+        title: r.get('name'),
+        key: r.id,
+      },
+    ];
+    if (r?.get('parent')) {
+      arr.unshift(...(await this.formatNode(r?.get('parent').id)));
+    }
+    return arr;
+  }
+  handleCancel(): void {
+    this.parentMap = [];
+    this.accountIsVisible = false;
+    this.account = null;
+  }
+  randomPassword() {
+    this.account.password = this.tbookSer.randomPassword();
+    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;
+    }
+    this.account.username = this.account?.username.trim();
+    this.account.email = this.account?.email.trim();
+    this.account.phone = this.account?.phone.trim();
+    if (!this.account?.email.trim() && !this.account?.phone.trim()) {
+      this.message.error('邮箱或手机号必须填写一项');
+      return;
+    }
+    try {
+      let obj = Parse.Object.extend('_User');
+      let user = new obj();
+      user?.set(
+        'username',
+        this.account?.username || this.account?.email || this.account?.phone
+      );
+      user?.set('name', this.account?.name);
+      user?.set('phone', this.account?.phone);
+      this.account?.email && 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.pageIndex = 1;
+          this.getProfile();
+        },
+      });
+    } catch (err: any) {
+      console.warn('添加用户错误', err);
+      this.message.error(
+        err?.Error || '错误:请检查用户或邮箱及手机号是否已存在'
+      );
+      return;
+    }
+  }
 }

+ 1 - 12
projects/textbook/src/modules/nav-admin/page-user/user-create/user-create.component.ts

@@ -267,17 +267,6 @@ export class UserCreateComponent implements OnInit {
 
   password: string = '';
   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.password = str.join('');
-    console.log(this.password);
+    this.password = this.tbookSer.randomPassword()
   }
 }

+ 103 - 7
projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.html

@@ -110,24 +110,24 @@
           <div nz-col nzSpan="8">
             <div class="lable">创建时间</div>
             <div class="value">
-              {{ user?.createdAt | date : "yyyy-MM-dd HH:MM:ss" }}
+              {{ user?.createdAt | date : "yyyy-MM-dd HH:MM:ss" || '-'}}
             </div>
           </div>
           <div nz-col nzSpan="8">
             <div class="lable">最后登录时间</div>
             <div class="value">
-              {{ user?.get("lastLogin") | date : "yyyy-MM-dd HH:MM:ss" }}
+              {{ user?.get("lastLogin") ? (user?.get("lastLogin") | date : "yyyy-MM-dd HH:MM:ss") : '-'}}
             </div>
           </div>
           <div nz-col nzSpan="8">
             <div class="lable">最后登录IP</div>
-            <div class="value">{{ user?.get("lastIP") }}</div>
+            <div class="value">{{ user?.get("lastIP") || '-'}}</div>
           </div>
         </div>
         <div nz-row>
           <div nz-col nzSpan="8">
             <div class="lable">登录次数</div>
-            <div class="value">{{ user?.get("loginsCount") }}</div>
+            <div class="value">{{ user?.get("loginsCount") || '-'}}</div>
           </div>
           <div nz-col nzSpan="8">
             <div class="lable">用户类型</div>
@@ -196,7 +196,7 @@
           <div nz-col nzSpan="8">
             <div class="lable">单位类型</div>
             <div class="value">
-              {{ profile?.get("companyType") || "未填写" }}
+              {{ userDataJson.companyType || "未填写" }}
             </div>
           </div>
           <div nz-col nzSpan="8">
@@ -221,7 +221,34 @@
           </div>
         </div>
       </div>
-      <div class="title">所属组织部门</div>
+      <div class="title-row">
+        <div class="bar">
+          所属组织部门
+          <a class="btn" (click)="showModalDepart()">选择部门</a>
+        </div>
+      </div>
+      <div class="fill-setp">
+        @if (userDataJson.department?.id) {
+        <nz-tag
+          [nzBordered]="false"
+          style="color: black"
+          [nzColor]="'#efefef'"
+          >“十四五”高等教育国家规划教材申报系统</nz-tag
+        >
+        /
+        <nz-tag
+          [nzBordered]="false"
+          style="color: black"
+          [nzColor]="'#efefef'"
+          >{{ userDataJson.companyType }}</nz-tag
+        >
+        /
+        <nz-tag [nzBordered]="false" [nzColor]="'geekblue'">{{
+          userDataJson.department?.get('name')
+        }}</nz-tag>
+        }@else { 待选择 }
+      </div>
+      <!-- <div class="title">所属组织部门</div>
       <div class="fill-template">
         <nz-tag [nzBordered]="false" style="color: black" [nzColor]="'#efefef'"
           >“十四五”高等教育国家规划教材申报系统</nz-tag
@@ -237,7 +264,7 @@
         <nz-tag [nzBordered]="false" [nzColor]="'geekblue'">{{
           user?.get("department")?.get('name') || user?.get("companyName")
         }}</nz-tag>
-      </div>
+      </div> -->
       <div class="title">原始JSON数据</div>
       <div class="fill-template">
         <div nz-row>
@@ -274,3 +301,72 @@
     />
   </ng-container>
 </nz-modal>
+
+<nz-modal
+  [(nzVisible)]="isShowModal"
+  nzTitle="选择部门"
+  (nzOnCancel)="handleCancel()"
+  nzWidth="400px"
+  nzCentered
+>
+  <ng-container *nzModalContent>
+    <div nz-row class="depart-modal">
+      <div nz-col nzSpan="24">
+        <div class="row">
+          <div class="value">
+            <input
+              nz-input
+              [ngModel]="userDataJson.department?.get('name')"
+              (ngModelChange)="provinceChange(parent, $event)"
+              placeholder="搜索或选择一个下方部门"
+              type="text"
+            />
+          </div>
+        </div>
+        <div class="select">
+          <div class="bar">
+            <a style="color: #86909c">教材遴选</a>
+            @for (data of parentMap; track data.title) {
+            <span style="margin: 0 10px">/</span>
+            @if($index == 0){
+              @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>
+            } }
+          </div>
+          <div class="tree">
+            @for (data of parentList; track $index) {
+            <div class="li" (click)="onCheckedDepart(data)">
+              <label
+                nz-radio
+                [ngModel]="userDataJson.department?.id == data.id"
+                [nzValue]="data.id"
+                (click)="onCheckedDepart(data)"
+                >{{ data.get("name") }}</label
+              >
+              @if (!data.get("branch")) {
+              <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)="completeChange()"
+    >
+      确定
+    </button>
+  </div>
+</nz-modal>

+ 41 - 0
projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.scss

@@ -93,6 +93,47 @@
     width: 100%;
     min-height: 150px;
   }
+  .title-row {
+    font-family: PingFang SC;
+    font-size: 20px;
+    font-weight: 500;
+    line-height: 32px;
+    text-align: left;
+    margin: 28px 0 10px;
+    .btn {
+      margin-left: 20px;
+      flex-shrink: 0;
+      color: #006ded;
+      font-size: 14px;
+      font-weight: 400;
+    }
+    .bar {
+      display: flex;
+      justify-content: space-between;
+      width: 100%;
+    }
+  }
+}
+.depart-modal{
+  .row{
+    width: 90%;
+    margin-bottom: 20px;
+  }
+  .tree{
+    height: 180px;
+    overflow-y: scroll;
+    .li{
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 4px;
+      span{
+        flex-shrink: 0;
+      }
+    }
+    .li:hover{
+      background-color: #f9f9f9;
+    }
+  }
 }
 ::ng-deep .ant-page-header-heading-title {
   white-space: normal;

+ 140 - 21
projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.ts

@@ -3,10 +3,7 @@ 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 { ProcessComponent } from '../components/process/process.component';
-import { CreateCollectionComponent } from '../collection/create-collection/create-collection.component';
 import { ActivatedRoute, Router } from '@angular/router';
-import Parse from 'parse';
 import { NzAvatarModule } from 'ng-zorro-antd/avatar';
 import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
 import { NzPopoverModule } from 'ng-zorro-antd/popover';
@@ -14,6 +11,9 @@ import { NzTagModule } from 'ng-zorro-antd/tag';
 import { NzModalModule } from 'ng-zorro-antd/modal';
 import { NzMessageService } from 'ng-zorro-antd/message';
 import { textbookServer } from '../../../services/textbook';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+import Parse from 'parse';
+
 @Component({
   selector: 'app-user-edit',
   templateUrl: './user-edit.component.html',
@@ -23,13 +23,12 @@ import { textbookServer } from '../../../services/textbook';
     NzSpaceModule,
     CommonCompModule,
     NzTabsModule,
-    ProcessComponent,
-    CreateCollectionComponent,
     NzAvatarModule,
     NzDropDownModule,
     NzPopoverModule,
     NzTagModule,
     NzModalModule,
+    NzRadioModule,
   ],
   standalone: true,
 })
@@ -45,7 +44,19 @@ export class UserEditComponent implements OnInit {
   user: Parse.Object | any;
   profile: Parse.Object | any;
   password: string = '';
-  edit:boolean = false //编辑权限
+  edit: boolean = false; //编辑权限
+
+  //可编辑
+  userDataJson: any = {
+    companyType: '',
+    department: null,
+  };
+  parentMap: Array<any> = [];
+  parentList: Array<any> = []; //单位列表
+  isShowModal: boolean = false;
+  searchValue: string = ''; //搜索部门内容
+  unitTypes: Array<any> = [];
+
   constructor(
     public tbookSer: textbookServer,
     private activeRoute: ActivatedRoute,
@@ -59,26 +70,33 @@ export class UserEditComponent implements OnInit {
       console.log(id);
       if (id) {
         let query = new Parse.Query('_User');
-        query.include('department')
+        query.include('department');
         this.user = await query.get(id);
         let queryProfile = new Parse.Query('Profile');
         queryProfile.equalTo('user', id);
         this.profile = await queryProfile.first();
+        this.userDataJson = {
+          companyType: this.profile?.get('companyType'),
+          department: this.user.get('department'),
+        };
       }
-      let arr = ['个人', '评审专家','高校联系人']
-      if ( this.tbookSer.profile.identity == '国家级管理员'
-      || (this.tbookSer.profile.identity =='工作联系人' && arr.includes(this.profile.identity))
-      || (this.tbookSer.profile.identity =='高校联系人' && this.profile.identity == '个人')
+      let arr = ['个人', '评审专家', '高校联系人'];
+      if (
+        this.tbookSer.profile.identity == '国家级管理员' ||
+        (this.tbookSer.profile.identity == '工作联系人' &&
+          arr.includes(this.profile.identity)) ||
+        (this.tbookSer.profile.identity == '高校联系人' &&
+          this.profile.identity == '个人')
       ) {
-        this.edit = true
+        this.edit = true;
       }
     });
   }
   async updateUser(type: string) {
     console.log(type);
-    if(!this.edit){
-      this.message.warning('同级身份暂无权限操作')
-      return
+    if (!this.edit) {
+      this.message.warning('同级身份暂无权限操作');
+      return;
     }
     switch (type) {
       case '已认证':
@@ -95,14 +113,14 @@ export class UserEditComponent implements OnInit {
     this.ngOnInit();
   }
   async handleOk() {
-    if(!this.edit){
-      this.message.warning('同级身份暂无权限操作')
-      return
+    if (!this.edit) {
+      this.message.warning('同级身份暂无权限操作');
+      return;
     }
-    this.password = this.password.trim()
-    if(!this.password){
+    this.password = this.password.trim();
+    if (!this.password) {
       this.message.warning('密码格式错误');
-      return
+      return;
     }
     try {
       this.user.set('password', this.password);
@@ -113,4 +131,105 @@ export class UserEditComponent implements OnInit {
     }
     this.isVisible = false;
   }
+  //选择部门
+  async showModalDepart() {
+    if(this.unitTypes.length == 0){
+     await this.getUnitTypes()
+    }
+    let parent = this.unitTypes.find(
+      (item) => item.name == this.userDataJson.companyType
+    );
+    if (parent?.id) {
+      this.parentMap = await this.formatNode(parent.id);
+    }
+    this.provinceChange(this.parentMap[this.parentMap.length - 1]?.id);
+    this.isShowModal = true;
+  }
+  async getUnitTypes() {
+    let query = new Parse.Query('Department');
+    query.equalTo('branch', undefined);
+    query.equalTo('parent', undefined);
+    query.notEqualTo('isDeleted', true);
+    query.select('name');
+    let r = await query.find();
+    r.forEach((item) => {
+      this.unitTypes.push({ id: item.id, name: item.get('name') });
+    });
+  }
+  //根据所选单位类型获取对应单位
+  async provinceChange(id?: string, val?: string) {
+    let query = new Parse.Query('Department');
+    query.select('name', 'branch', 'parent');
+    if (this.tbookSer.profile.identity != '国家级管理员') {
+      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);
+    let r = await query.find();
+    this.parentList = r;
+  }
+
+  async formatNode(id: string): Promise<Array<any>> {
+    let arr = [];
+    if (id) {
+      let query = new Parse.Query('Department');
+      query.equalTo('objectId', id);
+      query.select('parent', 'name');
+      let r = await query.first();
+      arr.push({
+        title: r?.get('name'),
+        id: r?.id,
+      });
+      if (r?.get('parent')) {
+        arr.unshift(...(await this.formatNode(r?.get('parent')?.id)));
+      }
+    }
+    return arr;
+  }
+  onCheck(e: any) {
+    console.log(e);
+    this.provinceChange();
+  }
+  parent:string = '' //搜索时传入的id
+  //选择部门
+  async onCheckedDepart(e: any) {
+    console.log(e);
+    if (e?.get('parent')?.id) {
+      this.userDataJson.department = e;
+      this.parent = e?.get('parent')?.id
+    } else {
+      this.provinceChange(e.id);
+      this.parent = e?.id
+    }
+    this.parentMap = await this.formatNode(e.id);
+  }
+  handleCancel(): void {
+    console.log('Button cancel clicked!');
+    this.userDataJson = {
+      companyType: this.profile?.get('companyType'),
+      department: this.user.get('department'),
+    };
+    this.isShowModal = false;
+    this.parent = ''
+  }
+  async completeChange(): Promise<void> {
+    if(!this.userDataJson.department?.id){
+      this.message.warning('请选择部门')
+      return
+    }
+    this.userDataJson.companyType = this.userDataJson.department.get('branch');
+    this.profile?.set('companyType', this.userDataJson.companyType)
+    await this.profile?.save()
+    this.user?.set('department', this.userDataJson.department?.toPointer())
+    await this.user?.save()
+    this.ngOnInit()
+    this.isShowModal = false;
+    this.parent = ''
+  }
 }

+ 38 - 22
projects/textbook/src/modules/nav-province-contact/page-role/page-role.component.html

@@ -110,8 +110,8 @@
         <ul nz-menu>
           <li nz-menu-item (click)="showModalDepart('add')">添加下级部门</li>
           @if (activatedNode?.parentNode) {
-            <li nz-menu-item (click)="showModalDepart('edit')">编辑部门</li>
-            <li nz-menu-item (click)="onDelDepart()">删除部门</li>
+          <li nz-menu-item (click)="showModalDepart('edit')">编辑部门</li>
+          <li nz-menu-item (click)="onDelDepart()">删除部门</li>
           }
         </ul>
       </nz-dropdown-menu>
@@ -175,8 +175,7 @@
             </td>
             <td nzRight>
               @if(data?.user?.objectId == user?.id || data?.identity ==
-              '工作联系人' || data?.identity ==
-              '高校联系人'){ - } @else {
+              '工作联系人' || data?.identity == '高校联系人'){ - } @else {
               <button
                 nz-button
                 nz-dropdown
@@ -270,18 +269,18 @@
         </div>
         <div class="select">
           <div class="bar">
-            <a style="color: #86909c" (click)="onPre()">教材遴选</a>
+            <a style="color: #86909c">教材遴选</a>
             @for (data of parentMap; track data.title) {
-              /
-              @if ($index !=0) {
-                <a (click)="onPre(data, $index)">{{ data.title }}</a>
-              }@else {
-                <a style="color: #86909c">{{ data.title }}</a>
-              }
-            }
+            <span style="margin: 0 10px">/</span>
+            @if ($index !=0 && data?.verify) {
+            <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) {
+            @if(parentList.length > 0){ @for (data of parentList; track $index)
+            {
             <div class="li" (click)="onCheckedDepart('branch', data)">
               <label
                 nz-radio
@@ -294,6 +293,8 @@
               <span nz-icon nzType="right" nzTheme="outline"></span>
               }
             </div>
+            } }@else {
+            <nz-empty nzNotFoundContent="暂无下级部门"></nz-empty>
             }
           </div>
         </div>
@@ -361,10 +362,22 @@
           <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="请输入密码" />
+              <input
+                type="password"
+                [(ngModel)]="account.password"
+                nz-input
+                placeholder="请输入密码"
+              />
             </nz-input-group>
             <ng-template #suffixIconButton>
-              <button nz-button (click)="randomPassword()" nzType="primary" nzSearch>自动生成密码</button>
+              <button
+                nz-button
+                (click)="randomPassword()"
+                nzType="primary"
+                nzSearch
+              >
+                自动生成密码
+              </button>
             </ng-template>
           </div>
         </div>
@@ -379,6 +392,7 @@
             /> -->
             <nz-select
               style="width: 100%"
+              [disabled]="!this.account.department?.id"
               nzShowSearch
               nzAllowClear
               [(ngModel)]="account.identity"
@@ -410,18 +424,18 @@
         </div>
         <div class="select">
           <div class="bar">
-            <a style="color: #86909c" (click)="onPre()">教材遴选</a>
+            <a style="color: #86909c">教材遴选</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>
+            @if ($index !=0 && data?.verify) {
+            <a (click)="onPre(data, $index)">{{ data.title }}</a>
             }@else {
-              <a style="color: #86909c">{{ data.title }}</a>
-            }
-            }
+            <a style="color: #86909c">{{ data.title }}</a>
+            } }
           </div>
           <div class="tree">
-            @for (data of parentList; track $index) {
+            @if(parentList.length > 0){ @for (data of parentList; track $index)
+            {
             <div class="li" (click)="onCheckedDepart('account', data)">
               <label
                 nz-radio
@@ -434,6 +448,8 @@
               <span nz-icon nzType="right" nzTheme="outline"></span>
               }
             </div>
+            } }@else {
+            <nz-empty nzNotFoundContent="暂无下级部门"></nz-empty>
             }
           </div>
         </div>

+ 87 - 74
projects/textbook/src/modules/nav-province-contact/page-role/page-role.component.ts

@@ -109,6 +109,7 @@ export class PageRoleComponent implements OnInit {
     companyType: '',
   };
   userType: Array<string> = ['个人'];
+  parents: Array<any> = []; //所有一级列表
 
   constructor(
     public tbookSer: textbookServer,
@@ -119,51 +120,52 @@ export class PageRoleComponent implements OnInit {
     private message: NzMessageService
   ) {
     this.user = Parse.User.current();
-    this.className = this.Department.className;
-    this.fieldsArray = this.Department.fieldsArray;
-    this.queryParams = {
-      where: {
-        // user:this.user?.toPointer(),
-        isDeleted: { $ne: true },
-      },
-    };
+    // this.className = this.Department.className;
+    // this.fieldsArray = this.Department.fieldsArray;
+    // this.queryParams = {
+    //   where: {
+    //     // user:this.user?.toPointer(),
+    //     isDeleted: { $ne: true },
+    //   },
+    // };
   }
 
   ngOnInit(): void {
     this.activeRoute.paramMap.subscribe(async (params) => {
-      // let isDeleted = params.get('isDeleted');
-      // if (isDeleted) {
-      //   this.queryParams.where['isDeleted'] = { $eq: true };
-      // } else {
-      //   this.queryParams.where['isDeleted'] = { $ne: true };
-      // }
-      // this.list?.ngOnInit();
-      if (!this.tbookSer.profile.user.department?.objectId) {
-        this.message.warning('权限不足');
-        history.back();
-        return;
-      }
-      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')
-        },
-      ];
+      this.refresh();
     });
   }
+  async refresh() {
+    if (!this.tbookSer.profile.user.department?.objectId) {
+      this.message.warning('权限不足');
+      history.back();
+      return;
+    }
+    let query = new Parse.Query('Department');
+    query.equalTo('objectId', this.tbookSer.profile.user.department?.objectId);
+    query.select('code', 'name', 'branch', 'parent', 'type', 'hasChildren');
+    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'),
+        parent: r?.get('parent')?.id, //上级
+        type: r?.get('branch'),
+      },
+    ];
+    let query2 = new Parse.Query('Department');
+    query2.select('name');
+    query2.equalTo('parent', null);
+    this.parents = await query2.find();
+  }
   async getDepart(
     parent?: string,
     searchValue?: string,
@@ -176,12 +178,12 @@ export class PageRoleComponent implements OnInit {
     }
     searchValue && query.contains('name', searchValue);
     query.notEqualTo('isDeleted', true);
-    query.select('code', 'name', 'branch', 'parent', 'type','hasChildren');
+    query.select('code', 'name', 'branch', 'parent', 'type', 'hasChildren');
     query.descending('createdAt');
-    query.equalTo('branch',this.tbookSer.profile.companyType)
+    query.equalTo('branch', this.tbookSer.profile.companyType);
     console.log(searchValue);
-    if(searchValue && searchValue != undefined){
-      query.equalTo('parent',this.tbookSer.profile.user.department?.objectId)
+    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);
@@ -192,7 +194,9 @@ export class PageRoleComponent implements OnInit {
         key: item.id,
         children: [],
         branch: item.get('branch'),
+        parent: item.get('parent')?.id, //上级
         isLeaf: !item.get('hasChildren') ? true : false, //是否是最下级
+        type: item.get('type'),
       });
     });
     return nodes;
@@ -207,6 +211,7 @@ export class PageRoleComponent implements OnInit {
   }
   //添加成员
   addMember() {
+    this.radio = ''
     this.parentList = this.nodes;
     this.account = {
       name: '',
@@ -232,7 +237,6 @@ export class PageRoleComponent implements OnInit {
         let data = await this.getDepart(node.key);
         node.addChildren(data);
       }
-      console.log(this.nodes);
     } else {
       // if (node.origin.isParent) {
       this.currentDepart = node.origin;
@@ -313,6 +317,7 @@ export class PageRoleComponent implements OnInit {
   }
   //新建打开弹窗
   async showModalDepart(type: string) {
+    this.radio = ''
     this.parentList = this.nodes;
     this.editObject = {
       name: '',
@@ -365,7 +370,7 @@ export class PageRoleComponent implements OnInit {
   //格式化链
   async formatNode(id: string): Promise<Array<any>> {
     let query = new Parse.Query('Department');
-    query.select('name', 'parent','hasChildren');
+    query.select('name', 'parent', 'hasChildren');
     let r = await query.get(id);
     let arr = [
       {
@@ -380,25 +385,42 @@ export class PageRoleComponent implements OnInit {
     return arr;
   }
   async onPre(data?: any, index?: number) {
+    console.log(data);
     if (!data?.key || !data.hasChildren) {
       // this.parentList = await this.getDepart();
       // this.parentMap = []
       return;
     }
     // if(index == this.parentMap.length-1) return
-    if (index !== 0 && !index) this.parentMap.splice(index || 0 + 1);
+    this.parentMap.splice((index || 0) + 1);
     this.parentList = await this.getDepart(data?.key);
   }
   //选择所属类别下级列表
   async onCheckedDepart(type: string, e: any, checked?: boolean) {
     this.radio = e.key;
     console.log(e);
+    if (type == 'account') this.account.identity = '';
     this.parentMap = await this.formatNode(e.key);
+    let index = this.parentMap.findIndex(
+      (item) => this.tbookSer.profile.user.department?.objectId == item.key
+    );
+    if (index != -1) {
+      this.parentMap.forEach((item, i) => {
+        if (i >= index) {
+          this.parentMap[i].verify = true;
+        }
+      });
+    }
     if (checked) {
       // this.editObject.name = e.title
       if (type == 'account') {
         this.account.department = { title: e.title, id: e.key };
         this.account.companyType = e.branch || e.title;
+        this.userType = this.parents.some((item) => e.parent == item.id)
+          ? ['评审专家', '个人']
+          : e.type
+          ? ['评审专家', '高校联系人', '个人']
+          : ['评审专家', '个人'];
       } else {
         this.editObject.parent = {
           title: e.title,
@@ -411,10 +433,6 @@ export class PageRoleComponent implements OnInit {
     if (e.isLeaf) {
       return;
     }
-    // this.parentMap.push({
-    //   title: e.title,
-    //   key: e.key,
-    // });
     this.parentList = await this.getDepart(e?.key);
   }
   async handleOk(): Promise<void> {
@@ -446,22 +464,22 @@ export class PageRoleComponent implements OnInit {
       this.activeDepart?.set('branch', this.editObject.branch);
     }
     await this.activeDepart?.save();
-    await this.updateChildren()
+    await this.updateChildren();
     this.isVisible = false;
     this.message.success(this.editType == 'edit' ? '保存' : '添加' + '成功');
     this.activeDepart = undefined;
-    this.ngOnInit()
+    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()
+  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
+    return;
   }
   handleCancel(): void {
     console.log('Button cancel clicked!');
@@ -481,18 +499,7 @@ export class PageRoleComponent implements OnInit {
     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);
+    this.account.password = this.tbookSer.randomPassword();
   }
   /* 添加账号 */
   async accountComplete() {
@@ -513,13 +520,19 @@ export class PageRoleComponent implements OnInit {
       this.message.error('请填写正确手机号');
       return;
     }
+    this.account.email = this.account?.email.trim();
+    this.account.phone = this.account?.phone.trim();
+    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);
+      this.account?.email && user?.set('email', this.account?.email);
       user?.set('password', this.account.password);
       user?.set('accountState', '已认证');
       user?.set('department', {

+ 13 - 1
projects/textbook/src/services/textbook.ts

@@ -18,5 +18,17 @@ export class textbookServer {
     }
     return true;
   }
-
+  randomPassword():string {
+    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)
+    }
+    return str.join('')
+  }
 }