身份激活页面-可选编辑表单.md 15 KB

身份激活页面 - 可选编辑表单(最终版)

功能概述

身份激活页面提供智能表单功能:

  • ✅ 所有字段自动从企微获取并预填充
  • ✅ 所有字段均可由用户修改
  • ✅ 只有真实姓名为必填项
  • ✅ 部门、角色、手机号为可选项

实现日期

2025年11月3日

字段说明

字段名 类型 必填 自动获取 说明
真实姓名 文本输入 ✅ 是 ✅ 是 从企微自动填充,用户可修改
所属部门 下拉选择 ❌ 否 ✅ 是 使用 getDepartment() 获取初始值
职位角色 下拉选择 ❌ 否 ✅ 是 使用 getUserRole() 获取初始值
手机号 电话输入 ❌ 否 ✅ 是 从企微自动填充,用户可修改
员工ID 只读显示 - ✅ 是 企微 userid,不可编辑

界面效果

┌─────────────────────────────────────┐
│  用户身份确认                       │
│  请确认您的身份信息                 │
├─────────────────────────────────────┤
│         [头像]                      │
│      张设计师                       │
│         组员                        │
├─────────────────────────────────────┤
│  真实姓名 *                         │
│  [张设计师        ] ← 自动填充     │
│                                     │
│  所属部门                           │
│  [▼ 设计部        ] ← 自动选中     │
│  • 设计部                           │
│  • 建模部                           │
│  • 渲染部...                        │
│                                     │
│  职位角色                           │
│  [▼ 组员          ] ← 自动选中     │
│  • 组员                             │
│  • 组长                             │
│  • 主管...                          │
│                                     │
│  手机号                             │
│  [13800138000     ] ← 自动填充     │
│                                     │
│  员工ID                             │
│  [test_user_001   ] ← 只读         │
├─────────────────────────────────────┤
│       [✓ 确认身份]                  │
└─────────────────────────────────────┘

核心实现

1. 数据模型(TypeScript)

表单数据(第52-58行):

formData = {
  realname: '',      // 真实姓名(必填)
  department: '',    // 所属部门(可选)
  roleName: '',      // 职位角色(可选)
  mobile: ''         // 手机号(可选)
};

部门列表(第60-69行):

departmentList = [
  '设计部',
  '建模部',
  '渲染部',
  '软装部',
  '后期部',
  '综合部',
  '管理部'
];

角色列表(第71-77行):

roleList = [
  '组员',
  '组长',
  '主管',
  '经理'
];

2. 自动填充逻辑

使用原有方法获取初始值(第163-177行):

private populateFormData(): void {
  // 姓名:从Profile或企微获取
  this.formData.realname = this.profile?.get('realname') || 
                           this.profile?.get('name') || 
                           this.userInfo?.name || '';
  
  // 手机号:从Profile或企微获取
  this.formData.mobile = this.profile?.get('mobile') || 
                         this.userInfo?.mobile || '';
  
  // 部门:使用原有方法获取(智能处理多种格式)
  this.formData.department = this.getDepartment();
  
  // 角色:使用原有方法获取
  this.formData.roleName = this.getUserRole();
  
  console.log('📝 自动填充表单数据:', this.formData);
}

3. 表单验证

只验证必填字段:姓名(第222-228行):

// 表单验证
if (!this.formData.realname?.trim()) {
  alert('请填写您的真实姓名');
  return;
}

// 部门和角色为可选,不做必填验证

4. 数据保存

保存用户选择的所有信息(第248-278行):

// 设置激活标记并保存表单数据
if (this.profile) {
  this.profile.set('isActivated', true);
  this.profile.set('activatedAt', new Date());
  
  // 保存用户编辑的信息
  this.profile.set('realname', this.formData.realname);
  this.profile.set('name', this.formData.realname); // 同时更新name字段
  
  // 保存部门和角色(可选)
  if (this.formData.department) {
    this.profile.set('department', this.formData.department);
    this.profile.set('departmentName', this.formData.department);
  }
  if (this.formData.roleName) {
    this.profile.set('roleName', this.formData.roleName);
  }
  
  // 保存手机号(可选)
  if (this.formData.mobile) {
    this.profile.set('mobile', this.formData.mobile);
  }
  
  await this.profile.save();
}

5. HTML 表单结构

真实姓名(第47-58行):

<div class="form-group">
  <label class="form-label">
    <span class="label-text">真实姓名</span>
    <span class="required">*</span>
  </label>
  <input 
    type="text" 
    class="form-input" 
    [(ngModel)]="formData.realname"
    placeholder="请输入您的真实姓名"
    required />
</div>

所属部门(第60-72行):

<div class="form-group">
  <label class="form-label">
    <span class="label-text">所属部门</span>
  </label>
  <select 
    class="form-select" 
    [(ngModel)]="formData.department">
    <option value="">请选择部门(可选)</option>
    @for (dept of departmentList; track dept) {
      <option [value]="dept">{{ dept }}</option>
    }
  </select>
</div>

职位角色(第74-86行):

<div class="form-group">
  <label class="form-label">
    <span class="label-text">职位角色</span>
  </label>
  <select 
    class="form-select" 
    [(ngModel)]="formData.roleName">
    <option value="">请选择角色(可选)</option>
    @for (role of roleList; track role) {
      <option [value]="role">{{ role }}</option>
    }
  </select>
</div>

工作流程

初始化流程

用户访问激活页面
    ↓
initAuth() - 初始化企微认证
    ↓
获取企微用户信息
    ├─ userid
    ├─ name
    ├─ mobile
    ├─ department (多种格式)
    └─ avatar
    ↓
checkActivationStatus() - 查询Profile
    ↓
populateFormData() - 自动填充表单
    ├─ formData.realname ← name
    ├─ formData.mobile ← mobile
    ├─ formData.department ← getDepartment() 🔑
    └─ formData.roleName ← getUserRole() 🔑
    ↓
显示表单(所有字段已填充)

用户操作流程

表单已自动填充
    ↓
用户查看信息
    ↓
可选操作:
    ├─ 修改姓名 ✏️
    ├─ 重新选择部门 ✏️
    ├─ 重新选择角色 ✏️
    ├─ 修改手机号 ✏️
    └─ 或保持默认值 ✅
    ↓
点击"确认身份"
    ↓
验证姓名不为空
    ↓
保存所有字段到Profile
    ↓
激活成功

关键方法复用

getDepartment() 方法

位置:第368-393行

功能:智能处理企微部门信息的多种格式

getDepartment(): string {
  const dept = this.userInfo?.department;
  
  // 处理数组格式
  if (Array.isArray(dept) && dept.length > 0) {
    return `部门${dept[0]}`;
  }
  
  // 处理对象格式
  if (dept && typeof dept === 'object' && !Array.isArray(dept)) {
    return dept.name || dept.departmentName || '未知部门';
  }
  
  // 处理字符串格式
  if (typeof dept === 'string') {
    return dept;
  }
  
  // 从 Profile 获取部门信息
  const profileDept = this.profile?.get('department') || 
                     this.profile?.get('departmentName');
  if (profileDept) {
    return typeof profileDept === 'string' ? 
           profileDept : (profileDept.name || '未知部门');
  }
  
  return '未分配部门';
}

优势

  • ✅ 兼容企微的多种部门数据格式
  • ✅ 多级降级逻辑,确保总能获取到值
  • ✅ 优先使用Profile中的部门信息(用户之前保存的)

getUserRole() 方法

位置:第361-363行

功能:获取用户角色

getUserRole(): string {
  return this.profile?.get('roleName') || '员工';
}

优势

  • ✅ 优先从Profile获取(用户之前保存的角色)
  • ✅ 默认值为"员工"
  • ✅ 简洁高效

使用场景

场景1:企微信息完整准确

企微返回:
  name: "王刚"
  department: "设计部"
  roleName: "组员"
  mobile: "13800138000"
    ↓
表单自动填充:
  真实姓名: [王刚]
  所属部门: [设计部] (已选中)
  职位角色: [组员] (已选中)
  手机号: [13800138000]
    ↓
用户点击"确认身份"
    ↓
✅ 激活成功(无需修改)

场景2:部门信息为数组格式

企微返回:
  department: [1, 2]
    ↓
getDepartment() 处理:
  return "部门1"
    ↓
表单显示:
  所属部门: [部门1] (已选中)
    ↓
用户可以:
  • 保持"部门1"
  • 或选择"设计部"等其他部门
    ↓
✅ 保存用户选择

场景3:用户修改信息

企微自动填充:
  真实姓名: [测试员工]
  所属部门: [部门1]
  职位角色: [员工]
    ↓
用户修改:
  真实姓名: [李设计师] ✏️
  所属部门: [设计部] ✏️
  职位角色: [组员] ✏️
    ↓
点击"确认身份"
    ↓
✅ 保存修改后的信息

场景4:部门角色留空

用户操作:
  真实姓名: [王刚] ✅ (必填)
  所属部门: [请选择部门(可选)] (未选择)
  职位角色: [请选择角色(可选)] (未选择)
  手机号: [] (未填写)
    ↓
点击"确认身份"
    ↓
✅ 激活成功
    ↓
Profile保存:
  realname: "王刚"
  department: null (未设置)
  roleName: null (未设置)
  mobile: null (未设置)

数据库保存结果

示例1:所有字段填写完整

{
  // 必填字段
  realname: "王刚",
  name: "王刚",
  
  // 可选字段(用户填写)
  department: "设计部",
  departmentName: "设计部",
  roleName: "组员",
  mobile: "13800138000",
  
  // 企微字段(自动同步)
  userid: "WangGang001",
  avatar: "https://...",
  
  // 系统字段
  isActivated: true,
  activatedAt: "2025-11-03T12:00:00.000Z"
}

示例2:仅填写必填字段

{
  // 必填字段
  realname: "李设计师",
  name: "李设计师",
  
  // 可选字段(未填写,不保存)
  // department: undefined
  // departmentName: undefined
  // roleName: undefined
  // mobile: undefined
  
  // 企微字段
  userid: "test_user_001",
  
  // 系统字段
  isActivated: true,
  activatedAt: "2025-11-03T12:00:00.000Z"
}

核心优势

✅ 智能自动化

  • 自动获取:所有字段从企微自动获取
  • 智能处理:使用原有的 getDepartment()getUserRole() 方法
  • 多格式兼容:支持企微的多种数据格式

✅ 灵活可选

  • 必填最小化:只有姓名为必填
  • 可选字段:部门、角色、手机号均可选
  • 自由修改:所有字段均可由用户修改

✅ 用户体验

  • 零输入启动:字段已预填充,大部分情况一键确认
  • 清晰提示:"请选择部门(可选)"明确告知可选
  • 即时反馈:头像下方的角色实时更新

✅ 数据质量

  • 智能降级:多级数据源降级,确保有值
  • 条件保存:只保存用户填写的可选字段
  • 字段同步:同时更新 name/realnamedepartment/departmentName

样式说明

表单样式已在 profile-activation.component.scss 中定义:

输入框样式

.form-input {
  width: 100%;
  padding: 12px 16px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  
  &:focus {
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
  }
}

下拉框样式

.form-select {
  appearance: none;
  background-image: url("data:image/svg+xml,..."); // 自定义箭头
  padding-right: 36px;
  cursor: pointer;
}

只读字段样式

.form-group.readonly {
  .readonly-value {
    padding: 12px 16px;
    background: #f5f5f5;
    border-radius: 8px;
    color: #666;
  }
}

测试建议

功能测试

  • 访问激活页面,所有字段已自动填充
  • 修改姓名后保存成功
  • 选择不同部门后保存成功
  • 选择不同角色后保存成功
  • 修改手机号后保存成功
  • 清空姓名后无法提交(必填验证)
  • 部门和角色留空仍可提交(可选)
  • 头像下方角色实时更新

数据测试

  • 企微返回数组格式部门,正确处理
  • 企微返回对象格式部门,正确处理
  • 企微返回字符串格式部门,正确处理
  • Profile有部门数据,优先使用Profile数据
  • 可选字段未填写时,不保存到数据库

UI测试

  • 下拉框显示自定义箭头
  • 输入框聚焦时紫色高亮
  • 必填字段有红色 * 标记
  • 可选字段有"(可选)"提示
  • 只读字段(员工ID)灰色背景
  • 移动端布局正常(16px字体)

后续优化建议

1. 从数据库加载部门列表

async loadDepartments() {
  const Parse = FmodeParse.with('nova');
  const query = new Parse.Query('Department');
  const departments = await query.find();
  this.departmentList = departments.map(d => d.get('name'));
}

2. 添加部门搜索功能

<input type="text" 
       placeholder="搜索部门" 
       (input)="filterDepartments($event)" />

3. 角色权限关联

// 根据选择的角色显示不同的权限说明
getRoleDescription(role: string): string {
  const descriptions = {
    '组员': '普通设计师,负责具体项目执行',
    '组长': '小组负责人,管理团队成员',
    '主管': '部门主管,负责部门整体运营',
    '经理': '部门经理,负责战略决策'
  };
  return descriptions[role] || '';
}

4. 手机号格式验证

validateMobile(): boolean {
  if (!this.formData.mobile) return true; // 可选字段
  return /^1[3-9]\d{9}$/.test(this.formData.mobile);
}

5. 表单防抖保存

// 用户修改后自动保存草稿
private saveDraft = debounce(() => {
  localStorage.setItem('activation_draft', JSON.stringify(this.formData));
}, 500);

总结

最终方案实现了"智能自动 + 灵活可选"的完美平衡:

  • 🚀 高效:所有字段自动获取,大部分情况一键确认
  • 🔄 复用:使用原有的 getDepartment()getUserRole() 方法
  • ✏️ 灵活:所有字段均可修改,满足个性化需求
  • 📋 可选:只有姓名必填,其他字段可选
  • 🎯 准确:智能处理多种数据格式,确保数据质量
  • 🎨 友好:清晰的视觉提示和即时反馈

这种设计既保证了激活流程的便捷性,又给用户提供了充分的自主权,是企业内部系统的最佳实践。