# 员工身份激活与问卷整合方案 ## 一、需求概述 ### 1.1 业务目标 基于**现有企微身份认证**功能,增加**员工问卷填写**和**问卷查看**功能: - **员工端**:首次登录完成企微认证后,引导填写问卷 - **管理端**:组长/管理员可以查看员工完整信息和问卷答案 > **注意**:企业微信身份激活功能已存在,本方案复用现有认证流程,仅增加问卷引导和查看功能。 ### 1.2 核心场景 #### 场景1:员工首次登录(企微端) ``` 员工打开企微应用 ↓ [企微自动认证] ← 现有功能 ├─ WxworkAuth.authenticate() ├─ 获取企微用户信息 └─ 自动登录/注册 ↓ [检查问卷状态] ← 新增功能 ↓ 未填写 [引导填写问卷弹窗] ← 新增功能 ├─ 提示:完善能力画像 ├─ [稍后填写] [立即填写] └─ 点击"立即填写" ↓ [员工问卷页面] ← 已完成 ├─ 填写17道调研题目 ├─ 逐题自动保存 └─ 提交完成 ↓ [问卷完成提示] ← 新增功能 ├─ 显示能力画像 └─ 进入工作台 ``` #### 场景2:组长查看设计师详情 ``` 组长工作台 ↓ 点击设计师卡片/列表 ↓ [设计师详情弹窗] ├─ Tab1: 基本信息 ├─ Tab2: 负载概况 ├─ Tab3: 负载详细日历 └─ Tab4: 能力问卷 ← 新增 ├─ 显示问卷填写状态 ├─ 查看完整问卷答案 └─ 查看能力画像摘要 ``` --- ## 二、数据模型设计 ### 2.1 Profile 表扩展(员工表) 需要在现有 Profile 表中增加以下字段: | 字段名 | 类型 | 说明 | 示例值 | |--------|------|------|--------| | **isActivated** | Boolean | 是否已激活 | true | | **activatedAt** | Date | 激活时间 | 2025-10-30T10:00:00Z | | **surveyCompleted** | Boolean | 是否完成问卷 | true | | **surveyCompletedAt** | Date | 问卷完成时间 | 2025-10-30T10:30:00Z | | **surveyLogId** | String | 关联的SurveyLog ID | "survey_xxx" | > 这些字段用于快速判断员工状态,避免每次都查询 SurveyLog 表 ### 2.2 SurveyLog 表(复用现有) 已实现,用于存储问卷答案: | 字段名 | 类型 | 说明 | |--------|------|------| | profile | Pointer → Profile | 关联员工 | | type | String | 'survey-profile' | | data | Object | 问卷答案 | | isCompleted | Boolean | 是否完成 | | completedAt | Date | 完成时间 | --- ## 三、核心功能设计 > **注意**:企微身份认证已完成,本方案仅需增加问卷引导和查看功能 ### 3.1 工作台问卷引导(修改现有组件) **组件路径**:根据项目架构,可能在以下位置之一 - `src/app/pages/designer/dashboard/dashboard.ts` (设计师工作台) - `src/modules/wxwork/pages/home/home.component.ts` (企微端首页) #### 3.1.1 增加问卷状态检查 **在工作台组件中增加**: ```typescript export class Dashboard implements OnInit { private wxAuth: WxworkAuth | null = null; private currentProfile: FmodeObject | null = null; // 新增:问卷相关属性 showSurveyGuide: boolean = false; surveyCompleted: boolean = false; async ngOnInit() { // 1. 企微认证(现有功能) await this.authenticateAndLoadData(); // 2. 检查问卷状态(新增功能) await this.checkSurveyStatus(); // 3. 显示问卷引导(新增功能) if (!this.surveyCompleted) { this.showSurveyGuide = true; } } /** * 认证并加载数据(现有方法) */ private async authenticateAndLoadData(): Promise { if (!this.wxAuth) return; try { // 执行企微认证 await this.wxAuth.authenticate(); // 获取当前员工 this.currentProfile = await this.wxAuth.currentProfile(); console.log('当前员工:', this.currentProfile); // 加载工作台数据 await this.loadDashboardData(); } catch (error) { console.error('认证或加载数据失败:', error); // 使用模拟数据降级 this.loadMockData(); } } /** * 检查问卷状态(新增方法) */ async checkSurveyStatus() { if (!this.currentProfile?.id) return; try { const Parse = (window as any).Parse; const query = new Parse.Query('SurveyLog'); query.equalTo('profile', this.currentProfile.toPointer()); query.equalTo('type', 'survey-profile'); query.equalTo('isCompleted', true); const surveyLog = await query.first(); this.surveyCompleted = !!surveyLog; console.log('问卷状态:', this.surveyCompleted ? '已填写' : '未填写'); } catch (err) { console.error('检查问卷状态失败:', err); } } /** * 跳转到问卷页面(新增方法) */ goToSurvey() { const cid = this.wxAuth?.config.cid || ''; window.location.href = `/wxwork/${cid}/survey/profile`; } /** * 关闭问卷引导(新增方法) */ closeSurveyGuide() { this.showSurveyGuide = false; // 可以记录到localStorage,避免每次都弹出 localStorage.setItem('survey_guide_closed', 'true'); } } ``` #### 3.1.2 问卷引导弹窗(新增HTML) **在工作台HTML中增加**: ```html @if (showSurveyGuide) {

完善您的能力画像

为了更精准地为您匹配合适的项目,
请花8-10分钟完成能力调研问卷。

  • 智能匹配项目,发挥您的专长
  • 避免不匹配项目导致返工
  • 合理分配工作量,提升效率

预计用时:8-10分钟

} ``` #### 3.1.3 问卷引导样式(新增SCSS) ```scss // 问卷引导弹窗样式 .survey-guide-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 9999; animation: fadeIn 0.3s ease; } .survey-guide-modal { background: white; border-radius: 16px; padding: 32px; max-width: 480px; width: 90%; position: relative; animation: slideUp 0.3s ease; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); .close-btn { position: absolute; top: 16px; right: 16px; width: 32px; height: 32px; border: none; background: #f3f4f6; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s; &:hover { background: #e5e7eb; } ion-icon { width: 20px; height: 20px; } } .guide-content { text-align: center; .guide-icon { width: 80px; height: 80px; margin: 0 auto 24px; background: linear-gradient(135deg, #3B82F6, #60A5FA); border-radius: 50%; display: flex; align-items: center; justify-content: center; ion-icon { width: 40px; height: 40px; color: white; } } h2 { margin: 0 0 16px; font-size: 24px; font-weight: 700; color: #1F2937; } .guide-description { margin: 0 0 24px; font-size: 15px; line-height: 1.6; color: #6B7280; } .guide-benefits { list-style: none; padding: 0; margin: 0 0 32px; text-align: left; li { display: flex; align-items: center; gap: 12px; padding: 12px; margin-bottom: 8px; background: #EFF6FF; border-radius: 8px; ion-icon { width: 20px; height: 20px; color: #3B82F6; flex-shrink: 0; } span { font-size: 14px; color: #1F2937; } } } .guide-actions { display: flex; gap: 12px; margin-bottom: 16px; button { flex: 1; padding: 14px 24px; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 8px; ion-icon { width: 18px; height: 18px; } } .btn-secondary { background: #F3F4F6; color: #1F2937; &:hover { background: #E5E7EB; } } .btn-primary { background: linear-gradient(135deg, #3B82F6, #60A5FA); color: white; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); &:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(59, 130, 246, 0.5); } } } .guide-hint { display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 13px; color: #6B7280; margin: 0; ion-icon { width: 16px; height: 16px; } } } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } ``` --- ### 3.2 员工详情弹窗扩展(修改现有) **组件路径**:`src/app/pages/team-leader/components/designer-detail-modal.component.ts` #### 3.2.1 增加"能力问卷"Tab **HTML 模板修改**: ```html ``` #### 3.2.2 TypeScript 逻辑 ```typescript export class DesignerDetailModalComponent implements OnInit { // 现有属性... // 新增:问卷相关属性 currentTab: string = 'info'; surveyLoading: boolean = false; surveyData: any = null; surveyCompletedAt: Date | null = null; showDetails: boolean = false; async ngOnInit() { // 加载基本信息 await this.loadDesignerInfo(); // 检查问卷状态 await this.checkSurveyStatus(); } /** * 检查员工问卷状态 */ async checkSurveyStatus() { if (!this.designer?.id) return; try { const query = new Parse.Query('SurveyLog'); query.equalTo('profile', this.designer.toPointer()); query.equalTo('type', 'survey-profile'); query.equalTo('isCompleted', true); query.descending('updatedAt'); const surveyLog = await query.first(); if (surveyLog) { this.designer.surveyCompleted = true; this.designer.surveyCompletedAt = surveyLog.get('completedAt'); } else { this.designer.surveyCompleted = false; } } catch (err) { console.error('检查问卷状态失败:', err); } } /** * 加载问卷数据 */ async loadSurvey() { if (!this.designer?.surveyCompleted || this.surveyData) return; this.surveyLoading = true; try { const query = new Parse.Query('SurveyLog'); query.equalTo('profile', this.designer.toPointer()); query.equalTo('type', 'survey-profile'); query.equalTo('isCompleted', true); query.descending('updatedAt'); const surveyLog = await query.first(); if (surveyLog) { this.surveyData = surveyLog.get('data') || {}; this.surveyCompletedAt = surveyLog.get('completedAt'); } } catch (err) { console.error('加载问卷数据失败:', err); window?.fmode?.alert('加载问卷数据失败,请重试'); } finally { this.surveyLoading = false; } } /** * 切换详细答案显示 */ toggleDetails() { this.showDetails = !this.showDetails; } /** * 格式化数组答案 */ formatArrayAnswer(arr: string[] | string): string { if (Array.isArray(arr)) { return arr.join('、'); } return arr || '未填写'; } /** * 发送填写提醒 */ async sendSurveyReminder() { if (!this.designer?.id) return; try { // TODO: 通过企微发送消息提醒填写问卷 const surveyUrl = `${window.location.origin}/wxwork/${this.cid}/profile/activation`; // 调用企微API发送消息 // await this.wxwork.sendMessage({ // touser: this.designer.get('userid'), // msgtype: 'text', // text: { // content: `请完成员工能力问卷填写:${surveyUrl}` // } // }); window?.fmode?.alert('已发送填写提醒'); } catch (err) { console.error('发送提醒失败:', err); window?.fmode?.alert('发送提醒失败,请重试'); } } /** * 发送更新提醒 */ async sendSurveyUpdateReminder() { if (!this.designer?.id) return; try { const surveyUrl = `${window.location.origin}/wxwork/${this.cid}/survey/profile`; window?.fmode?.alert('已通知员工更新问卷'); } catch (err) { console.error('发送更新提醒失败:', err); window?.fmode?.alert('发送更新提醒失败,请重试'); } } } ``` --- ## 四、完整实施流程 > **简化说明**:由于企微身份认证已完成,本方案只需增加问卷引导和查看功能 ### 4.1 Phase 1:工作台增加问卷引导(4小时) #### 步骤1:修改工作台组件 TypeScript - **文件**:`src/app/pages/designer/dashboard/dashboard.ts` - **修改内容**: 1. 增加问卷状态属性 2. 增加 `checkSurveyStatus()` 方法 3. 增加 `goToSurvey()` 方法 4. 增加 `closeSurveyGuide()` 方法 5. 在 `ngOnInit()` 中调用问卷检查 #### 步骤2:增加问卷引导弹窗 HTML - **文件**:`src/app/pages/designer/dashboard/dashboard.html` - **修改内容**: 1. 添加问卷引导弹窗HTML代码 2. 添加条件显示逻辑 `@if (showSurveyGuide)` #### 步骤3:添加弹窗样式 SCSS - **文件**:`src/app/pages/designer/dashboard/dashboard.scss` - **修改内容**: 1. 添加 `.survey-guide-overlay` 样式 2. 添加 `.survey-guide-modal` 样式 3. 添加动画效果 --- ### 4.2 Phase 2:员工详情弹窗扩展(6小时) #### 步骤1:修改组件 HTML - **文件**:参考图二所示的设计师详情弹窗组件 - **修改内容**: 1. 增加"能力问卷"Tab 2. 实现问卷数据展示界面 3. 添加折叠/展开功能 4. 添加未填写状态展示 #### 步骤2:实现数据加载逻辑 - **修改内容**: 1. 增加 `checkSurveyStatus()` 方法 2. 增加 `loadSurvey()` 方法 3. 增加 `formatArrayAnswer()` 方法 4. 增加 `toggleDetails()` 方法 #### 步骤3:添加提醒功能 - **修改内容**: 1. 增加 `sendSurveyReminder()` 方法(未填写时) 2. 增加 `sendSurveyUpdateReminder()` 方法(已填写时) 3. 集成企微消息发送API --- ### 4.3 Phase 3:测试和优化(2小时) #### 测试项: 1. 首次登录是否弹出问卷引导 2. 点击"立即填写"跳转正确 3. 点击"稍后填写"关闭弹窗 4. 已填写问卷不再弹出引导 5. 组长查看员工详情正常显示问卷 6. 未填写问卷显示提示和发送提醒按钮 7. 问卷数据格式化正确显示 --- ### 4.4 Phase 4:路由配置(已完成) 员工问卷路由已配置: ```typescript // src/app/app.routes.ts { path: 'survey/profile', loadComponent: () => import('../modules/profile/pages/profile-survey/profile-survey.component').then(m => m.ProfileSurveyComponent), title: '员工技能调研' } ``` 无需额外配置路由。 --- ## 五、UI/UX 设计规范 ### 5.1 员工激活页面 **主题色**: - 主色:蓝色 #3B82F6 - 强调色:橙色 #F59E0B - 成功色:绿色 #10B981 **布局**: - 最大宽度:640px - 表单卡片:白色背景,圆角12px,阴影 - 按钮:渐变背景,高度48px ### 5.2 员工详情弹窗问卷Tab **布局结构**: ``` 能力画像摘要卡片(固定显示) ↓ 详细问卷答案(可折叠) ↓ 问卷填写时间 + 操作按钮 ``` **样式规范**: - 标签(Tag):蓝色系(风格)、绿色系(空间)、紫色系(优势) - 徽章(Badge):项目难度使用主色徽章 - 折叠按钮:右侧显示箭头图标 --- ## 六、数据流程图 ### 6.1 员工激活流程 ```mermaid sequenceDiagram participant E as 员工端 participant A as 激活组件 participant P as Profile表 participant S as SurveyLog表 E->>A: 打开企微应用 A->>A: 检查 isActivated alt 未激活 A->>E: 显示身份填写页面 E->>A: 填写姓名、手机号等 A->>P: 保存基本信息 A->>E: 显示问卷页面 E->>A: 填写17道题目 A->>S: 保存问卷答案 A->>P: 更新 isActivated=true, surveyCompleted=true A->>E: 显示激活成功页面 else 已激活 A->>E: 进入工作台 end ``` ### 6.2 组长查看问卷流程 ```mermaid sequenceDiagram participant L as 组长端 participant D as 详情弹窗 participant S as SurveyLog表 L->>D: 点击设计师卡片 D->>D: 加载基本信息 L->>D: 点击"能力问卷"Tab D->>S: 查询 SurveyLog (type='survey-profile') alt 已填写 S->>D: 返回问卷数据 D->>L: 显示能力画像 + 详细答案 else 未填写 D->>L: 显示"未填写"状态 L->>D: 点击"发送填写提醒" D-->>员工: 发送企微消息 end ``` --- ## 七、测试用例 ### 7.1 员工激活测试 | 用例ID | 测试场景 | 操作步骤 | 预期结果 | |--------|----------|----------|----------| | ACT-01 | 首次登录激活 | 1. 新员工首次登录
2. 自动跳转激活页面 | 显示身份填写表单 | | ACT-02 | 身份信息验证 | 1. 不填姓名点击下一步
2. 填写错误手机号 | 显示验证错误提示 | | ACT-03 | 头像上传 | 1. 点击头像上传
2. 选择图片 | 预览上传的头像 | | ACT-04 | 问卷填写 | 1. 完成身份填写
2. 进入问卷页面
3. 填写17道题 | 逐题保存,最后提交成功 | | ACT-05 | 激活成功 | 1. 完成所有步骤
2. 查看成功页面 | 显示欢迎信息和能力画像 | | ACT-06 | 重复访问 | 1. 已激活员工再次登录 | 直接进入工作台,不显示激活页面 | ### 7.2 组长查看问卷测试 | 用例ID | 测试场景 | 操作步骤 | 预期结果 | |--------|----------|----------|----------| | SUR-01 | 查看已填写问卷 | 1. 组长打开设计师详情
2. 点击"能力问卷"Tab | 显示完整问卷数据和能力画像 | | SUR-02 | 查看未填写状态 | 1. 查看未填写问卷的员工 | 显示"未填写"提示和发送提醒按钮 | | SUR-03 | 发送填写提醒 | 1. 点击"发送填写提醒"按钮 | 成功发送企微消息给员工 | | SUR-04 | 折叠详细答案 | 1. 点击"详细问卷答案"
2. 再次点击 | 答案区域展开/折叠 | | SUR-05 | 问卷数据格式 | 1. 查看各类题型答案 | 多选题用顿号分隔,文本题完整显示 | --- ## 八、实施时间表 > **简化后的时间表**:基于现有功能,只需增加问卷引导和查看 | 阶段 | 任务 | 预计工时 | 负责人 | |------|------|----------|--------| | Phase 1 | 工作台增加问卷引导弹窗 | 4h | 前端开发 | | Phase 2 | 员工详情弹窗增加问卷Tab | 6h | 前端开发 | | Phase 3 | 测试和优化 | 2h | 前端开发 | | **总计** | | **12小时** | | **对比原方案**: - ✅ 节省14小时(无需开发激活组件) - ✅ 复用现有企微认证流程 - ✅ 复用现有员工问卷组件 - ✅ 仅需增加UI引导和数据展示 --- ## 九、技术要点 ### 9.1 员工激活状态检查 ```typescript // 检查员工激活状态 async checkActivationStatus(): Promise { const profile = await this.wxAuth.currentProfile(); return { isActivated: profile.get('isActivated') || false, surveyCompleted: profile.get('surveyCompleted') || false, needsActivation: !profile.get('isActivated'), needsSurvey: profile.get('isActivated') && !profile.get('surveyCompleted') }; } ``` ### 9.2 问卷数据查询优化 ```typescript // 批量查询多个员工的问卷状态(用于列表展示) async batchLoadSurveyStatus(profiles: FmodeObject[]): Promise> { const profileIds = profiles.map(p => p.id); const query = new Parse.Query('SurveyLog'); query.containedIn('profile', profileIds.map(id => ({ __type: 'Pointer', className: 'Profile', objectId: id }))); query.equalTo('type', 'survey-profile'); query.equalTo('isCompleted', true); query.select('profile'); const results = await query.find(); const statusMap = new Map(); results.forEach(log => { const profileId = log.get('profile')?.id; if (profileId) { statusMap.set(profileId, true); } }); return statusMap; } ``` ### 9.3 Profile表更新逻辑 ```typescript // 激活完成后更新Profile async completeActivation(profile: FmodeObject, surveyLogId: string) { profile.set('isActivated', true); profile.set('activatedAt', new Date()); profile.set('surveyCompleted', true); profile.set('surveyCompletedAt', new Date()); profile.set('surveyLogId', surveyLogId); await profile.save(); } ``` --- ## 十、后续优化方向 ### 10.1 智能订单匹配 基于问卷数据自动匹配最适合的设计师: - 风格匹配度算法 - 负载平衡算法 - 紧急订单优先级算法 ### 10.2 团队能力分析 在管理后台增加团队能力分析页面: - 风格分布饼图 - 技能矩阵雷达图 - 承接能力统计 ### 10.3 问卷版本管理 支持问卷题目迭代: - 记录问卷版本号 - 对比不同版本答案变化 - 提醒员工更新旧版本问卷 ### 10.4 数据导出 支持导出员工能力数据: - Excel格式导出 - PDF格式能力报告 - 团队能力分析报告 --- ## 十一、附录 ### 附录A:API接口清单 | 接口路径 | 方法 | 说明 | |---------|------|------| | `/api/profile/activate` | POST | 激活员工账号 | | `/api/profile/check-status` | GET | 检查激活状态 | | `/api/survey/save` | POST | 保存问卷答案 | | `/api/survey/get` | GET | 获取问卷数据 | | `/api/survey/send-reminder` | POST | 发送填写提醒 | ### 附录B:数据库表结构 #### Profile 表字段(新增) ```sql ALTER TABLE Profile ADD COLUMN isActivated BOOLEAN DEFAULT FALSE; ALTER TABLE Profile ADD COLUMN activatedAt TIMESTAMP NULL; ALTER TABLE Profile ADD COLUMN surveyCompleted BOOLEAN DEFAULT FALSE; ALTER TABLE Profile ADD COLUMN surveyCompletedAt TIMESTAMP NULL; ALTER TABLE Profile ADD COLUMN surveyLogId VARCHAR(255) NULL; ``` ### 附录C:企微消息模板 #### 填写提醒消息 ```json { "msgtype": "text", "text": { "content": "【员工能力调研】\n\n您好,请完成员工能力问卷填写,这将帮助我们更好地为您匹配合适的项目。\n\n点击链接填写:[链接]\n\n预计用时:8-10分钟" } } ``` #### 更新提醒消息 ```json { "msgtype": "text", "text": { "content": "【问卷更新提醒】\n\n您好,为了更准确地匹配项目,建议您更新一下能力问卷信息。\n\n点击链接更新:[链接]" } } ``` --- **文档版本**:v1.0 **创建时间**:2025-10-31 **最后更新**:2025-10-31 **状态**:待评审