# 企业微信身份激活与问卷整合调试指南 ## 📋 目录 1. [功能概述](#功能概述) 2. [调试方法总览](#调试方法总览) 3. [方法1:使用测试工具页面(推荐)](#方法1使用测试工具页面推荐) 4. [方法2:浏览器Console调试](#方法2浏览器console调试) 5. [方法3:真实企微环境测试](#方法3真实企微环境测试) 6. [常见问题排查](#常见问题排查) 7. [数据库Schema说明](#数据库schema说明) --- ## 功能概述 ### 完整流程 ``` 员工扫码进入企微应用 ↓ WxworkAuth.authenticateAndLogin() ↓ 身份激活/Profile同步 ↓ 跳转到设计师工作台 ↓ 检查 Profile.surveyCompleted ↓ 未完成 → 显示问卷引导弹窗 → 跳转问卷页面 ↓ 已完成 → 正常使用工作台 ``` ### 涉及的核心代码文件 1. **身份激活** - `src/app/pages/designer/dashboard/dashboard.ts` - 工作台认证逻辑 - `fmode-ng/core` 的 `WxworkAuth` - 企微SDK 2. **问卷功能** - `src/modules/profile/pages/profile-survey/profile-survey.component.ts` - 问卷组件 - `src/app/pages/designer/dashboard/dashboard.ts` - 问卷引导逻辑 3. **组长查看** - `src/app/pages/team-leader/dashboard/dashboard.ts` - 组长端查看问卷 --- ## 调试方法总览 | 方法 | 适用场景 | 优点 | 缺点 | |------|---------|------|------| | **测试工具页面** | 本地开发调试 | 可视化、直观、易操作 | 需要编译运行 | | **Console调试** | 快速验证 | 最快速、无需额外代码 | 需要手动输入命令 | | **真实企微环境** | 最终验证 | 最真实 | 需要配置外网域名 | --- ## 方法1:使用测试工具页面(推荐) ### 1.1 启动测试工具 **步骤1:启动开发服务器** ```bash ng serve ``` **步骤2:访问测试页面** ``` 浏览器打开: http://localhost:4200/test-wxwork-activation/test ``` 其中 `test` 是公司ID(cid),可以替换为: - `test` - 测试公司 - `demo` - 演示公司 - 或您的真实公司ID ### 1.2 测试工具功能 测试页面提供以下功能: #### 📊 显示测试步骤进度 ``` ✓ 1. 初始化企微认证 ✓ 2. 执行身份激活 ✓ 3. 检查问卷状态 ✓ 4. 跳转到相应页面 ``` #### 📝 实时执行日志 ``` 10:30:15 获取公司ID: test 10:30:16 初始化企微认证... 10:30:17 ✅ 企微认证初始化成功 10:30:18 执行身份激活... 10:30:19 ✅ 身份激活成功: 王刚 10:30:20 角色: 组员 10:30:21 检查问卷状态... 10:30:22 ⚠️ 问卷未完成 10:30:23 测试完成! ``` #### 👤 员工信息展示 ``` 员工ID: woAs2qCQAAPjkaSBZg3GVdXjIG3vxAOg 姓名: 王刚 角色: 组员 问卷状态: ❌ 未完成 ``` #### 🔧 测试操作按钮 1. **开始测试** - 执行完整的激活+问卷检查流程 2. **重置问卷状态** - 将问卷状态改为未完成(用于重复测试) 3. **前往问卷页面** - 跳转到问卷填写页面 4. **前往工作台** - 跳转到设计师工作台 ### 1.3 测试场景示例 #### 场景1:测试首次激活流程 ``` 1. 访问测试页面 2. 点击"开始测试" 3. 等待测试完成 4. 查看员工信息,确认问卷状态为"未完成" 5. 点击"前往工作台" 6. 验证是否显示问卷引导弹窗 ``` #### 场景2:测试问卷填写流程 ``` 1. 在测试页面点击"前往问卷页面" 2. 填写21个问题 3. 提交问卷 4. 返回测试页面 5. 点击"开始测试"刷新状态 6. 确认问卷状态变为"已完成" ``` #### 场景3:测试重复激活 ``` 1. 完成问卷后,点击"重置问卷状态" 2. 点击"前往工作台" 3. 验证是否再次显示问卷引导弹窗 ``` --- ## 方法2:浏览器Console调试 ### 2.1 重置问卷状态 打开任意页面,按 `F12` 打开Console,粘贴以下代码: ```javascript // 重置问卷状态函数 async function resetSurvey() { // 动态加载Parse SDK const Parse = (await import('https://api.fmode.cn/parse/sdk.js')).default; Parse.initialize('nova'); Parse.serverURL = 'https://api.fmode.cn/parse'; // 获取当前Profile ID const profileId = localStorage.getItem('Parse/ProfileId'); if (!profileId) { console.error('❌ 未找到Profile ID,请先登录'); return; } // 查询并更新Profile const Profile = Parse.Object.extend('Profile'); const query = new Parse.Query(Profile); try { const profile = await query.get(profileId); console.log('当前问卷状态:', profile.get('surveyCompleted')); // 重置为未完成 profile.set('surveyCompleted', false); profile.unset('surveyCompletedAt'); profile.unset('surveyLogId'); await profile.save(); console.log('✅ 问卷状态已重置为未完成'); console.log('🔄 刷新页面以查看效果'); // 自动刷新 setTimeout(() => location.reload(), 1000); } catch (error) { console.error('❌ 重置失败:', error); } } // 执行重置 resetSurvey(); ``` ### 2.2 查看问卷状态 ```javascript async function checkSurveyStatus() { const Parse = (await import('https://api.fmode.cn/parse/sdk.js')).default; Parse.initialize('nova'); Parse.serverURL = 'https://api.fmode.cn/parse'; const profileId = localStorage.getItem('Parse/ProfileId'); if (!profileId) { console.error('❌ 未找到Profile ID'); return; } const Profile = Parse.Object.extend('Profile'); const query = new Parse.Query(Profile); try { const profile = await query.get(profileId); console.log('==== 员工信息 ===='); console.log('员工ID:', profile.id); console.log('姓名:', profile.get('realname') || profile.get('name')); console.log('角色:', profile.get('roleName')); console.log('问卷状态:', profile.get('surveyCompleted') ? '✅ 已完成' : '❌ 未完成'); if (profile.get('surveyCompletedAt')) { console.log('完成时间:', profile.get('surveyCompletedAt')); } // 如果已完成,查询问卷答案 if (profile.get('surveyCompleted')) { const SurveyLog = Parse.Object.extend('SurveyLog'); const surveyQuery = new Parse.Query(SurveyLog); surveyQuery.equalTo('profile', profile.toPointer()); surveyQuery.equalTo('type', 'survey-profile'); surveyQuery.descending('createdAt'); surveyQuery.limit(1); const survey = await surveyQuery.first(); if (survey) { console.log('==== 问卷答案 ===='); const answers = survey.get('answers') || []; answers.forEach((answer, index) => { console.log(`Q${index + 1}: ${answer.question}`); console.log(`A${index + 1}:`, answer.answer); console.log('---'); }); } } } catch (error) { console.error('❌ 查询失败:', error); } } checkSurveyStatus(); ``` ### 2.3 手动触发问卷引导 如果您在工作台页面,可以手动触发问卷引导弹窗: ```javascript // 在设计师工作台页面的Console执行 // 注意:这个方法需要页面已经加载完成 // 方法1:直接修改组件状态(仅演示用) // 实际需要通过Angular的机制访问组件实例 // 方法2:直接跳转到问卷页面 const cid = location.pathname.split('/')[2] || 'test'; location.href = `/wxwork/${cid}/survey/profile`; ``` --- ## 方法3:真实企微环境测试 ### 3.1 环境准备 **1. 使用内网穿透工具** 由于企微需要访问公网域名,需要使用内网穿透: ```bash # 安装 ngrok brew install ngrok # macOS # 或从 https://ngrok.com 下载 # 启动穿透 ngrok http 4200 ``` 会得到一个公网URL,例如: ``` https://abc123.ngrok.io ``` **2. 配置企业微信应用** 登录企业微信管理后台 (https://work.weixin.qq.com): 1. 进入"应用管理" → 找到您的CRM应用 2. 设置"应用主页": ``` https://abc123.ngrok.io/wxwork/你的公司ID ``` 3. 设置"可信域名": ``` abc123.ngrok.io ``` 4. 保存配置 ### 3.2 扫码测试 **步骤1:在手机企微中打开应用** ``` 打开企业微信 → 工作台 → 找到CRM应用 → 点击进入 ``` **步骤2:观察激活流程** 在浏览器开发者工具 Network 标签中观察: ``` 1. GET /wxwork/:cid/designer/dashboard → 加载工作台页面 2. POST /parse/functions/wxworkAuth → 执行企微认证 3. GET /parse/classes/Profile?where=... → 查询Profile 4. GET /parse/classes/Profile/:id → 检查问卷状态 ``` **步骤3:验证问卷引导** - 如果 `Profile.surveyCompleted` 为 `false`,应该显示问卷引导弹窗 - 点击"立即填写",应该跳转到 `/wxwork/:cid/survey/profile` - 填写完问卷后,返回工作台,不应再显示引导 ### 3.3 真机调试技巧 **在企微内使用vConsole** 在页面HTML的 `
` 中临时添加: ```html ``` 这样在手机上可以查看Console日志。 --- ## 常见问题排查 ### Q1: 测试页面显示"企微认证初始化失败" **原因**: `WxworkAuth` 初始化失败 **解决方法**: 1. 检查 `cid` 参数是否正确 2. 确认 `fmode-ng/core` 已正确安装 3. 查看Console是否有详细错误信息 ```bash # 重新安装依赖 npm install fmode-ng@latest ``` ### Q2: 显示"未能获取Profile信息" **原因**: - 数据库中没有对应的Profile记录 - 企微认证失败 - 权限问题 **解决方法**: 1. 使用测试模式(cid设为`test`或`demo`) 2. 检查数据库中是否有对应的Profile记录 3. 确认当前用户有查询权限 ```javascript // Console中手动创建测试Profile async function createTestProfile() { const Parse = (await import('https://api.fmode.cn/parse/sdk.js')).default; Parse.initialize('nova'); Parse.serverURL = 'https://api.fmode.cn/parse'; const Profile = Parse.Object.extend('Profile'); const profile = new Profile(); profile.set('name', '测试员工'); profile.set('realname', '王刚'); profile.set('roleName', '组员'); profile.set('company', { __type: 'Pointer', className: 'Company', objectId: 'test_company_001' }); profile.set('surveyCompleted', false); const saved = await profile.save(); console.log('✅ 测试Profile已创建:', saved.id); localStorage.setItem('Parse/ProfileId', saved.id); return saved; } createTestProfile(); ``` ### Q3: 工作台不显示问卷引导弹窗 **原因**: - `surveyCompleted` 字段为 `true` - 检查逻辑未执行 - 弹窗被CSS隐藏 **解决方法**: ```javascript // 1. 检查状态 checkSurveyStatus(); // 2. 如果状态错误,重置 resetSurvey(); // 3. 检查DOM document.querySelector('.survey-guide-overlay'); // 应该返回弹窗元素,如果为null说明未渲染 ``` ### Q4: 组长端看不到问卷答案 **原因**: - 员工名字不匹配 - SurveyLog记录不存在 - 数据加载失败 **解决方法**: ```javascript // 在组长工作台Console执行 async function debugEmployeeSurvey(employeeName) { const Parse = (await import('https://api.fmode.cn/parse/sdk.js')).default; Parse.initialize('nova'); Parse.serverURL = 'https://api.fmode.cn/parse'; console.log('==== 调试员工问卷 ===='); console.log('查找员工:', employeeName); // 1. 查找Profile const Profile = Parse.Object.extend('Profile'); const profileQuery = new Parse.Query(Profile); profileQuery.equalTo('realname', employeeName); profileQuery.limit(1); const profile = await profileQuery.first(); if (!profile) { console.error('❌ 未找到员工Profile,请检查名字是否正确'); return; } console.log('✅ 找到Profile:', profile.id); console.log(' 问卷状态:', profile.get('surveyCompleted')); // 2. 查找SurveyLog if (profile.get('surveyCompleted')) { const SurveyLog = Parse.Object.extend('SurveyLog'); const surveyQuery = new Parse.Query(SurveyLog); surveyQuery.equalTo('profile', profile.toPointer()); surveyQuery.equalTo('type', 'survey-profile'); surveyQuery.descending('createdAt'); surveyQuery.limit(1); const survey = await surveyQuery.first(); if (survey) { console.log('✅ 找到问卷记录:', survey.id); console.log(' 答案数量:', survey.get('answers')?.length || 0); console.log(' 提交时间:', survey.get('createdAt')); } else { console.error('❌ 未找到问卷记录'); } } } // 使用:替换为实际员工名字 debugEmployeeSurvey('王刚'); ``` ### Q5: 问卷提交失败 **原因**: - 必填题未填写 - 网络问题 - 权限问题 **解决方法**: 1. 打开Console查看错误信息 2. 检查必填题是否都已填写 3. 检查网络连接 4. 确认Parse权限配置 ```javascript // 手动测试提交问卷 async function testSubmitSurvey() { const Parse = (await import('https://api.fmode.cn/parse/sdk.js')).default; Parse.initialize('nova'); Parse.serverURL = 'https://api.fmode.cn/parse'; const profileId = localStorage.getItem('Parse/ProfileId'); const SurveyLog = Parse.Object.extend('SurveyLog'); const surveyLog = new SurveyLog(); const Profile = Parse.Object.extend('Profile'); const profile = new Profile(); profile.id = profileId; surveyLog.set('type', 'survey-profile'); surveyLog.set('profile', profile.toPointer()); surveyLog.set('answers', [ { question: '测试问题', type: 'single', answer: '测试答案' } ]); try { await surveyLog.save(); console.log('✅ 问卷提交成功'); } catch (error) { console.error('❌ 提交失败:', error); } } testSubmitSurvey(); ``` --- ## 数据库Schema说明 ### Profile 表新增字段 ```typescript { surveyCompleted: Boolean, // 是否完成问卷 surveyCompletedAt: Date, // 问卷完成时间 surveyLogId: String // 关联的SurveyLog ID } ``` **添加字段SQL(如果使用PostgreSQL)**: ```sql -- 添加问卷相关字段到Profile表 ALTER TABLE "Profile" ADD COLUMN IF NOT EXISTS "surveyCompleted" BOOLEAN DEFAULT false, ADD COLUMN IF NOT EXISTS "surveyCompletedAt" TIMESTAMP, ADD COLUMN IF NOT EXISTS "surveyLogId" VARCHAR(255); -- 添加索引 CREATE INDEX IF NOT EXISTS "idx_profile_survey_completed" ON "Profile" ("surveyCompleted"); ``` ### SurveyLog 表结构 ```typescript { type: 'survey-profile', // 问卷类型 profile: Pointer