# 企业微信模块(活动 / 通知 / 通讯录)- 前端联调方案 ## 涉及页面 - 活动管理(列表 + 新建/编辑弹窗) - 活动详情(含报名记录列表、报名统计、二维码展示) - 通知管理(列表 + 新建/编辑弹窗) - 成员选择器(推送时挑选部门/成员,数据来自企业微信通讯录) - 活动报名落地页(**不需要前端开发**,由后端直接渲染 HTML,见文末说明) ## 本次新增能力 | 能力 | 说明 | |------|------| | 发起活动 | 创建草稿 → 编辑 → 发布 | | 推送给成员 | 通过企业微信应用消息推送,成员点消息里的按钮直达报名页 | | 报名二维码 | 后端生成 PNG,可后台展示/下载打印 | | 扫码报名 | 成员扫码打开报名页,填姓名+手机号提交,**免登录** | ## API 所有接口前缀 `/admin-api`。 ### 活动管理 | 方法 | 路径 | 权限标识 | 说明 | |------|------|----------|------| | POST | `/wxcp/activity/create` | `wxcp:activity:create` | 创建活动(存为草稿) | | PUT | `/wxcp/activity/update` | `wxcp:activity:update` | 修改活动 | | DELETE | `/wxcp/activity/delete` | `wxcp:activity:delete` | 删除活动 | | GET | `/wxcp/activity/get` | `wxcp:activity:query` | 活动详情 | | GET | `/wxcp/activity/page` | `wxcp:activity:query` | 活动分页 | | PUT | `/wxcp/activity/publish` | `wxcp:activity:update` | 发布活动 | | PUT | `/wxcp/activity/finish` | `wxcp:activity:update` | 结束活动 | | PUT | `/wxcp/activity/cancel` | `wxcp:activity:update` | 取消活动 | **活动分页请求参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | pageNo | Integer | 是 | 页码 | | pageSize | Integer | 是 | 每页条数 | | title | String | 否 | 活动标题,模糊匹配 | | status | Integer | 否 | 状态:0草稿 1已发布 2已结束 3已取消 | | pushStatus | Integer | 否 | 推送状态:0未推送 1已推送 | | startTime | DateTime[] | 否 | 活动开始时间范围,格式 `yyyy-MM-dd HH:mm:ss` | **创建/修改请求参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | Long | 修改时必填 | 编号 | | title | String | 是 | 活动标题,≤128 字符 | | content | String | 否 | 活动详情 | | coverUrl | String | 否 | 封面图地址 | | location | String | 否 | 活动地点,≤255 字符 | | startTime | DateTime | 否 | 活动开始时间 | | endTime | DateTime | 否 | 活动结束时间 | | signupStartTime | DateTime | 否 | 报名开始时间 | | signupEndTime | DateTime | 否 | 报名结束时间 | | signupLimit | Integer | 是 | 报名上限,**不限传 0** | | pushScope | Integer | 是 | 推送范围:0全员 1指定部门 2指定成员 | | pushTarget | String | 否 | 推送目标,逗号分隔;范围选 1 或 2 时必填 | | remark | String | 否 | 备注 | **活动响应新增字段:** | 字段 | 类型 | 说明 | |------|------|------| | statusName | String | 状态名,如「已发布」 | | pushScopeName | String | 推送范围名,如「全员」 | | signupCount | Integer | 已报名人数 | | pushStatus | Integer | 0未推送 1已推送 | | pushTime | DateTime | 最近推送时间 | ### 推送与二维码 | 方法 | 路径 | 权限标识 | 说明 | |------|------|----------|------| | POST | `/wxcp/activity/push` | `wxcp:activity:push` | 推送给企业微信成员 | | GET | `/wxcp/activity/qrcode` | `wxcp:activity:query` | 获取报名二维码(返回 PNG) | | GET | `/wxcp/activity/signup-url` | `wxcp:activity:query` | 获取报名页地址 | **推送请求参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | Long | 是 | 活动编号 | | pushScope | Integer | 否 | 覆盖活动上的推送范围,不传则沿用 | | pushTarget | String | 否 | 覆盖活动上的推送目标,不传则沿用 | **推送响应:** ```json { "code": 0, "data": { "success": true, "errCode": 0, "message": "推送成功" } } ``` **二维码接口:** 直接返回 `image/png` 字节流,不是 JSON。 页面里把这个 URL 当图片地址用即可(如 ``),需要带令牌时自行拼查询参数。 ### 报名记录 | 方法 | 路径 | 权限标识 | 说明 | |------|------|----------|------| | GET | `/wxcp/activity/signup/page` | `wxcp:signup:query` | 报名记录分页 | | PUT | `/wxcp/activity/signup/cancel` | `wxcp:signup:update` | 取消报名 | | PUT | `/wxcp/activity/signup/check-in` | `wxcp:signup:update` | 签到 | **分页请求参数:** `activityId`、`name`(模糊)、`mobile`(模糊)、`status` **响应字段:** `id`、`activityId`、`activityTitle`、`wxUserId`、`name`、`mobile`、`deptName`、`status`、`statusName`、`signupTime`、`checkInTime`、`remark` ### 公开接口(免登录,前端**不需要**调用) | 方法 | 路径 | 说明 | |------|------|------| | GET | `/wxcp/activity/signup-page?id={活动编号}` | 报名落地页 HTML,由后端渲染 | | POST | `/wxcp/activity/signup` | 提交报名(支持 `content` 文字 + `blobIds` 附件) | ### 报名统计与附件 | 方法 | 路径 | 权限标识 | 说明 | |------|------|----------|------| | GET | `/wxcp/activity/stats?id=` | `wxcp:activity:query` | 报名统计 | | GET | `/wxcp/activity/signup/attachments?signupId=` | `wxcp:signup:query` | 某条报名上传的图片/附件 | **统计响应:** ```json { "activityId": 1, "activityTitle": "2026春季团建活动", "signupLimit": 50, "totalSignup": 1, "signedUpCount": 1, "checkedInCount": 0, "canceledCount": 0, "remaining": 49, "signupPercent": 2, "checkInPercent": 0, "deptStats": [{ "name": "技术部", "count": 3 }], "dateStats": [{ "name": "2026-09-29", "count": 1 }], "sourceStats": [{ "name": "企业微信身份", "count": 0 }, { "name": "表单填写", "count": 1 }] } ``` > `remaining` / `signupPercent` 在活动**不限名额**(`signupLimit=0`)时为 `null`,前端要显示"不限"。 > 统计**不含已取消**的报名,所以 `totalSignup ≠ 报名记录总条数`。 ### 通知管理 | 方法 | 路径 | 权限标识 | 说明 | |------|------|----------|------| | POST | `/wxcp/notice/create` | `wxcp:notice:create` | 创建通知(存为草稿) | | PUT | `/wxcp/notice/update` | `wxcp:notice:update` | 修改通知 | | DELETE | `/wxcp/notice/delete` | `wxcp:notice:delete` | 删除通知 | | GET | `/wxcp/notice/get` | `wxcp:notice:query` | 通知详情 | | GET | `/wxcp/notice/page` | `wxcp:notice:query` | 通知分页 | | PUT | `/wxcp/notice/publish` | `wxcp:notice:update` | 发布通知 | | PUT | `/wxcp/notice/revoke` | `wxcp:notice:update` | 撤回通知 | | POST | `/wxcp/notice/push` | `wxcp:notice:push` | 推送通知 | **通知与活动的差异:** | 项 | 活动 | 通知 | |----|------|------| | 推送消息类型 | 文本卡片(带「立即报名」按钮跳报名页) | 纯文本 | | 正文长度 | 由卡片模板限制 | 超过 600 字**自动截断**并提示到系统内查看 | | 状态 | 草稿/已发布/已结束/已取消 | 草稿/已发布/已撤回 | | 推送前提 | 必须「已发布」 | 必须「已发布」 | ### 企业微信通讯录 | 方法 | 路径 | 权限标识 | 说明 | |------|------|----------|------| | GET | `/wxcp/member/department/tree` | `wxcp:member:query` | 部门树(含 children) | | GET | `/wxcp/member/department/list` | `wxcp:member:query` | 部门平铺列表(做下拉更方便) | | GET | `/wxcp/member/list?departmentId=&fetchChild=` | `wxcp:member:query` | 成员列表 | **成员响应字段:** `userId`(推送时填这个)、`name`、`mobile`、`email`、`avatar`、`position`、`gender`、`status`、`departmentIds`、`departmentNames` > 这几个接口是**实时调用企业微信**的,企业微信对通讯录接口有频率限制。前端请做缓存(如打开选择器时拉一次),**不要**在搜索框里每输入一个字符就请求一次。 ## 业务流程与数据带入 1. 新建活动 → 保存为**草稿**,此时扫码报名会提示「报名已结束」 2. 点「发布」→ 状态变「已发布」,此时二维码和报名页才可用 3. 点「推送」→ 成员企业微信收到卡片消息,标题为活动标题,正文含**活动时间 / 地点 / 报名截止 / 名额**,按钮「立即报名」直达报名页 4. 成员点按钮或扫码 → 打开报名页 → 填姓名和手机号 → 提交 5. 报名成功 → 活动 `signupCount` 自动 +1 6. 后台在活动详情的报名记录列表里可看到该成员,可执行「取消报名」「签到」 **数据带入关系:** | 来源 | 目标 | 带入内容 | |------|------|----------| | 活动编辑保存 | 推送消息 | 标题、时间、地点、报名截止、名额 | | 活动编号 | 二维码内容 | `{signupUrlPrefix}/admin-api/wxcp/activity/signup-page?id={id}` | | 扫码报名 | 报名记录 | 活动编号 | ## 字段展示规则 | 字段 | 展示位置 | 说明 | |------|----------|------| | title | 列表、详情 | 活动标题,点击进详情 | | statusName | 列表、详情 | 用后端返回的中文名,前端不要自己映射 | | startTime ~ endTime | 列表、详情 | 活动时间范围 | | signupCount / signupLimit | 列表、详情 | `0/50` 这种形式;上限为 0 时显示「不限」 | | pushStatus / pushTime | 列表、详情 | 未推送 / 已推送 + 时间 | | pushScopeName | 详情 | 推送范围中文名 | | qrcode(图片) | 详情 | 报名二维码,需支持下载 | | statusName(报名) | 报名列表 | 已报名 / 已取消 / 已签到 | ## 业务规则说明 | 场景 | 规则 | |------|------| | 创建活动 | 一律存为草稿,不可直接创建为已发布 | | 修改活动 | 已结束、已取消的活动不可修改;草稿和已发布可改 | | 推送前提 | **活动必须是「已发布」状态**,草稿状态推送会报「活动尚未发布,请先发布后再推送」 | | 推送范围=全员 | 使用企业微信 `@all`,即应用可见范围内所有人 | | 推送范围=指定成员 | `pushTarget` 填成员账号(UserId),多个用**英文逗号**分隔 | | 推送范围=指定部门 | `pushTarget` 填部门 ID,多个用英文逗号分隔 | | 推送失败 | 接口返回 `success=false` + 企业微信错误码,**不会抛异常**;前端应把 `message` 直接提示给用户 | | 推送成功后 | 活动 `pushStatus` 置 1、`pushTime` 记录时间;重复推送会覆盖这两个字段 | | 报名时间窗口 | 未到 `signupStartTime` 或已过 `signupEndTime` 均不能报名 | | 报名名额 | `signupLimit=0` 表示不限;已满时提示「报名人数已满」 | | 重复报名 | 有企业微信身份按成员账号判重;普通浏览器按**手机号**判重 | | 取消后重新报名 | 复用原记录,不会产生第二条历史 | | 取消报名 | 活动 `signupCount` 自动 -1 | ## 注意事项 - **二维码接口返回的是图片二进制,不是 JSON**,不要用普通 request 封装去取,直接用图片 URL。 - **`signupLimit` 必填**,不限要传 `0` 而不是留空,否则会校验失败。 - **推送范围选「指定部门/成员」时 `pushTarget` 不能为空**,否则报「推送目标不能为空」。 - 推送是**同步**调用企业微信接口,成员多时可能耗时 1~3 秒,前端按钮要有 loading。 - 活动 `content` 字段在报名页是**按纯文本渲染**的(做了 HTML 转义防 XSS)。如果活动详情需要富文本排版,需要另行接入富文本白名单过滤,当前版本不支持。 - 报名页地址依赖后端配置 `wx.cp.signup-url-prefix`(当前为 `http://wcf.viphk.nnhk.cc`)。该地址必须是**成员手机能访问到的外网地址**,否则二维码扫出来打不开。 - 报名记录里的 `wxUserId` 只有在**企业微信内打开报名页且带上了参数**时才有值;普通浏览器扫码报名时为空,此时按手机号去重。 - 权限标识需要在「系统管理 → 菜单管理」里配置好,否则接口会返回 403。 - **推送目标用的是 `userId`(成员账号,如 `LiuYi`),不是姓名、也不是手机号。** 从通讯录接口取 `userId` 填进 `pushTarget`。 - **附件上传走 system 模块的公共上传接口**:报名页先调 `POST /admin-api/system/storage-blob/public-upload`(表单字段名 `files`)拿到 `blobId`,再把 `blobIds` 随报名提交,由**服务端**完成与报名记录的绑定。管理后台查附件用 `/wxcp/activity/signup/attachments`。 - **报名的 `recordType` 固定为 `wxcp_activity_signup`**,已注册进 system 模块的 `StorageRecordTypeEnum` 白名单。以后新增别的附件关联类型,必须同步注册,否则绑定会报「无效的记录类型」。 - 通讯录接口**实时调企业微信**,有频率限制,前端务必缓存,不要随输入实时请求。 - 通知正文超过 600 字会**自动截断**(企业微信文本消息有长度上限),截断提示会写在消息末尾。 ## 关于报名落地页 报名页 `GET /admin-api/wxcp/activity/signup-page?id=xxx` 由**后端直接渲染 HTML**(含内联样式和少量 JS),前端**无需开发**。 这样做的原因:二维码必须指向一个成员手机能直接打开的页面,而后端渲染可以立即跑通「推送 → 扫码 → 报名」闭环,不必等前端排期。若后续希望改为前端 SPA 页面,把二维码内容改成前端路由地址即可,后端接口(`/wxcp/activity/signup`)不用动。 > 该页面的路径刻意放在 `/admin-api` 下(而非 `/app-api`),因为前端 Vite 开发代理只转发 `/admin-api`,放 `/app-api` 会导致扫码后请求打到前端静态服务返回 404。