warrior há 8 meses atrás
pai
commit
8afa52b690
27 ficheiros alterados com 1213 adições e 543 exclusões
  1. 21 25
      projects/textbook/src/app/comp-manage/comp-manage.component.ts
  2. 31 72
      projects/textbook/src/modules/login/account-info/account-info.component.html
  3. 1 1
      projects/textbook/src/modules/login/account-info/account-info.component.scss
  4. 108 109
      projects/textbook/src/modules/login/account-info/account-info.component.ts
  5. 0 4
      projects/textbook/src/modules/login/login/login.component.ts
  6. 5 5
      projects/textbook/src/modules/login/modules.routes.ts
  7. 78 0
      projects/textbook/src/modules/login/register/parse-authing.ts
  8. 41 97
      projects/textbook/src/modules/login/register/register.component.html
  9. 97 34
      projects/textbook/src/modules/login/register/register.component.scss
  10. 169 107
      projects/textbook/src/modules/login/register/register.component.ts
  11. 13 12
      projects/textbook/src/modules/nav-admin/collection-edit/collection-edit.component.html
  12. 5 0
      projects/textbook/src/modules/nav-admin/modules.routes.ts
  13. 7 9
      projects/textbook/src/modules/nav-admin/page-textbook/page-textbook.component.ts
  14. 35 2
      projects/textbook/src/modules/nav-admin/page-user/page-user.component.html
  15. 30 0
      projects/textbook/src/modules/nav-admin/page-user/page-user.component.scss
  16. 40 27
      projects/textbook/src/modules/nav-admin/page-user/page-user.component.ts
  17. 176 0
      projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.html
  18. 106 0
      projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.scss
  19. 24 0
      projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.spec.ts
  20. 55 0
      projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.ts
  21. 2 1
      projects/textbook/src/modules/textbook/page-home/header/header.component.html
  22. 75 0
      projects/textbook/src/schemas/Profile-list.ts
  23. 3 3
      projects/textbook/src/schemas/Profile.ts
  24. 14 14
      projects/textbook/src/services/auth.service.ts
  25. 3 2
      server/db/index.js
  26. 58 0
      server/db/schemas/Department.js
  27. 16 19
      server/db/schemas/Profile.js

+ 21 - 25
projects/textbook/src/app/comp-manage/comp-manage.component.ts

@@ -46,39 +46,35 @@ export class CompManageComponent implements OnInit {
         id:'2',
         child:[
           {
-            name:'用户审核',
-            id:'2-1',
-          },
-          {
-            name:'注册账户',
+            name:'用户列表',
             path:"/nav-admin/manage/user",
-            id:'2-2',
+            id:'2-1',
           },
           {
-            name:'用户组管理',
+            name:'组管理',
             path:"/nav-admin/manage/role",
             id:'2-3',
           },
         ]
       },
-      {
-        name:'品牌化',
-        id:'3',
-        child:[
-          {
-            name:'登录框',
-            id:'3-1',
-          },
-          {
-            name:'消息设置',
-            id:'3-2',
-          },
-        ]
-      },
-      {
-        name:'字段管理',
-        id:'4',
-      },
+      // {
+      //   name:'品牌化',
+      //   id:'3',
+      //   child:[
+      //     {
+      //       name:'登录框',
+      //       id:'3-1',
+      //     },
+      //     {
+      //       name:'消息设置',
+      //       id:'3-2',
+      //     },
+      //   ]
+      // },
+      // {
+      //   name:'字段管理',
+      //   id:'4',
+      // },
     ],
     "省级教育行政部门":[
       {

+ 31 - 72
projects/textbook/src/modules/login/account-info/account-info.component.html

@@ -27,9 +27,9 @@
         </defs>
       </svg>
     </div>
-    完善账号信息
+    注册账号
     <div class="tips">
-      欢迎使用教材遴选系统 ,为了激活账号使用权限,请先完善您的资料信息
+      欢迎使用教材遴选系统,注册信息提交后由管理员审核,审核通过后即已激活可直接登录
     </div>
   </div>
   <div class="form">
@@ -53,16 +53,13 @@
         </nz-input-group>
       </nz-form-item>
       <nz-form-item>
-        <nz-form-label class="label" [nzNoColon]="true" nzRequired
-          >手机号</nz-form-label
-        >
+        <nz-form-label class="label" [nzNoColon]="true">手机号</nz-form-label>
         <nz-input-group>
           <input
             nz-input
             type="text"
-            [disabled]="true"
-            [ngModel]="mobile"
-            [ngModelOptions]="{standalone: true}"
+            formControlName="phone"
+            [disabled]="user?.get('phone')"
             placeholder="请填写手机号"
           />
         </nz-input-group> </nz-form-item
@@ -74,6 +71,7 @@
           <input
             nz-input
             type="email"
+            [disabled]="user?.get('email')"
             formControlName="email"
             placeholder="请填写电子邮箱"
           />
@@ -84,23 +82,15 @@
           <input
             nz-input
             type="text"
-            [(ngModel)]="nonRequired.workPhone"
-            [ngModelOptions]="{standalone: true}"
+            [(ngModel)]="nonRequired.telephone"
+            [ngModelOptions]="{ standalone: true }"
             placeholder="请填写办公电话"
           />
         </nz-input-group>
       </nz-form-item>
       <nz-form-item>
-        <nz-form-label class="label" [nzNoColon]="true" nzRequired
-          >省份</nz-form-label
-        >
+        <nz-form-label class="label" [nzNoColon]="true">省份</nz-form-label>
         <nz-input-group>
-          <!-- <input
-            nz-input
-            type="text"
-            formControlName="province"
-            placeholder="请填写用户名"
-          /> -->
           <nz-select
             style="width: 100%"
             nzShowSearch
@@ -121,18 +111,13 @@
           >单位类型</nz-form-label
         >
         <nz-input-group>
-          <!-- <input
-            nz-input
-            type="text"
-            formControlName="unitType"
-            placeholder="请填写用户名"
-          /> -->
           <nz-select
             style="width: 100%"
             nzShowSearch
             nzAllowClear
             nzPlaceHolder="请选择单位类型"
-            formControlName="unitType"
+            formControlName="companyType"
+            (ngModelChange)="provinceChange()"
           >
             @for(item of unitTypes; track item;let index = $index){
             <nz-option nzCustomContent [nzValue]="item" [nzLabel]="item">{{
@@ -147,12 +132,20 @@
           >单位名称</nz-form-label
         >
         <nz-input-group>
-          <input
-            nz-input
-            type="text"
-            formControlName="unit"
-            placeholder="请填写单位名称"
-          />
+          <nz-select
+            style="width: 100%"
+            nzShowSearch
+            nzAllowClear
+            nzPlaceHolder="请选择单位名称"
+            formControlName="companyName"
+            (ngModelChange)="provinceChange()"
+          >
+            @for(item of companys; track item;let index = $index){
+            <nz-option nzCustomContent [nzValue]="item" [nzLabel]="item">{{
+              item
+            }}</nz-option>
+            }
+          </nz-select>
         </nz-input-group>
       </nz-form-item>
       <nz-form-item>
@@ -161,8 +154,7 @@
           <input
             nz-input
             type="text"
-            [(ngModel)]="nonRequired.branch"
-            [ngModelOptions]="{standalone: true}"
+            formControlName="departmentName"
             placeholder="请填写所在部门"
           />
         </nz-input-group>
@@ -173,13 +165,12 @@
           <input
             nz-input
             type="text"
-            [(ngModel)]="nonRequired.job"
-            [ngModelOptions]="{standalone: true}"
+            formControlName="postName"
             placeholder="请填写职务"
           />
         </nz-input-group>
       </nz-form-item>
-      <nz-form-item>
+      <!-- <nz-form-item>
         <nz-form-label class="label" [nzNoColon]="true" nzRequired
           >身份证号</nz-form-label
         >
@@ -191,44 +182,12 @@
             placeholder="请填写身份证号"
           />
         </nz-input-group>
-      </nz-form-item>
-      <nz-form-item>
-        <nz-form-label class="label" [nzNoColon]="true" nzRequired
-          >用户类型</nz-form-label
-        >
-        <nz-input-group>
-          <!-- <input
-            nz-input
-            type="text"
-            formControlName="identity"
-            placeholder="请选择用户类型"
-          /> -->
-          <nz-select
-            style="width: 100%"
-            nzShowSearch
-            nzAllowClear
-            nzPlaceHolder="请选择单位类型"
-            formControlName="identity"
-          >
-            @for(item of identitys; track item;let index = $index){
-            <nz-option nzCustomContent [nzValue]="item" [nzLabel]="item">{{
-              item
-            }}</nz-option>
-            }
-          </nz-select>
-        </nz-input-group>
-      </nz-form-item>
+      </nz-form-item> -->
       <nz-form-item>
-        <nz-form-label class="label" [nzNoColon]="true" nzRequired
+        <nz-form-label class="label" [nzNoColon]="true"
           >单位联系人认证文件</nz-form-label
         >
         <nz-input-group>
-          <!-- <input
-            nz-input
-            type="text"
-            formControlName="file"
-            placeholder="请填写用户名"
-          /> -->
           <nz-upload
             formControlName="file"
             nzAction="https://www.mocky.io/v2/5cc8019d300000980a055e76"
@@ -255,7 +214,7 @@
       mat-button
       (click)="submitForm()"
     >
-      保存
+      提交
     </button>
     <div class="menu">
       <a nz-dropdown [nzDropdownMenu]="menu">

+ 1 - 1
projects/textbook/src/modules/login/account-info/account-info.component.scss

@@ -18,7 +18,7 @@
     }
   }
   .account-form {
-    height: 300px;
+    height: 320px;
     overflow-y: scroll;
     // scrollbar-width: none; /* firefox */
     // -ms-overflow-style: none; /* IE 10+ */

+ 108 - 109
projects/textbook/src/modules/login/account-info/account-info.component.ts

@@ -5,6 +5,8 @@ import { Router } from '@angular/router';
 import { catchError } from 'rxjs/operators';
 import { NzUploadChangeParam } from 'ng-zorro-antd/upload';
 import { NzUploadModule } from 'ng-zorro-antd/upload';
+import { NzModalModule } from 'ng-zorro-antd/modal';
+import { NzModalService } from 'ng-zorro-antd/modal';
 import {
   FormControl,
   FormGroup,
@@ -17,7 +19,6 @@ import { AuthServr } from '../../../services/auth.service';
 import { CaptchaComponent } from '../../../app/captcha/captcha.component';
 import { NzMessageService } from 'ng-zorro-antd/message';
 import { provinces } from '../../../services/provinces';
-
 import Parse from 'parse';
 @Component({
   selector: 'app-account-info',
@@ -26,142 +27,110 @@ import Parse from 'parse';
     ReactiveFormsModule,
     CommonCompModule,
     CaptchaComponent,
-    NzUploadModule,
+    NzUploadModule,NzModalModule
   ],
   templateUrl: './account-info.component.html',
   styleUrls: ['./account-info.component.scss'],
 })
 export class AccountInfoComponent implements OnInit {
-  user: Parse.Object = Parse.User.current()!;
+  user: Parse.Object|undefined
   validateForm: FormGroup<{
     name: FormControl<string>; //姓名
-    // mobile: FormControl<string>; //手机号
+    phone: FormControl<string>; //手机号
     email: FormControl<string>; //电子邮箱
+    telephone: FormControl<string>; //办公电话
     province: FormControl<string>; //省份
-    unitType: FormControl<string>; //单位类型
-    unit: FormControl<string>; //单位名称
-    idcard: FormControl<string>; //身份证号
-    identity: FormControl<string>; //用户类型
-    file: FormControl<string>; //单位联系人认证文件
+    companyType: FormControl<string>; //单位类型
+    companyName: FormControl<string>; //单位名称
+    // idcard: FormControl<string>; //身份证号
+    departmentName:FormControl<string>; //所在部门
+    postName:FormControl<string>; //职务
+    identityFile: FormControl<string>; //单位联系人认证文件
   }> = this.fb.group({
     name: ['', [Validators.required]],
-    // mobile: ['', [Validators.required]],
+    phone: [''],
     email: ['', [Validators.required]],
-    province: ['', [Validators.required]],
-    unitType: ['', [Validators.required]],
-    unit: ['', [Validators.required]],
-    idcard: ['', [Validators.required]],
-    identity: ['', [Validators.required]],
-    file: ['暂无', [Validators.required]],
+    telephone: [''],
+    province: [''],
+    companyType: ['', [Validators.required]],
+    companyName: ['', [Validators.required]],
+    // idcard: ['', [Validators.required]],
+    departmentName: [''],
+    postName: [''],
+    identityFile: [''],
   });
   nonRequired: any = {
-    workPhone: '',
-    branch: '',
     job: '',
   };
-  mobile: string = '';
 
   //省份
   provinces: Array<string> = provinces.options
   unitTypes: Array<string> = [
-    '个体工商户',
-    '企业公司',
-    '国有机构或事业单位',
-    '非营利组织或公益机构',
-    '教育机构',
-    '自由职业者或个人',
-    '其他特定单位类型',
-  ];
-  identitys: Array<string> = [
-    '国家级管理员',
+    '教育部直属高校',
+    '中央有关部门(单位)',
+    '教育司(局)',
     '省级教育行政部门',
-    '合集管理员',
-    '省属高校联系人',
-    '教材评审组成员',
-    '作者/教师/主编',
+    '出版单位',
   ];
+  companys: Array<string> = [];
   constructor(
     public tbookSer: textbookServer,
     private fb: NonNullableFormBuilder,
     public router: Router,
     private authServr: AuthServr,
     private message: NzMessageService,
+    private modal: NzModalService,
     private http: HttpClient
   ) {}
 
   ngOnInit() {
-    if (Parse.User.current()?.id) {
-      // this.validateForm.value.mobile = Parse.User.current()?.get('mobile');
-      this.mobile = this.user.get('mobile');
-      this.authProfile();
-    } else {
-      this.router.navigate(['user/login']);
+    let user = Parse.User.current()
+    if(user?.id) history.back()
+    this.user = user
+    this.validateForm.value.phone = user?.get('phone')
+    this.validateForm.value.email = user?.get('email')
+    if(user?.get('accountState') == '已认证'){
+      this.message.success('账号已认证,无需重复认证')
+      this.authServr.profileVerify()
+      return
+    }else if(user?.get('accountState') == '已禁用'){
+      this.message.success('账号已禁用')
+      this.router.navigate(['/user/register'],{ replaceUrl: true })
+      return
     }
   }
-  async authProfile() {
-    let query = new Parse.Query('Profile');
-    query.equalTo('user', Parse.User.current()?.id);
-    query.notEqualTo('isDeleted', true);
-    let r = await query.first();
-    if (r?.id) {
-      if (!r.get('verify')) {
-        this.message.info('您已完成身份认证,即将跳转到管理后台');
-        return;
-      }
-      this.validateForm = this.fb.group({
-        name: [r.get('name'), [Validators.required]],
-        // mobile: [Parse.User.current()?.get('mobile') || r.get('mobile'), [Validators.required]],
-        email: [r.get('email'), [Validators.required]],
-        province: [r.get('province'), [Validators.required]],
-        unitType: [r.get('unitType'), [Validators.required]],
-        unit: [r.get('unit'), [Validators.required]],
-        idcard: [r.get('idcard'), [Validators.required]],
-        identity: [r.get('identity'), [Validators.required]],
-        file: [r.get('file'), [Validators.required]],
-      });
-      this.nonRequired = {
-        workPhone: r.get('workPhone'),
-        branch: r.get('branch'),
-        job: r.get('job'),
-      };
+  //根据所选单位类型获取对应单位
+  async provinceChange(val?:string){
+    this.companys = []
+    console.log(this.validateForm.value.companyType);
+    let query = new Parse.Query('Department')
+    query.equalTo('branch',this.validateForm.value.companyType)
+    val && query.contains('name',val)
+    let r = await query.find()
+    if(r.length > 0){
+      this.companys = r.map((item:Parse.Object)=> item.get('name'))
     }
   }
   submitForm(): void {
     console.log(this.validateForm.value);
     if (this.validateForm.valid) {
-      let {
-        name,
-        // mobile,
-        email,
-        province,
-        unitType,
-        unit,
-        idcard,
-        identity,
-        file,
-      } = this.validateForm.value;
-  
-      let a =
-        /^\d{6}((((((19|20)\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(((19|20)\d{2})(0[13578]|1[02])31)|((19|20)\d{2})02(0[1-9]|1\d|2[0-8])|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))0229))\d{3})|((((\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|((\d{2})(0[13578]|1[02])31)|((\d{2})02(0[1-9]|1\d|2[0-8]))|(([13579][26]|[2468][048]|0[048])0229))\d{2}))(\d|X|x)$/;
-      if (!String(idcard).match(a)) {
-        this.message.error('身份证格式错误');
+      // let {
+      //   idcard,
+      // } = this.validateForm.value;
+      // let a =
+      //   /^\d{6}((((((19|20)\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(((19|20)\d{2})(0[13578]|1[02])31)|((19|20)\d{2})02(0[1-9]|1\d|2[0-8])|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))0229))\d{3})|((((\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|((\d{2})(0[13578]|1[02])31)|((\d{2})02(0[1-9]|1\d|2[0-8]))|(([13579][26]|[2468][048]|0[048])0229))\d{2}))(\d|X|x)$/;
+      // if (!String(idcard).match(a)) {
+      //   this.message.error('身份证格式错误');
+      //   return;
+      // }
+      let a = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+       if (!String(this.validateForm.value.email).match(a)) {
+        this.message.error('邮箱格式不正确');
         return;
       }
-      let params = {
-        name,
-        mobile: this.mobile,
-        email,
-        province,
-        unitType,
-        unit,
-        idcard,
-        identity,
-        file,
-        workPhone: this.nonRequired.workPhone,
-        branch: this.nonRequired.branch,
-        job: this.nonRequired.job,
-      };
+      let params = this.validateForm.value
       this.profileSave(params);
+      
     } else {
       this.message.warning('请填写完整的信息');
       Object.values(this.validateForm.controls).forEach((control) => {
@@ -174,6 +143,18 @@ export class AccountInfoComponent implements OnInit {
   }
 
   async profileSave(params: any) {
+    if(!await this.authUser()){
+      return
+    }
+
+    this.user?.set('name', params.name);
+    this.user?.set('email', params.email);
+    this.user?.set('phone', params.email);
+    this.user?.set('province', params.province);
+    this.user?.set('companyName', params.companyName);
+    this.user?.set('departmentName', params.departmentName);
+    await this.user?.save()
+
     console.log(params);
     let query = new Parse.Query('Profile');
     query.equalTo('user', Parse.User.current()?.id);
@@ -189,22 +170,40 @@ export class AccountInfoComponent implements OnInit {
       className: 'Company',
       objectId: this.tbookSer.company,
     });
-    profile?.set('name', params.name);
-    profile?.set('email', params.email);
-    profile?.set('mobile', params.email);
-    profile?.set('province', params.province);
-    profile?.set('unitType', params.unitType);
-    profile?.set('unit', params.unit);
-    profile?.set('idcard', params.idcard);
-    profile?.set('identity', params.identity);
-    profile?.set('file', params.file);
-    profile?.set('workPhone', params.workPhone);
-    profile?.set('branch', params.branch);
-    profile?.set('job', params.job);
-    profile?.set('verify', true);
+    profile?.set('telephone', params.telephone);
+    profile?.set('companyType', params.companyType);
+    // profile?.set('idcard', params.idcard);
+    profile?.set('identity', '报送联系人');
+    profile?.set('identityFile', params.identityFile);
+    profile?.set('postName', params.postName);
     let res = await profile?.save();
-    this.authServr.profileVerify()
+    this.modal.success({
+      nzTitle: '提交成功',
+      nzContent: '',
+      nzOnOk: () => this.router.navigate(['/user/register'],{replaceUrl:true}),
+    });
   }
+  //验证手机号或邮箱是否注册
+  async authUser():Promise<boolean>{
+    let query = new Parse.Query('_User')
+    if(this.validateForm.value.phone){
+      query.equalTo('phone',this.validateForm.value.phone)
+      let r = await query.first()
+      if(r?.id){
+        this.message.warning('手机号已被注册')
+        return false
+      }
+    }else if(this.validateForm.value.email){
+      query.equalTo('email',this.validateForm.value.email)
+      let r = await query.first()
+      if(r?.id){
+        this.message.warning('邮箱已被注册')
+        return false
+      }
+    }
+    return true
+  }
+
   handleChange(info: NzUploadChangeParam): void {
     if (info.file.status !== 'uploading') {
       console.log(info.file, info.fileList);

+ 0 - 4
projects/textbook/src/modules/login/login/login.component.ts

@@ -36,10 +36,6 @@ export class LoginComponent implements OnInit{
       login:(user,authClient)=>{
         console.log(user,authClient)
         this.authServr.profileVerify()
-        // this.authServr.login(user.id).catch(err=>{
-        //   console.warn(err);
-        //   this.message.error(err?.message || '登录失败')
-        // })
       }
     });
     parseAuthing.initLoginModal();

+ 5 - 5
projects/textbook/src/modules/login/modules.routes.ts

@@ -1,7 +1,7 @@
 import { NgModule } from "@angular/core";
 import { RouterModule, Routes } from "@angular/router";
 import { LoginComponent } from './login/login.component';
-import { ResetPasswordComponent } from './reset-password/reset-password.component';
+// import { ResetPasswordComponent } from './reset-password/reset-password.component';
 import { RegisterComponent } from './register/register.component';
 import { AccountInfoComponent } from './account-info/account-info.component'
 const routes: Routes = [
@@ -13,10 +13,10 @@ const routes: Routes = [
     path:'account_info',
     component:AccountInfoComponent
   },
-  {
-    path: 'reset_password',
-    component: ResetPasswordComponent,
-  },
+  // {
+  //   path: 'reset_password',
+  //   component: ResetPasswordComponent,
+  // },
   {
     path: 'register',
     component: RegisterComponent,

+ 78 - 0
projects/textbook/src/modules/login/register/parse-authing.ts

@@ -0,0 +1,78 @@
+import Parse from "parse";
+declare var GuardFactory:any;
+/**
+ * ParseAuthing 通过Authing实现登录逻辑,再同步至Parse.User.become身份
+ */
+export class ParseAuthing{
+    authingGurad:any
+
+    /**
+     * Event
+     * @desc
+     * https://cdn.authing.co/packages/guard/doc/v5/guide/essentials/events.html
+     *  */
+    event:{[key:string]:Function|undefined} = {
+        onLoad:undefined, // Guard初始化完成,开始渲染页面
+        login:undefined, // 登陆成功回调
+        loginError:undefined, // 登陆失败
+        beforeLogin:undefined, // 用户触发登录前(返回<boolean | Promise<boolean>>用于控制本次登录是否继续)。
+        register:undefined, // 用户注册成功
+        beforeRegister:undefined, // 用户触发注册前(返回<boolean | Promise<boolean>>用于控制本次注册是否继续)。
+    }
+    constructor(options?:{
+        login?:{(user:any,authenticationClient:any):void}
+        loginError?:Function
+        beforeLogin?:Function
+        register?:Function
+        beforeRegister?:Function
+    }){
+        this.event['login'] = options?.login
+        this.event['login-error'] = options?.loginError
+        this.event['beforeLogin'] = options?.beforeLogin
+        this.event['register'] = options?.register
+        this.event['beforeRegister'] = options?.beforeRegister
+    }
+    initLoginModal(){
+        this.authingGurad = new GuardFactory.Guard({
+            host: 'https://textbook.u2-dev.hep.com.cn', // 应用的认证地址
+            appId: '6682ab96b7bd5db59d6785a0',
+            redirectUri:location.origin
+        })
+        // 使用 start 方法挂载 Guard 组件到你指定的 DOM 节点,登录成功后返回 userInfo
+        this.authingGurad.start('#authing-guard-container').then((userInfo:any) => {
+            // console.log('userInfo in start: ', userInfo)
+        })
+        // 生命周期事件绑定
+        Object.keys(this.event).forEach(key=>{
+            if(this.event[key]){
+                this.authingGurad.on(key,this.event[key])
+            }
+        })
+        // 定制特殊生命周期
+        this.authingGurad.on("login",async (user:any,authClient:any)=>{
+            // 获取Authing
+            let token = user?.token;
+            let result = await Parse.Cloud.run("authingLogin",{
+                token:token
+            })
+            // console.log(result?.sessionToken)
+            if(result?.sessionToken){
+                let user = await Parse.User.become(result?.sessionToken);
+                // console.log(user)
+            }
+
+            // 执行回调
+            if(typeof this.event["login"] == 'function'){
+                this.event["login"](user,authClient)
+            }
+        })
+    }
+    logout(){
+        this.authingGurad.logout();
+    }
+    async current(){
+        // 获取当前用户信息
+        const userInfo = await this.authingGurad.trackSession();
+        console.log(userInfo);
+    }
+}

+ 41 - 97
projects/textbook/src/modules/login/register/register.component.html

@@ -1,102 +1,46 @@
-<div class="card">
-  <div class="resgister-title">欢迎注册</div>
-  <!--手机验证码-->
-  <div class="login-card">
-    <nz-input-group nzCompact>
-      <nz-select [ngModel]="'86'">
-        <nz-option [nzLabel]="'+86'" [nzValue]="'86'"></nz-option>
-      </nz-select>
-      <input
-        type="text"
-        nz-input
-        [(ngModel)]="mobile"
-        [nzStatus]="tbookSer.authMobile(mobile) ? '' : 'error'"
-        style="width: 230px"
-        placeholder="请填写手机号"
-      />
-    </nz-input-group>
-
-    <div class="login-input-box local-code">
-      <input
-        nz-input
-        class="code-input"
-        type="text"
-        [(ngModel)]="localCodeNum"
-        maxlength="4"
-        placeholder="验证码"
-        [nzStatus]="localCodeNum.length == 4 ? '' : 'error'"
-      />
-      <!-- <app-canvas-code
-        [canvas_id]="'canvas_register'"
-        [(drawCode)]="drawCode"
-        #codelogin
-      ></app-canvas-code> -->
-    </div>
-    <div class="login-input-box">
-      <nz-input-group nzSearch [nzAddOnAfter]="suffixButton">
-        <input
-          type="text"
-          nz-input
-          placeholder="请填写短信验证码"
-          class="msg-code"
-          placeholder="请填写短信验证码"
-          [(ngModel)]="code"
-          maxlength="8"
-          [nzStatus]="code.length >= 4 ? '' : 'error'"
-        />
-      </nz-input-group>
-      <ng-template #suffixButton>
-        <button
-          nz-button
-          nzType="primary"
-          [value]="buttonText"
-          [disabled]="isCountingdown"
-          (click)="startCountdown()"
-        >
-          {{ buttonText }}
-        </button>
-      </ng-template>
-    </div>
-    <div class="login-input-box">
-      <nz-input-group>
-        <input
-          type="password"
-          nz-input
-          placeholder="请填写密码(至少8位数)"
-          [(ngModel)]="password"
-          [nzStatus]="password.length >= 8 ? '' : 'error'"
-        />
-      </nz-input-group>
-    </div>
-    <div class="login-input-box">
-      <nz-input-group>
-        <input
-          type="password"
-          nz-input
-          placeholder="请确认密码"
-          [(ngModel)]="confirmPassword"
-          [nzStatus]="confirmPassword.length >= 8 ? '' : 'error'"
-        />
-      </nz-input-group>
-    </div>
-    <div class="login-submit-btn">
-      <!-- <button
-        nz-button
-        nzType="primary"
-        nzBlock
-        [nzLoading]="isConfirm"
-        (click)="onRegister()"
+<div class="region">
+  <div class="nav">
+    <div>
+      <svg
+        width="24"
+        height="27"
+        viewBox="0 0 18 20"
+        fill="none"
+        xmlns="http://www.w3.org/2000/svg"
       >
-        注册
-      </button> -->
+        <path
+          d="M18 2H4C3.46957 2 2.96086 2.21071 2.58579 2.58579C2.21071 2.96086 2 3.46957 2 4C2 4.53043 2.21071 5.03914 2.58579 5.41421C2.96086 5.78929 3.46957 6 4 6H18V19C18 19.2652 17.8946 19.5196 17.7071 19.7071C17.5196 19.8946 17.2652 20 17 20H4C2.93913 20 1.92172 19.5786 1.17157 18.8284C0.421427 18.0783 0 17.0609 0 16V4C0 2.93913 0.421427 1.92172 1.17157 1.17157C1.92172 0.421427 2.93913 0 4 0H17C17.2652 0 17.5196 0.105357 17.7071 0.292893C17.8946 0.48043 18 0.734783 18 1V2Z"
+          fill="url(#paint0_linear_5_11023)"
+        />
+        <defs>
+          <linearGradient
+            id="paint0_linear_5_11023"
+            x1="9"
+            y1="-2.21282e-07"
+            x2="25.5"
+            y2="40"
+            gradientUnits="userSpaceOnUse"
+          >
+            <stop stop-color="#E04860" />
+            <stop offset="1" stop-color="#E99306" />
+          </linearGradient>
+        </defs>
+      </svg>
     </div>
+    登录教材遴选管理系统
   </div>
-  <div class="card-fonter">
-    <a class="fonter-left"></a>
-    <div class="fonter-right">
-      已有账号?<a style="color: #40a9ff" [routerLink]="['/user/login']"
-        >去登录</a
-      >
-    </div>
+  <div class="form">
+    <nz-tabset [nzSelectedIndex]="active" (nzSelectChange)="onChange($event)">
+      <nz-tab nzTitle="密码登录">
+        <form
+          nz-form
+          [formGroup]="validateForm"
+          class="login-form"
+          (ngSubmit)="submitForm('account')"
+        >
+          <div id="authing-guard-container"></div>
+        </form>
+      </nz-tab>
+    </nz-tabset>
   </div>
 </div>

+ 97 - 34
projects/textbook/src/modules/login/register/register.component.scss

@@ -1,43 +1,106 @@
-.card {
-  width: 320px;
-  margin: 10px auto;
-  box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
-  background-color: white;
-
-  .resgister-title {
-    text-align: center;
-    margin: 10px auto;
-    font-size: 16px;
+.region {
+  width: 348px;
+  margin: 20px auto 10px;
+  .nav {
+    text-align: left;
+    margin: 10px 0 20px;
+    font-family: PingFang SC;
+    font-size: 24px;
     font-weight: 600;
+    line-height: 33.6px;
   }
-  .login-card {
-    padding: 0 10px;
-    .login-input-box {
+  .form {
+    width: 100%;
+    // padding: 10px;
+    // box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    .title {
+      text-align: left;
+      margin-bottom: 6px;
+      font-size: 14px;
+      font-weight: 600;
+    }
+    .login-form {
+      max-width: 100%;
+      nz-form-item {
+        margin-bottom: 16px;
+      }
+    }
+    .login-form-left {
+      float: left;
+    }
+    .login-form-forgot {
+      float: right;
+    }
+    .login-form-margin {
+      margin-bottom: 8px;
+      margin-top: 80px;
+    }
+    .login-form-button {
+      width: 100%;
+      height: 46px;
+      margin: 10px 0;
+      background-color: #c6233f;
+      color: white;
+      border-radius: 4px;
+    }
+    .ipt {
+      padding: 8px;
+    }
+    .vrifly-btn {
+      height: 48px;
+      color: white;
+      border-radius: 4px;
+      margin-left: 10px;
+    }
+    .row-code {
+      width: 100%;
       display: flex;
-      justify-content: space-between;
       align-items: center;
-      margin-top: 10px;
-      .phone-input {
-        flex: 1;
-      }
-      .code-input {
-        width: 100px;
-      }
-      .msg-code {
-        width: 150px;
-      }
     }
-    .login-submit-btn {
-      margin: 50px auto 20px;
+    .checked {
+      font-size: 14px;
+      display: flex;
+      align-items: center;
+      a {
+        color: #c6233f;
+      }
     }
-  }
-  .card-fonter {
-    display: flex;
-    justify-content: space-between;
-    padding: 10px;
-    color: #6d6d6d;
-    .fonter-left {
-      color: #6d6d6d;
+    .menu {
+      width: 100%;
+      margin: 18px 0 0;
     }
   }
 }
+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-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
+  color: #c6233f;
+}
+::ng-deep .ant-tabs-ink-bar {
+  background: #c6233f;
+}
+::ng-deep .ant-tabs-tab:hover {
+  color: #e97488;
+}
+::ng-deep .ant-tabs-tab-btn:active {
+  color: #e97488;
+}
+// ::ng-deep .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover{
+//   border-color: #c6233f;
+// }
+::ng-deep .ant-checkbox-checked .ant-checkbox-inner {
+  background-color: #c6233f;
+  border-color: #c6233f;
+}
+::ng-deep .ant-checkbox-wrapper:hover .ant-checkbox-inner {
+  border-color: #c6233f;
+}

+ 169 - 107
projects/textbook/src/modules/login/register/register.component.ts

@@ -1,66 +1,189 @@
-import { Component, OnInit, ViewChild } from "@angular/core";
-import { CommonCompModule } from '../common.modules'
+import { Component, ViewChild,OnInit } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
 import { HttpClient } from "@angular/common/http";
-import { NzMessageService } from "ng-zorro-antd/message";
-import { NzModalService } from "ng-zorro-antd/modal";
-import { Router,RouterModule } from "@angular/router";
+import { Router } from '@angular/router';
 import { catchError } from "rxjs/operators";
-import { textbookServer } from "../../../services/textbook";
-import { AuthServr } from "../../../services/auth.service";
+import {
+  FormControl,
+  FormGroup,
+  NonNullableFormBuilder,
+  Validators,
+  ValidatorFn,
+  AbstractControl,
+} from '@angular/forms';
+import { CommonCompModule } from '../common.modules';
+import { textbookServer } from '../../../services/textbook';
+import { AuthServr } from '../../../services/auth.service';
+import { CaptchaComponent } from '../../../app/captcha/captcha.component';
 import Parse from "parse";
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { ParseAuthing } from './parse-authing';
 @Component({
   selector: 'app-register',
   standalone: true,
-  imports: [CommonCompModule,RouterModule],
+  imports: [ReactiveFormsModule, CommonCompModule,CaptchaComponent],
   templateUrl: './register.component.html',
   styleUrl: './register.component.scss'
 })
-export class RegisterComponent implements OnInit {
-  @ViewChild("codelogin") codelogin: any; //本地校验码绘画
+export class RegisterComponent implements OnInit{
+  @ViewChild("codelogin") codelogin: any; 
+  @ViewChild("codeloginSign") codeloginSign: any; 
 
-  company: string = "";
+  ngOnInit(){
+    let parseAuthing = new ParseAuthing({
+      // 监听事件:登陆成功后,返回用户信息
+      login:(user,authClient)=>{
+        console.log(user,authClient)
+        // this.authServr.profileVerify()
+        this.router.navigate(['/user/account_info'])
+      }
+    });
+    parseAuthing.initLoginModal();
+  }
+  code:string = '' //本地生成验证码
 
-  mobile: string = "";
-  password: string = "";
-  confirmPassword: string = "";
-  code: string = ""; //验证码
-  localCodeNum: string = ""; //用户输入验证码
-  drawCode: Array<string> = []; //本地生成验证码(短信验证码登录)
+  active:number = 0
+  validateForm: FormGroup<{
+    userName: FormControl<string>;
+    password: FormControl<string>;
+    code: FormControl<string>;
+    checked: FormControl<boolean>;
+  }> = this.fb.group({
+    userName: ['', [Validators.required]],
+    password: ['', [Validators.required]],
+    code: ['', [Validators.required]],
+    checked: [false]
+  });
+  //校验手机号
+  confirmationValidator: ValidatorFn = (control: AbstractControl): { [s: string]: boolean } => {
+    let a = /^1[3456789]\d{9}$/;
+    if (!control.value || !String(control.value).match(a)) {
+      return { required: true };
+    }
+    return {};
+  };
 
-  passwordVisible: boolean = false;
-  buttonText = "获取验证码";
-  isConfirm: boolean = false; //点击注册
+  validateFormPhone: FormGroup<{
+    phoneNumber: FormControl<string>;
+    code: FormControl<string>;
+    checkCode:FormControl<string>;
+    checked: FormControl<boolean>;
+  }> = this.fb.group({
+    phoneNumber: ['', [Validators.required, this.confirmationValidator]],
+    code: ['', [Validators.required]],
+    checkCode: ['', [Validators.required]],
+    checked: [false]
+  });
 
   constructor(
+    public tbookSer: textbookServer,
+    private fb: NonNullableFormBuilder,
+    public router: Router,
+    private authServr: AuthServr,
+    private message: NzMessageService,
     private http: HttpClient,
-    private msg: NzMessageService,
-    private modal: NzModalService,
-    private router: Router,
-    private loginServr: AuthServr,
-    public tbookSer: textbookServer
   ) {
-    this.company = this.tbookSer.company;
-    console.log(this.company);
-    console.log(this.router.url);
+    Parse?.User?.logOut()
   }
 
-  ngOnInit() {}
 
-  codeDown: boolean = false;
+  onChangeCode(e:any){
+    let { code } = e
+    this.code = code
+  }
+
+  submitForm(type:string): void {
+    console.log(this.code);
+    if(type == 'account'){//登录
+      if (this.validateForm.valid) {
+        let {userName, password, code, checked } = this.validateForm.value
+        console.log(userName, password, code);
+        if(this.code.toLowerCase() != code?.toLowerCase()){
+          this.message.warning('验证码错误')
+          return
+        }else if(!checked){
+          this.message.warning('请勾选隐私协议与服务条款')
+          return
+        }
+        // this.authServr.login(userName, password, this.tbookSer.company).then(()=>{
+        //   this.codelogin.updateDrawCode();
+        // }) .catch(err=>{
+        //   console.warn(err);
+        //   this.message.error(err?.message || '登录失败')
+        // })
+      } else {
+        this.message.warning('填写信息不正确')
+        Object.values(this.validateForm.controls).forEach((control) => {
+          if (control.invalid) {
+            control.markAsDirty();
+            control.updateValueAndValidity({ onlySelf: true });
+          }
+        });
+      }
+    }else{//手机号登录/注册
+      console.log(this.validateFormPhone.value);
+      if (this.validateFormPhone.valid) {
+        let {phoneNumber, code } = this.validateFormPhone.value
+        console.log(phoneNumber, code);
+        if(this.code.toLowerCase() != code?.toLowerCase()){
+          this.message.warning('验证码错误')
+          return
+        }
+        this.authServr.register(phoneNumber, code, this.tbookSer.company).then(()=>{
+          this.codeloginSign.updateDrawCode();
+        }).catch((err) => {
+          console.warn(err);
+          this.message.error(err?.message || '登录失败')
+        });
+      } else {
+        this.message.warning('填写信息不正确')
+        Object.values(this.validateFormPhone.controls).forEach((control) => {
+          if (control.invalid) {
+            control.markAsDirty();
+            control.updateValueAndValidity({ onlySelf: true });
+          }
+        });
+      }
+    }
+
+  }
+
+  onChange(e: any) {
+    console.log(e);
+    this.active = e.index
+    this.validateForm.reset()
+    this.validateFormPhone.reset()
+    this.codelogin.updateDrawCode();
+    this.codeloginSign.updateDrawCode();
+  }
+
+  goUrl(path: string) {
+    this.router.navigate([
+      path,
+      {
+        // type: this.currentProfile.type,
+      },
+    ]);
+  }
+
+  buttonText = "获取验证码";
   //添加倒计时开始和结束的判断
   isCountingdown = false;
+  /* 获取验证码 */
+  codeDown: boolean = false;
   startCountdown() {
     let a = /^1[3456789]\d{9}$/;
-    if (!String(this.mobile).match(a)) {
-      this.msg.error("请填写正确手机号");
+    let { phoneNumber, code } = this.validateFormPhone.value
+    console.log(phoneNumber);
+    if (!String(phoneNumber).match(a)) {
+      this.message.error("请填写正确手机号");
       return;
     }
-    let str = this.drawCode.join("");
-    if (this.localCodeNum.toLowerCase() != str.toLowerCase()) {
-      this.msg.error("验证码不正确");
+    if (code?.toLowerCase() != this.code.toLowerCase()) {
+      this.message.error("验证码不正确");
       return;
     }
-    if (this.codeDown) return;
+    if (this.codeDown || this.isCountingdown) return;
     this.codeDown = true;
     let host =
       (Parse as any).serverURL?.split("parse")?.[0] ||
@@ -68,14 +191,14 @@ export class RegisterComponent implements OnInit {
 
     this.http
       .post(host + "api/apig/message", {
-        company: this.company,
-        mobile: this.mobile,
+        company: this.tbookSer.company,
+        mobile: phoneNumber,
       })
       .pipe(
         catchError(async (e) => {
           // 显示报错
           console.log(e);
-          this.msg.create("error", e.error.mess || "验证码获取失败");
+          this.message.create("error", e.error.mess || "验证码获取失败");
           this.codeDown = false;
           this.isCountingdown = false;
           return;
@@ -83,90 +206,29 @@ export class RegisterComponent implements OnInit {
       )
       .subscribe((res: any) => {
         console.log(res);
-        this.codelogin.updateDrawCode();
         if(res){
-          this.msg.success("发送成功");
+          this.message.success("发送成功");
           this.isCountingdown = true;
           this.time();
         }
+        this.codeloginSign.updateDrawCode();
         this.codeDown = false;
       });
   }
   /* 倒计时 */
   time() {
     this.isCountingdown = true;
-    this.buttonText = `${this.loginServr.regcountdown}秒`;
+    this.buttonText = `${this.authServr.countdown}秒`;
     const timer = setInterval(() => {
-      this.loginServr.regcountdown--;
-      this.buttonText = `${this.loginServr.regcountdown}秒`;
-      if (this.loginServr.regcountdown === 0) {
+      this.authServr.countdown--;
+      this.buttonText = `${this.authServr.countdown}秒`;
+      if (this.authServr.countdown === 0) {
         clearInterval(timer);
         this.buttonText = "重新发送";
         this.isCountingdown = false;
       }
     }, 1000);
   }
-  onRegister() {
-    if (!this.tbookSer.authMobile(this.mobile)) {
-      this.msg.error("请填写正确的手机号");
-      return;
-    }
-    if (!this.code.trim()) {
-      this.msg.error("请填写短信验证码");
-      return;
-    }
-    if (this.password.trim().length < 8) {
-      this.msg.error("请填写正确的密码");
-      return;
-    }
-    if (this.password != this.confirmPassword) {
-      this.msg.error("密码不一致");
-      return;
-    }
-    if (this.isConfirm) return;
-    this.isConfirm = true
-    let host =
-      (Parse as any).serverURL?.split("parse")?.[0] ||
-      "https://server.fmode.cn/";
 
-    this.http
-      .post(host + "/api/auth/register", {
-        company: this.company,
-        mobile: this.mobile,
-        code: this.code,
-        password: this.password,
-      })
-      .pipe(
-        catchError(async (e) => {
-          // 显示报错
-          console.log(e);
-          this.msg.create("error", "注册失败:" + e.error?.mess);
-          this.isConfirm = false
-          return;
-        })
-      )
-      .subscribe((res: any) => {
-        console.log(res);
-        this.isConfirm = false
-        if(res){
-          this.modal.success({
-            nzTitle: "注册成功",
-            nzContent: "您已成功注册开发者账号,直接登录?",
-            nzCancelText: "取消",
-            nzOnOk: () => {
-              // this.loginServr
-              // .login(this.mobile, this.password, this.company)
-              // .then((data) => {
-              //   console.log(data);
-              //   this.router.navigate(["developer/app-list"])
-              // })
-              // .catch((err) => {
-              //   console.log(err);
-              //   this.msg.error(err.message);
-              // });
-            }
-          });
-        }
-      });
-  }
+
 }

+ 13 - 12
projects/textbook/src/modules/nav-admin/collection-edit/collection-edit.component.html

@@ -4,7 +4,7 @@
     <nz-breadcrumb-item><a>报送合集</a></nz-breadcrumb-item>
   </nz-breadcrumb>
   <nz-page-header-title
-    >{{eduCollection?.get('name')}}
+    >{{ eduCollection?.get("name") }}
     <br />
     <div class="subtitle">
       在合集中,你可以创建多个教材推荐报送流程,指定用户为流程管理员,邀请作者、联系人、评审员注册系统,创建并提交教材,由流程管理员提交至合集管理员完成报送
@@ -19,7 +19,7 @@
         nzType="primary"
         (click)="onCreateProcess()"
       >
-      发起报送流程
+        发起报送流程
       </button>
     </nz-space>
   </nz-page-header-extra>
@@ -36,16 +36,17 @@
       <app-create-collection [isEdit]="true"></app-create-collection>
       }
     </nz-tab>
-    <nz-tab nzTitle="全部教材"> @if (active == 2) {
+    <nz-tab nzTitle="全部教材">
+      @if (active == 2) {
       <comp-table-list
-      #list
-      [schema]="EduTextbook"
-      *ngIf="className && fieldsArray"
-      [className]="className"
-      [fieldsArray]="fieldsArray"
-      [queryParams]="queryParams"
-    ></comp-table-list>
-      
-    } </nz-tab>
+        #list
+        [schema]="EduTextbook"
+        *ngIf="className && fieldsArray"
+        [className]="className"
+        [fieldsArray]="fieldsArray"
+        [queryParams]="queryParams"
+      ></comp-table-list>
+      }
+    </nz-tab>
   </nz-tabset>
 </div>

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

@@ -7,6 +7,7 @@ import { PageRoleComponent } from './page-role/page-role.component';
 import { PageTextbookComponent } from './page-textbook/page-textbook.component';
 import { PageUserComponent } from './page-user/page-user.component';
 import { ProcessCreateComponent } from './process-create/process-create.component';
+import { UserEditComponent } from './user-edit/user-edit.component';
 const routes: Routes = [
   {
     path: '',
@@ -40,6 +41,10 @@ const routes: Routes = [
         path: 'user', //用户列表
         component: PageUserComponent,
       },
+      {
+        path: 'user/edit', //用户管理&编辑
+        component: UserEditComponent,
+      },
       {
         path: 'role', //列表
         component: PageRoleComponent,

+ 7 - 9
projects/textbook/src/modules/nav-admin/page-textbook/page-textbook.component.ts

@@ -35,19 +35,17 @@ export class PageTextbookComponent implements OnInit {
     this.queryParams = {
       where: {
         isDeleted: { $ne: true },
-        discard: this.discard ? { $eq: true } : { $ne: true },
-        render: this.render ? { $eq: true } : { $ne: true },
+        render: true,
+        discard:{$ne: true},
       },
     };
   }
 
   ngOnInit(): void {
-    console.log(this.discard);
-    console.log(this.render);
-    this.queryParams.where = {
-      isDeleted: { $ne: true },
-      discard: this.discard ? { $eq: true } : { $ne: true },
-      render: this.render ? { $eq: true } : { $ne: true },
-    };
+    // this.queryParams.where = {
+    //   isDeleted: { $ne: true },
+    //   discard: this.discard ? { $eq: true } : { $ne: true },
+    //   render: this.render ? { $eq: true } : { $ne: true },
+    // };
   }
 }

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

@@ -1,8 +1,41 @@
-<comp-table-list
+<nz-page-header>
+  <nz-page-header-title
+    >用户列表
+    <br />
+    <div class="subtitle">
+      当前用户池的所有用户,在这里你可以对用户进行统一管理。
+    </div>
+  </nz-page-header-title>
+  <nz-page-header-extra>
+    <nz-space>
+      <button
+        style="background: #3e49b3; border: 1px #3e49b3"
+        *nzSpaceItem
+        nz-button
+        nzType="primary"
+        (click)="createUser()"
+      >
+      创建用户
+      </button>
+    </nz-space>
+  </nz-page-header-extra>
+</nz-page-header>
+  
+
+<!-- <comp-table-list
   #list
   [schema]="_User"
   *ngIf="className && fieldsArray"
   [className]="className"
   [fieldsArray]="fieldsArray"
   [queryParams]="queryParams"
-></comp-table-list>
+></comp-table-list> -->
+
+<comp-table-list
+  #list
+  [schema]="ProfileList"
+  *ngIf="className && fieldsArray"
+  [className]="className"
+  [fieldsArray]="fieldsArray"
+  [queryParams]="queryParams"
+></comp-table-list>

+ 30 - 0
projects/textbook/src/modules/nav-admin/page-user/page-user.component.scss

@@ -0,0 +1,30 @@
+.subtitle{
+  margin-right: 12px;
+  color: #00000073;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.5715;
+  // overflow: hidden;
+  // white-space: nowrap;
+  // text-overflow: ellipsis;
+}
+.edit-content{
+  margin: 0 0 20px;
+  padding: 0 24px;
+  height: calc(100vh - 250px);
+}
+::ng-deep .ant-page-header-heading-title{
+  white-space: normal;
+}
+::ng-deep .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn{
+  color: #c6233f;
+}
+::ng-deep .ant-tabs-ink-bar{
+  background: #c6233f;
+}
+::ng-deep .ant-tabs-tab:hover{
+  color: #e97488;
+}
+::ng-deep .ant-tabs-tab-btn:active{
+  color: #e97488;
+}

+ 40 - 27
projects/textbook/src/modules/nav-admin/page-user/page-user.component.ts

@@ -1,52 +1,65 @@
 import { Component, OnInit, ViewChild } from '@angular/core';
 import { ActivatedRoute, RouterOutlet } from '@angular/router';
 import { CompTableListComponent } from '../../../app/comp-table/comp-table-list/comp-table-list.component';
-import _User from '../../../schemas/_User';
-// import { TranslateService } from '@ngx-translate/core';
-import * as Parse from "parse";
+// import _User from '../../../schemas/_User';
+import * as Parse from 'parse';
 import { CommonModule } from '@angular/common';
-
-
+import { NzPageHeaderModule } from 'ng-zorro-antd/page-header';
+import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb';
+import { NzSpaceModule } from 'ng-zorro-antd/space';
+import { CommonCompModule } from '../../../services/common.modules';
+import { Profile } from '../../../schemas/Profile-list';
 @Component({
   selector: 'app-page-user',
   templateUrl: './page-user.component.html',
   styleUrls: ['./page-user.component.scss'],
-  imports: [CommonModule,RouterOutlet,CompTableListComponent],
+  imports: [
+    CommonModule,
+    RouterOutlet,
+    NzSpaceModule,
+    NzPageHeaderModule,
+    CompTableListComponent,
+    NzBreadCrumbModule,
+    CommonCompModule,
+  ],
   standalone: true,
 })
-export class PageUserComponent  implements OnInit {
-  @ViewChild(CompTableListComponent) list:CompTableListComponent|undefined
+export class PageUserComponent implements OnInit {
+  @ViewChild(CompTableListComponent) list: CompTableListComponent | undefined;
 
-  _User = _User
-  user:Parse.User|undefined
-  className:string|undefined
-  queryParams:any|undefined
-  fieldsArray:Array<any>|undefined
+  // _User = _User;
+  ProfileList = Profile;
+  user: Parse.User | undefined;
+  className: string | undefined;
+  queryParams: any | undefined;
+  fieldsArray: Array<any> | undefined;
 
   constructor(
     private route: ActivatedRoute,
-    private activeRoute: ActivatedRoute,
-    // private translate:TranslateService,
+    private activeRoute: ActivatedRoute // private translate:TranslateService,
   ) {
     this.user = Parse.User.current();
-    this.className = this._User.className
-    this.fieldsArray = this._User.fieldsArray
-    this.queryParams = {where:{
-      // user:this.user?.toPointer(),
-      isDeleted:{$ne:true},
-    }}
+    this.className = this.ProfileList.className;
+    this.fieldsArray = this.ProfileList.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}
+      let isDeleted = params.get('isDeleted');
+      if (isDeleted) {
+        this.queryParams.where['isDeleted'] = { $eq: true };
+      } else {
+        this.queryParams.where['isDeleted'] = { $ne: true };
       }
-      this.list?.ngOnInit()
+      this.list?.ngOnInit();
     });
   }
 
+  createUser() {}
 }

+ 176 - 0
projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.html

@@ -0,0 +1,176 @@
+<nz-page-header>
+  <nz-breadcrumb nz-page-header-breadcrumb>
+    <nz-breadcrumb-item>用户管理</nz-breadcrumb-item>
+    <nz-breadcrumb-item>用户列表</nz-breadcrumb-item>
+    <nz-breadcrumb-item><a>用户详情</a></nz-breadcrumb-item>
+  </nz-breadcrumb>
+  <nz-page-header-content>
+    <div class="user-header">
+      <div class="header-left">
+        <nz-avatar
+          nzSize="large"
+          nzIcon="user"
+          nzSrc="//zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
+        ></nz-avatar>
+        <div class="name-data">
+          <div style="display: flex; align-items: center">
+            <div class="name">{{ user?.get("mobile") }}</div>
+            <div class="status">
+              <nz-tag [nzBordered]="false" [nzColor]="'geekblue'"
+                >待认证</nz-tag
+              >
+            </div>
+          </div>
+          <div class="id">ID:{{ user?.id }}</div>
+        </div>
+      </div>
+      <div class="header-right">
+        <div style="margin-right: 10px">
+          <button
+            nz-button
+            nz-dropdown
+            [nzDropdownMenu]="menu"
+            [nzPlacement]="'bottomLeft'"
+          >
+            更多
+            <span nz-icon nzType="down" nzTheme="outline"></span>
+          </button>
+          <nz-dropdown-menu #menu="nzDropdownMenu">
+            <ul nz-menu>
+              <li nz-menu-item>
+                <button nz-button nzType="link" style="color: #231c1f">
+                  <span
+                    nz-icon
+                    nzType="safety-certificate"
+                    nzTheme="outline"
+                  ></span
+                  >通过认证
+                </button>
+              </li>
+              <li nz-menu-item>
+                <button nz-button nzType="link" style="color: #231c1f">
+                  <span nz-icon nzType="stop" nzTheme="outline"></span>禁用账号
+                </button>
+              </li>
+              <li nz-menu-item>
+                <button nz-button nzType="link" style="color: #231c1f">
+                  <span nz-icon nzType="delete" nzTheme="outline"></span
+                  >删除账号
+                </button>
+              </li>
+            </ul>
+          </nz-dropdown-menu>
+        </div>
+        <button
+          style="background: #3e49b3; border: 1px #3e49b3"
+          nz-button
+          nzType="primary"
+        >
+          修改密码
+        </button>
+      </div>
+    </div>
+  </nz-page-header-content>
+</nz-page-header>
+<div class="edit-content">
+  <nz-tabset>
+    <nz-tab nzTitle="用户信息">
+      <div class="title">账号信息</div>
+      <div class="fill-template">
+        <div nz-row>
+          <div nz-col nzSpan="8">
+            <div class="lable">创建时间</div>
+            <div class="value">
+              {{ user.createdAt | date : "yyyy-MM-dd HH:MM:ss" }}
+            </div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">最后登录时间</div>
+            <div class="value">
+              {{ user.updatedAt | date : "yyyy-MM-dd HH:MM:ss" }}
+            </div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">最后登录IP</div>
+            <div class="value">119.119.01.02</div>
+          </div>
+        </div>
+        <div nz-row>
+          <div nz-col nzSpan="8">
+            <div class="lable">登录次数</div>
+            <div class="value">123</div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">用户来源</div>
+            <div class="value">注册</div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">用户类型</div>
+            <div class="value">单位联系人/作者/主编</div>
+          </div>
+        </div>
+        <div nz-row>
+          <div nz-col nzSpan="8">
+            <div class="lable">注册浏览器</div>
+            <div class="value">360</div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">注册设备</div>
+            <div class="value">Mozilla/5.0 (Windows NT 10.0; Win64; x64)</div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">认证文件</div>
+            <div class="value">
+              <a href="">查看文件</a>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="title">手机号</div>
+      <div class="fill-template">
+        <div nz-row>
+          <div nz-col nzSpan="8">
+            <div class="lable">创建时间</div>
+            <div class="value">
+              {{ user?.get("mobile") }}
+            </div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">邮箱</div>
+            <div class="value">
+              {{ user?.get("email") || "暂无" }}
+            </div>
+          </div>
+          <div nz-col nzSpan="8">
+            <div class="lable">姓名</div>
+            <div class="value">cb</div>
+          </div>
+        </div>
+      </div>
+      <div class="title">扩展信息</div>
+      <div class="fill-template">
+        <div nz-row>
+          <div nz-col nzSpan="8">
+            <div class="lable">学校</div>
+            <div class="value">美国加里敦大学</div>
+          </div>
+        </div>
+      </div>
+      <div class="title">原始JSON数据</div>
+      <div class="fill-template">
+        <div nz-row>
+          <div nz-col nzSpan="24">
+            <textarea
+              rows="4"
+              disabled="true"
+              nz-input
+              [(ngModel)]="inputValue"
+            ></textarea>
+          </div>
+        </div>
+      </div>
+      <div class="title">所属组织</div>
+      <div class="fill-template"></div>
+    </nz-tab>
+  </nz-tabset>
+</div>

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

@@ -0,0 +1,106 @@
+.subtitle {
+  margin-right: 12px;
+  color: #00000073;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 1.5715;
+  // overflow: hidden;
+  // white-space: nowrap;
+  // text-overflow: ellipsis;
+}
+.user-header {
+  display: flex;
+  justify-content: space-between;
+  .header-left {
+    display: flex;
+    align-items: center;
+    .name-data {
+      display: flex;
+      flex-direction: column;
+      .name {
+        font-family: PingFang SC;
+        font-size: 24px;
+        font-weight: 600;
+        line-height: 32px;
+        text-align: left;
+      }
+      .status{
+        margin-left: 4px;
+      }
+      .id {
+        font-family: PingFang SC;
+        font-size: 14px;
+        font-weight: 400;
+        line-height: 20px;
+        text-align: left;
+        color: #231c1f99;
+      }
+    }
+  }
+  .header-right {
+    display: flex;
+    align-items: center;
+    .form-button {
+      width: 80px;
+      height: 40px;
+      margin: 20px 0;
+      color: white !important;
+      background-color: #3E49B3!important;
+      border-radius: 4px;
+      font-family: PingFang SC;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 20px;
+      text-align: center;
+    }
+  }
+}
+.edit-content {
+  margin: 0 0 20px;
+  padding: 0 24px;
+  height: calc(100vh - 250px);
+  min-width: 1000px;
+  .title {
+    //styleName: 字体/标题-中-Medium;
+    font-family: PingFang SC;
+    font-size: 20px;
+    font-weight: 500;
+    line-height: 32px;
+    text-align: left;
+    margin: 28px 0 10px;
+  }
+  .fill-template{
+    font-family: PingFang SC;
+    font-size: 14px;
+    .lable{
+      color: #24272299;
+      margin-bottom: 6px;
+    }
+    .value{
+      width: 80%;
+      padding: 4px 8px;
+      background: #f9f9f9;
+      border-radius: 4px;
+      color: #242722;
+      margin-bottom: 10px;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+  }
+}
+::ng-deep .ant-page-header-heading-title {
+  white-space: normal;
+}
+::ng-deep .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
+  color: #c6233f;
+}
+::ng-deep .ant-tabs-ink-bar {
+  background: #c6233f;
+}
+::ng-deep .ant-tabs-tab:hover {
+  color: #e97488;
+}
+::ng-deep .ant-tabs-tab-btn:active {
+  color: #e97488;
+}

+ 24 - 0
projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.spec.ts

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

+ 55 - 0
projects/textbook/src/modules/nav-admin/user-edit/user-edit.component.ts

@@ -0,0 +1,55 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { NzSpaceModule } from 'ng-zorro-antd/space';
+import { CommonCompModule } from '../../../services/common.modules';
+import { NzTabsModule } from 'ng-zorro-antd/tabs';
+import { ProcessComponent } from '../components/process/process.component';
+import { CreateCollectionComponent } from '../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';
+import { NzTagModule } from 'ng-zorro-antd/tag';
+@Component({
+  selector: 'app-user-edit',
+  templateUrl: './user-edit.component.html',
+  styleUrls: ['./user-edit.component.scss'],
+  imports: [
+    CommonModule,
+    NzSpaceModule,
+    CommonCompModule,
+    NzTabsModule,
+    ProcessComponent,
+    CreateCollectionComponent,
+    NzAvatarModule,
+    NzDropDownModule,
+    NzPopoverModule,
+    NzTagModule
+  ],
+  standalone: true,
+})
+export class UserEditComponent  implements OnInit {
+  visible: boolean = false;
+  inputValue:string = ''
+  
+  user:Parse.Object |any
+  profile:Parse.Object |any
+
+  constructor(
+    private activeRoute:ActivatedRoute,
+    private router: Router,
+  ) { }
+
+  ngOnInit() {
+    this.activeRoute.paramMap.subscribe(async (params) => {
+      let id = params.get('id');
+      console.log(id);
+      if (id) {
+        let query = new Parse.Query("_User")
+        this.user = await query.get(id)
+      }
+    })
+  }
+
+}

+ 2 - 1
projects/textbook/src/modules/textbook/page-home/header/header.component.html

@@ -39,7 +39,8 @@
         <!--登录按钮-->
         <div class="login-box">
             <!-- <button class="login-button" (click)="loginModal.authServ.isModalShow = true">登录</button> -->
-            <button class="login-button" routerLink="/user/login">登录</button>
+            <!-- <button class="login-button" routerLink="/user/login">登录</button> -->
+            <button class="login-button" routerLink="/user/register">注册申请</button>
         </div>
     </div>
     <!-- 已登录 -->

+ 75 - 0
projects/textbook/src/schemas/Profile-list.ts

@@ -0,0 +1,75 @@
+import { MatDialog } from "@angular/material/dialog";
+import { Router } from "@angular/router";
+import Parse from "parse";
+import { ParseSchema } from "./func-parse";
+
+export const Profile:ParseSchema = {
+    title:"报送人",
+    className:"Profile",
+    emptyImg:"/img/webhook-empty.png",
+    include:["user"],
+    buttons:[
+        {
+            name:"编辑",
+            place:"item",
+            show:(options:{object:Parse.Object})=>{
+                if(location?.pathname=='/nav-admin/manage/user'){
+                    return true
+                }
+                return false
+            },
+            handle:(options:{dialog:MatDialog,object:Parse.Object,router?:Router})=>{
+                options.router?.navigate(['/nav-admin/manage/user/edit',{id:options.object?.get('user').id}])
+            }
+        },
+        {
+            name:"通过账号",
+            place:"item",
+            show:(options:{object:Parse.Object})=>{
+                if(location?.pathname=='/nav-admin/manage/user'){
+                    return true
+                }
+                return false
+            },
+            handle:(options:{dialog:MatDialog,object:Parse.Object,router?:Router})=>{
+               
+            }
+        },
+        {
+            name:"禁用账号",
+            place:"item",
+            show:(options:{object:Parse.Object})=>{
+                if(location?.pathname=='/nav-admin/manage/user'){
+                    return true
+                }
+                return false
+            },
+            handle:(options:{dialog:MatDialog,object:Parse.Object,router?:Router})=>{
+                
+            }
+        },
+        {
+            name:"删除账号",
+            place:"item",
+            show:(options:{object:Parse.Object})=>{
+                if(location?.pathname=='/nav-admin/manage/user'){
+                    return true
+                }
+                return false
+            },
+            handle:(options:{dialog:MatDialog,object:Parse.Object,router?:Router})=>{
+                
+            }
+        }
+    ],
+    fieldsArray:[
+        {key:"user",name:"用户",type:"Pointer",targetClass:"_User",isHeader:true,showName:"${mobile}"},
+        {key:"user",name:"姓名",type:"Pointer",targetClass:"_User",isHeader:true,showName:"${name}"},
+        {key:"user",name:"手机号",type:"Pointer",targetClass:"_User",isHeader:true,showName:"${phone}"},
+        {key:"user",name:"邮箱",type:"Pointer",targetClass:"_User",isHeader:true,showName:"${email}"},
+        // {key:"name",name:"姓名",type:"String",isHeader:true},
+        // {key:"mobile",name:"手机号",type:"String",isHeader:true},
+        // {key:"emal",name:"邮箱",type:"String",isHeader:true},
+        {key:"createdAt",name:"创建时间",type:"Date",isHeader:true,},
+    ]
+}

+ 3 - 3
projects/textbook/src/schemas/Profile.ts

@@ -24,8 +24,8 @@ export const Profile:ParseSchema = {
         }
     ],
     fieldsArray:[
-        {key:"name",name:"姓名",type:"String",isHeader:true},
-        {key:"mobile",name:"手机号",type:"String",isHeader:true},
-        {key:"emal",name:"邮箱",type:"String",isHeader:true},
+        {key:"user",name:"姓名",type:"Pointer",targetClass:"_User",isHeader:true,showName:"${name}"},
+        {key:"user",name:"手机号",type:"Pointer",targetClass:"_User",isHeader:true,showName:"${phone}"},
+        {key:"user",name:"邮箱",type:"Pointer",targetClass:"_User",isHeader:true,showName:"${email}"},
     ]
 }

+ 14 - 14
projects/textbook/src/services/auth.service.ts

@@ -141,21 +141,21 @@ export class AuthServr {
     query.notEqualTo('isDeleted',true)
     let r = await query.first()
     if(r?.id){
-      if(r.get('identity')){
-        this.router.navigate([this.roterPath[r.get('identity')]])
-        let profile = r.toJSON()
-        localStorage.setItem(
-          'profile',
-          JSON.stringify(profile)
-        );
-        this.textbook.profile = profile;
+      let user = Parse.User.current()
+      if(user?.get('accountState') == '已认证'){
+        if(r.get('identity')){
+          this.router.navigate([this.roterPath[r.get('identity')]])
+          let profile = r.toJSON()
+          localStorage.setItem(
+            'profile',
+            JSON.stringify(profile)
+          );
+          this.textbook.profile = profile;
+        }
+        this.message.warning('已认证暂无身份')
+        return
       }
-      else if(!r.get('verify')){
-        this.message.warning('请完善个人信息')
-        this.router.navigate(['/user/account_info'])
-      }
-    }else{
-      this.router.navigate(['/user/account_info'])
+      this.message.error('账号未认证完成或已禁用')
     }
   }
 

+ 3 - 2
server/db/index.js

@@ -7,7 +7,7 @@ import {_Role} from "./schemas/_Role"
 import {_Session} from "./schemas/_Session"
 import {Submitted} from "./schemas/Submitted"
 import {EduProcess} from "./schemas/EduProcess"
-
+import { Department } from "./schemas/Department"
 export const EduSchemas = [
     _User,
     _Role,
@@ -17,6 +17,7 @@ export const EduSchemas = [
     Profile,
     EduCollection,
     Submitted,
-    EduProcess
+    EduProcess,
+    Department
 ]
 module.exports.EduSchemas = EduSchemas

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

@@ -0,0 +1,58 @@
+export const Department = {
+  "className": "Department",
+  "fields": {
+    "parent": {//上级
+      "type": "Pointer",
+      "targetClass": "Department",
+      "required": false
+    },
+    "name": {//单位、组织名称
+      "type": "String",
+      "required": false
+    },
+    "code": {//标识码
+      "type": "String",
+      "required": false
+    },
+    "branch": {//主管部门
+      "type": "String",
+      "required": false
+    },
+    "address": {//所在地
+      "type": "String",
+      "required": false
+    },
+    "type": {//办学层次
+      "type": "String",
+      "required": false
+    },
+  },
+  "classLevelPermissions": {
+    "find": {
+      "*": true
+    },
+    "get": {
+      "*": true
+    },
+    "count": {
+      "*": true
+    },
+    "create": {
+      "*": true
+    },
+    "update": {
+      "*": true
+    },
+    "delete": {
+      "*": true
+    },
+    "addField": {
+      "*": true
+    },
+    "protectedFields": {
+      "*": []
+    }
+  }
+
+}
+module.exports.Department = Department

+ 16 - 19
server/db/schemas/Profile.js

@@ -6,31 +6,25 @@ export const Profile = {
       "targetClass": "_User",
       "required": false
     },
-    "name": {
-      "type": "String"
-    },
-    "mobile": {
-      "type": "String"
-    },
-    "email": {
-      "type": "String"
-    },
-    "workPhone": {
+    // "name": {
+    //   "type": "String"
+    // },
+    // "phone": {
+    //   "type": "String"
+    // },
+    // "email": {
+    //   "type": "String"
+    // },
+    "telephone": {
       "type": "String"
     },
     "province": {
       "type": "String"
     },
-    "unitType": {
+    "companyType": {
       "type": "String"
     },
-    "unit": {
-      "type": "String"
-    },
-    "branch": {
-      "type": "String"
-    },
-    "job": {
+    "postName": {
       "type": "String"
     },
     "idcard": {
@@ -39,7 +33,10 @@ export const Profile = {
     "identity": {
       "type": "String"
     },
-    "file": {
+    "identityFile": {
+      "type": "String"
+    },
+    "majorSubject": {
       "type": "String"
     }
   },