wxwork-activation-test.component.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. import { Component, OnInit } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { Router, ActivatedRoute } from '@angular/router';
  4. import { WxworkAuth } from 'fmode-ng/core';
  5. /**
  6. * 企业微信身份激活测试组件
  7. * 用于调试身份激活 + 问卷整合流程
  8. */
  9. @Component({
  10. selector: 'app-wxwork-activation-test',
  11. standalone: true,
  12. imports: [CommonModule],
  13. host: {
  14. 'class': 'full-page-component'
  15. },
  16. template: `
  17. <div class="test-wrapper">
  18. <div class="test-container">
  19. <h1>🧪 企业微信身份激活测试</h1>
  20. <div class="test-section">
  21. <h2>测试步骤:</h2>
  22. <ol>
  23. <li [class.active]="currentStep >= 1">初始化企微认证</li>
  24. <li [class.active]="currentStep >= 2">执行身份激活</li>
  25. <li [class.active]="currentStep >= 3">检查问卷状态</li>
  26. <li [class.active]="currentStep >= 4">跳转到相应页面</li>
  27. </ol>
  28. </div>
  29. <div class="test-section" *ngIf="logs.length > 0">
  30. <h2>执行日志:</h2>
  31. <div class="log-container">
  32. <div *ngFor="let log of logs" [class]="'log-' + log.type">
  33. <span class="log-time">{{ log.time }}</span>
  34. <span class="log-message">{{ log.message }}</span>
  35. </div>
  36. </div>
  37. </div>
  38. <div class="test-section" *ngIf="profileInfo">
  39. <h2>员工信息:</h2>
  40. <table>
  41. <tr>
  42. <td>员工ID:</td>
  43. <td>{{ profileInfo.id }}</td>
  44. </tr>
  45. <tr>
  46. <td>姓名:</td>
  47. <td>{{ profileInfo.realname }}</td>
  48. </tr>
  49. <tr>
  50. <td>角色:</td>
  51. <td>{{ profileInfo.roleName }}</td>
  52. </tr>
  53. <tr>
  54. <td>问卷状态:</td>
  55. <td>
  56. <span [class]="profileInfo.surveyCompleted ? 'status-completed' : 'status-pending'">
  57. {{ profileInfo.surveyCompleted ? '✅ 已完成' : '❌ 未完成' }}
  58. </span>
  59. </td>
  60. </tr>
  61. </table>
  62. </div>
  63. <div class="test-section">
  64. <h2>测试操作:</h2>
  65. <button (click)="startTest()" [disabled]="testing">
  66. {{ testing ? '测试中...' : '开始测试' }}
  67. </button>
  68. <button (click)="resetSurvey()" [disabled]="!profileInfo">
  69. 重置问卷状态
  70. </button>
  71. <button (click)="goToSurvey()" [disabled]="!profileInfo">
  72. 前往问卷页面
  73. </button>
  74. <button (click)="goToDashboard()" [disabled]="!profileInfo">
  75. 前往工作台
  76. </button>
  77. </div>
  78. <div class="test-section" *ngIf="error">
  79. <h2 style="color: red;">❌ 错误信息:</h2>
  80. <pre>{{ error }}</pre>
  81. </div>
  82. </div>
  83. </div>
  84. `,
  85. styles: [`
  86. /* 强制突破父容器限制 */
  87. :host {
  88. display: block;
  89. position: fixed !important;
  90. top: 0 !important;
  91. left: 0 !important;
  92. right: 0 !important;
  93. bottom: 0 !important;
  94. width: 100vw !important;
  95. height: 100vh !important;
  96. overflow-y: auto !important;
  97. overflow-x: hidden !important;
  98. z-index: 1;
  99. background: #f5f5f5;
  100. -webkit-overflow-scrolling: touch;
  101. }
  102. .test-wrapper {
  103. min-height: 100vh;
  104. width: 100%;
  105. overflow-y: auto;
  106. overflow-x: hidden;
  107. }
  108. .test-container {
  109. max-width: 900px;
  110. margin: 40px auto;
  111. padding: 20px 20px 60px;
  112. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  113. }
  114. h1 {
  115. color: #333;
  116. margin-bottom: 30px;
  117. font-size: 28px;
  118. }
  119. .test-section {
  120. background: white;
  121. border-radius: 12px;
  122. padding: 24px;
  123. margin-bottom: 20px;
  124. box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  125. }
  126. h2 {
  127. color: #555;
  128. font-size: 18px;
  129. margin: 0 0 16px 0;
  130. }
  131. ol {
  132. margin: 0;
  133. padding-left: 24px;
  134. }
  135. li {
  136. padding: 8px 0;
  137. color: #999;
  138. transition: all 0.3s;
  139. }
  140. li.active {
  141. color: #007aff;
  142. font-weight: 500;
  143. }
  144. .log-container {
  145. background: #f5f5f5;
  146. border-radius: 8px;
  147. padding: 16px;
  148. max-height: 400px;
  149. height: 400px;
  150. overflow-y: auto;
  151. overflow-x: hidden;
  152. font-family: 'Courier New', monospace;
  153. font-size: 13px;
  154. -webkit-overflow-scrolling: touch;
  155. }
  156. .log-container > div {
  157. padding: 4px 0;
  158. display: flex;
  159. gap: 12px;
  160. }
  161. .log-time {
  162. color: #999;
  163. min-width: 80px;
  164. }
  165. .log-info { color: #007aff; }
  166. .log-success { color: #34c759; }
  167. .log-warning { color: #ff9500; }
  168. .log-error { color: #ff3b30; }
  169. table {
  170. width: 100%;
  171. border-collapse: collapse;
  172. }
  173. td {
  174. padding: 12px;
  175. border-bottom: 1px solid #eee;
  176. }
  177. td:first-child {
  178. font-weight: 500;
  179. color: #666;
  180. width: 120px;
  181. }
  182. .status-completed {
  183. color: #34c759;
  184. font-weight: 500;
  185. }
  186. .status-pending {
  187. color: #ff9500;
  188. font-weight: 500;
  189. }
  190. button {
  191. padding: 12px 24px;
  192. margin-right: 12px;
  193. border: none;
  194. border-radius: 8px;
  195. background: #007aff;
  196. color: white;
  197. font-size: 14px;
  198. font-weight: 500;
  199. cursor: pointer;
  200. transition: all 0.2s;
  201. }
  202. button:hover:not(:disabled) {
  203. background: #0051d5;
  204. transform: translateY(-1px);
  205. }
  206. button:disabled {
  207. background: #ccc;
  208. cursor: not-allowed;
  209. transform: none;
  210. }
  211. pre {
  212. background: #f5f5f5;
  213. padding: 16px;
  214. border-radius: 8px;
  215. overflow-x: auto;
  216. white-space: pre-wrap;
  217. word-wrap: break-word;
  218. }
  219. /* 确保移动端也能滚动 */
  220. @media (max-width: 768px) {
  221. .test-container {
  222. margin: 20px auto;
  223. padding: 16px 16px 40px;
  224. }
  225. .log-container {
  226. height: 300px;
  227. max-height: 300px;
  228. }
  229. button {
  230. width: 100%;
  231. margin-right: 0;
  232. margin-bottom: 8px;
  233. }
  234. }
  235. `]
  236. })
  237. export class WxworkActivationTestComponent implements OnInit {
  238. cid: string = '';
  239. currentStep: number = 0;
  240. testing: boolean = false;
  241. logs: Array<{ time: string; message: string; type: string }> = [];
  242. profileInfo: any = null;
  243. error: string = '';
  244. private wxAuth: WxworkAuth | null = null;
  245. constructor(
  246. private router: Router,
  247. private route: ActivatedRoute
  248. ) {}
  249. ngOnInit() {
  250. // 从URL获取cid(同步处理)
  251. const snapshot = this.route.snapshot;
  252. this.cid = snapshot.paramMap.get('cid') || 'test';
  253. this.addLog('获取公司ID: ' + this.cid, 'info');
  254. // 设置localStorage用于测试模式
  255. if (this.cid === 'test' || this.cid === 'demo') {
  256. localStorage.setItem('company', this.cid);
  257. this.addLog('🧪 测试模式已启用', 'info');
  258. }
  259. }
  260. /**
  261. * 开始测试
  262. */
  263. async startTest() {
  264. this.testing = true;
  265. this.error = '';
  266. this.logs = [];
  267. this.currentStep = 0;
  268. try {
  269. // 检查cid
  270. if (!this.cid) {
  271. throw new Error('公司ID未设置,请刷新页面重试');
  272. }
  273. // Step 1: 初始化企微认证(仅非测试模式)
  274. this.currentStep = 1;
  275. if (this.cid !== 'test' && this.cid !== 'demo') {
  276. this.addLog('初始化企微认证...', 'info');
  277. await this.initAuth();
  278. } else {
  279. this.addLog('🧪 测试模式,跳过企微认证初始化', 'info');
  280. }
  281. // Step 2: 执行身份激活
  282. this.currentStep = 2;
  283. this.addLog('执行身份激活...', 'info');
  284. await this.activate();
  285. // Step 3: 检查问卷状态
  286. this.currentStep = 3;
  287. this.addLog('检查问卷状态...', 'info');
  288. await this.checkSurvey();
  289. // Step 4: 决定跳转
  290. this.currentStep = 4;
  291. this.addLog('测试完成!', 'success');
  292. if (!this.profileInfo.surveyCompleted) {
  293. this.addLog('检测到问卷未完成,可以点击"前往问卷页面"按钮测试', 'warning');
  294. } else {
  295. this.addLog('问卷已完成,可以点击"前往工作台"按钮测试', 'success');
  296. }
  297. } catch (err: any) {
  298. this.error = err.message || String(err);
  299. this.addLog('测试失败: ' + this.error, 'error');
  300. } finally {
  301. this.testing = false;
  302. }
  303. }
  304. /**
  305. * 初始化认证
  306. */
  307. private async initAuth() {
  308. try {
  309. // 检查cid是否有效
  310. if (!this.cid) {
  311. throw new Error('公司ID(cid)未设置');
  312. }
  313. this.wxAuth = new WxworkAuth({ cid: this.cid, appId: 'crm' });
  314. this.addLog('✅ 企微认证初始化成功', 'success');
  315. } catch (error) {
  316. throw new Error('企微认证初始化失败: ' + error);
  317. }
  318. }
  319. /**
  320. * 执行激活
  321. */
  322. private async activate() {
  323. try {
  324. // 测试模式:使用模拟数据
  325. if (this.cid === 'test' || this.cid === 'demo') {
  326. this.addLog('🧪 使用测试模式,创建模拟Profile', 'warning');
  327. await this.createTestProfile();
  328. return;
  329. }
  330. // 真实模式:执行企微认证
  331. const { user, profile } = await this.wxAuth!.authenticateAndLogin();
  332. if (!profile) {
  333. throw new Error('未能获取Profile信息');
  334. }
  335. this.profileInfo = {
  336. id: profile.id,
  337. realname: profile.get('realname') || profile.get('name'),
  338. roleName: profile.get('roleName'),
  339. surveyCompleted: profile.get('surveyCompleted') || false
  340. };
  341. this.addLog(`✅ 身份激活成功: ${this.profileInfo.realname}`, 'success');
  342. this.addLog(` 角色: ${this.profileInfo.roleName}`, 'info');
  343. } catch (error) {
  344. throw new Error('身份激活失败: ' + error);
  345. }
  346. }
  347. /**
  348. * 创建测试Profile
  349. */
  350. private async createTestProfile() {
  351. const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
  352. try {
  353. // 尝试查找现有的测试Profile
  354. const query = new Parse.Query('Profile');
  355. query.equalTo('name', '测试员工');
  356. query.limit(1);
  357. let profile = await query.first();
  358. // 如果不存在,创建新的
  359. if (!profile) {
  360. const Profile = Parse.Object.extend('Profile');
  361. profile = new Profile();
  362. profile.set('name', '测试员工');
  363. profile.set('realname', '王刚');
  364. profile.set('roleName', '组员');
  365. profile.set('surveyCompleted', false);
  366. profile = await profile.save();
  367. this.addLog('✅ 已创建新的测试Profile', 'success');
  368. } else {
  369. this.addLog('✅ 找到现有测试Profile', 'success');
  370. }
  371. // 保存Profile信息
  372. this.profileInfo = {
  373. id: profile.id,
  374. realname: profile.get('realname') || profile.get('name'),
  375. roleName: profile.get('roleName'),
  376. surveyCompleted: profile.get('surveyCompleted') || false
  377. };
  378. // 缓存Profile ID
  379. localStorage.setItem('Parse/ProfileId', profile.id);
  380. this.addLog(` 员工姓名: ${this.profileInfo.realname}`, 'info');
  381. this.addLog(` 员工角色: ${this.profileInfo.roleName}`, 'info');
  382. } catch (error) {
  383. throw new Error('创建测试Profile失败: ' + error);
  384. }
  385. }
  386. /**
  387. * 检查问卷状态
  388. */
  389. private async checkSurvey() {
  390. const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
  391. try {
  392. const query = new Parse.Query('Profile');
  393. const profile = await query.get(this.profileInfo.id);
  394. const surveyCompleted = profile.get('surveyCompleted') || false;
  395. this.profileInfo.surveyCompleted = surveyCompleted;
  396. if (surveyCompleted) {
  397. this.addLog('✅ 问卷已完成', 'success');
  398. } else {
  399. this.addLog('⚠️ 问卷未完成', 'warning');
  400. }
  401. } catch (error) {
  402. this.addLog('⚠️ 无法查询问卷状态: ' + error, 'warning');
  403. }
  404. }
  405. /**
  406. * 重置问卷状态
  407. */
  408. async resetSurvey() {
  409. if (!this.profileInfo) return;
  410. const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
  411. try {
  412. this.addLog('重置问卷状态...', 'info');
  413. const query = new Parse.Query('Profile');
  414. const profile = await query.get(this.profileInfo.id);
  415. profile.set('surveyCompleted', false);
  416. profile.unset('surveyCompletedAt');
  417. profile.unset('surveyLogId');
  418. await profile.save();
  419. this.profileInfo.surveyCompleted = false;
  420. this.addLog('✅ 问卷状态已重置', 'success');
  421. } catch (error) {
  422. this.addLog('❌ 重置失败: ' + error, 'error');
  423. }
  424. }
  425. /**
  426. * 前往问卷页面
  427. */
  428. goToSurvey() {
  429. this.addLog('跳转到问卷页面...', 'info');
  430. this.router.navigate(['/wxwork', this.cid, 'survey', 'profile']);
  431. }
  432. /**
  433. * 前往工作台
  434. */
  435. goToDashboard() {
  436. this.addLog('跳转到工作台...', 'info');
  437. this.router.navigate(['/wxwork', this.cid, 'designer', 'dashboard']);
  438. }
  439. /**
  440. * 添加日志
  441. */
  442. private addLog(message: string, type: 'info' | 'success' | 'warning' | 'error') {
  443. const time = new Date().toLocaleTimeString();
  444. this.logs.push({ time, message, type });
  445. // 自动滚动到底部
  446. setTimeout(() => {
  447. const container = document.querySelector('.log-container');
  448. if (container) {
  449. container.scrollTop = container.scrollHeight;
  450. }
  451. }, 100);
  452. }
  453. }