Browse Source

feat: task wxwork project

ryanemax 2 days ago
parent
commit
f9e567f44a

+ 77 - 0
docs/task/20251015-wxwork-project.md

@@ -0,0 +1,77 @@
+# 任务:企业微信嵌入显示管理模块开发
+请您参考./rules/所有及子级.md的开发规范和范式要求。
+分析目前实现的静态页面./src/app/pages/designer/project-detail/ 所有内容。
+构思设计企业微信端项目管理所需的功能
+
+## 企微端项目管理页面
+- 项目预加载页面:会话数据及员工数据
+    - 根据wxwork相关组件及示例,在当前页面加载会话窗口上下文、当前企业员工数据
+    - 窗口上下文决定入口,还有对应的ContactInfo.id和GroupChat.id
+    - 企业员工数据,认定了当前用户的身份,对应不同功能和提示
+    - 预加载的信息中,如果查询不到当前项目,可以展示群聊相关的其他项目(能可能为空)
+        - 可以用骨架屏,引导用户对当前群组创建项目
+        - 默认项目名称是群聊名字,并提供修改
+        - 创建项目后,加载组件:项目详情(组件@input传入群组GroupChat、项目Project、员工Profile)
+- 两个入口:群聊进管项目,客户进查画像
+    - 企业微信中客户联系工具条,可以从群聊和客户两处打开
+        - 群聊打开,获取的是GroupChat
+            - 根据参数,加载群聊当前项目,展示组件:项目详情
+        - 联系人打开,获取的是ContactInfo
+            - 根据参数,展示组件:客户画像 
+                - 显示该客户基础信息、所在群聊及项目、跟进记录
+                    - 点击群聊,通过WxworkSDK 跳转进入群聊
+
+## 架构设计和规则
+- 通过预加载页面获取群组GroupChat、项目Project、员工Profile数据后,再通过组件加载对应功能页面
+    - 主要为了方便调试,调试时候,可以通过路由携带:groupid :projectid :profileid
+    - 让对应功能组件从route参数加载对应的数据,而不需要只依赖于@Input传入
+        - 此处需要判断若@Input上面的对应FmodeObject空,则根据路由传入参数加载
+- 企微会话进入的页面,都是手机竖屏的布局,要考虑到内容设计主次分明,且项目各阶段方便切换
+- 企业微信开发相关部分,请您充分参考:./rules/wxwork/*.md
+    - 合理设计企微上下文及当前用户获取参数,尽量优化加载速度的同时,确保新员工新会话都能正常同步创建对应的Profile ContactInfo GroupChat数据到数据库,方便项目管理功能调用。
+- 数据请参考./rules/schemas.md,使用FmodeParse FmodeObject FmodeQuery实现増删查改
+- 项目详情页,还有四大阶段和状态的区分,因此需要合理使用二级路由,和二级子组件来合理规划拆分项目结构,方便维护
+
+## 功能模块目录
+./src/modules/project 项目管理模块下
+    - 项目预加载页面
+    - 客户画像组件
+    - 项目详情组件
+        - 需要充分展示出项目各个阶段的核心功能
+            - 客户信息:
+                - 只有客服能看手机、微信、邮箱
+                - 客户标签和项目相关的偏好画像、需求、方案是全员能看
+            - 订单分配:主要由客服操作
+                - 填写项目必备信息,创建对应的报价细项
+            - 确认需求:全员操作,主要由组长组员完成
+                - 重点功能是上传参考图、CAD图,并说明图的具体内容
+                    - 其中参考图,提供色彩分析弹出组件
+                    - CAD图,只有人工填写描述,方便全员查看
+                    - 其他需求创建和碳写(根据聊天问答获取的其他需求卡片)
+                - AI生成方案页
+                    - AI分析的完整方案分析(可参考大模型调用示例项目 /home/ryan/workspace/nova/nova-admin/projects/ai-k12-daofa/src/modules/daofa/search/search.component.ts)
+                        - 根据参考图、参考图获取的色彩分析报告,在根据方案所需各个维度的参数
+                        - 进行调用大模型,输出方案分析文档,辅助设计师理解
+                        - 第一次需要生成,方案生成后要保存到需求分析中,方便其他人加载查看
+                            - 也可以在参考图更多,填写了更多需求,可以重新分析
+            - 交付执行:主要是组员操作
+                - 完成各个场景对应交付物上传(根据报价项目清单中,不同的场景),每个场景都需要
+                    - 白模:上传白模图片
+                    - 软装:上传软装小图
+                    - 渲染:展示渲染进度信息
+                    - 后期:上传后期处理后图片
+                - 每个交付物都有一个自查项,打勾的交互,方便组员自查
+                - 组长可以在交付执行页面,给组员对应的交付,提出ProjectIssue,来自组长的Issue
+                    - 组员需要更明显得看到,以便于根据要求修改调整
+                - 发起交付
+                    - 组员上传完所有交付物,可以发起交付
+                    - 通知项目群聊的客户,已完成,并且发起收款请求
+            - 售后归档:主要是客服操作
+                - 处理尾款、客户评价、项目复盘等工作
+                - 完成后,可以点击归档,该群聊当前项目将设为空,该项目为已归档历史项目,显示在列表里
+
+## 任务计划
+
+请您完成上面所有核心功能模块的设计和产品结构分析,写在./docs/prd/<端>-<功能>-<页面>。
+
+请您逐个完成上述所有功能,包括页面、数据对接、大模型调用,并确保正常运行。

+ 247 - 0
rules/wxwork/auth-user-confirm.md

@@ -0,0 +1,247 @@
+# 用户身份确认页面
+
+## 页面概述
+
+用户身份确认页面 (`page-user-confirm`) 是一个受企微路由守卫保护的单页面应用,用于展示当前企业微信用户的详细身份信息,并允许用户确认其身份。
+
+``` ts
+import { WxworkSDK } from 'fmode-ng/core';
+import { FmodeParse } from 'fmode-ng/parse';
+import { WxworkAuthGuard } from 'fmode-ng';
+```
+
+## 访问路径
+
+- **路径1**: `/:cid/auth/user-confirm`
+- **路径2**: `/:cid/auth/:appId/user-confirm`
+
+**示例**:
+- 脑控科技身份确认 https://app.fmode.cn/dev/crm/auth/E4KpGvTEto/user-confirm
+- 映三色身份确认 https://app.fmode.cn/dev/crm/auth/cDL6R1hgSi/user-confirm
+
+## 路由守卫
+
+使用 `WxworkAuthGuard` 企微路由守卫,确保:
+
+1. **获取路由参数**:
+   - `cid`: 公司帐套ID (Company.objectId)
+   - `appId`: 应用ID (可选)
+
+2. **识别用户身份**:
+   - 企业员工: 通过 `UserId` 识别,关联到 `Profile` 表
+   - 外部用户: 通过 `external_userid` 或 `OpenId` 识别,关联到 `UserSocial` 表
+
+3. **数据持久化**:
+   - localStorage 存储位置: `{{cid}}/USERINFO`
+   - 获取函数: `wxsdk.getUserinfo()`
+
+## 功能模块
+
+### 1. 用户信息加载
+
+#### Profile (企业员工)
+
+从 Parse Server 的 `Profile` 表查询用户信息,查询条件:
+- `userId`: 企业员工的微信UserId
+- `company`: Pointer 指向当前 Company (cid)
+
+**展示字段**:
+- `name`: 姓名
+- `avatar`: 头像
+- `mobile`: 手机号
+- `email`: 邮箱
+- `department`: 部门
+- `position`: 职位
+- `corpName`: 企业名称
+- `isVerified`: 身份确认状态
+- `userId`: 员工ID
+
+#### UserSocial (外部用户)
+
+从 Parse Server 的 `UserSocial` 表查询用户信息,查询条件:
+- `externalUserId`: 外部用户的微信external_userid
+- `company`: Pointer 指向当前 Company (cid)
+
+**展示字段**:
+- `name`: 姓名
+- `avatar`: 头像
+- `mobile`: 手机号
+- `externalName`: 外部名称
+- `externalType`: 外部用户类型
+- `corpName`: 企业名称
+- `isVerified`: 身份确认状态
+- `externalUserId`: 外部用户ID
+
+### 2. 用户信息展示
+
+页面采用卡片式布局,包含以下区域:
+
+#### 头像区域
+- 居中展示用户头像(圆形,100px)
+- 无头像时显示默认占位图标
+- 显示用户姓名
+- 显示用户类型标签(企业员工/外部用户)
+
+#### 身份状态徽章
+- **已确认**: 绿色徽章,显示"已确认身份"和对勾图标
+- **待确认**: 黄色徽章,显示"待确认"
+
+#### 详细信息列表
+根据用户类型展示不同的字段信息,每项包含:
+- 图标标识
+- 字段标题
+- 字段值
+
+### 3. 身份确认功能
+
+#### 确认按钮状态
+- **可点击**: 用户未确认 && 用户记录存在 && 未加载中
+- **禁用**: 用户已确认 || 用户记录不存在 || 加载中
+
+#### 确认流程
+1. 点击"确认身份"按钮
+2. 更新对应表记录的 `isVerified` 字段为 `true`
+3. 显示成功提示
+4. 更新页面状态为"已确认身份"
+
+#### 异常处理
+- **用户记录不存在**: 提示"系统中未找到您的用户记录,请联系管理员"
+- **确认失败**: 显示错误提示信息
+
+## 数据模型
+
+### Profile 表
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| userId | String | 企业员工微信UserId |
+| company | Pointer<Company> | 所属企业 |
+| name | String | 姓名 |
+| avatar | String | 头像URL |
+| mobile | String | 手机号 |
+| email | String | 邮箱 |
+| department | String | 部门 |
+| position | String | 职位 |
+| corpName | String | 企业名称 |
+| isVerified | Boolean | 身份确认状态 |
+
+### UserSocial 表
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| externalUserId | String | 外部用户微信external_userid |
+| company | Pointer<Company> | 所属企业 |
+| name | String | 姓名 |
+| avatar | String | 头像URL |
+| mobile | String | 手机号 |
+| externalName | String | 外部名称 |
+| type | String | 外部用户类型 |
+| corpName | String | 企业名称 |
+| isVerified | Boolean | 身份确认状态 |
+
+## UI/UX 设计
+
+### 布局特点
+- 响应式设计,适配移动端和桌面端
+- 居中布局,最大宽度600px
+- 卡片式信息展示,清晰分层
+- 图标辅助,增强信息可读性
+
+### 交互状态
+1. **加载状态**: 显示加载动画和提示文字
+2. **信息展示**: 完整展示用户信息
+3. **确认成功**: 按钮变为禁用状态,徽章变为绿色
+4. **错误状态**: 显示友好的错误提示
+
+### 视觉元素
+- **主色调**: Ionic 默认主题色 (#3880ff)
+- **成功色**: 绿色 (success)
+- **警告色**: 黄色 (warning)
+- **字体大小**:
+  - 标题: 20px
+  - 用户名: 24px
+  - 正文: 16px
+  - 辅助文本: 14px
+
+## 技术实现
+
+### 核心依赖
+- **Angular**: 独立组件架构
+- **Ionic**: UI 组件库
+- **fmode-ng**:
+  - `WxworkSDK`: 企微SDK
+  - `FmodeParse`: Parse Server 数据服务
+
+### 主要方法
+
+#### `ngOnInit()`
+- 获取路由参数 (cid, appId)
+- 调用 `loadUserInfo()` 加载用户信息
+
+#### `loadUserInfo()`
+- 初始化 WxworkSDK
+- 从 localStorage 获取缓存的用户信息
+- 根据用户类型调用相应的加载方法
+
+#### `loadProfileInfo(userId: string)`
+- 查询 Profile 表
+- 构建 UserInfo 对象
+
+#### `loadUserSocialInfo(cachedInfo: any)`
+- 查询 UserSocial 表
+- 构建 UserInfo 对象
+
+#### `confirmIdentity()`
+- 更新 isVerified 字段为 true
+- 刷新页面状态
+- 显示成功提示
+
+## 使用场景
+
+### 场景1: 首次登录确认
+企业员工首次通过企业微信进入系统时,需要确认其身份信息,系统记录确认状态。
+
+### 场景2: 外部用户验证
+外部客户通过企业微信接入时,展示其在系统中的信息,确认身份后才能使用完整功能。
+
+### 场景3: 信息核对
+管理员可以引导用户访问此页面,核对和更新用户信息。
+
+## 扩展性
+
+### 未来可能的增强功能
+1. 允许用户编辑部分信息(如手机号、邮箱)
+2. 添加人脸识别或其他二次验证
+3. 记录确认时间和IP地址
+4. 支持批量身份确认
+5. 增加身份过期和重新确认机制
+
+## 注意事项
+
+1. **路由守卫**: 必须配置 `WxworkAuthGuard`,否则无法获取用户信息
+2. **数据隐私**: 敏感信息需要适当脱敏显示
+3. **错误处理**: 网络异常、Parse查询失败等都需要友好提示
+4. **状态同步**: isVerified 状态更新后需要同步到其他使用该字段的模块
+5. **权限控制**: 未确认身份的用户可能需要限制部分功能访问
+
+## 文件结构
+
+```
+src/modules/auth/page-user-confirm/
+├── page-user-confirm.component.ts       # 组件逻辑
+├── page-user-confirm.component.html     # 模板
+├── page-user-confirm.component.scss     # 样式
+└── page-user-confirm.component.spec.ts  # 单元测试
+```
+
+## 路由配置
+
+```typescript
+// app.routes.ts
+{
+    path:"auth/:cid/user-confirm",
+    canActivate:[WxworkAuthGuard],
+    loadComponent:()=>import("../modules/auth/page-user-confirm/page-user-confirm.component")
+        .then((m)=>m.PageUserConfirmComponent)
+}
+```

+ 1126 - 0
rules/wxwork/group-chat-contact.md

@@ -0,0 +1,1126 @@
+# 企业微信群聊与联系人管理
+
+## 概述
+
+本文档介绍如何在企业微信聊天窗口的工具栏入口页面中,获取当前会话的群组信息或联系人信息,并同步到 Parse Server 的 `GroupChat` 和 `Contact` 表中。
+
+## 引用方式
+
+```typescript
+import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
+import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
+import { WxworkAuthGuard } from 'fmode-ng';
+```
+
+## 核心功能
+
+### 1. 获取当前聊天上下文
+使用 `WxworkSDK.getCurrentChat()` 方法获取当前企微窗口的会话信息。
+
+### 2. 同步群组信息
+使用 `WxworkSDK.syncGroupChat()` 方法同步群聊信息到 `GroupChat` 表。
+
+### 3. 同步联系人信息
+使用 `WxworkSDK.syncContact()` 方法同步外部联系人信息到 `Contact` 表。
+
+### 4. ⭐ 统一获取方法(推荐)
+使用 `WxworkSDK.getCurrentChatObject()` 一次性获取当前会话对象(群聊或联系人)。
+
+## WxworkSDK 核心方法
+
+### getCurrentChat() - 获取当前会话上下文
+
+```typescript
+/**
+ * 获取当前企微窗口的会话信息
+ * @returns WxworkCurrentChat 对象
+ */
+async getCurrentChat(): Promise<WxworkCurrentChat>
+
+interface WxworkCurrentChat {
+  type: "chatId" | "userId";  // 会话类型
+  id?: string;                 // 群聊ID或用户ID
+  group?: any;                 // 群聊详细信息
+  contact?: any;               // 联系人详细信息
+  follow_user?: any;           // 跟进人信息
+}
+```
+
+**使用场景**:
+- 从企微聊天栏工具进入页面时,自动识别当前会话
+- 支持群聊和单聊两种场景
+
+### syncGroupChat() - 同步群组信息
+
+```typescript
+/**
+ * 同步群聊信息到 GroupChat 表
+ * @param groupInfo 企微群聊信息
+ * @returns GroupChat Parse Object
+ */
+async syncGroupChat(groupInfo: any): Promise<FmodeObject>
+```
+
+**功能**:
+- 根据 `chat_id` 查询或创建 GroupChat 记录
+- 自动生成入群链接 (`joinUrl`) 和入群二维码 (`joinQrcode`)
+- 同步群聊基本信息(名称、群主、公告等)
+- 同步成员列表 (`member_list`) 和成员版本 (`member_version`)
+- 仅在数据变化时保存,避免不必要的数据库写入
+
+### syncContact() - 同步联系人信息
+
+```typescript
+/**
+ * 同步外部联系人信息到 Contact 表
+ * @param contactInfo 企微外部联系人信息(包含 external_contact 和 follow_user)
+ * @returns Contact Parse Object
+ */
+async syncContact(contactInfo: any): Promise<FmodeObject>
+```
+
+**功能**:
+- 根据 `external_userid` 和 `company` 查询或创建 Contact 记录
+- **独立字段**: `name`(姓名)、`mobile`(手机号)、`external_userid`(外部用户ID)
+- **data 字段**: 其他所有信息统一存储在 `data` Object 类型字段中
+- 包含完整的企微联系人信息和 `follow_user` 跟进人列表
+- 仅在数据变化时保存,避免不必要的数据库写入
+
+**参数兼容**:
+```typescript
+// 方式1:传入完整的 contactInfo(推荐)
+const contactInfo = await wecorp.externalContact.get(externalUserId);
+await wxwork.syncContact(contactInfo);
+
+// 方式2:只传入 external_contact
+await wxwork.syncContact(contactInfo.external_contact);
+```
+
+### ⭐ getCurrentChatObject() - 统一获取方法(推荐)
+
+```typescript
+/**
+ * 获取当前聊天对象(群聊或联系人)
+ * @returns { GroupChat?, Contact?, currentChat }
+ */
+async getCurrentChatObject(): Promise<{
+  GroupChat?: FmodeObject,
+  Contact?: FmodeObject,
+  currentChat: WxworkCurrentChat | null
+}>
+```
+
+**功能**:
+- 一次调用同时获取会话上下文和对应的 Parse Object
+- 自动识别群聊或联系人场景
+- 自动调用 `syncGroupChat` 或 `syncContact` 同步数据
+- 返回结果包含 `GroupChat` 或 `Contact`,用户自主判断后续逻辑
+
+**返回值**:
+- `GroupChat`: 群聊对象(群聊场景下存在)
+- `Contact`: 联系人对象(单聊场景下存在)
+- `currentChat`: 原始会话上下文
+
+**使用示例**:
+```typescript
+const { GroupChat, Contact, currentChat } = await wxwork.getCurrentChatObject();
+
+if (GroupChat) {
+  // 群聊场景
+  console.log("群名:", GroupChat.get("name"));
+  console.log("成员数:", GroupChat.get("member_list").length);
+} else if (Contact) {
+  // 联系人场景
+  console.log("姓名:", Contact.get("name"));
+  console.log("企业:", Contact.get("data").corp_name);
+}
+```
+
+## 数据范式
+
+### GroupChat 表(群聊)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| chat_id | String | 企微群聊ID(唯一标识) |
+| name | String | 群聊名称 |
+| owner | String | 群主 userid |
+| notice | String | 群公告 |
+| member_list | Array | 群成员列表 |
+| member_version | String | 成员版本号(用于判断成员变化) |
+| joinUrl | String | 入群链接(scene=1) |
+| joinQrcode | String | 入群二维码链接(scene=2) |
+| createdAt | Date | 创建时间 |
+| updatedAt | Date | 更新时间 |
+
+**member_list 结构**:
+```json
+[
+  {
+    "userid": "zhangsan",
+    "type": 1,
+    "join_time": 1605171726,
+    "join_scene": 1,
+    "invitor": {
+      "userid": "lisi"
+    }
+  }
+]
+```
+
+### Contact 表(联系人)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| external_userid | String | 外部联系人ID(唯一标识) |
+| company | Pointer<Company> | 所属企业 |
+| name | String | 联系人姓名 |
+| mobile | String | 手机号 |
+| data | Object | 完整的企微联系人信息(包含所有字段) |
+| createdAt | Date | 创建时间 |
+| updatedAt | Date | 更新时间 |
+
+**⭐ 数据结构说明**:
+- **独立字段**:仅 `name`(姓名)、`mobile`(手机号)、`external_userid`(外部用户ID)为独立字段
+- **data 字段**:其他所有企微联系人信息统一存储在 `data` Object 类型字段中
+
+**data 字段包含的信息**:
+```json
+{
+  "external_userid": "wmxxx",
+  "name": "张三",
+  "avatar": "http://...",
+  "type": 1,
+  "gender": 1,
+  "unionid": "oxxx",
+  "position": "产品经理",
+  "corp_name": "ABC公司",
+  "corp_full_name": "ABC科技有限公司",
+  "external_profile": {
+    "external_attr": [
+      {
+        "type": 0,
+        "name": "职位",
+        "text": { "value": "产品经理" }
+      }
+    ]
+  },
+  "follow_user": [
+    {
+      "userid": "zhangsan",
+      "remark": "备注名",
+      "description": "描述",
+      "createtime": 1605171726,
+      "tags": [
+        {
+          "group_name": "标签组",
+          "tag_name": "标签名",
+          "type": 1
+        }
+      ],
+      "remark_corp_name": "备注企业名称",
+      "remark_mobiles": ["13800138000"]
+    }
+  ]
+}
+```
+
+**访问 data 字段示例**:
+```typescript
+const contact = await wxwork.syncContact(contactInfo);
+
+// 独立字段直接访问
+const name = contact.get("name");
+const mobile = contact.get("mobile");
+
+// data 字段内容需通过 .get("data") 访问
+const data = contact.get("data");
+const corpName = data.corp_name;
+const avatar = data.avatar;
+const followUsers = data.follow_user;
+```
+
+## 页面开发示例
+
+### 示例1:群聊消息发送页面
+
+从聊天栏工具入口,自动识别当前群聊并发送消息。
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
+import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
+
+const Parse = FmodeParse.with("nova");
+
+export class PageGroupMessageComponent implements OnInit {
+  cid: string = '';
+  gid: string = '';
+  chatId: string = '';
+
+  wework: WxworkSDK;
+  wecorp: WxworkCorp;
+  currentChat: WxworkCurrentChat | null = null;
+  groupChat: FmodeObject | null = null;
+
+  constructor(private route: ActivatedRoute) {}
+
+  async ngOnInit() {
+    this.route.paramMap.subscribe(async params => {
+      this.cid = params.get('cid') || '';
+      this.gid = params.get('gid') || '';
+
+      if (this.cid) {
+        this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
+        this.wecorp = new WxworkCorp(this.cid);
+        await this.loadGroupChat();
+      }
+    });
+  }
+
+  /**
+   * 加载群聊信息
+   */
+  async loadGroupChat() {
+    try {
+      if (this.gid) {
+        // 方式1:通过路由参数 gid 加载
+        let query = new Parse.Query("GroupChat");
+        this.groupChat = await query.get(this.gid);
+        this.chatId = this.groupChat.get("chat_id");
+
+        // 从企微API获取最新群聊信息
+        let chatInfo = await this.wecorp.externalContact.groupChat.get(this.chatId);
+        this.currentChat = {
+          id: chatInfo?.chat_id,
+          type: "chatId",
+          group: chatInfo?.group_chat || chatInfo
+        };
+      } else {
+        // 方式2:从当前企微窗口获取上下文
+        this.currentChat = await this.wework.getCurrentChat();
+
+        if (this.currentChat?.group) {
+          this.chatId = this.currentChat.id || '';
+
+          // ⭐ 同步群聊信息到 Parse Server
+          this.groupChat = await this.wework.syncGroupChat(this.currentChat.group);
+          this.gid = this.groupChat.id;
+        }
+      }
+
+      console.log("群聊ID:", this.chatId);
+      console.log("群聊名称:", this.groupChat?.get("name"));
+      console.log("群成员数:", this.groupChat?.get("member_list")?.length);
+    } catch (error) {
+      console.error('加载群聊失败:', error);
+    }
+  }
+
+  /**
+   * 发送文本消息
+   */
+  async sendTextMessage() {
+    if (!this.chatId) {
+      alert('请先加载群聊信息');
+      return;
+    }
+
+    const content = "这是一条测试消息";
+    const result = await this.wecorp.appchat.sendText(this.chatId, content);
+
+    if (result?.errcode === 0) {
+      console.log('发送成功');
+    } else {
+      console.error('发送失败:', result?.errmsg);
+    }
+  }
+}
+```
+
+### 示例2:联系人详情页面
+
+从联系人侧边栏工具入口,自动识别当前联系人并显示详情。
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
+import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
+
+const Parse = FmodeParse.with("nova");
+
+export class PageContactDetailComponent implements OnInit {
+  cid: string = '';
+  contactId: string = '';
+  externalUserId: string = '';
+
+  wework: WxworkSDK;
+  wecorp: WxworkCorp;
+  currentChat: WxworkCurrentChat | null = null;
+  contact: FmodeObject | null = null;
+
+  constructor(private route: ActivatedRoute) {}
+
+  async ngOnInit() {
+    this.route.paramMap.subscribe(async params => {
+      this.cid = params.get('cid') || '';
+      this.contactId = params.get('contactId') || '';
+
+      if (this.cid) {
+        this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
+        this.wecorp = new WxworkCorp(this.cid);
+        await this.loadContact();
+      }
+    });
+  }
+
+  /**
+   * 加载联系人信息
+   */
+  async loadContact() {
+    try {
+      if (this.contactId) {
+        // 方式1:通过路由参数 contactId 加载
+        let query = new Parse.Query("Contact");
+        this.contact = await query.get(this.contactId);
+        this.externalUserId = this.contact.get("external_userid");
+      } else {
+        // 方式2:从当前企微窗口获取上下文
+        this.currentChat = await this.wework.getCurrentChat();
+
+        if (this.currentChat?.contact) {
+          this.externalUserId = this.currentChat.id || '';
+
+          // 获取完整的联系人信息
+          const contactInfo = await this.wecorp.externalContact.get(this.externalUserId);
+
+          // ⭐ 同步联系人信息到 Parse Server
+          this.contact = await this.wework.syncContact(contactInfo.external_contact);
+          this.contactId = this.contact.id;
+        }
+      }
+
+      console.log("联系人ID:", this.externalUserId);
+      console.log("联系人姓名:", this.contact?.get("name"));
+
+      // ⭐ 访问 data 字段中的信息
+      const data = this.contact?.get("data");
+      console.log("所属企业:", data?.corp_name);
+      console.log("跟进人:", data?.follow_user);
+    } catch (error) {
+      console.error('加载联系人失败:', error);
+    }
+  }
+
+  /**
+   * 更新联系人备注
+   */
+  async updateRemark(remark: string) {
+    if (!this.contact) return;
+
+    this.contact.set("remark", remark);
+    await this.contact.save();
+
+    console.log('备注更新成功');
+  }
+}
+```
+
+### 示例3:⭐ 使用 getCurrentChatObject() 的最简模板(推荐)
+
+适用于快速开发企微工具栏页面,使用统一方法一次性获取会话对象。
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
+import { FmodeObject } from 'fmode-ng/parse';
+
+export class MyToolComponent implements OnInit {
+  cid: string = '';
+  wework: WxworkSDK;
+  wecorp: WxworkCorp;
+
+  groupChat: FmodeObject | null = null;
+  contact: FmodeObject | null = null;
+
+  constructor(private route: ActivatedRoute) {}
+
+  async ngOnInit() {
+    this.cid = this.route.snapshot.paramMap.get('cid') || '';
+    this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
+    this.wecorp = new WxworkCorp(this.cid);
+
+    // ⭐ 一次性获取当前会话对象
+    const { GroupChat, Contact } = await this.wework.getCurrentChatObject();
+
+    if (GroupChat) {
+      // 群聊场景
+      this.groupChat = GroupChat;
+      console.log("当前群聊:", GroupChat.get("name"));
+      console.log("成员数:", GroupChat.get("member_list").length);
+    } else if (Contact) {
+      // 联系人场景
+      this.contact = Contact;
+      console.log("当前联系人:", Contact.get("name"));
+
+      // ⭐ 访问 data 字段
+      const data = Contact.get("data");
+      console.log("企业:", data.corp_name);
+      console.log("跟进人:", data.follow_user);
+    }
+
+    // 开始业务逻辑...
+  }
+}
+```
+
+### 示例4:传统方式(兼容旧代码)
+
+使用 getCurrentChat() 分步处理的方式。
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
+import { FmodeObject } from 'fmode-ng/parse';
+
+export class MyToolComponent implements OnInit {
+  cid: string = '';
+  wework: WxworkSDK;
+  wecorp: WxworkCorp;
+  currentChat: WxworkCurrentChat | null = null;
+
+  // 群聊或联系人 Parse Object
+  chatObject: FmodeObject | null = null;
+
+  constructor(private route: ActivatedRoute) {}
+
+  async ngOnInit() {
+    this.cid = this.route.snapshot.paramMap.get('cid') || '';
+    this.wework = new WxworkSDK({ cid: this.cid, appId: 'crm' });
+    this.wecorp = new WxworkCorp(this.cid);
+
+    // 获取当前会话
+    this.currentChat = await this.wework.getCurrentChat();
+
+    if (this.currentChat?.type === "chatId") {
+      // 群聊场景
+      this.chatObject = await this.wework.syncGroupChat(this.currentChat.group);
+      console.log("当前群聊:", this.chatObject.get("name"));
+    } else if (this.currentChat?.type === "userId") {
+      // 单聊场景
+      const contactInfo = await this.wecorp.externalContact.get(this.currentChat.id);
+      this.chatObject = await this.wework.syncContact(contactInfo);
+      console.log("当前联系人:", this.chatObject.get("name"));
+    }
+
+    // 开始业务逻辑...
+  }
+}
+```
+
+### 示例5:⭐ 完整的聊天上下文页面(page-chat-context)
+
+这是一个完整的演示页面,展示了如何同时获取和展示员工信息及聊天上下文(群聊或联系人)。
+
+**路由配置**:
+- `chat/:cid/context`
+- `chat/:cid/:appId/context`
+
+**页面功能**:
+1. 显示当前登录员工信息(Profile/UserSocial)
+2. 显示当前聊天上下文(群聊或联系人)
+3. 根据场景展示不同的信息卡片
+
+**核心代码**:
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
+import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
+
+const Parse = FmodeParse.with("nova");
+
+@Component({
+  selector: 'app-page-chat-context',
+  standalone: true,
+  templateUrl: './page-chat-context.component.html'
+})
+export class PageChatContextComponent implements OnInit {
+  cid: string = '';
+  appId: string = '';
+
+  loading: boolean = true;
+  wxwork: WxworkSDK | null = null;
+  wecorp: WxworkCorp | null = null;
+
+  // 当前登录的员工信息
+  currentUser: FmodeObject | null = null;
+
+  // 当前会话信息
+  currentChat: WxworkCurrentChat | null = null;
+  chatType: 'group' | 'contact' | 'none' = 'none';
+
+  // 群聊或联系人对象
+  groupChat: FmodeObject | null = null;
+  contact: FmodeObject | null = null;
+
+  constructor(private route: ActivatedRoute) {}
+
+  async ngOnInit() {
+    this.route.paramMap.subscribe(async params => {
+      this.cid = params.get('cid') || '';
+      this.appId = params.get('appId') || 'crm';
+
+      if (!this.cid) {
+        alert('缺少企业ID参数');
+        return;
+      }
+
+      await this.loadData();
+    });
+  }
+
+  async loadData() {
+    try {
+      this.loading = true;
+
+      // 初始化 SDK
+      this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
+      this.wecorp = new WxworkCorp(this.cid);
+
+      // 1️⃣ 加载当前登录员工信息(由 WxworkAuthGuard 自动登录)
+      this.currentUser = await this.wxwork.getCurrentUser();
+
+      // 2️⃣ 加载当前聊天对象(群聊或联系人)
+      const chatObject = await this.wxwork.getCurrentChatObject();
+      this.currentChat = chatObject.currentChat;
+
+      if (chatObject.GroupChat) {
+        this.chatType = 'group';
+        this.groupChat = chatObject.GroupChat;
+      } else if (chatObject.Contact) {
+        this.chatType = 'contact';
+        this.contact = chatObject.Contact;
+      }
+
+    } catch (error) {
+      console.error('加载数据失败:', error);
+      alert('加载失败: ' + error);
+    } finally {
+      this.loading = false;
+    }
+  }
+
+  // Helper: 获取当前员工姓名
+  getCurrentUserName(): string {
+    if (!this.currentUser) return '未知';
+    return this.currentUser.get('name') || this.currentUser.get('userid') || '未知';
+  }
+
+  // Helper: 获取当前员工类型
+  getCurrentUserType(): string {
+    if (!this.currentUser) return '';
+    return this.currentUser.className === 'Profile' ? '企业员工' : '外部用户';
+  }
+
+  // Helper: 获取联系人详细数据
+  getContactData(): any {
+    return this.contact?.get('data') || {};
+  }
+
+  // Helper: 获取联系人企业信息
+  getContactCorpName(): string {
+    const data = this.getContactData();
+    return data.corp_name || data.corp_full_name || '未知';
+  }
+
+  // Helper: 获取联系人头像
+  getContactAvatar(): string {
+    const data = this.getContactData();
+    return data.avatar || '';
+  }
+
+  // Helper: 获取跟进人列表
+  getFollowUsers(): any[] {
+    const data = this.getContactData();
+    return data.follow_user || [];
+  }
+
+  // Helper: 获取群成员列表
+  getGroupMembers(): any[] {
+    const memberList = this.groupChat?.get('member_list');
+    return Array.isArray(memberList) ? memberList : [];
+  }
+}
+```
+
+**模板示例(HTML)**:
+
+```html
+<div class="chat-context-container">
+  <!-- 加载状态 -->
+  <div *ngIf="loading" class="loading-container">
+    <ion-spinner></ion-spinner>
+    <p>加载中...</p>
+  </div>
+
+  <!-- 内容展示 -->
+  <div *ngIf="!loading" class="content">
+    <!-- 卡片1: 当前员工信息 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>
+          <ion-icon name="person"></ion-icon>
+          当前员工
+        </ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <ion-item>
+          <ion-label>
+            <h3>姓名</h3>
+            <p>{{ getCurrentUserName() }}</p>
+          </ion-label>
+        </ion-item>
+        <ion-item>
+          <ion-label>
+            <h3>类型</h3>
+            <p>{{ getCurrentUserType() }}</p>
+          </ion-label>
+        </ion-item>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 卡片2: 群聊信息 -->
+    <ion-card *ngIf="chatType === 'group' && groupChat">
+      <ion-card-header>
+        <ion-card-title>
+          <ion-icon name="people"></ion-icon>
+          群聊信息
+        </ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <ion-item>
+          <ion-label>
+            <h3>群名</h3>
+            <p>{{ groupChat.get('name') }}</p>
+          </ion-label>
+        </ion-item>
+        <ion-item>
+          <ion-label>
+            <h3>成员数</h3>
+            <p>{{ getGroupMembers().length }}</p>
+          </ion-label>
+        </ion-item>
+        <ion-item>
+          <ion-label>
+            <h3>群主</h3>
+            <p>{{ groupChat.get('owner') }}</p>
+          </ion-label>
+        </ion-item>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- 卡片3: 联系人信息 -->
+    <ion-card *ngIf="chatType === 'contact' && contact">
+      <ion-card-header>
+        <ion-card-title>
+          <ion-icon name="person"></ion-icon>
+          联系人信息
+        </ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <!-- 头像和基本信息 -->
+        <div class="contact-header">
+          <ion-avatar *ngIf="getContactAvatar()">
+            <img [src]="getContactAvatar()" />
+          </ion-avatar>
+          <div class="contact-basic">
+            <h2>{{ contact.get('name') }}</h2>
+            <p>{{ getContactCorpName() }}</p>
+          </div>
+        </div>
+
+        <!-- 详细信息 -->
+        <ion-item>
+          <ion-label>
+            <h3>手机号</h3>
+            <p>{{ contact.get('mobile') || '未绑定' }}</p>
+          </ion-label>
+        </ion-item>
+
+        <!-- ⭐ 访问 data 字段中的信息 -->
+        <h3>企业信息</h3>
+        <ion-item>
+          <ion-label>
+            <h3>企业</h3>
+            <p>{{ getContactCorpName() }}</p>
+          </ion-label>
+        </ion-item>
+        <ion-item>
+          <ion-label>
+            <h3>职位</h3>
+            <p>{{ getContactData().position || '未知' }}</p>
+          </ion-label>
+        </ion-item>
+
+        <!-- 跟进人列表 -->
+        <h3>跟进人</h3>
+        <div class="follow-users">
+          <ion-item *ngFor="let follow of getFollowUsers()">
+            <ion-label>
+              <h3>{{ follow.userid }}</h3>
+              <p *ngIf="follow.remark">备注: {{ follow.remark }}</p>
+              <p *ngIf="follow.description">描述: {{ follow.description }}</p>
+            </ion-label>
+          </ion-item>
+        </div>
+      </ion-card-content>
+    </ion-card>
+  </div>
+</div>
+```
+
+**关键特性**:
+
+1. **统一方法调用**:使用 `getCurrentChatObject()` 一次性获取会话对象
+2. **自动场景识别**:根据返回的 `GroupChat` 或 `Contact` 自动判断场景
+3. **data 字段访问**:通过 `getContactData()` 统一访问 Contact.data 中的信息
+4. **Helper 方法**:封装数据访问逻辑,简化模板代码
+5. **响应式设计**:适配移动端和桌面端
+
+**访问链接示例**:
+- 脑控科技: https://app.fmode.cn/dev/crm/chat/E4KpGvTEto/context
+- 映三色: https://app.fmode.cn/dev/crm/chat/cDL6R1hgSi/crm/context
+
+## 入口场景识别
+
+企微工具栏入口有多种场景,可通过 `entry` 字段判断:
+
+```typescript
+const context = await this.wework.getContext();
+
+switch (context.entry) {
+  case "group_chat_tools":
+    // 群聊侧边栏工具
+    console.log("从群聊工具栏进入");
+    break;
+
+  case "single_chat_tools":
+    // 单聊侧边栏工具
+    console.log("从单聊工具栏进入");
+    break;
+
+  case "contact_profile":
+    // 联系人详情页
+    console.log("从联系人详情进入");
+    break;
+
+  case "normal":
+    // 普通应用首页
+    console.log("从应用首页进入");
+    break;
+}
+```
+
+## 常见应用场景
+
+### 场景1:群发消息工具
+
+```typescript
+async broadcastMessage(message: string) {
+  // 获取当前群聊
+  const currentChat = await this.wework.getCurrentChat();
+
+  if (currentChat?.type === "chatId") {
+    const groupChat = await this.wework.syncGroupChat(currentChat.group);
+    const chatId = groupChat.get("chat_id");
+
+    // 发送消息
+    await this.wecorp.appchat.sendText(chatId, message);
+    console.log("消息已发送到群聊:", groupChat.get("name"));
+  }
+}
+```
+
+### 场景2:群成员管理
+
+```typescript
+async listGroupMembers() {
+  const currentChat = await this.wework.getCurrentChat();
+
+  if (currentChat?.type === "chatId") {
+    const groupChat = await this.wework.syncGroupChat(currentChat.group);
+    const memberList = groupChat.get("member_list");
+
+    console.log("群成员总数:", memberList.length);
+    memberList.forEach(member => {
+      console.log("成员:", member.userid, "加入时间:", member.join_time);
+    });
+  }
+}
+```
+
+### 场景3:添加客户标签
+
+```typescript
+async addTagToContact(tagIds: string[]) {
+  // ⭐ 使用统一方法
+  const { Contact } = await this.wework.getCurrentChatObject();
+
+  if (Contact) {
+    const externalUserId = Contact.get("external_userid");
+
+    // ⭐ 从 data 字段获取跟进人
+    const data = Contact.get("data");
+    const userid = data.follow_user[0].userid;
+
+    // 调用企微API添加标签
+    await this.wecorp.externalContact.markTag({
+      userid: userid,
+      external_userid: externalUserId,
+      add_tag: tagIds
+    });
+
+    console.log("标签已添加到联系人:", Contact.get("name"));
+  }
+}
+```
+
+### 场景4:查看客户跟进记录
+
+```typescript
+async showFollowHistory() {
+  // ⭐ 使用统一方法
+  const { Contact } = await this.wework.getCurrentChatObject();
+
+  if (Contact) {
+    // ⭐ 从 data 字段获取跟进人列表
+    const data = Contact.get("data");
+    const followUsers = data.follow_user || [];
+
+    followUsers.forEach(follow => {
+      console.log("跟进人:", follow.userid);
+      console.log("备注:", follow.remark);
+      console.log("描述:", follow.description);
+      console.log("添加时间:", new Date(follow.createtime * 1000));
+    });
+  }
+}
+```
+
+## 数据同步最佳实践
+
+### 1. 按需同步
+
+```typescript
+// ✅ 推荐:仅在需要时同步
+async loadGroupChat() {
+  const currentChat = await this.wework.getCurrentChat();
+
+  if (currentChat?.group) {
+    // syncGroupChat 会自动判断是否需要保存
+    const groupChat = await this.wework.syncGroupChat(currentChat.group);
+    return groupChat;
+  }
+}
+
+// ❌ 不推荐:频繁无意义的同步
+setInterval(async () => {
+  const currentChat = await this.wework.getCurrentChat();
+  await this.wework.syncGroupChat(currentChat.group); // 浪费资源
+}, 1000);
+```
+
+### 2. 错误处理
+
+```typescript
+async loadContact() {
+  try {
+    const currentChat = await this.wework.getCurrentChat();
+
+    if (currentChat?.contact) {
+      const contactInfo = await this.wecorp.externalContact.get(currentChat.id);
+      const contact = await this.wework.syncContact(contactInfo.external_contact);
+      return contact;
+    } else {
+      console.warn("当前不在联系人会话中");
+      return null;
+    }
+  } catch (error) {
+    console.error("加载联系人失败:", error);
+
+    // 友好提示
+    if (error.code === 84061) {
+      alert("无权限访问该联系人信息");
+    } else {
+      alert("加载失败,请重试");
+    }
+
+    return null;
+  }
+}
+```
+
+### 3. 缓存策略
+
+```typescript
+export class MyComponent {
+  private cachedGroupChat: FmodeObject | null = null;
+  private cacheTime: number = 0;
+  private CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
+
+  async getGroupChat(): Promise<FmodeObject> {
+    const now = Date.now();
+
+    // 使用缓存
+    if (this.cachedGroupChat && (now - this.cacheTime < this.CACHE_DURATION)) {
+      return this.cachedGroupChat;
+    }
+
+    // 重新加载
+    const currentChat = await this.wework.getCurrentChat();
+    this.cachedGroupChat = await this.wework.syncGroupChat(currentChat.group);
+    this.cacheTime = now;
+
+    return this.cachedGroupChat;
+  }
+}
+```
+
+## 权限说明
+
+### 需要的企微权限
+
+1. **获取客户详情** - `externalcontact:get`
+2. **获取客户群详情** - `externalcontact_groupchat:get`
+3. **发送应用消息** - `appchat:send`
+4. **企业客户标签管理** - `externalcontact_tag:manage`
+
+### 权限检查
+
+```typescript
+async checkPermissions() {
+  try {
+    // 尝试获取当前会话
+    const currentChat = await this.wework.getCurrentChat();
+
+    if (!currentChat) {
+      console.error("无法获取会话信息,可能缺少权限");
+      return false;
+    }
+
+    return true;
+  } catch (error) {
+    console.error("权限检查失败:", error);
+    return false;
+  }
+}
+```
+
+## 调试技巧
+
+### 1. 使用 wxdebug 进行调试
+
+`wxdebug` 是专门为企微开发设计的调试工具,会在控制台输出并弹窗显示调试信息。
+
+```typescript
+import { wxdebug } from 'fmode-ng/social';
+
+// 调试单个值
+const currentChat = await this.wework.getCurrentChat();
+wxdebug('当前会话信息', currentChat);
+
+// 调试多个值
+wxdebug('步骤1', '初始化完成', { cid: this.cid });
+
+// 调试 Parse Object
+const groupChat = await this.wxwork.syncGroupChat(currentChat.group);
+wxdebug('群聊同步完成', groupChat.toJSON());
+```
+
+**推荐调试流程**(参考 page-chat-context):
+
+```typescript
+async loadData() {
+  this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
+  wxdebug('1. SDK初始化完成', { cid: this.cid });
+
+  try {
+    this.currentChat = await this.wxwork.getCurrentChat();
+    wxdebug('2. getCurrentChat返回', this.currentChat);
+  } catch (err) {
+    wxdebug('2. getCurrentChat失败', err);
+  }
+
+  if (this.currentChat?.type === "chatId" && this.currentChat?.group) {
+    wxdebug('3. 检测到群聊场景', this.currentChat.group);
+    this.groupChat = await this.wxwork.syncGroupChat(this.currentChat.group);
+    wxdebug('4. 群聊同步完成', this.groupChat?.toJSON());
+  }
+}
+```
+
+### 2. 查看当前会话信息
+
+```typescript
+const currentChat = await this.wework.getCurrentChat();
+console.log("会话类型:", currentChat.type);
+console.log("会话ID:", currentChat.id);
+console.log("详细信息:", currentChat);
+```
+
+### 3. 查看同步后的数据
+
+```typescript
+const groupChat = await this.wxwork.syncGroupChat(currentChat.group);
+console.log("GroupChat Parse Object:", groupChat.toJSON());
+```
+
+### 4. 模拟不同入口场景
+
+在开发环境可以通过路由参数模拟:
+
+```
+# 群聊场景
+/demo/:cid/group/:gid/message
+
+# 联系人场景
+/crm/:cid/contact/:contactId/detail
+
+# 聊天上下文演示
+/chat/:cid/context
+```
+
+## 注意事项
+
+1. **getCurrentChat() 依赖企微环境**
+   - 必须在企微客户端内使用
+   - 需要先调用 `registerCorpWithSuite()` 注册 JSAPI
+
+2. **syncGroupChat() 会生成入群方式**
+   - 首次同步会调用企微API生成入群链接和二维码
+   - 可能需要一定时间,建议显示加载状态
+
+3. **syncContact() 包含跟进人信息**
+   - `follow_user` 数组可能为空或包含多个跟进人
+   - 跟进人信息包含备注、标签等敏感数据
+
+4. **数据同步是增量的**
+   - `syncGroupChat` 和 `syncContact` 只在数据变化时保存
+   - 避免频繁调用,建议根据业务需要同步
+
+## 总结
+
+通过 `getCurrentChat()`、`syncGroupChat()` 和 `syncContact()` 三个核心方法,可以轻松实现企微工具栏页面的开发:
+
+✅ **一行获取上下文** - `await this.wework.getCurrentChat()`
+✅ **自动同步数据** - `syncGroupChat()` / `syncContact()`
+✅ **统一数据格式** - Parse Server 标准化存储
+✅ **支持离线查询** - 数据持久化到 Parse
+
+开发企微工具栏功能时,只需关注业务逻辑,数据同步由 SDK 自动处理!

+ 568 - 0
rules/wxwork/guard-wxwork.md

@@ -0,0 +1,568 @@
+# WxworkAuthGuard 企微路由守卫使用指南
+
+## 概述
+
+`WxworkAuthGuard` 是一个用于企业微信页面的路由守卫,提供以下核心功能:
+
+1. **企微授权认证** - 自动处理企业微信 OAuth 授权流程
+2. **用户信息同步** - 同步用户信息到 Profile(企业员工)或 UserSocial(外部用户)
+3. **自动登录/注册** - 自动创建/登录 Parse `_User` 账号
+4. **用户指针同步** - 自动同步 `user` 字段到 Profile/UserSocial
+
+## 核心特性
+
+### ⭐ 自动登录机制
+
+守卫会自动为通过企微认证的用户创建 Parse `_User` 账号:
+
+- **用户名**: `userid`(企业员工)或 `external_userid`(外部用户)
+- **密码**: userid/external_userid 的 **后6位**
+- **自动注册**: 用户不存在时自动注册
+- **自动登录**: 用户存在时自动登录
+- **用户指针**: 自动同步 `user` 字段到 Profile/UserSocial
+
+### 📍 标记位置
+
+守卫中的自动登录逻辑位于:
+- **文件**: `fmode-ng/src/lib/social/wxwork/wxwork-auth.guard.ts`
+- **位置**: 第 71-157 行 `autoLogin()` 函数
+- **调用点**: 第 21 行和第 66 行(标记为 ⭐)
+
+## 路由配置
+
+### 基础配置
+
+```typescript
+import { WxworkAuthGuard } from 'fmode-ng';
+
+export const routes: Routes = [
+  {
+    path: ":cid/auth/user-confirm",
+    canActivate: [WxworkAuthGuard],
+    loadComponent: () => import("./page-user-confirm.component")
+      .then((m) => m.PageUserConfirmComponent)
+  }
+];
+```
+
+### 路由参数
+
+守卫会自动从路由中提取以下参数:
+
+- **cid** (必需): 公司帐套ID(Company.objectId)
+- **appId** (可选): 应用ID,默认为 "crm"
+
+支持的参数来源:
+- `route.paramMap.get("cid")` - 路径参数
+- `route.queryParamMap.get("cid")` - 查询参数
+
+### 配置示例
+
+```typescript
+// 带 cid 和 appId 的路由
+{
+  path: ":cid/auth/:appId/user-confirm",
+  canActivate: [WxworkAuthGuard],
+  loadComponent: () => ...
+}
+
+// 仅带 cid 的路由(appId 默认为 "crm")
+{
+  path: ":cid/auth/user-confirm",
+  canActivate: [WxworkAuthGuard],
+  loadComponent: () => ...
+}
+
+// 查询参数方式
+// 访问: /some-page?cid=E4KpGvTEto
+{
+  path: "some-page",
+  canActivate: [WxworkAuthGuard],
+  loadComponent: () => ...
+}
+```
+
+## WxworkSDK 使用方法
+
+### 初始化 SDK
+
+```typescript
+import { WxworkSDK } from 'fmode-ng/core';
+
+// 在组件中初始化
+this.wxwork = new WxworkSDK({
+  cid: this.cid,      // 公司帐套ID
+  appId: this.appId   // 应用ID,默认 "crm"
+});
+```
+
+### ⭐ 核心方法:getCurrentUser()
+
+**最简单的方式获取当前用户信息**
+
+```typescript
+// 获取当前用户的 Profile 或 UserSocial
+const currentUser = await this.wxwork.getCurrentUser();
+
+if (currentUser) {
+  console.log("用户类型:", currentUser.className); // "Profile" 或 "UserSocial"
+  console.log("用户ID:", currentUser.id);
+  console.log("用户名:", currentUser.get("name"));
+  console.log("手机号:", currentUser.get("mobile"));
+
+  // Profile 特有字段
+  if (currentUser.className === "Profile") {
+    console.log("员工ID:", currentUser.get("userid"));
+    console.log("部门:", currentUser.get("department"));
+    console.log("职位:", currentUser.get("position"));
+  }
+
+  // UserSocial 特有字段
+  if (currentUser.className === "UserSocial") {
+    console.log("外部用户ID:", currentUser.get("data").external_userid);
+    console.log("外部用户类型:", currentUser.get("type"));
+  }
+}
+```
+
+### 其他常用方法
+
+#### getUserinfo() - 获取缓存的授权信息
+
+```typescript
+// 从 localStorage 获取 USERINFO
+const userInfo = await this.wxwork.getUserinfo();
+
+console.log(userInfo.userid);           // 企业员工ID
+console.log(userInfo.external_userid);  // 外部用户ID
+console.log(userInfo.mobile);           // 手机号
+console.log(userInfo.name);             // 姓名
+console.log(userInfo.avatar);           // 头像
+```
+
+**存储位置**: `localStorage[${cid}/USERINFO]`
+
+#### getContactOrProfile() - 查询或创建用户记录
+
+```typescript
+const userInfo = await this.wxwork.getUserinfo();
+const profileOrSocial = await this.wxwork.getContactOrProfile(userInfo);
+
+console.log(profileOrSocial.className); // "Profile" 或 "UserSocial"
+console.log(profileOrSocial.id);
+```
+
+## Parse.User.current() 使用
+
+由于守卫自动登录,任何挂载守卫的页面都可以直接使用:
+
+```typescript
+import { FmodeParse } from 'fmode-ng/parse';
+const Parse = FmodeParse.with("nova");
+
+// 获取当前登录的 _User
+const currentUser = Parse.User.current();
+
+if (currentUser) {
+  console.log("用户名:", currentUser.get("username")); // userid 或 external_userid
+  console.log("手机号:", currentUser.get("mobilePhoneNumber"));
+  console.log("邮箱:", currentUser.get("email"));
+
+  // 获取关联的 Profile/UserSocial
+  const wxwork = new WxworkSDK({ cid: this.cid, appId: "crm" });
+  const profileOrSocial = await wxwork.getCurrentUser();
+  console.log("关联用户:", profileOrSocial.get("name"));
+}
+```
+
+## 页面开发最佳实践
+
+### 推荐方式:使用 getCurrentUser()
+
+```typescript
+export class MyPageComponent implements OnInit {
+  cid: string = '';
+  appId: string = '';
+  wxwork: WxworkSDK;
+  currentUser: FmodeObject | null = null;
+
+  constructor(private route: ActivatedRoute) {}
+
+  async ngOnInit() {
+    this.route.paramMap.subscribe(async params => {
+      this.cid = params.get('cid') || '';
+      this.appId = params.get('appId') || 'crm';
+
+      // ⭐ 最简单的方式:直接获取当前用户
+      this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
+      this.currentUser = await this.wxwork.getCurrentUser();
+
+      if (this.currentUser) {
+        console.log("用户类型:", this.currentUser.className);
+        console.log("用户姓名:", this.currentUser.get("name"));
+
+        // 直接使用用户信息
+        this.loadUserData();
+      }
+    });
+  }
+
+  async loadUserData() {
+    // 使用 this.currentUser 进行业务逻辑
+    if (this.currentUser.className === "Profile") {
+      // 企业员工逻辑
+    } else {
+      // 外部用户逻辑
+    }
+  }
+}
+```
+
+### 传统方式对比
+
+```typescript
+// ❌ 不推荐:手动查询(代码冗长)
+async loadUserInfo() {
+  const userInfo = await this.wxwork.getUserinfo();
+
+  if (userInfo.userid) {
+    const query = new Parse.Query('Profile');
+    query.equalTo('userid', userInfo.userid);
+    const profile = await query.first();
+    // ... 处理 Profile
+  } else if (userInfo.external_userid) {
+    const query = new Parse.Query('UserSocial');
+    query.equalTo('data.external_userid', userInfo.external_userid);
+    const userSocial = await query.first();
+    // ... 处理 UserSocial
+  }
+}
+
+// ✅ 推荐:使用 getCurrentUser()(简洁高效)
+async loadUserInfo() {
+  const currentUser = await this.wxwork.getCurrentUser();
+  // 直接使用 currentUser,无需判断类型
+}
+```
+
+## 数据结构
+
+### Profile 表(企业员工)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| userid | String | 企业员工微信ID |
+| company | Pointer<Company> | 所属企业 |
+| user | Pointer<_User> | 关联的 Parse 用户 |
+| name | String | 姓名 |
+| avatar | String | 头像URL |
+| mobile | String | 手机号 |
+| email | String | 邮箱 |
+| department | String | 部门 |
+| position | String | 职位 |
+| corp_name | String | 企业名称 |
+| isVerified | Boolean | 身份确认状态 |
+
+### UserSocial 表(外部用户)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| data.external_userid | String | 外部用户微信ID |
+| company | Pointer<Company> | 所属企业 |
+| user | Pointer<_User> | 关联的 Parse 用户 |
+| name | String | 姓名 |
+| avatar | String | 头像URL |
+| mobile | String | 手机号 |
+| type | String | 外部用户类型 |
+| corp_name | String | 企业名称 |
+| isVerified | Boolean | 身份确认状态 |
+
+### _User 表(Parse 用户)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| username | String | userid 或 external_userid |
+| password | String | userid/external_userid 后6位 |
+| mobilePhoneNumber | String | 手机号(同步自企微) |
+| email | String | 邮箱(同步自企微) |
+
+## 授权流程
+
+### 1. 移动端(微信环境)
+
+```
+用户访问页面
+    ↓
+检测到微信环境
+    ↓
+OAuth 跳转授权
+    ↓
+回调获取 code
+    ↓
+调用 getUserinfo(code)
+    ↓
+同步用户信息到 Profile/UserSocial
+    ↓
+自动登录/注册 _User
+    ↓
+同步 user 指针
+    ↓
+进入页面
+```
+
+### 2. PC 端(非微信环境)
+
+```
+用户访问页面
+    ↓
+弹出企微扫码登录
+    ↓
+扫码成功获取 code
+    ↓
+调用 getUserinfo(code)
+    ↓
+同步用户信息到 Profile/UserSocial
+    ↓
+自动登录/注册 _User
+    ↓
+同步 user 指针
+    ↓
+进入页面
+```
+
+### 3. 已授权用户(有缓存)
+
+```
+用户访问页面
+    ↓
+从 localStorage 读取 USERINFO
+    ↓
+验证用户信息有效
+    ↓
+同步用户信息到 Profile/UserSocial
+    ↓
+自动登录(如未登录)
+    ↓
+同步 user 指针
+    ↓
+直接进入页面(无需重新授权)
+```
+
+## 常见场景示例
+
+### 场景1:展示当前用户信息
+
+```typescript
+async showUserInfo() {
+  const currentUser = await this.wxwork.getCurrentUser();
+
+  if (currentUser) {
+    const name = currentUser.get("name");
+    const avatar = currentUser.get("avatar");
+    const mobile = currentUser.get("mobile");
+
+    console.log(`用户: ${name}, 手机: ${mobile}`);
+  }
+}
+```
+
+### 场景2:判断用户类型
+
+```typescript
+async checkUserType() {
+  const currentUser = await this.wxwork.getCurrentUser();
+
+  if (currentUser.className === "Profile") {
+    console.log("这是企业员工");
+    const userid = currentUser.get("userid");
+    // 执行员工相关逻辑
+  } else if (currentUser.className === "UserSocial") {
+    console.log("这是外部用户");
+    const externalUserId = currentUser.get("data").external_userid;
+    // 执行外部用户相关逻辑
+  }
+}
+```
+
+### 场景3:访问 Parse User
+
+```typescript
+async accessParseUser() {
+  // 方式1:通过 Parse.User.current()
+  const parseUser = Parse.User.current();
+  console.log("Parse 用户名:", parseUser.get("username"));
+
+  // 方式2:通过 Profile/UserSocial 的 user 指针
+  const currentUser = await this.wxwork.getCurrentUser();
+  const userPointer = currentUser.get("user");
+
+  if (userPointer) {
+    const user = await new Parse.Query("_User").get(userPointer.id);
+    console.log("关联的 Parse 用户:", user.get("username"));
+  }
+}
+```
+
+### 场景4:更新用户信息
+
+```typescript
+async updateUserInfo() {
+  const currentUser = await this.wxwork.getCurrentUser();
+
+  if (currentUser) {
+    currentUser.set("name", "新名字");
+    currentUser.set("mobile", "13800138000");
+    await currentUser.save();
+
+    console.log("用户信息更新成功");
+  }
+}
+```
+
+### 场景5:判断用户是否已确认身份
+
+```typescript
+async checkVerified() {
+  const currentUser = await this.wxwork.getCurrentUser();
+
+  if (currentUser.get("isVerified")) {
+    console.log("用户已确认身份");
+    // 允许访问完整功能
+  } else {
+    console.log("用户未确认身份");
+    // 跳转到身份确认页面
+    this.router.navigate([`/auth/${this.cid}/user-confirm`]);
+  }
+}
+```
+
+## 开发注意事项
+
+### 1. 守卫必须配置 cid 参数
+
+```typescript
+// ✅ 正确:路径中包含 cid
+{ path: ":cid/my-page", canActivate: [WxworkAuthGuard], ... }
+
+// ✅ 正确:查询参数中包含 cid
+// 访问: /my-page?cid=E4KpGvTEto
+{ path: "my-page", canActivate: [WxworkAuthGuard], ... }
+
+// ❌ 错误:缺少 cid 参数
+{ path: "my-page", canActivate: [WxworkAuthGuard], ... }
+```
+
+### 2. 初始化 SDK 时必须提供 cid
+
+```typescript
+// ✅ 正确
+this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
+
+// ❌ 错误:缺少 cid
+this.wxwork = new WxworkSDK({ appId: this.appId });
+```
+
+### 3. getCurrentUser() 依赖守卫先执行
+
+```typescript
+// getCurrentUser() 会读取守卫写入的 localStorage
+// 必须在守卫执行后调用
+async ngOnInit() {
+  // 守卫已执行,localStorage 中有 USERINFO
+  const currentUser = await this.wxwork.getCurrentUser();
+}
+```
+
+### 4. Parse.User.current() 可用性
+
+```typescript
+// 只有在守卫执行后,Parse.User.current() 才会有值
+const currentUser = Parse.User.current();
+
+if (currentUser) {
+  // 用户已自动登录
+} else {
+  // 用户未登录(不应该出现在守卫保护的页面)
+}
+```
+
+### 5. user 指针同步
+
+守卫会自动同步 `user` 字段,但如果需要手动同步:
+
+```typescript
+async syncUserPointer() {
+  const parseUser = Parse.User.current();
+  const currentUser = await this.wxwork.getCurrentUser();
+
+  if (parseUser && currentUser && !currentUser.get("user")) {
+    currentUser.set("user", parseUser.toPointer());
+    await currentUser.save();
+  }
+}
+```
+
+## 调试技巧
+
+### 1. 查看 localStorage 中的 USERINFO
+
+```typescript
+const userInfoStr = localStorage.getItem(`${this.cid}/USERINFO`);
+const userInfo = JSON.parse(userInfoStr);
+console.log("缓存的用户信息:", userInfo);
+```
+
+### 2. 查看当前登录用户
+
+```typescript
+const currentUser = Parse.User.current();
+console.log("Parse User:", currentUser?.toJSON());
+```
+
+### 3. 查看 Profile/UserSocial
+
+```typescript
+const currentUser = await this.wxwork.getCurrentUser();
+console.log("Profile/UserSocial:", currentUser?.toJSON());
+```
+
+### 4. 清除缓存重新授权
+
+```typescript
+// 清除用户信息缓存
+localStorage.removeItem(`${this.cid}/USERINFO`);
+
+// 退出登录
+await Parse.User.logOut();
+
+// 刷新页面重新授权
+location.reload();
+```
+
+## 安全性说明
+
+### 密码生成规则
+
+- **规则**: 使用 userid/external_userid 的后6位作为密码
+- **示例**:
+  - userid: `ZhangSan123456` → 密码: `123456`
+  - external_userid: `wmABCDEF123456` → 密码: `123456`
+
+### 安全建议
+
+1. **企业内部使用**: 该机制适用于企业内部系统,通过企微授权保证安全性
+2. **不建议外部暴露**: 不要将密码规则暴露给外部用户
+3. **升级密码策略**: 如需更高安全性,可修改 `autoLogin()` 函数的密码生成逻辑
+4. **ACL 控制**: 为 Profile/UserSocial 配置合适的 ACL 权限
+
+## 总结
+
+使用 `WxworkAuthGuard` 和 `WxworkSDK.getCurrentUser()` 可以极大简化企微页面开发:
+
+✅ **一行代码获取用户** - `await this.wxwork.getCurrentUser()`
+✅ **自动登录** - 无需手动处理 Parse 用户登录
+✅ **统一接口** - Profile 和 UserSocial 统一处理
+✅ **用户指针同步** - 自动关联 Parse _User
+✅ **Parse.User.current() 可用** - 方便其他模块使用
+
+开发企微功能页面时,只需配置守卫,然后使用 `getCurrentUser()` 即可快速开始业务逻辑开发!