chore(docs): docs/ 目录全面重新编号 + 重组
**重构前**(旧编号 02-11): - docs/02-产品需求/ → 00 产品规划/PRD - docs/03-技术架构/ → 01-05 子目录散落 - docs/04-原型设计/ → 01-02 产品设计(HTML 原型) - docs/05-原型设计/ → screens/ - docs/06-测试素材/ → 02-E2E / 03-功能 / 04-版本测试 - docs/07-项目管理/ → 任务说明书/日报/计划 - docs/08-安全审计/ → 审计报告 - docs/09-堡垒运维/ → toolbox / deploy - docs/10-项目管理/ → 任务说明书(重复) - docs/11-历史归档/ → deploy-nas-archived **重构后**(新编号 00-07,语义化): - docs/00-产品开发流程与文档管理规范.md - docs/00-版本迭代总览.md - docs/01-产品文档/ (PRD/原型/认证/会话/AI 服务/坐席/集成) - docs/02-技术文档/ (技术方案/架构图/重构记录/前端改造/实现配置) - docs/03-测试文档/ (E2E/功能用例/版本报告/缺陷单) - docs/04-运维文档/ (部署运维/运维指南) - docs/05-运营文档/ (品牌推广/用户手册) - docs/06-安全审计/ (审计报告) - docs/07-项目管理/ (任务说明书/日报/计划/看板) **净收益**: - 目录编号与产品文档管理规范对齐(按文档阶段 01-07 编号) - 消除 02-产品需求 与 10-项目管理 的编号重叠 - 子目录按文档类型分组(如 01-产品文档/00-产品规划、01-产品文档/01-认证与登录) - 把运维/安全/项目管理从 0X 散落改为 04/06/07 合计 494 文件 + 78495 行 / - 14076 行
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
# PRD: OpenClaw 安全合规检查与通知系统
|
||||
|
||||
> **版本**: v1.0
|
||||
> **日期**: 2026-07-20
|
||||
> **状态**: [草稿]
|
||||
> **作者**: Simon
|
||||
> **需求编号**: REQ-集成-001
|
||||
> **关联文档**:
|
||||
> - 原型: `01-产品文档/08-集成生态/原型-REQ-集成-001-OpenClaw合规检查-v1.0.html`
|
||||
> - 技术: `02-技术文档/实现配置/技术方案-REQ-集成-001-OpenClaw合规检查-v1.0.md`
|
||||
> - 测试: `03-测试文档/03-功能测试用例/TC-REQ-集成-001-OpenClaw合规检查.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 问题陈述 (Problem Statement)
|
||||
|
||||
### 背景
|
||||
在重大安全活动期间(如国家网络安全周、重大会议保障期),公司需要对终端进行合规审计。
|
||||
|
||||
> 💡 **本期目标**:以 OpenClaw 检测为 **一期试点**,验证安全策略检查与通知框架的可行性。
|
||||
>
|
||||
> **扩展性设计**:本系统设计为 **通用安全策略检查平台**,未来可低成本扩展其他策略和推送渠道。
|
||||
|
||||
### 问题描述
|
||||
- **现状**:目前依赖手工导出联软数据或通过火绒终端管理查看,缺乏自动化的定期检查和通知机制
|
||||
- **痛点**:
|
||||
- 无法及时发现违规软件安装
|
||||
- 依赖人工通知,效率低、易遗漏
|
||||
- 无法形成定期的合规审计报告
|
||||
|
||||
### 影响
|
||||
- **安全风险**:未经授权的远程软件可能成为攻击入口
|
||||
- **合规风险**:无法满足重大活动期间的安全审计要求
|
||||
- **业务影响**:IT 运维人员需要投入大量人工排查
|
||||
|
||||
---
|
||||
|
||||
## 2. 目标 (Goals)
|
||||
|
||||
| # | 目标 | 成功衡量标准 |
|
||||
|---|------|-------------|
|
||||
| G1 | **一期试点**:自动检测 OpenClaw 安装 | 系统可自动发现所有已安装 OpenClaw 的终端,覆盖率 ≥ 95% |
|
||||
| G2 | 及时通知当事人 | 检测到违规软件后,24 小时内通过企微发送通知给终端用户 |
|
||||
| G3 | 支持定时+手动执行 | 支持每日定时执行 + 管理员手动触发两种方式 |
|
||||
| G4 | 合规审计留痕 | 支持导出 Excel 审计报告,保留检测记录至少 6 个月 |
|
||||
| G5 | 活动期间管控 | 支持设置活动期间,自动开启检查 |
|
||||
| G6 | **框架可扩展** | 新增其他安全策略和推送渠道的成本 ≤ 1人天 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 非目标 (Non-Goals)
|
||||
|
||||
| # | 非目标 | 原因 |
|
||||
|---|--------|------|
|
||||
| N1 | **自动封禁/断网** | 仅做通知提醒,不做自动阻断,避免影响业务连续性 |
|
||||
| N2 | ~~**支持其他软件检查**~~ | ⚠️ 已调整为扩展性设计,详见下方框架说明 |
|
||||
| N3 | **实时监控** | 采用定时检查(非实时推送),5 分钟级延迟可接受 |
|
||||
| N4 | **移动端管理** | 本期仅支持 PC Web 端管理 |
|
||||
| N5 | **火绒+联软自动同步** | 联软数据本期采用手动导入 Excel,不做自动 API 对接 |
|
||||
|
||||
---
|
||||
|
||||
## 3.1 扩展性框架设计(重要)
|
||||
|
||||
> 📐 **架构理念**:将"检测策略"与"推送渠道"解耦,实现可扩展的安全合规平台
|
||||
|
||||
### 3.1.1 策略类型(Strategy)
|
||||
|
||||
| 策略ID | 策略名称 | 检测规则 | 数据源 | 状态 |
|
||||
|--------|----------|----------|--------|------|
|
||||
| openclaw_remote | OpenClaw/向日葵远程软件 | 软件名包含 OpenClaw/向日葵/sunflower | 火绒API | 🔵 一期 |
|
||||
| unknown_usb | 未知USB设备 | 未登记的USB设备接入 | 联软/火绒 | ⚪ 二期 |
|
||||
| illegal_proxy | 非法代理软件 | 软件名包含 proxy/vpn | 火绒API | ⚪ 二期 |
|
||||
| unverified_software | 未授权软件 | 不在白名单内的软件 | 火绒API | ⚪ 二期 |
|
||||
|
||||
### 3.1.2 推送渠道(Channel)
|
||||
|
||||
| 渠道ID | 渠道名称 | 适用场景 | 状态 |
|
||||
|--------|----------|----------|------|
|
||||
| wecom_app | 企微应用消息 | 终端用户通知 | 🔵 一期 |
|
||||
| wecom_group | 企微群通知 | 部门汇总、管理员 | 🔵 一期 |
|
||||
| email | 邮件通知 | 正式留痕、邮件通知 | ⚪ 二期 |
|
||||
| sms | 短信通知 | 紧急告警、无企微用户 | ⚪ 二期 |
|
||||
| dingtalk | 钉钉通知 | 跨平台通知 | ⚪ 二期 |
|
||||
|
||||
### 3.1.3 策略-渠道映射
|
||||
|
||||
```
|
||||
策略配置:
|
||||
{
|
||||
"strategy_id": "openclaw_remote",
|
||||
"name": "OpenClaw远程软件检测",
|
||||
"channels": [
|
||||
{"id": "wecom_app", "template": "终端用户通知"},
|
||||
{"id": "wecom_group", "template": "部门汇总"}
|
||||
],
|
||||
"schedule": "0 8 * * *", # 每天8点
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
### 3.1.4 扩展成本
|
||||
|
||||
| 扩展项 | 工作量预估 | 说明 |
|
||||
|--------|------------|------|
|
||||
| 新增检测策略 | 0.5人天 | 仅需配置检测规则和数据源 |
|
||||
| 新增推送渠道 | 1人天 | 需开发渠道适配器 |
|
||||
| 新策略+新渠道 | 1人天 | 配置映射关系即可 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 用户故事 (User Stories)
|
||||
|
||||
### 角色定义
|
||||
- **IT 管理员**:负责配置检查策略、查看检测结果、触发手动检查
|
||||
- **终端用户**:收到通知的被检查对象
|
||||
- **部门负责人**:(可选)收到本部门汇总通知
|
||||
|
||||
### 用户故事列表
|
||||
|
||||
| # | 角色 | 用户故事 | 优先级 |
|
||||
|---|------|----------|--------|
|
||||
| US1 | IT 管理员 | 我希望系统能自动从火绒 API 获取终端软件列表,以便及时发现 OpenClaw 安装 | P0 |
|
||||
| US2 | IT 管理员 | 我希望能够导入联软导出的用户-终端关联 Excel,以便知道哪台设备属于哪个用户 | P0 |
|
||||
| US3 | IT 管理员 | 我希望系统能自动匹配"终端→用户",输出"谁在哪台设备安装了 OpenClaw",以便精准确认责任人 | P0 |
|
||||
| US4 | IT 管理员 | 我希望能够设置定时任务(如每天 8:00 自动检查),以便在活动期间自动执行 | P0 |
|
||||
| US5 | IT 管理员 | 我希望能够手动点击"立即检查"按钮,以便随时执行检查 | P0 |
|
||||
| US6 | IT 管理员 | 我希望能够编辑通知模板(包含变量),以便自定义通知内容 | P1 |
|
||||
| US7 | IT 管理员 | 我希望能够设置"是否通知部门负责人"开关,以便灵活配置通知范围 | P1 |
|
||||
| US8 | IT 管理员 | 我希望能够导出 Excel 报告,以便留存审计记录 | P0 |
|
||||
| US9 | 终端用户 | 我希望收到企微应用消息通知,告诉我哪台设备安装了违规软件,以便及时卸载 | P0 |
|
||||
| US10 | 部门负责人 | 我希望收到本部门的汇总通知(可选),以便了解本部门合规情况 | P1 |
|
||||
| US11 | IT 管理员 | 我希望能够新增其他安全策略(如未知USB、非法代理),以便扩展检测范围 | P0 |
|
||||
| US12 | IT 管理员 | 我希望能够配置不同策略使用不同推送渠道(如邮件、短信),以便灵活通知 | P1 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 需求规格 (Requirements)
|
||||
|
||||
### 5.1 功能需求
|
||||
|
||||
> 📌 **需求说明**:一期以 OpenClaw 为试点,但架构设计需支持策略和渠道的扩展。
|
||||
|
||||
#### T0: 策略与渠道管理(框架层)
|
||||
|
||||
| 需求ID | 描述 | 优先级 | 验收标准 |
|
||||
|--------|------|--------|----------|
|
||||
| REQ-001-00 | 策略配置:支持创建/编辑/启停安全策略 | P0 | 可配置策略名称、检测规则、数据源、推送渠道 |
|
||||
| REQ-001-00a | 预置策略:一期预置"OpenClaw远程软件"检测策略 | P0 | 内置关键词规则(OpenClaw/向日葵/sunflower) |
|
||||
| REQ-001-00b | 渠道配置:支持配置推送渠道参数 | P0 | 企微渠道:应用ID/Secret;邮件渠道:SMTP配置 |
|
||||
| REQ-001-00c | 渠道适配器:企微应用消息发送 | P0 | 复用现有企微消息能力 |
|
||||
| REQ-001-00d | 渠道适配器:(扩展)邮件发送 | P2 | 可延后至二期 |
|
||||
| REQ-001-00e | 渠道适配器:(扩展)短信发送 | P2 | 可延后至二期 |
|
||||
|
||||
#### T1: 数据采集层
|
||||
|
||||
> ⚠️ **注意**:火绒 API 集成功能已存在于系统中(`app/integrations/huorong`),无需重复开发。
|
||||
> - 现有 API:`GET /api/admin/integrations/huorong/terminals` - 获取终端列表
|
||||
> - 现有 API:`GET /api/admin/integrations/huorong/terminals/{client_id}` - 获取终端详情(含软件列表)
|
||||
> - 本期仅需确认现有 API 满足 OpenClaw 检测需求(获取终端软件列表)。
|
||||
|
||||
| 需求ID | 描述 | 优先级 | 验收标准 |
|
||||
|--------|------|--------|----------|
|
||||
| REQ-001-01 | ✅ 已存在:火绒 API 集成(`/api/clnts/_list` 获取终端列表) | - | 复用现有功能 |
|
||||
| REQ-001-02 | ✅ 已存在:火绒 API 集成(`/api/clnts/_info2` 获取软件列表) | - | 复用现有功能,确认支持 OpenClaw 检测 |
|
||||
| REQ-001-03 | 联软 Excel 导入:支持上传 .xlsx/.xls 文件 | P0 | 能解析联软导出的 Excel,提取用户-终端关联字段 |
|
||||
| REQ-001-04 | 联软字段映射:自动识别 `用户名`、`设备名称`、`MAC地址`、`设备IP`、`部门名称` | P0 | 上传文件后自动匹配字段,无需手动配置 |
|
||||
| REQ-001-05 | 联软数据存储:将导入的关联数据存入数据库 | P0 | 导入后数据持久化,支持后续匹配查询 |
|
||||
|
||||
#### T2: 数据融合引擎
|
||||
|
||||
| 需求ID | 描述 | 优先级 | 验收标准 |
|
||||
|--------|------|--------|----------|
|
||||
| REQ-001-06 | IP 地址匹配:优先通过 IP 完全匹配关联终端和用户 | P0 | 火绒终端 IP 与联软数据完全一致时,成功匹配 |
|
||||
| REQ-001-07 | 主机名匹配:IP 未匹配时,通过主机名包含匹配 | P0 | 火绒主机名包含联软设备名称时,成功匹配 |
|
||||
| REQ-001-08 | MAC 地址匹配:前两者未匹配时,通过 MAC 地址匹配 | P0 | 火绒 MAC 与联软 MAC 一致时,成功匹配 |
|
||||
| REQ-001-09 | 未匹配记录标记:无法关联到用户的终端需标记为"未匹配" | P0 | 输出结果中明确标注哪些终端无法确定责任人 |
|
||||
| REQ-001-10 | OpenClaw 命中过滤:仅输出安装了 OpenClaw 的终端 | P0 | 匹配关键词:`OpenClaw`, `向日葵`, `sunflower`(不区分大小写) |
|
||||
|
||||
#### T3: 策略规则与命中管理
|
||||
|
||||
| 需求ID | 描述 | 优先级 | 验收标准 |
|
||||
|--------|------|--------|----------|
|
||||
| REQ-001-11 | 白名单规则:支持配置排除的用户/终端 | P1 | 配置白名单后,命中用户不在通知范围内 |
|
||||
| REQ-001-12 | 部门汇总:支持按部门聚合命中结果 | P1 | 统计每个部门的违规终端数量 |
|
||||
| REQ-001-12a | 命中状态管理:支持手动修改命中记录状态 | P0 | 状态:待处理→已通知→已解决/误报 |
|
||||
| REQ-001-12b | 自动状态更新:再次检测时自动判断状态变化 | P0 | 历史命中用户再次检测未命中,自动标记"已解决" |
|
||||
| REQ-001-12c | 状态变更记录:记录状态变更时间和操作人 | P1 | 可查看每条记录的状态变更历史 |
|
||||
|
||||
#### T4: 通知模板管理
|
||||
|
||||
| 需求ID | 描述 | 优先级 | 验收标准 |
|
||||
|--------|------|--------|----------|
|
||||
| REQ-001-13 | 模板变量:支持变量替换({username}, {device}, {dept}, {software}, {detect_time}) | P0 | 通知内容中变量被正确替换 |
|
||||
| REQ-001-14 | 默认模板:内置默认通知模板 | P0 | 未配置模板时使用默认模板发送 |
|
||||
| REQ-001-15 | 模板编辑:支持富文本编辑通知模板 | P1 | 可编辑标题和正文内容 |
|
||||
| REQ-001-16 | 企微消息发送:通过企微应用消息接口发送通知 | P0 | 成功发送至终端用户企微 |
|
||||
|
||||
#### T5: 调度与执行
|
||||
|
||||
| 需求ID | 描述 | 优先级 | 验收标准 |
|
||||
|--------|------|--------|----------|
|
||||
| REQ-001-17 | 定时任务:支持配置 cron 表达式执行检查 | P0 | 每天 8:00 自动执行检查任务 |
|
||||
| REQ-001-18 | 手动触发:提供"立即检查"按钮 | P0 | 点击后立即执行检查流程 |
|
||||
| REQ-001-19 | 活动期间开关:支持设置活动起止时间 | P0 | 活动期间自动开启检查,非活动期间可手动开启 |
|
||||
| REQ-001-20 | 执行日志:记录每次执行的起止时间、命中数量、发送结果 | P0 | 可查看历史执行记录 |
|
||||
| REQ-001-21 | Excel 导出:支持导出检测结果为 Excel | P0 | 导出字段:用户名、用户全名、部门、设备名称、IP、MAC、软件名称、安装日期 |
|
||||
|
||||
### 5.2 边缘情况处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| 火绒 API 调用失败 | 记录错误日志,发送告警给 IT 管理员,继续使用上次导入的联软数据 |
|
||||
| 联软 Excel 未导入 | 仅使用火绒数据,未匹配用户的终端标记为"用户未知" |
|
||||
| 用户无企微账户 | 跳过发送,标记为"发送失败",记录日志 |
|
||||
| 重复检测 | 同一用户同一天多次检测,仅发送首次通知(去重) |
|
||||
| 空结果(无违规) | 发送"本次检查无违规"的汇总通知给管理员 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 成功指标 (Success Metrics)
|
||||
|
||||
### 6.1 领先指标(Leading Indicators)
|
||||
|
||||
| 指标 | 定义 | 目标 | 测量方式 |
|
||||
|------|------|------|----------|
|
||||
| 检测覆盖率 | 成功关联到用户的终端数 / 火绒总终端数 | ≥ 85% | 数据库统计 |
|
||||
| 通知成功率 | 成功发送通知数 / 需通知总数 | ≥ 95% | 企微 API 返回 |
|
||||
| 执行完成率 | 定时任务成功执行次数 / 计划执行次数 | ≥ 99% | 调度日志 |
|
||||
|
||||
### 6.2 滞后指标(Lagging Indicators)
|
||||
|
||||
| 指标 | 定义 | 目标 | 测量方式 |
|
||||
|------|------|------|----------|
|
||||
| 闭环率 | 当事人确认处理(卸载)的比例 | ≥ 80% | 用户反馈或二次检测 |
|
||||
| 投诉率 | 通知后用户投诉误报的比例 | < 5% | 反馈记录 |
|
||||
| 合规达标率 | 活动期间最后一天违规终端清理比例 | 100% | 最终检测报告 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 开放问题 (Open Questions)
|
||||
|
||||
| # | 问题 | 负责人 | 状态 |
|
||||
|---|------|--------|------|
|
||||
| Q1 | 联软 Excel 导入频率?建议每日导入一次还是每次检查前导入? | 产品 | 待确认 |
|
||||
| Q2 | 部门负责人通知是否需要汇总(多条合并为1条)? | 产品 | 待确认 |
|
||||
| Q3 | 活动期间结束后,是否需要自动关闭定时任务? | 产品 | 待确认 |
|
||||
|
||||
---
|
||||
|
||||
## 7.1 任务拆分说明(重要)
|
||||
|
||||
> ⚠️ **系统现有火绒 API 集成,无需重复开发**
|
||||
|
||||
| 任务 | 说明 | 状态 |
|
||||
|------|------|------|
|
||||
| **T0: 策略与渠道管理** | 需开发:策略配置、渠道适配器框架 | 🔲 待开发 |
|
||||
| **T1a: 火绒API集成** | 现有功能已支持(`/api/admin/integrations/huorong/terminals`) | ✅ 已存在 |
|
||||
| **T1b: 联软Excel导入** | 需开发:从联软导出Excel导入用户-终端关联 | 🔲 待开发 |
|
||||
| T2: 数据融合引擎 | 需开发:IP/主机名/MAC三级匹配 | 🔲 待开发 |
|
||||
| T3: 策略规则引擎 | 需开发:白名单、命中过滤 | 🔲 待开发 |
|
||||
| T4: 通知模板管理 | 需开发:模板编辑、企微消息发送 | 🔲 待开发 |
|
||||
| T5: 调度与执行 | 需开发:定时任务、手动触发、活动开关 | 🔲 待开发 |
|
||||
|
||||
**实际开发任务**:T0 + T1b + T2 + T3 + T4 + T5(共6个任务)
|
||||
|
||||
---
|
||||
|
||||
## 8. 时间计划 (Timeline)
|
||||
|
||||
### 阶段划分
|
||||
|
||||
> ⚠️ **注**:T1a(火绒API)已存在,无需开发。
|
||||
|
||||
| 阶段 | 内容 | 预估工期 |
|
||||
|------|------|----------|
|
||||
| Phase 0 | T0(策略与渠道框架)+ T1b(联软导入) | 2 天 |
|
||||
| Phase 1 | T2(融合引擎)+ T3(策略规则) | 2 天 |
|
||||
| Phase 2 | T4(通知模板)+ T5(调度执行+导出) | 2 天 |
|
||||
| Phase 3 | 联调测试 + 验收 | 2 天 |
|
||||
| **合计** | 实际开发 | **8 天** |
|
||||
|
||||
### 里程碑
|
||||
|
||||
| 日期 | 里程碑 |
|
||||
|------|--------|
|
||||
| 2026-07-25 | Phase 1-2 完成,进入联调 |
|
||||
| 2026-07-28 | Phase 3-4 完成,进入测试 |
|
||||
| 2026-07-30 | UAT 通过,具备上线条件 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与依赖 (Risks & Dependencies)
|
||||
|
||||
| 风险/依赖 | 影响 | 缓解措施 |
|
||||
|-----------|------|----------|
|
||||
| 火绒 API 性能 | 终端数量多时可能超时 | 分页获取,增加重试机制 |
|
||||
| 联软数据更新 | 用户-终端关联可能变更 | 建议每次检查前重新导入最新 Excel |
|
||||
| 企微消息频率限制 | 大批量发送可能触发限制 | 增加发送间隔,批量改单发 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 附录
|
||||
|
||||
### 数据字段映射
|
||||
|
||||
#### 火绒终端字段(API 返回)
|
||||
- `client_id`: 终端唯一标识
|
||||
- `hostname`: 主机名
|
||||
- `ip`: IP 地址
|
||||
- `mac`: MAC 地址
|
||||
- `software`: 已安装软件列表
|
||||
|
||||
#### 联软 Excel 字段
|
||||
- `用户名`: 企微/AD 用户名
|
||||
- `用户全名`: 真实姓名
|
||||
- `部门名称`: 组织架构
|
||||
- `设备名称`: 终端名称
|
||||
- `设备IP`: IP 地址
|
||||
- `MAC地址`: MAC 地址
|
||||
- `软件名称`: 已安装软件
|
||||
- `安装日期`: 软件安装时间
|
||||
|
||||
### 匹配优先级
|
||||
1. IP 地址完全匹配
|
||||
2. 主机名包含匹配(联软设备名称 in 火绒主机名)
|
||||
3. MAC 地址匹配
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 日期 | 版本 | 变更内容 | 变更人 | 变更原因 |
|
||||
|------|------|----------|--------|----------|
|
||||
| 2026-07-20 | v1.0 | 初始版本 | Simon | 新建需求 |
|
||||
|
||||
---
|
||||
|
||||
**审批记录**
|
||||
|
||||
| 角色 | 姓名 | 日期 | 签字 |
|
||||
|------|------|------|------|
|
||||
| 产品负责人 | | | |
|
||||
| 技术负责人 | | | |
|
||||
| 业务负责人 | | | |
|
||||
@@ -0,0 +1,578 @@
|
||||
# 会议室预定 — 小鱼易联终端 PRD
|
||||
|
||||
> **版本**: v1.0
|
||||
> **日期**: 2026-07-15
|
||||
> **作者**: 许清楚 (Xu) · 产品经理
|
||||
> **状态**: 待评审
|
||||
> **所属项目**: `wecom_it_smart_desk` — IT智能服务台
|
||||
> **子系统**: 08-集成生态
|
||||
> **模块**: 会议室预定
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目信息
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| **项目名称** | `meetingroom_booking` |
|
||||
| **技术栈** | 终端页面: Vue3 + Tailwind CSS(横屏大屏适配)/ 后端: FastAPI + SQLAlchemy + PostgreSQL + Redis / 企微API: 会议室预定管理 + 会议室管理 / 小鱼易联: 终端管理API + Web SDK |
|
||||
| **语言** | 中文 |
|
||||
| **集成范围** | 小鱼易联终端定制页面 + 企微会议室预定API + 小鱼易联终端管理API |
|
||||
| **UI风格** | 深色主题(适配会议室大屏),大字号触控交互,accent = #07C160(企微绿) |
|
||||
|
||||
### 原始需求复述
|
||||
|
||||
税友集团IT支持组希望通过小鱼易联终端(安装在会议室的物理大屏设备)实现会议室预定功能。核心诉求:
|
||||
|
||||
1. 在小鱼易联终端上定制页面,集成企微自定义应用中的会议室预定能力
|
||||
2. 员工可通过H5/企微/终端页面多入口自助预定,预定结果同步到小鱼易联终端显示
|
||||
3. 有小鱼易联API文档+开发者账号,企微侧也有会议室API可对接
|
||||
|
||||
### 架构决策(初步方案)
|
||||
|
||||
采用**企微会议室API为数据源 + 小鱼易联终端为展示端 + 现有后端为中间层**的架构:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 系统架构概览 │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────────┐ │
|
||||
│ │ 企微H5端 │ │ 企微客户端App │ │ 小鱼易联终端(大屏) │ │
|
||||
│ │ (Vant4) │ │ (会议室入口) │ │ (Vue3+Tailwind) │ │
|
||||
│ └─────┬────┘ └──────┬───────┘ └──────────┬───────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └────────┬────────┘ │ │
|
||||
│ │ │ │
|
||||
│ ┌──────▼───────────────────────────────▼──────┐ │
|
||||
│ │ 现有后端 (FastAPI + Redis) │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌──────────────────┐ │ │
|
||||
│ │ │ 会议室API │ │ 终端管理API │ │ │
|
||||
│ │ │ (企微对接) │ │ (小鱼易联对接) │ │ │
|
||||
│ │ └──────┬──────┘ └────────┬─────────┘ │ │
|
||||
│ └─────────┼─────────────────┼─────────────┘ │
|
||||
│ │ │ │
|
||||
│ ┌─────────▼─────┐ ┌────────▼──────────┐ │
|
||||
│ │ 企微会议室API │ │ 小鱼易联开放平台 │ │
|
||||
│ │ (get_booking │ │ (终端管理/SDK) │ │
|
||||
│ │ book/cancel) │ │ │ │
|
||||
│ └───────────────┘ └──────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**关键设计决策**:
|
||||
- 企微会议室API为**唯一数据源**(single source of truth),不在本地维护独立的预定数据
|
||||
- 后端作为中间层:代理企微API调用(处理鉴权/限流/缓存),同时对接小鱼易联终端管理
|
||||
- 终端页面通过浏览器/WebView运行,与后端通过REST API + WebSocket通信
|
||||
|
||||
---
|
||||
|
||||
## 2. 产品定义
|
||||
|
||||
### 2.1 产品目标
|
||||
|
||||
| # | 目标 | 衡量标准 |
|
||||
|---|------|---------|
|
||||
| G1 | **终端可视化会议室状态**:在会议室物理大屏上实时显示当前会议室的预定状态、即将到来的会议、空闲时段,让路过的人一眼看到是否可用 | 终端页面打开后3秒内显示完整状态;状态更新延迟不超过30秒 |
|
||||
| G2 | **多入口自助预定闭环**:员工可通过企微H5、企微客户端、终端大屏三个入口完成「查看空闲→预定→取消」全流程,预定结果三端同步 | 任意入口预定后,其他入口在30秒内显示更新;预定操作完成时间不超过15秒 |
|
||||
| G3 | **与现有IT智能服务台无缝集成**:会议室预定作为IT智能服务台的新功能模块复用现有后端基础设施(鉴权/Redis/企微对接),不引入新的独立服务 | 复用现有Settings配置体系和数据库连接;新增代码以模块化方式集成到`backend/app/`,不破坏现有功能 |
|
||||
|
||||
### 2.2 用户故事
|
||||
|
||||
| # | 角色 | 用户故事 |
|
||||
|---|------|---------|
|
||||
| US-1 | 普通员工 | **As a** 员工, **I want** 在会议室门口的大屏上看到当前会议室是否空闲、下一个会议什么时候开始, **so that** 我不用打开手机就能判断能否临时使用这个会议室 |
|
||||
| US-2 | 普通员工 | **As a** 员工, **I want** 在终端大屏上直接点击空闲时段完成预定, **so that** 临时开会时不用掏手机操作,30秒内就能预定成功 |
|
||||
| US-3 | 普通员工 | **As a** 员工, **I want** 在企微H5端或企微客户端查看所有会议室的空闲状态并预定, **so that** 在回到会议室之前就提前预定好,到场直接使用 |
|
||||
| US-4 | 普通员工 | **As a** 员工, **I want** 预定后能随时取消(通过终端或企微), **so that** 会议取消后及时释放会议室资源 |
|
||||
| US-5 | IT管理员 | **As a** IT管理员, **I want** 在管理后台查看所有会议室的预定情况和使用率统计, **so that** 能识别热门/冷门会议室,优化资源配置 |
|
||||
| US-6 | IT管理员 | **As a** IT管理员, **I want** 将会议室与小鱼易联终端设备绑定(SN映射), **so that** 终端自动显示对应会议室的状态 |
|
||||
| US-7 | 访客 | **As a** 访客, **I want** 在终端大屏上看到会议室名称和当前状态(无需登录即可查看), **so that** 找会议室时能快速确认位置 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 需求池(P0/P1/P2)
|
||||
|
||||
### P0 — 必须实现(Must Have)
|
||||
|
||||
#### P0-1 企微会议室API对接 — access_token管理
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 在现有`WecomService`中新增会议室专用access_token管理。企微会议室API需要独立的"会议室"secret获取access_token(不同于普通应用secret和通讯录secret) |
|
||||
| **实现** | 在`config.py`中新增`wecom_meetingroom_secret`配置项;在`WecomService`中新增`get_meetingroom_access_token()`方法,复用Redis缓存模式(key: `wecom:meetingroom_access_token`,TTL: 6900秒) |
|
||||
| **验收** | 能成功获取会议室access_token;token缓存命中时不重复请求企微接口;token过期前自动刷新 |
|
||||
|
||||
#### P0-2 会议室列表查询接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 后端新增API:`GET /api/meetingroom/list`,代理调用企微会议室列表接口`POST /cgi-bin/oa/meetingroom/list`,返回会议室列表(ID、名称、容量、位置、设备、是否需要审批) |
|
||||
| **缓存** | 会议室列表变化频率低,Redis缓存10分钟 |
|
||||
| **验收** | 返回完整的会议室信息;支持按城市/楼宇/楼层过滤;缓存失效后自动刷新 |
|
||||
|
||||
#### P0-3 会议室预定状态查询接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 后端新增API:`GET /api/meetingroom/{meetingroom_id}/booking?date=YYYY-MM-DD`,代理调用企微`POST /cgi-bin/oa/meetingroom/get_booking_info`,返回指定会议室在指定日期的预定情况列表 |
|
||||
| **限制** | 企微API不支持跨天查询,后端按日期维度封装 |
|
||||
| **缓存** | 缓存30秒(保证实时性同时减少API调用) |
|
||||
| **验收** | 返回预定列表(booking_id、start_time、end_time、booker、status);时间戳转为可读时间格式 |
|
||||
|
||||
#### P0-4 预定会议室接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 后端新增API:`POST /api/meetingroom/book`,代理调用企微`POST /cgi-bin/oa/meetingroom/book` |
|
||||
| **参数** | meetingroom_id、subject(会议主题)、start_time、end_time、booker(预定人userid)、attendees(参与人列表,可选) |
|
||||
| **限制** | 时间自动按30分钟取整;仅可预定无需审批的会议室 |
|
||||
| **验收** | 预定成功返回booking_id;时间冲突时返回明确错误信息;预定成功后立即清除该会议室的状态缓存 |
|
||||
|
||||
#### P0-5 取消预定接口
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 后端新增API:`DELETE /api/meetingroom/booking/{booking_id}`,代理调用企微`POST /cgi-bin/oa/meetingroom/cancel_book` |
|
||||
| **参数** | booking_id、meetingroom_id(企微取消接口需要booking_id即可,但查询需要meetingroom_id+booking_id) |
|
||||
| **验收** | 取消成功返回确认;取消后立即清除缓存;非预定人取消时返回权限错误 |
|
||||
|
||||
#### P0-6 终端页面 — 会议室状态展示
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 新建前端项目(或模块)`frontend-terminal`,使用Vue3 + Tailwind CSS,为小鱼易联终端大屏定制横屏页面 |
|
||||
| **页面** | 终端状态页:全屏显示当前绑定的会议室名称、当前状态(空闲/使用中)、当前进行中的会议信息、时间轴展示当日预定情况 |
|
||||
| **适配** | 1920×1080横屏适配;大字号(标题48px+,正文24px+);深色主题减少大屏眩光 |
|
||||
| **验收** | 页面在终端浏览器/WebView中正常渲染;3秒内加载完毕;空闲/使用中状态视觉区分明显 |
|
||||
|
||||
#### P0-7 终端页面 — 快速预定
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 终端页面支持点击时间轴上的空闲时段进行快速预定 |
|
||||
| **交互** | 点击空闲时段→弹出预定面板(输入会议主题、选择时长)→确认预定→显示预定结果→3秒后返回状态页 |
|
||||
| **鉴权** | 终端预定时需要身份识别:扫码登录(企微扫码)或输入工号+姓名(访客模式仅查看,不可预定) |
|
||||
| **验收** | 30分钟以内的空闲时段可一键预定;预定结果实时显示在终端页面上 |
|
||||
|
||||
#### P0-8 终端-会议室绑定关系管理
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 后端新增`terminal_room_binding`表,存储小鱼易联终端SN与企微会议室ID的映射关系 |
|
||||
| **字段** | terminal_sn(终端序列号)、meetingroom_id(企微会议室ID)、terminal_name(终端名称)、location(位置描述)、created_at、updated_at |
|
||||
| **管理** | 管理后台CRUD接口:`GET/POST/PUT/DELETE /api/admin/terminal-bindings` |
|
||||
| **验收** | 支持一个终端绑定一个会议室;绑定后终端页面自动加载对应会议室的状态 |
|
||||
|
||||
### P1 — 应该实现(Should Have)
|
||||
|
||||
#### P1-1 实时状态同步 — WebSocket推送
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 当会议室预定状态发生变化时(预定/取消),通过WebSocket实时推送到绑定的终端页面 |
|
||||
| **方案** | 复用现有WebSocket基础设施(`ws.py`),新增终端WS端点`/ws/terminal/{terminal_sn}`。状态变更触发时:①清除Redis缓存 ②向已连接的终端推送状态更新消息 |
|
||||
| **降级** | WebSocket不可用时,终端页面降级为30秒轮询 |
|
||||
| **验收** | 一个入口预定后,其他入口的终端在5秒内显示更新;WS断线后自动降级为轮询 |
|
||||
|
||||
#### P1-2 企微回调 — 预定变更通知
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 配置企微事件回调,当会议室预定状态发生变化(企微客户端预定/取消/审批通过)时接收通知,主动刷新缓存并推送WS消息 |
|
||||
| **回调** | 企微回调URL配置在管理后台,接收事件后调用`get_booking_info`刷新缓存 |
|
||||
| **验收** | 企微客户端预定后,终端在10秒内收到更新(含回调延迟) |
|
||||
|
||||
#### P1-3 多会议室切换
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 终端页面支持查看其他会议室的状态(点击侧边栏/下拉选择) |
|
||||
| **交互** | 顶部会议室名称可点击→弹出会议室选择列表(显示名称+位置+实时状态)→选择后切换显示 |
|
||||
| **验收** | 切换后3秒内加载目标会议室状态;支持按楼宇/楼层筛选 |
|
||||
|
||||
#### P1-4 终端页面 — 会议详情展示
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 点击时间轴上的已预定时段,展示会议详情(主题、预定人、参与人、时间) |
|
||||
| **限制** | 终端仅展示预定人姓名(不展示userid),参与人列表最多显示5人 |
|
||||
| **验收** | 点击后弹出详情面板;信息来源为企微`bookinfo/get`接口 |
|
||||
|
||||
#### P1-5 H5端会议室预定页面
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 在现有H5端(Vue3 + Vant4)新增会议室预定页面,员工可在手机上查看所有会议室状态并预定 |
|
||||
| **入口** | H5端首页新增「会议室」入口卡片 |
|
||||
| **功能** | 会议室列表→选择会议室→查看时间轴→选择空闲时段→预定/取消 |
|
||||
| **验收** | 移动端交互流畅;预定结果同步到终端和企微客户端 |
|
||||
|
||||
#### P1-6 小鱼易联终端管理API集成
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 通过小鱼易联开放平台API实现终端设备管理:根据终端SN查询设备状态、推送自定义页面URL到终端 |
|
||||
| **接口** | 小鱼易联API基础URL:`https://sdk.xylink.com/api/rest/external/v1/`,使用enterpriseId + token鉴权(支持签名1.0/2.0) |
|
||||
| **功能** | ① 终端注册时自动上报SN ② 管理后台可远程推送页面URL到指定终端 ③ 查询终端在线状态 |
|
||||
| **验收** | 能通过API查询终端在线状态;能远程推送页面URL |
|
||||
|
||||
### P2 — 可以实现(Nice to Have)
|
||||
|
||||
#### P2-1 会议室使用统计报表
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 管理后台提供会议室使用率统计:按会议室/日期/时段统计预定时长、实际使用时长、空闲率 |
|
||||
| **数据来源** | 每日定时拉取预定记录存入本地数据库(作为统计快照,非实时数据源) |
|
||||
| **验收** | 支持按周/月查看统计图表;支持导出Excel |
|
||||
|
||||
#### P2-2 会议室维护模式
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 管理员可将会议室设为"维护中"状态,终端页面显示维护提示,禁止预定 |
|
||||
| **实现** | 本地维护状态表,终端查询时叠加本地状态判断 |
|
||||
| **验收** | 维护模式下终端显示维护提示;恢复后自动恢复预定功能 |
|
||||
|
||||
#### P2-3 终端页面 — 会议提醒
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 会议开始前5分钟,终端页面弹出提醒(如有下一个会议即将开始) |
|
||||
| **验收** | 提醒动画醒目但不影响当前操作;5分钟后自动消失 |
|
||||
|
||||
#### P2-4 终端页面 — 二维码预定
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 终端页面显示企微预定二维码,员工可扫码后在手机上完成预定 |
|
||||
| **验收** | 二维码指向H5端预定页面(带会议室ID参数);扫码后自动选中当前会议室 |
|
||||
|
||||
#### P2-5 小鱼易联Web SDK — 视频会议集成
|
||||
|
||||
| 项 | 说明 |
|
||||
|------|------|
|
||||
| **需求** | 预定会议后,可在终端上一键发起小鱼易联视频会议,将会议链接关联到企微预定记录 |
|
||||
| **SDK** | `@xylink/xy-rtc-sdk`,初始化需要clientId + clientSecret + extId(企业ID) |
|
||||
| **验收** | 终端可发起视频会议;会议信息关联到企微日程 |
|
||||
|
||||
---
|
||||
|
||||
## 4. UI设计稿(文字描述)
|
||||
|
||||
### 4.1 终端状态页(主页面)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📍 18F-会议室A 2026-07-15 周三 14:32 │
|
||||
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 空闲中 │ ← 当前状态(大字 96px) │
|
||||
│ │ │ │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
│ 距下一个会议:28分钟 │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 今日日程 14:32 ▮ │ │
|
||||
│ │ ┌─────────┐ ┌──────────────┐ ┌──────────────────┐ ┌──────────┐ │ │
|
||||
│ │ │ 09:00 │ │ 10:00 │ │ 14:00 │ │ 15:00 │ │ │
|
||||
│ │ │ ~ 10:00 │ │ ~ 11:30 │ │ ~ 15:00 ← 进行中 │ │ ~ 16:00 │ │ │
|
||||
│ │ │ 周度例会 │ │ 产品评审 │ │ 技术方案讨论 │ │ 空闲可预定│ │ │
|
||||
│ │ │ 张三 │ │ 李四 │ │ 王五 │ │ 点击预定 │ │ │
|
||||
│ │ └─────────┘ └──────────────┘ └──────────────────┘ └──────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [ 📱 扫码预定 ] [ 🔄 切换会议室 ] [ ❓ 使用帮助 ] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**设计要点**:
|
||||
- **深色主题**:背景 `#1a1a2e`,卡片 `#16213e`,减少大屏眩光
|
||||
- **状态色**:空闲 = `#07C160`(企微绿),使用中 = `#FF6B6B`(红色),即将开始 = `#FFA502`(橙色)
|
||||
- **字体**:当前状态 96px,会议室名称 36px,时间轴 20px,操作按钮 24px
|
||||
- **时间轴**:横向滚动,每个时段卡片宽度固定,当前时间用竖线标记
|
||||
- **空闲时段**:显示「点击预定」提示,可点击
|
||||
- **已预定时段**:显示会议主题和预定人,不可点击(仅查看)
|
||||
|
||||
### 4.2 快速预定弹窗
|
||||
|
||||
```
|
||||
┌───────────────────────────────────┐
|
||||
│ 📅 预定会议室 │
|
||||
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
||||
│ │
|
||||
│ 会议室:18F-会议室A │
|
||||
│ 时段:15:00 ~ 15:30 │
|
||||
│ │
|
||||
│ 会议主题: │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ 请输入会议主题 │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ │
|
||||
│ 时长: │
|
||||
│ [ 30分钟 ] [ 60分钟 ] [ 90分钟 ] │
|
||||
│ [ 自定义 ] │
|
||||
│ │
|
||||
│ 预定人:张三(扫码登录) │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌──────────────┐ │
|
||||
│ │ 取消 │ │ 确认预定 │ │
|
||||
│ └─────────────┘ └──────────────┘ │
|
||||
└───────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.3 交互流程
|
||||
|
||||
```
|
||||
终端页面加载
|
||||
│
|
||||
├── 读取终端SN → 查询绑定关系 → 获取meetingroom_id
|
||||
│ └── 无绑定 → 显示「请联系IT管理员绑定会议室」提示页
|
||||
│
|
||||
├── 获取会议室信息 + 当日预定状态
|
||||
│ └── 加载失败 → 显示错误页面 + 重试按钮
|
||||
│
|
||||
├── 渲染状态页
|
||||
│ ├── 空闲 → 显示「空闲中」+ 时间轴空闲时段可点击
|
||||
│ └── 使用中 → 显示「使用中」+ 当前会议信息
|
||||
│
|
||||
├── 用户点击空闲时段
|
||||
│ ├── 未登录 → 弹出企微扫码登录二维码
|
||||
│ │ └── 扫码成功 → 获取userid → 进入预定弹窗
|
||||
│ └── 已登录 → 直接进入预定弹窗
|
||||
│
|
||||
├── 确认预定
|
||||
│ ├── 调用后端API → 企微book接口
|
||||
│ ├── 成功 → 显示「预定成功」动画 → 3秒后返回状态页(状态已更新)
|
||||
│ └── 失败 → 显示错误原因(冲突/权限/网络) → 返回预定弹窗
|
||||
│
|
||||
└── 实时更新
|
||||
├── WebSocket连接 → 收到状态变更 → 刷新页面
|
||||
└── WebSocket断线 → 降级为30秒轮询
|
||||
```
|
||||
|
||||
### 4.4 H5端预定页面
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ ← 会议室预定 │
|
||||
│ ━━━━━━━━━━━━━━━━━━━━━ │
|
||||
│ │
|
||||
│ 📍 选择会议室 │
|
||||
│ ┌───────────────────┐ │
|
||||
│ │ 18F-会议室A 10人 │ │
|
||||
│ │ ● 空闲 │ │
|
||||
│ └───────────────────┘ │
|
||||
│ ┌───────────────────┐ │
|
||||
│ │ 18F-会议室B 20人 │ │
|
||||
│ │ ● 使用中 │ │
|
||||
│ └───────────────────┘ │
|
||||
│ │
|
||||
│ 📅 2026-07-15 │
|
||||
│ ┌───────────────────┐ │
|
||||
│ │ 时间轴(纵向) │ │
|
||||
│ │ 09:00 ████████ │ │
|
||||
│ │ 10:00 ████████ │ │
|
||||
│ │ 11:00 ░░░░░░░░ │ │
|
||||
│ │ 14:00 ████████ │ │
|
||||
│ │ 15:00 ░░░░░░░░ │ │
|
||||
│ │ 16:00 ░░░░░░░░ │ │
|
||||
│ └───────────────────┘ │
|
||||
│ │
|
||||
│ [ + 预定空闲时段 ] │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.5 管理后台 — 终端绑定管理
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 会议室管理 > 终端绑定 │
|
||||
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
|
||||
│ │
|
||||
│ [+ 新增绑定] [搜索: ___________] │
|
||||
│ │
|
||||
│ ┌────────┬──────────┬──────────┬──────────┬────────────┐ │
|
||||
│ │ 终端SN │ 终端名称 │ 会议室 │ 位置 │ 操作 │ │
|
||||
│ ├────────┼──────────┼──────────┼──────────┼────────────┤ │
|
||||
│ │ XY001 │ 大屏A │ 18F-会议室A│ 18F东区 │ 编辑 删除 │ │
|
||||
│ │ XY002 │ 大屏B │ 18F-会议室B│ 18F西区 │ 编辑 删除 │ │
|
||||
│ │ XY003 │ 大屏C │ 19F-会议室C│ 19F东区 │ 编辑 删除 │ │
|
||||
│ └────────┴──────────┴──────────┴──────────┴────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 技术规范
|
||||
|
||||
### 5.1 企微会议室API对接
|
||||
|
||||
| API | 企微接口 | 方法 | 说明 |
|
||||
|-----|---------|------|------|
|
||||
| 会议室列表 | `POST /cgi-bin/oa/meetingroom/list` | `WecomService.get_meetingroom_list()` | 缓存10分钟 |
|
||||
| 预定状态查询 | `POST /cgi-bin/oa/meetingroom/get_booking_info` | `WecomService.get_booking_info()` | 缓存30秒 |
|
||||
| 预定会议室 | `POST /cgi-bin/oa/meetingroom/book` | `WecomService.book_meetingroom()` | 成功后清缓存 |
|
||||
| 取消预定 | `POST /cgi-bin/oa/meetingroom/cancel_book` | `WecomService.cancel_booking()` | 成功后清缓存 |
|
||||
| 预定详情 | `POST /cgi-bin/oa/meetingroom/bookinfo/get` | `WecomService.get_booking_detail()` | 缓存5分钟 |
|
||||
|
||||
**access_token管理**:
|
||||
- 企微会议室API需要独立的"会议室"secret获取access_token
|
||||
- 在`config.py`新增`wecom_meetingroom_secret`配置项
|
||||
- 在`WecomService`中新增`get_meetingroom_access_token()`方法
|
||||
- Redis缓存key: `wecom:meetingroom_access_token`,TTL: 6900秒(提前300秒刷新)
|
||||
- 参考现有`get_access_token()`和`_contact_token_cache`的实现模式
|
||||
|
||||
**企微API限制**:
|
||||
- 预定时间自动按30分钟取整(15:15→15:00,15:45→16:00)
|
||||
- 仅可预定无需审批的会议室(`need_approval=0`)
|
||||
- `get_booking_info`不支持跨天查询,后端按日期维度封装
|
||||
- 当前时间超过预定开始时间15分钟后不允许预定
|
||||
- 会议室设备类型:1=电视, 2=电话, 3=投影, 4=白板, 5=视频
|
||||
|
||||
**官方文档**:
|
||||
- 会议室管理:https://developer.work.weixin.qq.com/document/path/93619
|
||||
- 会议室预定管理:https://developer.work.weixin.qq.com/document/path/93620
|
||||
|
||||
### 5.2 小鱼易联终端管理API对接
|
||||
|
||||
| 能力 | 接口 | 说明 |
|
||||
|------|------|------|
|
||||
| 鉴权 | enterpriseId + token | 支持1.0(signature)和2.0两种签名方式 |
|
||||
| 终端管理 | `https://sdk.xylink.com/api/rest/external/v1/` | 根据SN或终端号管理设备配置 |
|
||||
| 终端状态查询 | 终端管理API | 查询终端在线状态 |
|
||||
| Web SDK | `@xylink/xy-rtc-sdk` | 初始化需要clientId + clientSecret + extId(企业ID) |
|
||||
|
||||
**配置项**(新增到`config.py`):
|
||||
- `xylink_enterprise_id`:小鱼易联企业ID
|
||||
- `xylink_client_id`:SDK客户端ID
|
||||
- `xylink_client_secret`:SDK客户端密钥
|
||||
- `xylink_api_base`:API基础URL(默认`https://sdk.xylink.com/api/rest/external/v1/`)
|
||||
|
||||
**官方文档**:https://openapi.xylink.com/
|
||||
|
||||
### 5.3 后端新增文件结构
|
||||
|
||||
```
|
||||
backend/app/
|
||||
├── api/
|
||||
│ ├── meetingroom.py # 会议室预定API路由
|
||||
│ └── admin/
|
||||
│ └── terminal_binding.py # 终端绑定管理(管理后台)
|
||||
├── services/
|
||||
│ ├── meetingroom_service.py # 会议室预定业务逻辑
|
||||
│ └── xylink_service.py # 小鱼易联终端管理服务
|
||||
├── models/
|
||||
│ └── terminal_room_binding.py # 终端-会议室绑定模型
|
||||
└── config.py # 新增配置项
|
||||
```
|
||||
|
||||
### 5.4 前端新增项目
|
||||
|
||||
```
|
||||
frontend-terminal/ # 小鱼易联终端页面(新建)
|
||||
├── src/
|
||||
│ ├── views/
|
||||
│ │ ├── StatusView.vue # 终端状态页(主页面)
|
||||
│ │ └── BookingView.vue # 快速预定弹窗
|
||||
│ ├── components/
|
||||
│ │ ├── Timeline.vue # 时间轴组件
|
||||
│ │ ├── StatusBadge.vue # 状态标识
|
||||
│ │ └── QrLogin.vue # 扫码登录组件
|
||||
│ ├── api/
|
||||
│ │ └── meetingroom.ts # API调用
|
||||
│ └── App.vue
|
||||
├── tailwind.config.js # 横屏大屏适配配置
|
||||
└── vite.config.ts
|
||||
```
|
||||
|
||||
### 5.5 实时同步方案
|
||||
|
||||
| 方案 | 机制 | 延迟 | 适用场景 |
|
||||
|------|------|------|---------|
|
||||
| WebSocket(P1) | 终端建立WS连接,后端状态变更时推送 | <5秒 | 终端在线时的实时同步 |
|
||||
| 轮询(P0降级) | 终端每30秒请求一次状态 | <30秒 | WS不可用时的降级方案 |
|
||||
| 企微回调(P1) | 企微预定变更时回调后端,触发缓存刷新+WS推送 | <10秒 | 企微客户端预定的同步 |
|
||||
|
||||
**P0阶段**:终端页面使用30秒轮询保证基本实时性。
|
||||
**P1阶段**:升级为WebSocket + 企微回调,实现5秒内同步。
|
||||
|
||||
### 5.6 终端页面部署方案
|
||||
|
||||
| 方案 | 说明 | 优劣 |
|
||||
|------|------|------|
|
||||
| **方案A(推荐)** | 终端通过浏览器/WebView访问后端提供的URL | 灵活度高,更新无需触达终端;但依赖网络稳定 |
|
||||
| 方案B | 通过小鱼易联终端管理API推送自定义页面URL | 更深度集成;但需要小鱼易联API支持自定义页面推送 |
|
||||
| 方案C | 使用小鱼易联Web SDK在终端原生渲染 | 最深度集成;但开发成本高,P2阶段考虑 |
|
||||
|
||||
**P0推荐方案A**:终端浏览器全屏访问 `https://itsupport.servyou.com.cn/terminal/{sn}`,后端根据SN返回对应会议室的状态页面。
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据模型
|
||||
|
||||
### 6.1 终端-会议室绑定表 `terminal_room_binding`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Integer (PK) | 自增主键 |
|
||||
| terminal_sn | String(64) | 小鱼易联终端序列号(唯一) |
|
||||
| terminal_name | String(100) | 终端名称(如"18F东区大屏") |
|
||||
| meetingroom_id | Integer | 企微会议室ID |
|
||||
| meetingroom_name | String(100) | 会议室名称(冗余字段,便于展示) |
|
||||
| location | String(200) | 位置描述(如"18F东区") |
|
||||
| is_active | Boolean | 是否启用(默认True) |
|
||||
| created_at | DateTime | 创建时间 |
|
||||
| updated_at | DateTime | 更新时间 |
|
||||
|
||||
### 6.2 会议室预定快照表 `meetingroom_booking_snapshot`(P2)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | Integer (PK) | 自增主键 |
|
||||
| meetingroom_id | Integer | 企微会议室ID |
|
||||
| booking_id | String(64) | 企微预定ID |
|
||||
| subject | String(200) | 会议主题 |
|
||||
| booker | String(64) | 预定人userid |
|
||||
| start_time | DateTime | 开始时间 |
|
||||
| end_time | DateTime | 结束时间 |
|
||||
| status | Integer | 预定状态(0=已预定, 1=已取消) |
|
||||
| snapshot_date | Date | 快照日期 |
|
||||
| created_at | DateTime | 记录创建时间 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 待确认问题
|
||||
|
||||
| # | 问题 | 影响范围 | 建议 |
|
||||
|---|------|---------|------|
|
||||
| Q1 | **企微"会议室"secret是否已申请?** 会议室API需要独立的secret获取access_token,不同于现有应用secret和通讯录secret。需要在企微管理后台「应用管理 > 会议室 > 可调用接口的应用」中配置。 | P0核心前置条件 | 请IT支持组确认是否已有会议室secret,或协助申请 |
|
||||
| Q2 | **企微会议室是否已在管理后台创建?** 需要先在企微管理后台创建会议室(设置名称、容量、位置、设备等),API只能查询和预定已创建的会议室。 | P0核心前置条件 | 请确认企微后台已创建公司所有会议室 |
|
||||
| Q3 | **小鱼易联终端的具体型号和运行环境?** 需要确认终端是通过内置浏览器/WebView运行页面,还是有其他自定义页面机制。不同型号可能支持的页面渲染能力不同。 | 影响前端技术选型 | 请提供终端型号,或确认是否支持浏览器全屏访问 |
|
||||
| Q4 | **小鱼易联开发者账号的具体权限?** 需要确认开发者账号是否有终端管理API的调用权限,以及Web SDK的使用授权。 | P1小鱼易联API集成 | 请确认开发者账号的API权限范围 |
|
||||
| Q5 | **终端身份识别方案?** 终端是公共设备,员工预定时需要身份识别。方案一:企微扫码登录(推荐,与现有系统一致);方案二:输入工号+短信验证码;方案三:企微NFC碰一碰。 | P0预定功能 | 建议方案一(企微扫码),与现有H5端登录方式一致 |
|
||||
| Q6 | **是否需要支持访客预定?** 访客通常没有企微账号,无法通过企微API预定。如需支持,需要额外的访客登记流程。 | P0-P1 | 建议P0阶段仅支持企微员工预定,访客仅查看状态 |
|
||||
| Q7 | **会议室是否需要与小鱼易联视频会议联动?** 即预定会议室后,是否需要自动在终端上发起小鱼易联视频会议? | P2功能 | 建议P0阶段仅做预定,P2阶段考虑视频会议联动 |
|
||||
| Q8 | **终端页面部署在哪个域名下?** 是复用现有`itsupport.servyou.com.cn`域名(新增`/terminal/`路径),还是使用独立域名? | 影响部署和Nginx配置 | 建议复用现有域名,新增`/terminal/`路由 |
|
||||
| Q9 | **与现有IT智能服务台的关系?** 会议室预定是作为独立功能模块(仅在终端和H5端提供入口),还是也需要集成到坐席端(如坐席帮员工预定)? | 影响功能范围 | 建议P0阶段聚焦终端+H5端,坐席端集成放到P1 |
|
||||
| Q10 | **企微回调URL是否已配置?** 企微会议室预定状态变更回调需要配置回调URL,且需要企微后台配置可信域名。 | P1企微回调 | 请确认企微后台是否支持会议室事件回调配置 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 里程碑建议
|
||||
|
||||
| 阶段 | 内容 | 预估工期 | 依赖 |
|
||||
|------|------|---------|------|
|
||||
| **M1** | 企微会议室API对接(token管理+CRUD接口)+ 终端状态页 | 1周 | Q1/Q2确认 |
|
||||
| **M2** | 终端快速预定功能 + 终端-会议室绑定管理 | 1周 | M1完成 |
|
||||
| **M3** | WebSocket实时同步 + 企微回调 + H5端预定页面 | 1.5周 | M2完成 |
|
||||
| **M4** | 小鱼易联终端管理API集成 + 多会议室切换 | 1周 | Q3/Q4确认 |
|
||||
| **M5** | 统计报表 + 维护模式 + P2功能 | 按需 | M4完成 |
|
||||
|
||||
**总预估**:4-5周(P0+P1),P2按需排期。
|
||||
@@ -0,0 +1,658 @@
|
||||
# 智能IT支持服务台 — 管理后台增量 PRD
|
||||
|
||||
> **文档版本**: v1.2
|
||||
> **创建日期**: 2026-06-16 · **更新日期**: 2026-07-28
|
||||
> **产品经理**: 许清楚 (Xu) · 宋献
|
||||
> **状态**: 阶段一 1B — 已迭代(v1.2 分配模式收编坐席管理 Tab)
|
||||
> **关联文档**: `docs/PRD.md` (主 PRD §18-20)、`原型-REQ-集成-000-管理后台-v1.0.html`(原型,与本文档同步更新)
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [项目信息](#1-项目信息)
|
||||
2. [产品定义与目标](#2-产品定义与目标)
|
||||
3. [用户故事](#3-用户故事)
|
||||
4. [功能需求池](#4-功能需求池)
|
||||
5. [页面清单与导航结构](#5-页面清单与导航结构)
|
||||
6. [已实现功能映射](#6-已实现功能映射)
|
||||
7. [数据模型扩展方案](#7-数据模型扩展方案)
|
||||
8. [API 设计概要](#8-api-设计概要)
|
||||
9. [占位模块规格](#9-占位模块规格)
|
||||
10. [技术约束与约定](#10-技术约束与约定)
|
||||
11. [待确认问题](#11-待确认问题)
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目信息
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|------|
|
||||
| 产品名称 | 智能IT支持服务台 — 管理后台 |
|
||||
| 项目代号 | `wecom_it_smart_desk`(第三端:admin) |
|
||||
| 编程语言 | 前端: Vue 3 + TypeScript + Element Plus + Pinia · 后端: FastAPI + SQLAlchemy + PostgreSQL + Redis |
|
||||
| 部署路径 | `/itadmin/`(与 H5 `/itdesk/`、坐席 `/itagent/` 并列) |
|
||||
| 文档语言 | 中文 |
|
||||
| 原型参考 | `docs/prototypes/admin-dashboard-v1.html`(深色科技风,8页面) |
|
||||
| 所属阶段 | 阶段一 1B — 管理后台骨架 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 产品定义与目标
|
||||
|
||||
### 2.1 产品定位
|
||||
|
||||
管理后台是 IT 智能服务台的**第三端产品**,与员工端 H5(`/itdesk/`)和坐席工作台(`/itagent/`)并列,面向**坐席组长/IT运维负责人**(以下简称"组长"),提供系统配置、人员管理、内容运营三项核心能力。
|
||||
|
||||
### 2.2 阶段一 1B 目标
|
||||
|
||||
> 先把已实现功能相关的管理后台功能实现,后续项目功能开发时同步完成管理功能开发,未进行的管理功能预留占位。
|
||||
|
||||
**1B 交付范围**:
|
||||
1. **功能开关/参数管理**(P0)— 可视化编辑 `system_configs` 表中的配置项
|
||||
2. **坐席人员管理**(P0)— 坐席列表 + 角色/技能标签编辑 + 状态查看
|
||||
3. **快速回复管理**(P1)— 分类列表 + 审核发布流程(坐席提交→组长审核→全员可见)
|
||||
4. **外部系统集成配置**(P0 占位)— 6个外部系统卡片展示 + 配置入口(仅 Dify 可用)
|
||||
5. **消息分配模式**(P1 占位,v1.2 起收编为坐席管理页第 2 Tab「分配策略」)— 手动接单为当前模式,其他模式灰化锁定
|
||||
6. **排查模板管理**(P1 占位)— 模板列表查看 + JSON 导入导出入口(阶段三启用)
|
||||
7. **运营总览仪表盘**(P0)— 关键指标统计卡片 + 待处理事项 + 系统健康状态
|
||||
|
||||
### 2.3 产品目标(3个正交目标)
|
||||
|
||||
| # | 目标 | 衡量标准 |
|
||||
|---|------|---------|
|
||||
| G1 | **运维自助化** — 组长无需改代码即可调整系统参数、管理人员、审核内容 | 100% 配置项可通过管理后台修改,无需重启 |
|
||||
| G2 | **操作可追溯** — 配置变更、审核操作记录版本历史 | 每次变更记录操作人和时间戳 |
|
||||
| G3 | **页面可扩展** — 已规划的 10 个模块在导航中均有对应位置,未实现页面以占位方式呈现 | 所有已规划模块在菜单可见,未来阶段功能有明确入口 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 用户故事
|
||||
|
||||
| ID | 用户故事 | 涉及模块 | 优先级 |
|
||||
|----|---------|---------|--------|
|
||||
| US1 | 作为**坐席组长**,我希望能通过可视化界面开关功能模块(如应急模式),而不需要登录服务器修改数据库 | 功能开关 | P0 |
|
||||
| US2 | 作为**坐席组长**,我希望能查看所有坐席的在线状态、技能标签和当前负载,以便合理分配工作 | 坐席管理 | P0 |
|
||||
| US3 | 作为**坐席组长**,我希望能编辑坐席的角色(组长/坐席)和技能标签(电脑/网络/软件等),以匹配实际分工 | 坐席管理 | P0 |
|
||||
| US4 | 作为**坐席组长**,我希望能审核坐席提交的快速回复模板,通过后全员可见,驳回后仅提交人可见 | 快速回复管理 | P1 |
|
||||
| US5 | 作为**坐席组长**,我希望能查看已连接的外部系统状态,并配置 API Key/URL | 系统集成 | P0 |
|
||||
| US6 | 作为**坐席组长**,我希望在运营总览页面一目了然地看到在线坐席数、今日会话量、AI命中率等关键指标 | 运营总览 | P0 |
|
||||
| US7 | 作为**坐席组长**,我希望能通过搜索快速找到某个配置项或坐席,而不需要逐页翻找 | 全局搜索 | P1 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 功能需求池
|
||||
|
||||
### 4.1 P0 功能(阶段一 1B 必须交付)
|
||||
|
||||
#### P0-01 运营总览仪表盘
|
||||
|
||||
- **功能描述**:管理后台首页,展示4个统计卡片(在线坐席、今日会话、平均响应时间、AI命中率)+ 待处理事项列表 + 系统健康状态
|
||||
- **对应原型**:`page-dashboard`
|
||||
- **数据来源**:
|
||||
- 在线坐席:`agents` 表 `status='online'` 计数
|
||||
- 今日会话:`conversations` 表当日创建数
|
||||
- 平均响应时间:`conversations` 表计算(若无则显示占位符)
|
||||
- AI命中率:`conversations` 表 `ai_hit` 字段统计
|
||||
- 待处理事项:快速回复待审核 + 系统告警
|
||||
- 系统健康:各集成系统连接状态
|
||||
- **后端模型**:`Agent`、`Conversation`、`SystemConfig`
|
||||
- **API**:需新建 `GET /api/admin/dashboard/overview`
|
||||
- **复杂度**:S(纯查询+聚合,无写入操作)
|
||||
|
||||
#### P0-02 功能开关/参数管理
|
||||
|
||||
- **功能描述**:以卡片网格展示按功能模块分组的配置开关,每组包含若干 toggle 开关。配置变更即时生效(更新 `system_configs` 表)。支持回滚(记录旧值)
|
||||
- **对应原型**:`page-features`(6张功能卡片)
|
||||
- **功能卡片清单**:
|
||||
|
||||
| 卡片 | 配置键 | 当前值来源 | 开关数 |
|
||||
|------|--------|-----------|--------|
|
||||
| AI 对话引擎 | `ai_auto_reply`、`hand_raise_keywords`、`intervene_round_threshold` | `system_configs` | 3 |
|
||||
| 人工服务 | `manual_pickup_enabled`、`invite_employee_enabled` | 新建 | 2 |
|
||||
| 排队系统 | 阶段二功能,开关灰化 | 无 | 2(灰化) |
|
||||
| 满意度评价 | 阶段二功能,开关灰化 | 无 | 2(灰化) |
|
||||
| 应急模式 | `emergency_mode` | `system_configs` | 1 |
|
||||
| 关键词管理 | `hand_raise_keywords`、`emotion_keywords_*` | `system_configs` | 2组关键词编辑 |
|
||||
|
||||
- **编辑交互**:点击关键词旁的"编辑"按钮弹出对话框,支持 JSON 数组编辑;普通开关直接 toggle
|
||||
- **后端模型**:`SystemConfig`
|
||||
- **API**:需新建 `GET /api/admin/configs`、`PUT /api/admin/configs/{key}`、`GET /api/admin/configs/{key}/history`
|
||||
- **复杂度**:M(涉及分组展示、JSON 编辑验证、变更历史)
|
||||
|
||||
#### P0-03 坐席人员管理
|
||||
|
||||
- **功能描述**:坐席列表(表格形式),支持按状态筛选(全部/在线/忙碌/离线),展示:头像、姓名、工号、状态、技能标签、角色、当前/最大负载、今日结单数。组长可编辑坐席的角色和技能标签
|
||||
- **对应原型**:`page-agents`
|
||||
- **扩展需求**(Agent 模型需新增字段):
|
||||
- `role`:VARCHAR(20),取值 `admin`(组长)/ `agent`(坐席),默认 `agent`
|
||||
- `skill_tags`:JSON 数组,取值从 7 大类中选择:`["电脑","软件","外设","网络","安全","资产","其他"]`
|
||||
- **后端模型**:`Agent`(需扩展)
|
||||
- **已有 API**:`GET /api/agents`(坐席端用,可复用获取列表)
|
||||
- **需新建 API**:`PUT /api/admin/agents/{id}`(编辑角色/技能标签)、`POST /api/admin/agents`(添加坐席)、`DELETE /api/admin/agents/{id}`(移除坐席)
|
||||
- **复杂度**:M(涉及模型扩展 + CRUD + 状态筛选)
|
||||
|
||||
#### P0-04 外部系统集成配置(占位)
|
||||
|
||||
- **功能描述**:展示 6 个外部系统的集成状态卡片(3×2 网格)。阶段一仅 Dify 和 RAGFlow 可配置(已有后端集成),其余 4 个系统仅展示"未连接"/"待确认"状态
|
||||
- **对应原型**:`page-integrations`
|
||||
- **6个系统**:
|
||||
|
||||
| 系统 | 阶段一状态 | 可操作 |
|
||||
|------|-----------|--------|
|
||||
| Dify AI | 已连接 | 配置(URL/Key)、测试连接 |
|
||||
| RAGFlow | 部分集成 | 配置(URL/Key) |
|
||||
| 数据平台 | 未连接 | 仅展示状态 |
|
||||
| 北森 eHR | 未连接 | 仅展示状态 |
|
||||
| 火绒安全 | 未连接 | 仅展示状态 |
|
||||
| 联软安全 | 待确认 | 仅展示状态 + "申请"按钮(无实际功能) |
|
||||
|
||||
- **后端模型**:需新建 `IntegrationConfig` 模型(或复用 `SystemConfig` 存 JSON)
|
||||
- **API**:需新建 `GET /api/admin/integrations`、`PUT /api/admin/integrations/{id}`
|
||||
- **复杂度**:S(大部分为静态展示 + 2个配置表单)
|
||||
|
||||
### 4.2 P1 功能(阶段一 1B 应交付)
|
||||
|
||||
#### P1-01 快速回复管理(审核流程)
|
||||
|
||||
- **功能描述**:卡片式列表展示快速回复模板,按 7 大分类筛选(电脑/软件/外设/网络/安全/资产/其他)。支持审核流程:坐席提交→状态"待审核"(仅提交人可用)→组长审核通过→"已审核"(全员可见)/ 驳回→返回修改。每个模板展示版本号、变量列表、最后更新时间
|
||||
- **对应原型**:`page-quickreply`
|
||||
- **审核状态机**:
|
||||
```
|
||||
draft(草稿)→ pending_review(待审核,仅提交人可用)
|
||||
├─→ approved(已审核,全员可见)
|
||||
└─→ rejected(驳回,返回修改)
|
||||
```
|
||||
- **后端模型**:`QuickReplyTemplate`(需扩展 `status`、`version`、`submitted_by` 字段)
|
||||
- **已有 API**:`GET/POST/PUT/DELETE /api/quick-replies`(坐席端用,需扩展审核逻辑)
|
||||
- **需新建 API**:`PUT /api/admin/quick-replies/{id}/review`(审核通过/驳回)、`GET /api/admin/quick-replies/pending`(待审核列表)
|
||||
- **复杂度**:L(涉及审核状态机 + 版本管理 + 权限可见性逻辑)
|
||||
|
||||
#### P1-02 消息分配模式(占位)
|
||||
|
||||
- **功能描述**:展示 6 种分配模式卡片,阶段一仅「手动接单」可选(当前启用),其余 5 种模式灰化锁定并显示解锁条件
|
||||
- **对应原型**:`page-agents` 第 2 Tab「分配策略」(v1.2 起收编,原 `page-assignment` 独立页已删除)
|
||||
- **6 种模式**:手动接单(✅启用)、轮询分配(P2锁定)、最少活跃优先(P2锁定)、加权比例分配(P3锁定)、技能匹配分配(P3锁定)、优先队列(P3锁定)
|
||||
- **后端模型**:可复用 `SystemConfig`(键 `assignment_mode`)
|
||||
- **API**:`GET /api/admin/assignment-mode`、`PUT /api/admin/assignment-mode`
|
||||
- **复杂度**:S(静态展示为主,仅1个配置读写)
|
||||
|
||||
#### P1-03 排查模板管理(占位)
|
||||
|
||||
- **功能描述**:展示排查模板列表(表格形式:名称、分类、节点数、版本、状态),提供 JSON 导入/导出按钮。阶段一仅展示已有模板数据,导入导出功能灰化标注"阶段三启用"
|
||||
- **对应原型**:`page-flowchart`
|
||||
- **后端模型**:`TroubleshootingTemplate`(已有,可直接查询)
|
||||
- **已有 API**:`GET /api/troubleshooting-templates`(可复用)
|
||||
- **复杂度**:S(数据展示 + 按钮占位)
|
||||
|
||||
#### P1-04 会话监控(占位,Demo预览)
|
||||
|
||||
- **功能描述**:展示会话统计卡片(进行中/等待中/今日已结单/异常告警)+ 实时会话表格。阶段一从数据库查询真实数据展示,标注"Demo 预览"
|
||||
- **对应原型**:`page-monitor`
|
||||
- **数据来源**:`conversations` 表实时查询
|
||||
- **API**:需新建 `GET /api/admin/monitor/sessions`
|
||||
- **复杂度**:S(只读查询展示)
|
||||
|
||||
### 4.3 P2 功能(阶段一预留占位)
|
||||
|
||||
以下模块仅需在导航菜单中预留入口(灰化 + "开发中"标识),页面内容为空白占位页:
|
||||
|
||||
| 模块 | 对应阶段 | 导航分组 |
|
||||
|------|---------|---------|
|
||||
| 主题模板 | 阶段二 | P2 高级功能 |
|
||||
| 数据看板 | 阶段四 | P2 高级功能 |
|
||||
| 知识库管理 | 阶段四 | P2 高级功能 |
|
||||
|
||||
### 4.4 全局功能
|
||||
|
||||
#### P0-05 导航布局框架
|
||||
|
||||
- 深色科技风侧边栏(220px宽)+ 顶部面包屑 + 内容区
|
||||
- 导航分组:概览 → P0 核心配置 → P1 运营管理 → P2 高级功能
|
||||
- 每个导航项显示优先级标签(P0红/P1黄/P2绿)
|
||||
- 灰化菜单项:不可点击,显示 tooltip "阶段X 开发中"
|
||||
|
||||
#### P0-06 RBAC 权限控制(最小实现)
|
||||
|
||||
- 认证方式:复用坐席端 Redis token 机制,`Agent` 模型新增 `role` 字段
|
||||
- 权限校验:`role='admin'` 可访问管理后台,`role='agent'` 返回 403
|
||||
- 后端中间件:`/api/admin/*` 路由组统一校验 token + role
|
||||
- 前端路由守卫:无 admin 角色跳转 403 页面
|
||||
|
||||
#### P1-05 全局搜索
|
||||
|
||||
- 顶部搜索框输入关键词,搜索范围:配置项名称、坐席姓名、快速回复标题
|
||||
- 搜索结果以下拉菜单展示,点击跳转到对应页面
|
||||
|
||||
---
|
||||
|
||||
## 5. 页面清单与导航结构
|
||||
|
||||
### 5.1 页面树(v1.1 重构 · 5 组 27 项)
|
||||
|
||||
> v1.1 调整背景:v1.0 采用的 4 组(概览 / P0 / P1 / P2)按优先级分组,但实际运营中按"用户任务流"分组更友好(运营主管看运营中心、合规审计员看安全审计、运维看系统集成、知识管理员看知识与 AI)。本次重构调整为**5 组按用户场景**的结构。
|
||||
|
||||
```
|
||||
管理后台 (/itadmin/)
|
||||
├── 🟦 运营中心(7 项 · 实时作战 + 实时监控)
|
||||
│ ├── 运营总览 /admin/dashboard P0 ✅ (含会话监控 widget)
|
||||
│ ├── 会话监控 /admin/monitor P1 ⚡ (Dashboard 详情页跳转)
|
||||
│ ├── 坐席管理 /admin/agents P0 ✅ (含「分配策略」Tab,v1.2 收编原独立页)
|
||||
│ ├── 角色管理 /admin/roles P0 ✅ 🆕
|
||||
│ ├── 权限矩阵 /admin/permissions-matrix P1 ⚡ 🆕 补漏
|
||||
│ ├── OTP 管理 /admin/mfa-manage P1 ✅
|
||||
│ └── 功能开关 /admin/configs P0 ✅
|
||||
├── 🟪 数据与监控(4 项 · 离线分析 + AI 指标)
|
||||
│ ├── 会话审计 /admin/session-audit P0 ✅
|
||||
│ ├── 坐席绩效 /admin/agent-performance P0 ✅
|
||||
│ ├── 满意度评价 /admin/evaluation-stats P1 ✅
|
||||
│ └── 自动化指标 /admin/automation-metrics P1 ✅
|
||||
├── 🟥 安全审计(5 项 🆕 · 合规追溯 + 系统安全)
|
||||
│ ├── 配置变更历史 /admin/system-logs P0 ✅ 🆕
|
||||
│ ├── 运行期日志 /admin/runtime-logs P1 ✅ 🆕
|
||||
│ ├── 安全审计日志 /admin/audit-logs P0 ✅ 🆕
|
||||
│ ├── 终端安全 /admin/terminal-security P0 ✅ 🆕 移入
|
||||
│ └── 快速回复审计日志 /admin/quick-rules/audit P1 ✅ 🆕 移入(v1.1 调整:从快速回复规则组移至安全审计)
|
||||
├── 🟩 系统与集成(4 项 · 底层能力配置)
|
||||
│ ├── 系统集成 /admin/integrations P0 ⚡
|
||||
│ ├── 欢迎与引导 /admin/welcome-config P1 ✅
|
||||
│ ├── 自动化场景 /admin/automation-scenarios P1 ✅
|
||||
│ └── 规则版本 /admin/automation-versions P1 ✅
|
||||
└── 🟪 知识与 AI(7 项 · 配置规则 + 知识沉淀 + 智能建议)
|
||||
├── 配置规则(3 项)
|
||||
│ ├── 快速回复规则(5 个子页:index/greeting/contacts/routing/targets)
|
||||
│ │ /admin/quick-rules P1 ✅ 🆕 包含模板 tab 收编
|
||||
│ ├── 排查流程图 /admin/flowcharts P1 ⚡ 🆕 移入
|
||||
│ └── 代答排除 /admin/exclusion-rules P1 ✅ 🆕
|
||||
├── 知识沉淀(3 项)
|
||||
│ ├── 知识迭代管理 /admin/knowledge-iteration P1 ✅ 🆕
|
||||
│ ├── RAGFlow 文档导入 /admin/ragflow-ingestion P1 ✅ 🆕
|
||||
│ └── 拓扑预览 /admin/topology-preview P1 ✅ 🆕
|
||||
└── 智能建议(1 项)
|
||||
└── 知识库建议 /admin/knowledge-suggestions P1 ✅ 🆕 移入
|
||||
```
|
||||
|
||||
> 图例:✅ 1B 实现 | ⚡ 部分实现/占位 | 🚧 仅占位页
|
||||
> 🆕 = v1.1 新增/移入/调整归属
|
||||
> 折叠区(不计入主导航):主题模板 / 数据看板 / 知识库管理(3 个 P2 占位,灰化)
|
||||
|
||||
### 5.2 导航分组(v1.1 重构)
|
||||
|
||||
| 导航分组 | 菜单数 | 包含菜单项 | 用户角色 |
|
||||
|---------|--------|-----------|---------|
|
||||
| 🟦 运营中心 | 7 | 运营总览 / 会话监控 / 坐席管理 / 角色管理 / 权限矩阵 / OTP 管理 / 功能开关 | 运营主管 / 坐席组长 |
|
||||
| 🟪 数据与监控 | 4 | 会话审计 / 坐席绩效 / 满意度评价 / 自动化指标 | 数据分析师 / 主管 |
|
||||
| 🟥 安全审计 | 5 | 配置变更历史 / 运行期日志 / 安全审计日志 / 终端安全 / 快速回复审计日志 | 合规审计员 / 安全管理员 |
|
||||
| 🟩 系统与集成 | 4 | 系统集成 / 欢迎与引导 / 自动化场景 / 规则版本 | 系统管理员 |
|
||||
| 🟪 知识与 AI | 7 | 快速回复规则 / 排查流程图 / 代答排除 / 知识迭代 / RAGFlow 导入 / 拓扑预览 / 知识库建议 | AI 运营 / 知识管理员 |
|
||||
| **合计** | **26** | + 折叠区占位 3 项;分配模式作为坐席管理第 2 Tab 收编 | — |
|
||||
|
||||
### 5.3 对应原型页面映射(v1.1 更新)
|
||||
|
||||
| 原型页面 `id` | PRD 模块 | 实现方式 | 所属分组 |
|
||||
|--------------|---------|---------|---------|
|
||||
| `page-dashboard` | 运营总览(含会话监控 widget) | Vue 组件实现 | 运营中心 |
|
||||
| `page-monitor` | 会话监控(详情页) | Vue 组件实现 | 运营中心 |
|
||||
| `page-agents` | 坐席管理(含「分配策略」Tab,v1.2 收编原 page-assignment) | Vue 组件实现(`el-tabs` 双 Tab) | 运营中心 🆕 v1.2 |
|
||||
| `page-assignment` | 分配模式(已收编为 `page-agents` 第 2 Tab,v1.2 起不再独立展示) | — | — |
|
||||
| `page-roles` | 角色管理 | Vue 组件实现 | 运营中心 🆕 |
|
||||
| `page-permissions-matrix` | 权限矩阵 | Vue 组件实现(RBAC 可视化) | 运营中心 🆕 |
|
||||
| `page-mfa-manage` | OTP 管理 | Vue 组件实现 | 运营中心 🆕 |
|
||||
| `page-features` | 功能开关 | Vue 组件实现 | 运营中心 |
|
||||
| `page-session-audit` | 会话审计 | Vue 组件实现 | 数据与监控 |
|
||||
| `page-agent-performance` | 坐席绩效 | Vue 组件实现 | 数据与监控 |
|
||||
| `page-evaluation-stats` | 满意度评价 | Vue 组件实现 | 数据与监控 |
|
||||
| `page-automation-metrics` | 自动化指标 | Vue 组件实现 | 数据与监控 🆕 |
|
||||
| `page-system-logs` | 配置变更历史 | Vue 组件实现 | 安全审计 🆕 |
|
||||
| `page-runtime-logs` | 运行期日志 | Vue 组件实现 | 安全审计 🆕 |
|
||||
| `page-audit-logs` | 安全审计日志 | Vue 组件实现 | 安全审计 🆕 |
|
||||
| `page-terminal-security` | 终端安全 | Vue 组件实现 | 安全审计 🆕 移入 |
|
||||
| `page-quick-rules-audit` | 快速回复审计日志 | Vue 组件实现 | 安全审计 🆕 移入(v1.1 调整:从快速回复规则组移出) |
|
||||
| `page-integrations` | 系统集成 | Vue 组件实现(部分占位) | 系统与集成 |
|
||||
| `page-welcome-config` | 欢迎与引导 | Vue 组件实现 | 系统与集成 🆕 |
|
||||
| `page-automation-scenarios` | 自动化场景 | Vue 组件实现 | 系统与集成 🆕 |
|
||||
| `page-automation-versions` | 规则版本 | Vue 组件实现 | 系统与集成 🆕 |
|
||||
| `page-quick-rules` | 快速回复规则(含 5 子页 + 模板 tab 收编) | Vue 组件实现 | 知识与 AI 🆕 |
|
||||
| `page-flowchart` | 排查流程图 | Vue 组件实现(大部分占位) | 知识与 AI 🆕 移入 |
|
||||
| `page-exclusion-rules` | 代答排除 | Vue 组件实现 | 知识与 AI 🆕 |
|
||||
| `page-knowledge-iteration` | 知识迭代管理 | Vue 组件实现 | 知识与 AI 🆕 |
|
||||
| `page-ragflow-ingestion` | RAGFlow 文档导入 | Vue 组件实现 | 知识与 AI 🆕 |
|
||||
| `page-topology-preview` | 拓扑预览 | Vue 组件实现 | 知识与 AI 🆕 |
|
||||
| `page-knowledge-suggestions` | 知识库建议 | Vue 组件实现 | 知识与 AI 🆕 移入 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 已实现功能映射
|
||||
|
||||
### 6.1 可直接复用的后端模型
|
||||
|
||||
| 模型 | 表名 | 复用方式 | 备注 |
|
||||
|------|------|---------|------|
|
||||
| `SystemConfig` | `system_configs` | 直接读写 | 已有 12 个配置键,功能开关页面直接映射 |
|
||||
| `Agent` | `agents` | 查询列表 + 扩展字段 | 需要新增 role/skill_tags 列 |
|
||||
| `QuickReplyTemplate` | `quick_reply_templates` | 查询列表 + 扩展字段 | 需要新增 status/version/submitted_by 列 |
|
||||
| `TroubleshootingTemplate` | `troubleshooting_templates` | 只读查询 | 排查流程图页面展示 |
|
||||
| `Conversation` | `conversations` | 只读聚合查询 | 仪表盘统计 + 会话监控 |
|
||||
| `Employee` | `employees` | 只读查询 | 关联坐席信息 |
|
||||
|
||||
### 6.2 可直接复用的后端 API
|
||||
|
||||
| 现有 API | 复用场景 | 是否需要修改 |
|
||||
|---------|---------|------------|
|
||||
| `GET /api/agents` | 坐席列表查询 | 是,增加 role 和 skill_tags 返回 |
|
||||
| `GET /api/quick-replies` | 快速回复列表 | 是,增加审核状态筛选 |
|
||||
| `GET /api/troubleshooting-templates` | 排查模板列表 | 否,直接复用 |
|
||||
| `GET /api/system/emergency-mode` | 应急模式状态读取 | 否,直接复用 |
|
||||
| `PUT /api/system/emergency-mode` | 应急模式开关切换 | 否,直接复用 |
|
||||
|
||||
### 6.3 需要新建的 API 路由组
|
||||
|
||||
所有管理后台 API 统一挂载到 `/api/admin/` 路由组下:
|
||||
|
||||
| API | 方法 | 用途 | 优先级 |
|
||||
|-----|------|------|--------|
|
||||
| `/api/admin/dashboard/overview` | GET | 仪表盘统计数据 | P0 |
|
||||
| `/api/admin/configs` | GET | 获取全部配置项(分组) | P0 |
|
||||
| `/api/admin/configs/{key}` | PUT | 更新单个配置项 | P0 |
|
||||
| `/api/admin/configs/{key}/history` | GET | 配置变更历史 | P0 |
|
||||
| `/api/admin/agents` | GET | 坐席列表(管理视图,含角色/标签) | P0 |
|
||||
| `/api/admin/agents` | POST | 添加坐席 | P0 |
|
||||
| `/api/admin/agents/{id}` | PUT | 编辑坐席(角色/技能标签/负载上限) | P0 |
|
||||
| `/api/admin/agents/{id}` | DELETE | 移除坐席 | P0 |
|
||||
| `/api/admin/integrations` | GET | 集成系统列表+状态 | P0 |
|
||||
| `/api/admin/integrations/{id}` | PUT | 更新集成配置 | P0 |
|
||||
| `/api/admin/integrations/{id}/test` | POST | 测试连接 | P1 |
|
||||
| `/api/admin/quick-replies/pending` | GET | 待审核模板列表 | P1 |
|
||||
| `/api/admin/quick-replies/{id}/review` | PUT | 审核通过/驳回 | P1 |
|
||||
| `/api/admin/assignment-mode` | GET/PUT | 分配模式读写 | P1 |
|
||||
| `/api/admin/monitor/sessions` | GET | 实时会话列表 | P1 |
|
||||
| `/api/admin/search` | GET | 全局搜索 | P1 |
|
||||
|
||||
### 6.4 需要扩展的已有模型
|
||||
|
||||
| 模型 | 新增字段 | 类型 | 默认值 | 说明 |
|
||||
|------|---------|------|--------|------|
|
||||
| `Agent` | `role` | VARCHAR(20) | `'agent'` | admin=组长, agent=坐席 |
|
||||
| `Agent` | `skill_tags` | JSON | `[]` | 如 `["电脑","网络"]` |
|
||||
| `QuickReplyTemplate` | `status` | VARCHAR(20) | `'approved'` | draft/pending_review/approved/rejected |
|
||||
| `QuickReplyTemplate` | `version` | INTEGER | `1` | 版本号,每次审核通过后 +1 |
|
||||
| `QuickReplyTemplate` | `submitted_by` | VARCHAR(36) | NULL | 提交人 agent_id(外键关联 agents) |
|
||||
|
||||
---
|
||||
|
||||
## 7. 数据模型扩展方案
|
||||
|
||||
### 7.1 Agent 模型扩展
|
||||
|
||||
```python
|
||||
# 在 Agent 模型中新增:
|
||||
role: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="agent",
|
||||
comment="角色:admin=组长, agent=坐席"
|
||||
)
|
||||
skill_tags: Mapped[List[str]] = mapped_column(
|
||||
JSON, nullable=False, default=list,
|
||||
comment="技能标签列表(电脑/软件/外设/网络/安全/资产/其他)"
|
||||
)
|
||||
```
|
||||
|
||||
已有种子数据:当前坐席(宋献 → 组长角色 + 电脑/网络/软件标签,王丽 → 坐席 + 外设/安全,张伟 → 坐席 + 资产/其他)。
|
||||
|
||||
### 7.2 QuickReplyTemplate 模型扩展
|
||||
|
||||
```python
|
||||
# 在 QuickReplyTemplate 模型中新增:
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="approved",
|
||||
comment="状态:draft/pending_review/approved/rejected"
|
||||
)
|
||||
version: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=1,
|
||||
comment="版本号"
|
||||
)
|
||||
submitted_by: Mapped[str] = mapped_column(
|
||||
String(36), nullable=True, default=None,
|
||||
comment="提交人 agent_id"
|
||||
)
|
||||
```
|
||||
|
||||
已有种子数据默认 `status='approved'`(无需审核)。
|
||||
|
||||
### 7.3 新建 IntegrationConfig 模型(可选)
|
||||
|
||||
若需要持久化集成系统的配置(API URL、Key 等),建议新建:
|
||||
|
||||
```python
|
||||
class IntegrationConfig(Base):
|
||||
__tablename__ = "integration_configs"
|
||||
id: str (UUID PK)
|
||||
system: str # dify/ragflow/data_platform/beisen/huorong/liansoft
|
||||
name: str # 显示名称
|
||||
api_url: str # API 地址
|
||||
api_key: str # API Key(加密存储)
|
||||
status: str # connected/partial/disconnected/pending
|
||||
updated_at: datetime
|
||||
```
|
||||
|
||||
阶段一可暂不复用此模型,直接硬编码 6 个系统的状态展示,Dify 和 RAGFlow 的配置暂时存 `system_configs`。
|
||||
|
||||
---
|
||||
|
||||
## 8. API 设计概要
|
||||
|
||||
### 8.1 路由注册
|
||||
|
||||
```python
|
||||
# 新建 backend/app/api/admin.py
|
||||
# 在 backend/app/api/router.py 中注册:
|
||||
from app.api.admin import router as admin_router
|
||||
api_router.include_router(admin_router, prefix="/admin", tags=["管理后台"])
|
||||
```
|
||||
|
||||
### 8.2 权限中间件
|
||||
|
||||
```python
|
||||
# 所有 /api/admin/* 路由需校验:
|
||||
# 1. token 有效性(复用坐席端 Redis token)
|
||||
# 2. Agent.role == 'admin'
|
||||
# 不满足条件返回统一错误响应(code=1003, message="无管理权限")
|
||||
```
|
||||
|
||||
### 8.3 响应格式
|
||||
|
||||
沿用项目统一的 `success_response` / `error_response` 格式:
|
||||
|
||||
```json
|
||||
{"code": 0, "message": "success", "data": {...}}
|
||||
{"code": 1003, "message": "无管理权限"}
|
||||
```
|
||||
|
||||
### 8.4 配置变更历史
|
||||
|
||||
`PUT /api/admin/configs/{key}` 时:
|
||||
1. 读取当前值存入日志(`config_change_logs` 表或 JSON 字段)
|
||||
2. 写入新值
|
||||
3. 返回变更前后对比
|
||||
|
||||
日志结构:`{config_key, old_value, new_value, changed_by, changed_at}`
|
||||
|
||||
---
|
||||
|
||||
## 9. 占位模块规格
|
||||
|
||||
### 9.1 占位页面交互规范
|
||||
|
||||
所有未来阶段的占位页面遵循统一的展示方式:
|
||||
|
||||
**页面内容**:
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 🚧 开发中 │
|
||||
│ │
|
||||
│ 该功能将在阶段 X 上线 │
|
||||
│ │
|
||||
│ 预计功能:{简短描述} │
|
||||
│ │
|
||||
│ [返回首页] │
|
||||
│ │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**导航菜单**:
|
||||
- 灰化样式:`opacity: 0.4; pointer-events: none;`
|
||||
- 不响应点击
|
||||
- Tooltip 悬停提示:"阶段X 开发中,敬请期待"
|
||||
|
||||
### 9.2 各占位页面规格
|
||||
|
||||
| 页面 | 占位类型 | 占位内容 |
|
||||
|------|---------|---------|
|
||||
| 主题模板 | 🚧 空白占位 | 居中显示"阶段二上线",描述:支持全局/坐席端/H5端三层主题配置 |
|
||||
| 数据看板 | 🚧 空白占位 | 居中显示"阶段四上线",描述:坐席绩效、满意度趋势、热点问题排行 |
|
||||
| 知识库管理 | 🚧 空白占位 | 居中显示"阶段四上线",描述:标注→知识条目→RAGFlow同步迭代闭环 |
|
||||
| 系统集成(部分) | ⚡ 功能占位 | 展示系统卡片但"配置""测试"按钮灰化,tooltip 说明"阶段二启用" |
|
||||
| 排查流程图 | ⚡ 功能占位 | 展示已有模板数据,导入导出按钮灰化标注"阶段三启用" |
|
||||
|
||||
---
|
||||
|
||||
## 10. 技术约束与约定
|
||||
|
||||
### 10.1 前端技术栈
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 框架 | Vue 3 + Composition API |
|
||||
| 语言 | TypeScript |
|
||||
| UI 库 | Element Plus |
|
||||
| 状态管理 | Pinia |
|
||||
| 构建工具 | Vite |
|
||||
| 样式方案 | Tailwind CSS(与坐席端一致) |
|
||||
| UI 风格 | 深色科技风(CSS 变量与原型一致) |
|
||||
| 部署路径 | `/itadmin/` |
|
||||
|
||||
### 10.2 CSS 变量(与原型对齐)
|
||||
|
||||
```css
|
||||
--bg-primary: #0f172a; /* 主背景 */
|
||||
--bg-secondary: #1e293b; /* 侧边栏/卡片 */
|
||||
--bg-tertiary: #334155; /* 表格表头 */
|
||||
--accent: #3b82f6; /* 主题色 */
|
||||
--success: #10b981; /* 成功/在线 */
|
||||
--warning: #f59e0b; /* 警告 */
|
||||
--danger: #ef4444; /* 危险/错误 */
|
||||
--text-primary: #f1f5f9; /* 主文字 */
|
||||
--text-secondary: #94a3b8; /* 辅助文字 */
|
||||
--text-muted: #64748b; /* 弱化文字 */
|
||||
```
|
||||
|
||||
### 10.3 后端技术栈
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 框架 | FastAPI |
|
||||
| ORM | SQLAlchemy 2.0 (async) |
|
||||
| 数据库 | PostgreSQL(生产)/ SQLite(开发) |
|
||||
| 缓存 | Redis(token + 配置热更新) |
|
||||
| 认证 | Redis token + Agent.role 权限校验 |
|
||||
| API 前缀 | `/api/admin/` |
|
||||
|
||||
### 10.4 部署
|
||||
|
||||
```nginx
|
||||
# nginx 配置示例(新增 /itadmin/ 路由)
|
||||
location /itadmin/ {
|
||||
alias /path/to/frontend-admin/dist/;
|
||||
try_files $uri $uri/ /itadmin/index.html;
|
||||
}
|
||||
|
||||
location /api/admin/ {
|
||||
proxy_pass http://backend:8000/api/admin/;
|
||||
}
|
||||
```
|
||||
|
||||
### 10.5 其他约定
|
||||
|
||||
- 项目名称:`frontend-admin`,目录与 `frontend-agent/`、`frontend-h5/` 并列
|
||||
- 后端新建文件:`backend/app/api/admin.py`、`backend/app/services/admin_service.py`(可选)
|
||||
- 数据库迁移:新增列使用 Alembic 迁移脚本
|
||||
- 中文界面,所有文案使用中文
|
||||
- 配置项命名遵循现有 `snake_case` 规范
|
||||
|
||||
---
|
||||
|
||||
## 11. 待确认问题
|
||||
|
||||
| # | 问题 | 影响范围 | 建议方案 | 确认人 |
|
||||
|---|------|---------|---------|--------|
|
||||
| Q1 | 坐席组长是否只有 1 人(宋献)?其他坐席是否需要管理后台访问权限? | RBAC 设计 | 阶段一仅宋献(role=admin),后续需新增组长时可扩展 | 宋献 |
|
||||
| Q2 | IntegrationConfig 模型是新建独立表还是复用 SystemConfig 存 JSON? | 数据模型 | 建议阶段一先用 SystemConfig,等集成系统配置复杂度上升后再建独立表 | 开发 |
|
||||
| Q3 | 快速回复的版本历史是存一张新表(quick_reply_versions)还是在主表用 JSON 存历史版本? | 数据模型 | 建议阶段一先用主表 version 字段递增 + JSON 字段存 diff,阶段二按需建版本表 | 开发 |
|
||||
| Q4 | 配置变更历史的存储粒度:每键独立日志表 vs 通用 JSON 日志? | 数据模型 | 建议阶段一在 config_change_logs 表中存 {key, old, new, who, when},简单够用 | 开发 |
|
||||
| Q5 | 仪表盘"平均响应时间"和"AI命中率"的计算口径需要确认(从坐席接单到首条回复?还是从员工发消息到坐席回复?) | 运营总览 | 建议阶段一先展示"今日会话数"和"在线坐席数"两个有把握的指标,其余用占位符 | 宋献 |
|
||||
| Q6 | 快速回复"仅提交人可用"(待审核期间)的权限粒度:坐席端 API 是否需要改? | 快速回复 | 是,坐席端 `GET /api/quick-replies` 返回需增加 status 筛选(全员可见的 approved + 自己的 pending_review) | 开发 |
|
||||
| Q7 | 应急模式开启时,H5用户端展示引导文案的内容是否需要管理后台可配? | 功能开关 | 建议阶段一固定文案(硬编码在 system.py 中),阶段二增加可配置 | 宋献 |
|
||||
|
||||
---
|
||||
|
||||
> **文档结束** — 本 PRD 覆盖管理后台阶段一 1B 的全部需求,与主 PRD §18-20 和原型 `admin-dashboard-v1.html` 对齐。后续阶段的功能将在迭代中增量补充。
|
||||
|
||||
---
|
||||
|
||||
## 12. 变更日志
|
||||
|
||||
| 版本 | 日期 | 变更内容 | 变更人 | 变更原因 | 影响范围 |
|
||||
|------|------|---------|--------|---------|---------|
|
||||
| v1.0 | 2026-06-16 | 初版 PRD(4 组导航:概览/P0 核心/P1 运营/P2 高级) | 许清楚 | 阶段一 1B 立项 | — |
|
||||
| v1.1 | 2026-07-28 | **导航重构为 5 组按用户场景**(运营中心/数据与监控/安全审计/系统与集成/知识与 AI);新增 12 项菜单(含 v1.0 未列出的角色管理/权限矩阵/OTP 管理/自动化指标/配置变更历史/运行期日志/安全审计日志/终端安全/快速回复审计/欢迎与引导/自动化场景/规则版本/知识迭代/RAGFlow 导入/拓扑预览/知识库建议/代答排除等);终端安全从系统与集成移入安全审计组;快速回复审计从快速回复规则组移入安全审计组;快速回复模板作为快速回复规则页内"模板"tab 收编;会话监控 widget 嵌入运营总览 | 宋献 | v1.0 按优先级分组的导航不符合运营实际使用场景;安全审计类功能分散;快速回复模板与规则功能重复;用户进入运营总览看不到实时会话 | §5 全部;§4.1/4.2/4.3 部分功能项;§9.2 占位规格微调;新增 27 项页面映射到原型 |
|
||||
| v1.2 | 2026-07-28 | **分配模式 Tab 收编到坐席管理**:原独立页 `page-assignment` 不再独立展示,整体内嵌为 `/admin/agents` 的第 2 个 Tab"分配策略";路由 `/admin/assignment-mode` 移除;菜单导航「知识与 AI」8 项 → 7 项(合计 26 项);坐席管理原型增加 `el-tabs` 双 Tab 切换 | 宋献 | 阶段一分配模式 6 个里 5 个灰化,占独立菜单过重;解锁条件强依赖坐席人数,Tab 合并后上下文最连贯;未来膨胀仍可拆回独立页 | §5.1/5.2/5.3 树状图与分组表;§9.2 占位表;§12 变更日志 |
|
||||
|
||||
### v1.2 关键变更说明
|
||||
|
||||
**1. 分配模式 Tab 收编到坐席管理**
|
||||
- 原 `/admin/assignment-mode` 独立菜单/独立页 → 收编为 `/admin/agents` 的第 2 个 Tab「分配策略」
|
||||
- 路由表删除 `/admin/assignment-mode` 项;菜单导航「知识与 AI」8 项 → 7 项,总菜单数 27 → 26
|
||||
- 坐席管理页实现方式:`<el-tabs v-model="activeTab">`,Tab1 = 坐席列表(原内容),Tab2 = 分配策略(迁移 `AssignmentMode.vue` 的 6 张模式卡片)
|
||||
- 后端零改动:分配模式的读写仍走 `system_configs.assignment_mode` + `GET/PUT /api/admin/assignment-mode`
|
||||
- `views/AssignmentMode.vue` 保留文件但停用导入(防止后续回滚时再拆出)
|
||||
|
||||
**2. 决策依据**
|
||||
- 阶段一分配模式 6 个里 5 个灰化占位,独立菜单过重
|
||||
- 解锁条件强依赖坐席人数(轮询 ≥3、加权 ≥5),Tab 内可见当前在岗数,无需跨页比对
|
||||
- 若阶段二/三分配模式膨胀(权重配置、技能匹配规则),可逆撤销为独立页
|
||||
|
||||
### v1.1 关键变更说明
|
||||
|
||||
**1. 5 组按用户场景分组**(替代 v1.0 的 4 组按优先级分组)
|
||||
- 运营中心:日常运营主入口(运营主管/组长)
|
||||
- 数据与监控:离线数据分析(数据分析师)
|
||||
- 安全审计:合规追溯(合规审计员/安全管理员)🆕
|
||||
- 系统与集成:底层能力配置(系统管理员)
|
||||
- 知识与 AI:智能引擎 + 知识沉淀(AI 运营/知识管理员)
|
||||
|
||||
**2. 快速回复模板收编为 Tab**(节省 1 个菜单项)
|
||||
- 原 `/admin/quick-replies` 独立菜单页面 → 整合为 `/admin/quick-rules` 内的"模板"tab
|
||||
- 原 5 个子路由(greeting/contacts/routing/targets/audit)+ 新增 template tab = 6 个 tab
|
||||
- 复用 quickReplyStore 与现有 API,无需新建后端
|
||||
|
||||
**3. 会话监控 widget 嵌入运营总览**(双视图模式)
|
||||
- Dashboard 顶部新增"实时会话 Top 5" widget
|
||||
- 点"查看更多"跳 `/admin/monitor` 详情页
|
||||
- 优势:用户进入 Dashboard 即可见实时状态,无需跳转
|
||||
|
||||
**4. 快速回复审计日志移出知识与 AI 组**(v1.1 关键调整)
|
||||
- 原规划:作为"快速回复规则"页的 6 个 tab 之一(与 greeting/contacts/routing/targets/template 并列)
|
||||
- 调整后:独立菜单项,归入"安全审计"组(与配置变更历史/运行期日志/安全审计日志/终端安全 并列)
|
||||
- 理由:审计类功能在性质上属于安全合规范畴,不应混杂在业务规则配置组
|
||||
|
||||
**5. 终端安全从系统与集成移入安全审计**
|
||||
- 原位置:与系统集成、自动化场景、规则版本同组(系统集成 + 终端安全 = 4 项)
|
||||
- 调整后:归入安全审计组(合规追溯 + 系统安全 = 5 项)
|
||||
- 理由:终端安全本质是合规管控(联软/火绒管控),与底层引擎集成(系统集成/自动化场景)性质不同
|
||||
|
||||
**6. 新增 12 项菜单覆盖完整功能域**
|
||||
- 运营中心:角色管理、权限矩阵、OTP 管理(v1.0 漏列)
|
||||
- 数据与监控:自动化指标(v1.0 漏列)
|
||||
- 安全审计:配置变更历史、运行期日志、安全审计日志(v1.0 漏列)
|
||||
- 系统与集成:欢迎与引导、自动化场景、规则版本(v1.0 漏列)
|
||||
- 知识与 AI:知识迭代管理、RAGFlow 文档导入、拓扑预览、知识库建议、代答排除、排查流程图(v1.0 部分漏列)
|
||||
@@ -0,0 +1,119 @@
|
||||
# PRD - SLA 仪表盘
|
||||
|
||||
> **REQ编号**: REQ-集成-003
|
||||
> **版本**: v1.0
|
||||
> **优先级**: P2
|
||||
> **阶段**: 远期(5-8个月)
|
||||
> **作者**: 宋献
|
||||
> **日期**: 2026-07-19
|
||||
|
||||
---
|
||||
|
||||
## 一、问题陈述
|
||||
|
||||
**用户问题**:
|
||||
- 管理层无法实时了解 SLA 达成情况
|
||||
- 需要人工统计报表,滞后
|
||||
- 缺乏预警机制,问题发现不及时
|
||||
|
||||
**业务目标**:
|
||||
- 管理层实时看到 SLA 达成率
|
||||
- 支持超时预警
|
||||
- 支持趋势分析
|
||||
|
||||
---
|
||||
|
||||
## 二、需求范围
|
||||
|
||||
### 2.1 核心功能
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| SLA 看板 | 展示 SLA 达成率、响应时间等核心指标 |
|
||||
| 实时刷新 | 数据实时更新 |
|
||||
| 超时预警 | 即将超时时自动预警 |
|
||||
| 趋势分析 | 支持时间维度分析 |
|
||||
|
||||
### 2.2 非目标
|
||||
|
||||
- 不支持自定义 SLA 规则配置
|
||||
- 不支持多租户
|
||||
|
||||
---
|
||||
|
||||
## 三、用户故事
|
||||
|
||||
| 角色 | 用户故事 | 验收标准 |
|
||||
|------|----------|---------|
|
||||
| 管理员 | 我可以查看 SLA 达成率 | 仪表盘展示 SLA 达成率 |
|
||||
| 管理员 | 我可以看到超时预警 | 即将超时时显示预警 |
|
||||
| 管理员 | 我可以看到趋势图 | 支持查看近7天/30天趋势 |
|
||||
|
||||
---
|
||||
|
||||
## 四、功能详情
|
||||
|
||||
### 4.1 核心指标
|
||||
|
||||
| 指标 | 描述 | 计算方式 |
|
||||
|------|------|---------|
|
||||
| SLA 达成率 | 按时解决的会话比例 | 按时解决数/总会话数 |
|
||||
| 平均响应时间 | 首次响应平均耗时 | 总响应时间/响应次数 |
|
||||
| 平均解决时间 | 问题解决平均耗时 | 总解决时间/解决次数 |
|
||||
| 转人工率 | 需人工处理的比例 | 转人工数/总会话数 |
|
||||
|
||||
### 4.2 看板布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ IT智能服务台 - SLA仪表盘 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ SLA达成率│ │平均响应 │ │平均解决 │ │
|
||||
│ │ 92% │ │ 1.2min │ │ 5.6min │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 趋势图 │
|
||||
│ [折线图:近7天SLA达成率趋势] │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 超时预警 │
|
||||
│ · 会话 #1234 即将超时(剩余2分钟) │
|
||||
│ · 会话 #1235 即将超时(剩余5分钟) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 坐席负载 │
|
||||
│ [柱状图:各坐席处理中的会话数] │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.3 预警规则
|
||||
|
||||
| 规则 | 触发条件 |
|
||||
|------|---------|
|
||||
| 即将超时 | 剩余时间 < 10分钟 |
|
||||
| 已超时 | 已超过 SLA 设定时间 |
|
||||
| 坐席繁忙 | 处理中会话 > 5个 |
|
||||
|
||||
---
|
||||
|
||||
## 五、指标设计
|
||||
|
||||
| 指标 | 目标 | 测量方式 |
|
||||
|------|------|---------|
|
||||
| SLA 达成率 | ≥ 90% | 按时解决数/总会话数 |
|
||||
| 数据刷新间隔 | ≤ 30s | 实时数据更新频率 |
|
||||
| 预警准确率 | 100% | 预警后确实超时的比例 |
|
||||
|
||||
---
|
||||
|
||||
## 六、技术方案
|
||||
|
||||
- 管理后台新增 SLA 仪表盘页面
|
||||
- 后端定时计算 SLA 指标
|
||||
- WebSocket 推送预警信息
|
||||
|
||||
---
|
||||
|
||||
## 七、关联文档
|
||||
|
||||
- 前置需求: REQ-集成-001 会议室预定(基础集成能力)
|
||||
- 后续需求: 无
|
||||
@@ -0,0 +1,139 @@
|
||||
# PRD - 自动化闭环
|
||||
|
||||
> **REQ编号**: REQ-集成-004
|
||||
> **版本**: v1.0
|
||||
> **优先级**: P2
|
||||
> **阶段**: 远期(5-8个月)
|
||||
> **作者**: 宋献
|
||||
> **日期**: 2026-07-19
|
||||
|
||||
---
|
||||
|
||||
## 一、问题陈述
|
||||
|
||||
**用户问题**:
|
||||
- 大量简单重复问题占用人工坐席时间
|
||||
- 问题分诊、升级、回访均需人工操作
|
||||
- 整体服务效率有提升空间
|
||||
|
||||
**业务目标**:
|
||||
- 实现问题自动分诊 → 自动升级 → 自动回访的闭环
|
||||
- 减少人工干预,提升服务效率
|
||||
- 释放坐席精力处理复杂问题
|
||||
|
||||
---
|
||||
|
||||
## 二、需求范围
|
||||
|
||||
### 2.1 核心功能
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| 自动分诊 | AI 判断问题类型,自动分配 |
|
||||
| 自动升级 | 符合条件时自动转人工 |
|
||||
| 自动回访 | 问题解决后自动回访确认 |
|
||||
| 规则引擎 | 支持配置自动化规则 |
|
||||
|
||||
### 2.2 非目标
|
||||
|
||||
- 不支持复杂规则配置
|
||||
- 不支持与外部系统集成(如 ITSM)
|
||||
|
||||
---
|
||||
|
||||
## 三、用户故事
|
||||
|
||||
| 角色 | 用户故事 | 验收标准 |
|
||||
|------|----------|---------|
|
||||
| 员工 | 我的问题被自动分到正确的队列 | AI 根据问题类型自动分配 |
|
||||
| 员工 | 我的问题被自动升级 | 符合条件时自动转人工 |
|
||||
| 员工 | 问题解决后有人回访 | 自动发送回访消息 |
|
||||
| 管理员 | 我可以配置自动化规则 | 支持配置触发条件和动作 |
|
||||
|
||||
---
|
||||
|
||||
## 四、功能详情
|
||||
|
||||
### 4.1 自动分诊
|
||||
|
||||
**触发时机**:员工首次提问
|
||||
|
||||
**分诊逻辑**:
|
||||
```
|
||||
员工提问 → AI分析问题类型 → 匹配队列规则 → 分配到对应队列
|
||||
```
|
||||
|
||||
**分诊依据**:
|
||||
- 问题类型(技术咨询、故障申报、咨询投诉)
|
||||
- 紧急程度(高/中/低)
|
||||
- 关键词匹配
|
||||
|
||||
### 4.2 自动升级
|
||||
|
||||
**触发时机**:满足升级条件
|
||||
|
||||
**升级条件**:
|
||||
| 条件 | 描述 |
|
||||
|------|------|
|
||||
| AI 无法解决 | 置信度 < 0.3 |
|
||||
| 多次未解决 | AI 回复 3 次后未解决 |
|
||||
| 用户要求 | 用户明确要求转人工 |
|
||||
| 特定关键词 | 包含"紧急"、"故障"等 |
|
||||
|
||||
**升级流程**:
|
||||
1. 触发升级条件
|
||||
2. 将会话状态标记为"待升级"
|
||||
3. 分配给空闲坐席
|
||||
4. 通知坐席
|
||||
|
||||
### 4.3 自动回访
|
||||
|
||||
**触发时机**:会话标记为"已解决"
|
||||
|
||||
**回访内容**:
|
||||
- "您的问题是否已解决?"
|
||||
- 满意/不满意选项
|
||||
- 反馈输入框
|
||||
|
||||
**回访时机**:
|
||||
- 解决后 30 分钟发送
|
||||
- 仅对 AI 解决的会话
|
||||
|
||||
### 4.4 规则引擎
|
||||
|
||||
**配置项**:
|
||||
| 配置 | 描述 |
|
||||
|------|------|
|
||||
| 触发条件 | 满足什么条件时触发 |
|
||||
| 执行动作 | 触发什么动作 |
|
||||
| 启用/禁用 | 是否启用 |
|
||||
|
||||
**示例规则**:
|
||||
- 触发条件:问题包含"VPN" → 执行动作:分配到"网络支持"队列
|
||||
- 触发条件:用户等待 > 5分钟 → 执行动作:提升优先级
|
||||
|
||||
---
|
||||
|
||||
## 五、指标设计
|
||||
|
||||
| 指标 | 目标 | 测量方式 |
|
||||
|------|------|---------|
|
||||
| 自动分诊准确率 | ≥ 85% | 分诊正确的比例 |
|
||||
| 自动升级准确率 | ≥ 90% | 升级合理的比例 |
|
||||
| 回访率 | ≥ 60% | 收到回访的用户比例 |
|
||||
| 人工干预率 | ≤ 30% | 需人工处理的比例 |
|
||||
|
||||
---
|
||||
|
||||
## 六、技术方案
|
||||
|
||||
- 后端规则引擎服务
|
||||
- 定时任务处理回访
|
||||
- 消息队列解耦各环节
|
||||
|
||||
---
|
||||
|
||||
## 七、关联文档
|
||||
|
||||
- 前置需求: REQ-AI-002 置信度门控
|
||||
- 后续需求: 无
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,806 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>安全策略检查平台 - 原型设计</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
.page {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
max-width: 1200px;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #333;
|
||||
}
|
||||
.nav {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px 24px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
max-width: 1200px;
|
||||
margin: 0 auto 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.nav ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.nav li {
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.nav li.active {
|
||||
background: #07C160;
|
||||
color: #fff;
|
||||
}
|
||||
.nav li:hover:not(.active) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.card {
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
color: #333;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
.stat-card.success {
|
||||
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
||||
}
|
||||
.stat-card.warning {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
.stat-card.info {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #07C160;
|
||||
}
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.btn-sm {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #07C160;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #06ad56;
|
||||
}
|
||||
.btn-default {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
color: #333;
|
||||
}
|
||||
.btn-default:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.btn-danger {
|
||||
background: #fff;
|
||||
border: 1px solid #f56c6c;
|
||||
color: #f56c6c;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 13px;
|
||||
}
|
||||
.table th {
|
||||
background: #fafafa;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
.table tr:hover {
|
||||
background: #fafafa;
|
||||
}
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.tag-danger {
|
||||
background: #fee;
|
||||
color: #f56c6c;
|
||||
}
|
||||
.tag-success {
|
||||
background: #e8f8f0;
|
||||
color: #07C160;
|
||||
}
|
||||
.tag-warning {
|
||||
background: #fdf6ec;
|
||||
color: #e6a23c;
|
||||
}
|
||||
.tag-info {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 12px;
|
||||
color: #333;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.text-muted {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.badge-primary {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li class="active" onclick="switchPage(this, 'overview')">检查概览</li>
|
||||
<li onclick="switchPage(this, 'strategy')">策略管理</li>
|
||||
<li onclick="switchPage(this, 'channel')">渠道管理</li>
|
||||
<li onclick="switchPage(this, 'datasource')">数据源</li>
|
||||
<li onclick="switchPage(this, 'records')">命中记录</li>
|
||||
<li onclick="switchPage(this, 'template')">通知模板</li>
|
||||
<li onclick="switchPage(this, 'schedule')">计划任务</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 页面1: 检查概览 -->
|
||||
<div class="page" id="page-overview">
|
||||
<div class="page-title">安全策略检查平台 - 检查概览</div>
|
||||
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">128</div>
|
||||
<div class="stat-label">总终端数</div>
|
||||
</div>
|
||||
<div class="stat-card warning">
|
||||
<div class="stat-value">3</div>
|
||||
<div class="stat-label">今日命中</div>
|
||||
</div>
|
||||
<div class="stat-card success">
|
||||
<div class="stat-value">125</div>
|
||||
<div class="stat-label">合规终端</div>
|
||||
</div>
|
||||
<div class="stat-card info">
|
||||
<div class="stat-value">97.6%</div>
|
||||
<div class="stat-label">覆盖率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-between" style="margin-bottom: 16px;">
|
||||
<div>
|
||||
<button class="btn btn-primary">立即检查</button>
|
||||
<button class="btn btn-default">导出报告</button>
|
||||
</div>
|
||||
<div class="text-muted">最后检查: 2026-07-20 08:00:00</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">最近执行记录</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>策略</th>
|
||||
<th>执行时间</th>
|
||||
<th>总终端</th>
|
||||
<th>命中数</th>
|
||||
<th>通知成功</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">OpenClaw远程软件</span></td>
|
||||
<td>2026-07-20 08:00</td>
|
||||
<td>128</td>
|
||||
<td>3</td>
|
||||
<td>3</td>
|
||||
<td><span class="tag tag-success">成功</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">OpenClaw远程软件</span></td>
|
||||
<td>2026-07-19 08:00</td>
|
||||
<td>127</td>
|
||||
<td>5</td>
|
||||
<td>5</td>
|
||||
<td><span class="tag tag-success">成功</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页面2: 策略管理 -->
|
||||
<div class="page" id="page-strategy" style="display: none;">
|
||||
<div class="page-title">策略管理</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<span>安全策略列表</span>
|
||||
<button class="btn btn-primary btn-sm">+ 新增策略</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>策略ID</th>
|
||||
<th>策略名称</th>
|
||||
<th>检测规则</th>
|
||||
<th>数据源</th>
|
||||
<th>推送渠道</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>openclaw_remote</td>
|
||||
<td>OpenClaw远程软件检测</td>
|
||||
<td>OpenClaw/向日葵/sunflower</td>
|
||||
<td>火绒API</td>
|
||||
<td>企微应用 + 企微群</td>
|
||||
<td><span class="tag tag-success">已启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-default btn-sm">编辑</button>
|
||||
<button class="btn btn-danger btn-sm">停用</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>unknown_usb</td>
|
||||
<td>未知USB设备检测</td>
|
||||
<td>未登记的USB设备</td>
|
||||
<td>联软/火绒</td>
|
||||
<td>企微应用</td>
|
||||
<td><span class="tag tag-warning">未启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-default btn-sm">编辑</button>
|
||||
<button class="btn btn-primary btn-sm">启用</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>illegal_proxy</td>
|
||||
<td>非法代理软件检测</td>
|
||||
<td>proxy/vpn类软件</td>
|
||||
<td>火绒API</td>
|
||||
<td>企微应用</td>
|
||||
<td><span class="tag tag-warning">未启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-default btn-sm">编辑</button>
|
||||
<button class="btn btn-primary btn-sm">启用</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">新增策略</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">策略ID</label>
|
||||
<input type="text" class="form-input" placeholder="如: unknown_usb">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">策略名称</label>
|
||||
<input type="text" class="form-input" placeholder="如: 未知USB设备检测">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">检测规则(关键词,多个用逗号分隔)</label>
|
||||
<input type="text" class="form-input" placeholder="如: proxy, vpn, 代理">
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">数据源</label>
|
||||
<select class="form-input">
|
||||
<option>火绒API</option>
|
||||
<option>联软Excel</option>
|
||||
<option>火绒API + 联软</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">推送渠道</label>
|
||||
<select class="form-input">
|
||||
<option>企微应用</option>
|
||||
<option>企微应用 + 企微群</option>
|
||||
<option>企微 + 邮件</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary">保存策略</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页面3: 渠道管理 -->
|
||||
<div class="page" id="page-channel" style="display: none;">
|
||||
<div class="page-title">渠道管理</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<span>推送渠道列表</span>
|
||||
<button class="btn btn-primary btn-sm">+ 新增渠道</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>渠道ID</th>
|
||||
<th>渠道名称</th>
|
||||
<th>类型</th>
|
||||
<th>配置参数</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>wecom_app</td>
|
||||
<td>企微应用消息</td>
|
||||
<td><span class="badge badge-primary">企微</span></td>
|
||||
<td class="text-muted">应用ID: wx123... Secret: ****</td>
|
||||
<td><span class="tag tag-success">已启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-default btn-sm">配置</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>wecom_group</td>
|
||||
<td>企微群通知</td>
|
||||
<td><span class="badge badge-primary">企微</span></td>
|
||||
<td class="text-muted">群ID: 123456789</td>
|
||||
<td><span class="tag tag-success">已启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-default btn-sm">配置</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>email</td>
|
||||
<td>邮件通知</td>
|
||||
<td><span class="tag tag-info">邮件</span></td>
|
||||
<td class="text-muted">SMTP: smtp.example.com</td>
|
||||
<td><span class="tag tag-warning">未启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-default btn-sm">配置</button>
|
||||
<button class="btn btn-primary btn-sm">启用</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>sms</td>
|
||||
<td>短信通知</td>
|
||||
<td><span class="tag tag-info">短信</span></td>
|
||||
<td class="text-muted">未配置</td>
|
||||
<td><span class="tag tag-warning">未启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-default btn-sm">配置</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">新增渠道配置</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">渠道ID</label>
|
||||
<input type="text" class="form-input" placeholder="如: email">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">渠道名称</label>
|
||||
<input type="text" class="form-input" placeholder="如: 邮件通知">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">渠道类型</label>
|
||||
<select class="form-input">
|
||||
<option value="wecom">企微应用</option>
|
||||
<option value="wecom_group">企微群</option>
|
||||
<option value="email">邮件</option>
|
||||
<option value="sms">短信</option>
|
||||
<option value="dingtalk">钉钉</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">配置参数</label>
|
||||
<textarea class="form-input" rows="3" placeholder="JSON格式配置"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary">保存渠道</button>
|
||||
<button class="btn btn-default">测试连接</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页面4: 数据源管理 -->
|
||||
<div class="page" id="page-datasource" style="display: none;">
|
||||
<div class="page-title">数据源管理</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<span>火绒API配置</span>
|
||||
<span class="text-muted" style="font-weight: normal; font-size: 12px;">(已存在,复用现有配置)</span>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">服务器地址</label>
|
||||
<input type="text" class="form-input" value="https://huorong.company.com" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">连接状态</label>
|
||||
<div style="padding: 10px;">
|
||||
<span class="tag tag-success">已连接</span>
|
||||
<span class="text-muted" style="margin-left: 8px;">最后同步: 2026-07-20 08:00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">联软数据导入</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">上传Excel文件</label>
|
||||
<input type="file" class="form-input" accept=".xlsx,.xls">
|
||||
</div>
|
||||
<div class="text-muted" style="margin-bottom: 12px;">
|
||||
已导入: 2026-07-19 15:30 (94条记录, 88用户, 91设备)
|
||||
</div>
|
||||
<button class="btn btn-primary">导入Excel</button>
|
||||
<button class="btn btn-default">查看历史</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页面5: 命中记录 -->
|
||||
<div class="page" id="page-records" style="display: none;">
|
||||
<div class="page-title">命中记录</div>
|
||||
|
||||
<div class="flex" style="margin-bottom: 16px;">
|
||||
<input type="text" class="form-input" placeholder="搜索用户名/设备名" style="max-width: 300px;">
|
||||
<select class="form-input" style="width: auto;">
|
||||
<option>全部策略</option>
|
||||
<option>OpenClaw远程软件</option>
|
||||
<option>未知USB设备</option>
|
||||
</select>
|
||||
<select class="form-input" style="width: auto;">
|
||||
<option>全部状态</option>
|
||||
<option>待处理</option>
|
||||
<option>已通知</option>
|
||||
<option>已解决</option>
|
||||
<option>误报</option>
|
||||
</select>
|
||||
<button class="btn btn-default">筛选</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<span>命中记录列表</span>
|
||||
<span class="text-muted" style="font-weight: normal; font-size: 12px;">共 3 条记录</span>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>策略</th>
|
||||
<th>用户名</th>
|
||||
<th>部门</th>
|
||||
<th>设备名称</th>
|
||||
<th>IP地址</th>
|
||||
<th>命中内容</th>
|
||||
<th>首次检测</th>
|
||||
<th>最近检测</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">OpenClaw</span></td>
|
||||
<td>zhangsan</td>
|
||||
<td>研发部</td>
|
||||
<td>DESKTOP-ABC123</td>
|
||||
<td>10.90.5.101</td>
|
||||
<td>OpenClaw</td>
|
||||
<td>2026-07-18</td>
|
||||
<td>2026-07-20</td>
|
||||
<td><span class="tag tag-danger">待处理</span></td>
|
||||
<td>
|
||||
<select class="form-input" style="width: auto; padding: 4px 8px; font-size: 12px;">
|
||||
<option>标记为...</option>
|
||||
<option>已通知</option>
|
||||
<option>已解决</option>
|
||||
<option>误报</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">OpenClaw</span></td>
|
||||
<td>lisi</td>
|
||||
<td>销售部</td>
|
||||
<td>DESKTOP-DEF456</td>
|
||||
<td>10.90.5.102</td>
|
||||
<td>向日葵</td>
|
||||
<td>2026-07-15</td>
|
||||
<td>2026-07-20</td>
|
||||
<td><span class="tag tag-warning">已通知</span></td>
|
||||
<td>
|
||||
<select class="form-input" style="width: auto; padding: 4px 8px; font-size: 12px;">
|
||||
<option>标记为...</option>
|
||||
<option selected>已解决</option>
|
||||
<option>误报</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">OpenClaw</span></td>
|
||||
<td>wangwu</td>
|
||||
<td>市场部</td>
|
||||
<td>DESKTOP-GHI789</td>
|
||||
<td>10.90.5.103</td>
|
||||
<td>OpenClaw</td>
|
||||
<td>2026-07-10</td>
|
||||
<td>2026-07-19</td>
|
||||
<td><span class="tag tag-success">已解决</span></td>
|
||||
<td>
|
||||
<span class="text-muted" style="font-size: 12px;">2026-07-19 IT管理员</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">状态说明</div>
|
||||
<div class="text-muted" style="font-size: 13px;">
|
||||
<span class="tag tag-danger" style="margin-right: 8px;">待处理</span> 新检测到,未处理<br><br>
|
||||
<span class="tag tag-warning" style="margin-right: 8px;">已通知</span> 已发送企微通知,等待用户处理<br><br>
|
||||
<span class="tag tag-success" style="margin-right: 8px;">已解决</span> 再次检测未命中(用户已卸载)或管理员手动标记<br><br>
|
||||
<span class="tag tag-info" style="margin-right: 8px;">误报</span> 管理员确认为误报
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页面6: 通知模板 -->
|
||||
<div class="page" id="page-template" style="display: none;">
|
||||
<div class="page-title">通知模板管理</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">模板变量说明</div>
|
||||
<div class="text-muted">
|
||||
<code>{username}</code> 用户名
|
||||
<code>{fullname}</code> 用户全名
|
||||
<code>{device}</code> 设备名称
|
||||
<code>{dept}</code> 部门
|
||||
<code>{software}</code> 命中内容
|
||||
<code>{strategy}</code> 策略名称
|
||||
<code>{detect_time}</code> 检测时间
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">企微应用消息模板</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">策略选择</label>
|
||||
<select class="form-input" style="max-width: 300px;">
|
||||
<option>OpenClaw远程软件检测</option>
|
||||
<option>未知USB设备检测</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">标题</label>
|
||||
<input type="text" class="form-input" value="【安全通知】您有设备需要处理">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">正文内容</label>
|
||||
<textarea class="form-input" rows="6">尊敬的 {fullname} 您好:
|
||||
|
||||
经检测,您使用的设备 {device}(IP: {ip})安装了非授权软件 {software},请及时卸载。
|
||||
|
||||
策略:{strategy}
|
||||
检测时间:{detect_time}
|
||||
|
||||
如有疑问,请联系IT部门。</textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary">保存模板</button>
|
||||
<button class="btn btn-default">预览效果</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">企微群通知模板(部门汇总)</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">标题</label>
|
||||
<input type="text" class="form-input" value="【{dept}部门安全合规周报】">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">正文内容</label>
|
||||
<textarea class="form-input" rows="4">部门:{dept}
|
||||
命中数量:{count}台
|
||||
详情:
|
||||
{detail}
|
||||
|
||||
请部门负责人督促相关人员及时处理。</textarea>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="checkbox" id="notifyDept" checked>
|
||||
<label for="notifyDept">启用部门负责人汇总通知</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页面7: 计划任务 -->
|
||||
<div class="page" id="page-schedule" style="display: none;">
|
||||
<div class="page-title">计划任务管理</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">活动期间配置</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label class="form-label">活动开始日期</label>
|
||||
<input type="date" class="form-input" value="2026-07-20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">活动结束日期</label>
|
||||
<input type="date" class="form-input" value="2026-09-30">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<input type="checkbox" id="activityEnabled" checked>
|
||||
<label for="activityEnabled">启用活动期间自动检查</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">策略定时配置</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>策略</th>
|
||||
<th>执行时间</th>
|
||||
<th>频率</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>OpenClaw远程软件检测</td>
|
||||
<td><input type="time" class="form-input" value="08:00" style="width: 120px;"></td>
|
||||
<td>每天</td>
|
||||
<td><span class="tag tag-success">已启用</span></td>
|
||||
<td><button class="btn btn-default btn-sm">编辑</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>未知USB设备检测</td>
|
||||
<td><input type="time" class="form-input" value="09:00" style="width: 120px;"></td>
|
||||
<td>每周一</td>
|
||||
<td><span class="tag tag-warning">已暂停</span></td>
|
||||
<td><button class="btn btn-default btn-sm">编辑</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">白名单管理</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">添加白名单用户/终端</label>
|
||||
<input type="text" class="form-input" placeholder="输入用户名或设备名,多个用逗号分隔">
|
||||
</div>
|
||||
<button class="btn btn-default">添加</button>
|
||||
<div class="text-muted" style="margin-top: 12px;">
|
||||
当前白名单: 2项 (IT管理员, 测试设备)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchPage(el, pageId) {
|
||||
document.querySelectorAll('.nav li').forEach(li => li.classList.remove('active'));
|
||||
el.classList.add('active');
|
||||
|
||||
document.querySelectorAll('.page').forEach(page => page.style.display = 'none');
|
||||
document.getElementById('page-' + pageId).style.display = 'block';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,746 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>方案三:邀请功能原型 — 交互流程</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0f172a;
|
||||
--bg2: #1e293b;
|
||||
--bg3: #334155;
|
||||
--border: #475569;
|
||||
--text: #f1f5f9;
|
||||
--dim: #94a3b8;
|
||||
--accent: #3b82f6;
|
||||
--accent-l: #60a5fa;
|
||||
--green: #22c55e;
|
||||
--amber: #f59e0b;
|
||||
--red: #ef4444;
|
||||
--purple: #a855f7;
|
||||
--cyan: #06b6d4;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;background:var(--bg);color:var(--text);line-height:1.6}
|
||||
|
||||
.app{max-width:1200px;margin:0 auto;padding:20px}
|
||||
|
||||
h1{font-size:1.8em;font-weight:700;background:linear-gradient(135deg,var(--accent-l),var(--cyan));-webkit-background-clip:text;-webkit-text-fill-color:transparent;text-align:center;margin-bottom:8px}
|
||||
.subtitle{text-align:center;color:var(--dim);margin-bottom:32px;font-size:0.95em}
|
||||
|
||||
h2{font-size:1.2em;font-weight:600;color:var(--accent-l);margin:24px 0 12px;padding-left:12px;border-left:3px solid var(--accent)}
|
||||
|
||||
.flow-steps{display:flex;gap:8px;margin-bottom:32px;overflow-x:auto;padding:8px 0}
|
||||
.step{flex-shrink:0;padding:10px 16px;border-radius:10px;background:var(--bg2);border:1px solid var(--border);cursor:pointer;transition:all .2s;font-size:0.88em;white-space:nowrap}
|
||||
.step:hover{border-color:var(--accent);background:#1e3a5f}
|
||||
.step.active{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
|
||||
.step .step-num{display:inline-block;width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,0.15);text-align:center;line-height:20px;font-size:0.75em;margin-right:6px}
|
||||
.step.active .step-num{background:rgba(255,255,255,0.3)}
|
||||
|
||||
.scene{display:none}
|
||||
.scene.show{display:block}
|
||||
|
||||
.card{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:16px}
|
||||
.card h3{color:var(--accent-l);margin-bottom:10px;font-size:1em}
|
||||
|
||||
.row2{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}
|
||||
@media(max-width:768px){.row2{grid-template-columns:1fr}}
|
||||
|
||||
.mockup{background:#111827;border-radius:12px;border:1px solid var(--border);overflow:hidden;position:relative}
|
||||
.mockup-bar{height:32px;background:var(--bg3);display:flex;align-items:center;padding:0 12px;gap:6px;border-bottom:1px solid var(--border)}
|
||||
.mockup-dot{width:10px;height:10px;border-radius:50%}
|
||||
.mockup-body{padding:16px;min-height:400px;position:relative}
|
||||
|
||||
.tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:0.78em;font-weight:600}
|
||||
.tag-blue{background:rgba(59,130,246,0.2);color:var(--accent-l)}
|
||||
.tag-green{background:rgba(34,197,94,0.2);color:var(--green)}
|
||||
.tag-amber{background:rgba(245,158,11,0.2);color:var(--amber)}
|
||||
|
||||
table{width:100%;border-collapse:collapse;font-size:0.88em}
|
||||
th,td{padding:8px 12px;text-align:left;border-bottom:1px solid var(--border)}
|
||||
th{background:var(--bg3);color:var(--accent-l);font-weight:600;font-size:0.85em}
|
||||
|
||||
.avatar{display:inline-flex;width:32px;height:32px;border-radius:50%;align-items:center;justify-content:center;font-size:0.8em;font-weight:600;flex-shrink:0}
|
||||
.av-user{background:#3b82f6;color:#fff}
|
||||
.av-agent{background:#22c55e;color:#fff}
|
||||
.av-invited{background:#f59e0b;color:#fff}
|
||||
|
||||
.msg{display:flex;gap:8px;margin-bottom:10px;align-items:flex-start}
|
||||
.msg-left{flex-direction:row}
|
||||
.msg-right{flex-direction:row-reverse}
|
||||
.msg-bubble{max-width:70%;padding:10px 14px;border-radius:12px;font-size:0.88em;line-height:1.5}
|
||||
.bubble-user{background:#1e3a5f;color:var(--text)}
|
||||
.bubble-agent{background:#1a3d2e;color:var(--text)}
|
||||
.bubble-sys{background:var(--bg3);color:var(--dim);font-size:0.82em;max-width:100%;text-align:center;padding:6px 12px;border-radius:8px}
|
||||
.bubble-invited{background:#3d2e1a;color:var(--text)}
|
||||
|
||||
.btn{padding:8px 16px;border-radius:8px;border:1px solid var(--border);background:var(--bg3);color:var(--text);cursor:pointer;font-size:0.85em;transition:all .15s;display:inline-flex;align-items:center;gap:6px}
|
||||
.btn:hover{border-color:var(--accent);background:#1e3a5f}
|
||||
.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}
|
||||
.btn-primary:hover{background:#2563eb}
|
||||
|
||||
.tree{padding:4px 0}
|
||||
.tree-item{display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:0.85em;transition:background .15s}
|
||||
.tree-item:hover{background:var(--bg3)}
|
||||
.tree-item.selected{background:rgba(59,130,246,0.15);outline:1px solid var(--accent)}
|
||||
.tree-expand{width:16px;text-align:center;color:var(--dim);font-size:0.8em;cursor:pointer}
|
||||
.tree-ck{width:16px;height:16px;border:1px solid var(--border);border-radius:3px;display:inline-flex;align-items:center;justify-content:center;font-size:0.7em;cursor:pointer;flex-shrink:0}
|
||||
.tree-ck.checked{background:var(--accent);border-color:var(--accent);color:#fff}
|
||||
|
||||
.desc{color:var(--dim);font-size:0.9em;margin:8px 0;line-height:1.7}
|
||||
.desc strong{color:var(--text)}
|
||||
|
||||
.callout{background:rgba(59,130,246,0.1);border-left:3px solid var(--accent);border-radius:0 8px 8px 0;padding:12px 16px;margin:12px 0;font-size:0.9em}
|
||||
.callout.warn{background:rgba(245,158,11,0.1);border-left-color:var(--amber)}
|
||||
.callout.ok{background:rgba(34,197,94,0.1);border-left-color:var(--green)}
|
||||
|
||||
.arrow{color:var(--accent);font-size:1.2em;font-weight:700;margin:0 6px}
|
||||
|
||||
.seq{display:flex;flex-direction:column;gap:0;margin:16px 0}
|
||||
.seq-row{display:flex;align-items:center;gap:12px;padding:8px 0}
|
||||
.seq-line{width:2px;height:20px;background:var(--accent);margin-left:15px}
|
||||
.seq-dot{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:0.8em;font-weight:700;flex-shrink:0}
|
||||
.seq-dot-blue{background:var(--accent);color:#fff}
|
||||
.seq-dot-green{background:var(--green);color:#fff}
|
||||
.seq-dot-amber{background:var(--amber);color:#fff}
|
||||
.seq-dot-purple{background:var(--purple);color:#fff}
|
||||
.seq-text{flex:1;font-size:0.88em;color:var(--dim)}
|
||||
.seq-text strong{color:var(--text)}
|
||||
.seq-actor{font-size:0.75em;color:var(--accent-l);font-weight:600}
|
||||
|
||||
.wecom-notif{background:var(--bg3);border-radius:10px;padding:14px;border:1px solid var(--border);max-width:340px;margin:12px auto}
|
||||
.wecom-notif-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
||||
.wecom-notif-app{font-size:0.8em;color:var(--accent-l);font-weight:600}
|
||||
.wecom-notif-time{font-size:0.7em;color:var(--dim)}
|
||||
.wecom-notif-title{font-weight:600;font-size:0.92em;margin-bottom:4px}
|
||||
.wecom-notif-body{font-size:0.85em;color:var(--dim);line-height:1.5}
|
||||
.wecom-notif-btn{display:inline-block;margin-top:8px;padding:6px 14px;border-radius:6px;background:var(--accent);color:#fff;font-size:0.82em;cursor:pointer}
|
||||
|
||||
.compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||||
@media(max-width:768px){.compare-grid{grid-template-columns:1fr}}
|
||||
.compare-card{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:16px}
|
||||
.compare-card.recommend{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
|
||||
.compare-card h3{text-align:center;margin-bottom:12px;font-size:1em}
|
||||
.compare-card ul{padding-left:16px;font-size:0.88em}
|
||||
.compare-card li{margin-bottom:4px;color:var(--dim)}
|
||||
|
||||
.badge{display:inline-block;padding:2px 10px;border-radius:12px;font-size:0.75em;font-weight:600}
|
||||
.badge-blue{background:rgba(59,130,246,0.2);color:var(--accent-l)}
|
||||
.badge-green{background:rgba(34,197,94,0.2);color:var(--green)}
|
||||
.badge-red{background:rgba(239,68,68,0.2);color:var(--red)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
|
||||
<h1>邀请功能 — 方案三交互原型</h1>
|
||||
<div class="subtitle">WebSocket + 应用消息双通道架构 | 在现有一对一对话上扩展多人参与</div>
|
||||
|
||||
<div class="flow-steps" id="flowSteps">
|
||||
<div class="step active" data-scene="s0">0. 架构概览</div>
|
||||
<div class="step" data-scene="s1">1. 一对一会话中</div>
|
||||
<div class="step" data-scene="s2">2. 点击邀请</div>
|
||||
<div class="step" data-scene="s3">3. 选人界面</div>
|
||||
<div class="step" data-scene="s4">4. 历史共享</div>
|
||||
<div class="step" data-scene="s5">5. 确认邀请</div>
|
||||
<div class="step" data-scene="s6">6. 被邀请方通知</div>
|
||||
<div class="step" data-scene="s7">7. 加入会话</div>
|
||||
<div class="step" data-scene="s8">8. 多人会话</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 0: 架构概览 ===== -->
|
||||
<div class="scene show" id="s0">
|
||||
<div class="card">
|
||||
<h3>方案三核心架构</h3>
|
||||
<p class="desc">在现有 <strong>WebSocket 双通道</strong> 架构上扩展,不引入群聊概念。后端维护 <strong>conversation.participants</strong> 数组,所有参与者共享同一个 WebSocket 会话通道。</p>
|
||||
</div>
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.8em;margin-left:8px">架构流程图</span></div>
|
||||
<div class="mockup-body" style="display:flex;align-items:center;justify-content:center;min-height:300px">
|
||||
<svg viewBox="0 0 700 260" style="max-width:100%">
|
||||
<defs>
|
||||
<marker id="arrowBlue" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#3b82f6"/></marker>
|
||||
<marker id="arrowGreen" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#22c55e"/></marker>
|
||||
<marker id="arrowAmber" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#f59e0b"/></marker>
|
||||
</defs>
|
||||
<rect x="20" y="80" width="120" height="100" rx="12" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1"/>
|
||||
<text x="80" y="118" text-anchor="middle" fill="#60a5fa" font-size="13" font-weight="600">H5 员工端</text>
|
||||
<text x="80" y="138" text-anchor="middle" fill="#94a3b8" font-size="10">发起者</text>
|
||||
<text x="80" y="155" text-anchor="middle" fill="#94a3b8" font-size="10">WebSocket</text>
|
||||
|
||||
<rect x="290" y="40" width="120" height="80" rx="12" fill="#1a3d2e" stroke="#22c55e" stroke-width="1"/>
|
||||
<text x="350" y="72" text-anchor="middle" fill="#4ade80" font-size="13" font-weight="600">后端</text>
|
||||
<text x="350" y="92" text-anchor="middle" fill="#94a3b8" font-size="10">FastAPI + Redis</text>
|
||||
<text x="350" y="107" text-anchor="middle" fill="#94a3b8" font-size="10">participants[]</text>
|
||||
|
||||
<rect x="290" y="160" width="120" height="80" rx="12" fill="#3d2e1a" stroke="#f59e0b" stroke-width="1"/>
|
||||
<text x="350" y="192" text-anchor="middle" fill="#fbbf24" font-size="13" font-weight="600">企微 API</text>
|
||||
<text x="350" y="210" text-anchor="middle" fill="#94a3b8" font-size="10">应用消息推送</text>
|
||||
<text x="350" y="225" text-anchor="middle" fill="#94a3b8" font-size="10">通讯录</text>
|
||||
|
||||
<rect x="560" y="30" width="120" height="80" rx="12" fill="#1a3d2e" stroke="#22c55e" stroke-width="1"/>
|
||||
<text x="620" y="62" text-anchor="middle" fill="#4ade80" font-size="13" font-weight="600">坐席工作台</text>
|
||||
<text x="620" y="82" text-anchor="middle" fill="#94a3b8" font-size="10">WebSocket</text>
|
||||
<text x="620" y="97" text-anchor="middle" fill="#94a3b8" font-size="10">接单+邀请</text>
|
||||
|
||||
<rect x="560" y="150" width="120" height="80" rx="12" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1"/>
|
||||
<text x="620" y="182" text-anchor="middle" fill="#60a5fa" font-size="13" font-weight="600">H5 被邀请人</text>
|
||||
<text x="620" y="202" text-anchor="middle" fill="#94a3b8" font-size="10">企微消息→H5</text>
|
||||
<text x="620" y="217" text-anchor="middle" fill="#94a3b8" font-size="10">WebSocket</text>
|
||||
|
||||
<line x1="140" y1="110" x2="290" y2="80" stroke="#3b82f6" stroke-width="1.5" marker-end="url(#arrowBlue)"/>
|
||||
<line x1="410" y1="80" x2="560" y2="70" stroke="#22c55e" stroke-width="1.5" marker-end="url(#arrowGreen)"/>
|
||||
<line x1="140" y1="140" x2="290" y2="80" stroke="#3b82f6" stroke-width="1.5" stroke-dasharray="4 3"/>
|
||||
<line x1="350" y1="120" x2="350" y2="160" stroke="#f59e0b" stroke-width="1.5" marker-end="url(#arrowAmber)"/>
|
||||
<line x1="410" y1="200" x2="560" y2="190" stroke="#3b82f6" stroke-width="1.5" marker-end="url(#arrowBlue)"/>
|
||||
|
||||
<text x="200" y="80" fill="#94a3b8" font-size="9">WS 双向</text>
|
||||
<text x="470" y="65" fill="#94a3b8" font-size="9">WS 双向</text>
|
||||
<text x="370" y="148" fill="#94a3b8" font-size="9">邀请通知</text>
|
||||
<text x="470" y="185" fill="#94a3b8" font-size="9">应用消息→H5→WS</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="callout ok"><strong>关键区别</strong>:不创建企微群聊,所有参与者通过 H5+WebSocket 接入同一个后端 conversation。企微应用消息仅用于「通知被邀请人」和「断连降级推送」。</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 1: 一对一会话中 ===== -->
|
||||
<div class="scene" id="s1">
|
||||
<div class="card">
|
||||
<h3>场景:坐席正在与用户一对一对话</h3>
|
||||
<p class="desc">用户「张三」遇到 VPN 问题,坐席「小李」正在处理。对话过程中发现需要网络安全组同事「王工」协助排查。</p>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">坐席工作台 — 会话区</span></div>
|
||||
<div class="mockup-body">
|
||||
<div style="display:flex;align-items:center;gap:8px;padding-bottom:10px;border-bottom:1px solid var(--border);margin-bottom:12px">
|
||||
<div class="avatar av-user">张</div>
|
||||
<div style="flex:1">
|
||||
<div style="font-weight:600;font-size:0.92em">张三</div>
|
||||
<div style="font-size:0.78em;color:var(--dim)">IT研发部 · 高级开发工程师</div>
|
||||
</div>
|
||||
<span class="tag tag-green">会话中</span>
|
||||
<button class="btn" style="font-size:0.8em" onclick="goScene('s2')">+ 邀请</button>
|
||||
</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div class="msg-bubble bubble-user">VPN 连接不上,提示证书错误</div>
|
||||
</div>
|
||||
<div class="msg msg-right">
|
||||
<div class="avatar av-agent" style="width:28px;height:28px;font-size:0.7em">李</div>
|
||||
<div class="msg-bubble bubble-agent">您好,请问您使用的是 ATrust 还是零信任客户端?</div>
|
||||
</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div class="msg-bubble bubble-user">ATrust,之前一直正常的,今天突然不行了</div>
|
||||
</div>
|
||||
<div class="msg msg-right">
|
||||
<div class="avatar av-agent" style="width:28px;height:28px;font-size:0.7em">李</div>
|
||||
<div class="msg-bubble bubble-agent">这个问题可能涉及网络策略变更,我邀请网络安全组的同事一起排查</div>
|
||||
</div>
|
||||
<div style="text-align:center;margin:16px 0">
|
||||
<button class="btn-primary btn" onclick="goScene('s2')">点击「+ 邀请」按钮</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="card">
|
||||
<h3>交互说明</h3>
|
||||
<div class="seq">
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-green">1</div><div class="seq-text"><strong>坐席点击「+ 邀请」</strong><br><span class="seq-actor">坐席工作台</span></div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-blue">2</div><div class="seq-text"><strong>弹出选人弹窗</strong>(组织架构树/搜索)<br><span class="seq-actor">坐席工作台</span></div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-amber">3</div><div class="seq-text"><strong>选择历史消息共享范围</strong><br><span class="seq-actor">选人弹窗</span></div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-blue">4</div><div class="seq-text"><strong>确认邀请 → 后端处理</strong><br><span class="seq-actor">后端 API</span></div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-amber">5</div><div class="seq-text"><strong>企微应用消息通知被邀请人</strong><br><span class="seq-actor">企微 API</span></div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-green">6</div><div class="seq-text"><strong>被邀请人打开 H5 加入会话</strong><br><span class="seq-actor">H5 员工端</span></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 2: 点击邀请 ===== -->
|
||||
<div class="scene" id="s2">
|
||||
<div class="card">
|
||||
<h3>步骤 2:点击「+ 邀请」→ 弹出选人弹窗</h3>
|
||||
<p class="desc">弹窗包含三个区域:<strong>组织架构树</strong>(左侧)、<strong>已选人员</strong>(右侧上方)、<strong>历史消息共享设置</strong>(右侧下方)。</p>
|
||||
</div>
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">坐席工作台 — 邀请弹窗</span></div>
|
||||
<div class="mockup-body" style="min-height:440px">
|
||||
<div style="display:flex;gap:16px;height:100%">
|
||||
<!-- 左侧:组织架构 -->
|
||||
<div style="flex:1;border-right:1px solid var(--border);padding-right:12px">
|
||||
<div style="font-weight:600;font-size:0.9em;margin-bottom:8px;color:var(--accent-l)">选择邀请对象</div>
|
||||
<input type="text" placeholder="搜索姓名/部门..." style="width:100%;padding:8px 12px;border-radius:8px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:0.85em;margin-bottom:10px;outline:none">
|
||||
<div class="tree">
|
||||
<div class="tree-item" onclick="toggleTree(this)">
|
||||
<div class="tree-expand">▼</div>
|
||||
<div class="tree-ck" onclick="event.stopPropagation();toggleCk(this)">✓</div>
|
||||
<span>信息技术部</span>
|
||||
</div>
|
||||
<div style="padding-left:20px">
|
||||
<div class="tree-item" onclick="toggleTree(this)">
|
||||
<div class="tree-expand">▶</div>
|
||||
<div class="tree-ck" onclick="event.stopPropagation();toggleCk(this)"></div>
|
||||
<span>IT支持组</span>
|
||||
</div>
|
||||
<div class="tree-item selected">
|
||||
<div class="tree-expand" style="visibility:hidden"></div>
|
||||
<div class="tree-ck checked" onclick="event.stopPropagation();toggleCk(this)">✓</div>
|
||||
<span>王工</span>
|
||||
<span style="font-size:0.75em;color:var(--dim);margin-left:4px">网络安全</span>
|
||||
</div>
|
||||
<div class="tree-item">
|
||||
<div class="tree-expand" style="visibility:hidden"></div>
|
||||
<div class="tree-ck" onclick="event.stopPropagation();toggleCk(this)"></div>
|
||||
<span>赵磊</span>
|
||||
<span style="font-size:0.75em;color:var(--dim);margin-left:4px">系统运维</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding-left:20px">
|
||||
<div class="tree-item">
|
||||
<div class="tree-expand" style="visibility:hidden"></div>
|
||||
<div class="tree-ck" onclick="event.stopPropagation();toggleCk(this)"></div>
|
||||
<span>软件开发组</span>
|
||||
<span style="font-size:0.75em;color:var(--dim);margin-left:4px">8人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-item" onclick="toggleTree(this)">
|
||||
<div class="tree-expand">▶</div>
|
||||
<div class="tree-ck" onclick="event.stopPropagation();toggleCk(this)"></div>
|
||||
<span>信息安全部</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧:已选+设置 -->
|
||||
<div style="flex:1;padding-left:12px">
|
||||
<div style="font-weight:600;font-size:0.9em;margin-bottom:8px;color:var(--accent-l)">已选择 (1)</div>
|
||||
<div style="background:var(--bg3);border-radius:8px;padding:10px;margin-bottom:16px">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<div class="avatar av-invited" style="width:28px;height:28px;font-size:0.7em">王</div>
|
||||
<div style="flex:1;font-size:0.88em">王工</div>
|
||||
<span style="font-size:0.75em;color:var(--dim)">信息技术部·网络安全</span>
|
||||
<span style="cursor:pointer;color:var(--red);font-size:0.85em">✕</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight:600;font-size:0.9em;margin-bottom:8px;color:var(--accent-l)">历史消息共享</div>
|
||||
<div style="background:var(--bg3);border-radius:8px;padding:12px;margin-bottom:16px">
|
||||
<div style="font-size:0.85em;color:var(--dim);margin-bottom:8px">被邀请人加入后能看到哪些历史消息?</div>
|
||||
<label style="display:flex;align-items:center;gap:8px;font-size:0.88em;margin-bottom:6px;cursor:pointer">
|
||||
<input type="radio" name="share" checked style="accent-color:var(--accent)"> 共享全部历史消息
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:8px;font-size:0.88em;margin-bottom:6px;cursor:pointer">
|
||||
<input type="radio" name="share" style="accent-color:var(--accent)"> 仅共享最近10条消息
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:8px;font-size:0.88em;cursor:pointer">
|
||||
<input type="radio" name="share" style="accent-color:var(--accent)"> 不共享历史消息
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="font-weight:600;font-size:0.9em;margin-bottom:8px;color:var(--accent-l)">邀请说明(可选)</div>
|
||||
<textarea placeholder="例如:需要你协助排查VPN证书问题..." style="width:100%;height:56px;padding:8px 12px;border-radius:8px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:0.85em;resize:none;outline:none"></textarea>
|
||||
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px">
|
||||
<button class="btn" onclick="goScene('s1')">取消</button>
|
||||
<button class="btn btn-primary" onclick="goScene('s5')">确认邀请</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 3: 选人界面(同Scene2,此处展示另一种交互:选部门) ===== -->
|
||||
<div class="scene" id="s3">
|
||||
<div class="card">
|
||||
<h3>步骤 3:选择人员 — 支持按部门批量邀请</h3>
|
||||
<p class="desc">勾选部门节点 = 邀请该部门<strong>所有人</strong>。每个被邀请人都会收到独立的应用消息通知。</p>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">选人 — 勾选整个部门</span></div>
|
||||
<div class="mockup-body" style="min-height:280px">
|
||||
<div class="tree">
|
||||
<div class="tree-item" style="background:rgba(59,130,246,0.1)">
|
||||
<div class="tree-expand">▼</div>
|
||||
<div class="tree-ck checked">✓</div>
|
||||
<span style="font-weight:600">信息安全部</span>
|
||||
<span class="tag tag-blue" style="margin-left:8px">全部邀请</span>
|
||||
</div>
|
||||
<div style="padding-left:20px">
|
||||
<div class="tree-item">
|
||||
<div class="tree-expand" style="visibility:hidden"></div>
|
||||
<div class="tree-ck checked">✓</div>
|
||||
<span>陈安全</span>
|
||||
<span style="font-size:0.75em;color:var(--dim);margin-left:4px">安全主管</span>
|
||||
</div>
|
||||
<div class="tree-item">
|
||||
<div class="tree-expand" style="visibility:hidden"></div>
|
||||
<div class="tree-ck checked">✓</div>
|
||||
<span>刘防护</span>
|
||||
<span style="font-size:0.75em;color:var(--dim);margin-left:4px">安全工程师</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-item">
|
||||
<div class="tree-expand">▶</div>
|
||||
<div class="tree-ck"></div>
|
||||
<span>IT支持组</span>
|
||||
<span style="font-size:0.75em;color:var(--dim);margin-left:4px">3人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="card">
|
||||
<h3>选人交互规则</h3>
|
||||
<table>
|
||||
<tr><th>操作</th><th>效果</th></tr>
|
||||
<tr><td>勾选人员</td><td>该人员加入已选列表</td></tr>
|
||||
<tr><td>勾选部门</td><td>该部门下所有人员加入已选列表</td></tr>
|
||||
<tr><td>搜索姓名</td><td>模糊匹配,点击结果直接加入已选</td></tr>
|
||||
<tr><td>取消勾选</td><td>从已选列表移除</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="callout warn"><strong>注意</strong>:邀请人数建议上限 10 人。超过 10 人时应提示「当前邀请人数较多,建议优先邀请关键人员」。这不是硬限制,而是用户体验优化。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 4: 历史消息共享 ===== -->
|
||||
<div class="scene" id="s4">
|
||||
<div class="card">
|
||||
<h3>步骤 4:历史消息共享设置</h3>
|
||||
<p class="desc">邀请时坐席可以选择被邀请人能看到哪些历史消息。这是<strong>隐私保护</strong>的重要设计——避免敏感信息(如员工个人账号问题)被无关人员看到。</p>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<div class="card">
|
||||
<h3>三种共享模式对比</h3>
|
||||
<table>
|
||||
<tr><th>模式</th><th>适用场景</th><th>隐私风险</th></tr>
|
||||
<tr><td><span class="badge badge-green">共享全部</span></td><td>通用IT问题(VPN/网络/打印),无敏感信息</td><td style="color:var(--green)">低</td></tr>
|
||||
<tr><td><span class="badge badge-blue">最近10条</span></td><td>对话较长,仅需上下文即可理解当前问题</td><td style="color:var(--amber)">中</td></tr>
|
||||
<tr><td><span class="badge badge-red">不共享</span></td><td>涉及员工个人账号/权限等敏感信息</td><td style="color:var(--green)">低</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>被邀请人视角 — 共享 vs 不共享</h3>
|
||||
<div style="display:flex;gap:12px">
|
||||
<div style="flex:1;background:var(--bg3);border-radius:8px;padding:10px">
|
||||
<div style="font-size:0.82em;font-weight:600;color:var(--green);margin-bottom:6px">共享全部</div>
|
||||
<div style="font-size:0.82em;color:var(--dim)">
|
||||
[张三] VPN连不上...<br>
|
||||
[小李] 请问用的是...<br>
|
||||
[张三] ATrust...<br>
|
||||
<span style="color:var(--accent-l)">↑ 可看到完整上下文</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;background:var(--bg3);border-radius:8px;padding:10px">
|
||||
<div style="font-size:0.82em;font-weight:600;color:var(--red);margin-bottom:6px">不共享</div>
|
||||
<div style="font-size:0.82em;color:var(--dim)">
|
||||
<span style="opacity:0.4">[历史消息不可见]</span><br>
|
||||
── 加入会话 ──<br>
|
||||
[王工] 我来看看VPN...<br>
|
||||
<span style="color:var(--amber)">↑ 仅能看到加入后的消息</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="callout"><strong>默认值</strong>:建议默认选中「最近10条」,在可见性和隐私之间取得平衡。坐席可根据实际情况调整。</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 5: 确认邀请 ===== -->
|
||||
<div class="scene" id="s5">
|
||||
<div class="card">
|
||||
<h3>步骤 5:确认邀请 → 后端处理流程</h3>
|
||||
</div>
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">确认邀请 — 后端处理时序</span></div>
|
||||
<div class="mockup-body" style="min-height:340px">
|
||||
<div class="seq">
|
||||
<div class="seq-row">
|
||||
<div class="seq-dot seq-dot-green">1</div>
|
||||
<div class="seq-text"><strong>坐席点击「确认邀请」</strong><div class="seq-actor">坐席工作台 → POST /api/conversations/:id/invite</div></div>
|
||||
</div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row">
|
||||
<div class="seq-dot seq-dot-blue">2</div>
|
||||
<div class="seq-text"><strong>后端更新 participants 数组</strong>,将王工 userid 加入 conversation.participants<div class="seq-actor">后端 → PostgreSQL</div></div>
|
||||
</div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row">
|
||||
<div class="seq-dot seq-dot-blue">3</div>
|
||||
<div class="seq-text"><strong>后端生成邀请卡片消息</strong>(含会话ID + H5入口链接 + 邀请说明 + 共享的历史消息摘要)<div class="seq-actor">后端 → 企微应用消息 API</div></div>
|
||||
</div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row">
|
||||
<div class="seq-dot seq-dot-amber">4</div>
|
||||
<div class="seq-text"><strong>企微推送应用消息给王工</strong>(单聊卡片消息,非群聊消息)<div class="seq-actor">企微 → 王工企微客户端</div></div>
|
||||
</div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row">
|
||||
<div class="seq-dot seq-dot-green">5</div>
|
||||
<div class="seq-text"><strong>坐席工作台显示「已邀请王工」系统消息</strong>,会话参与者列表更新<div class="seq-actor">WebSocket → 坐席工作台</div></div>
|
||||
</div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row">
|
||||
<div class="seq-dot seq-dot-blue">6</div>
|
||||
<div class="seq-text"><strong>坐席工作台显示「已邀请王工」系统消息</strong>,会话参与者列表更新<div class="seq-actor">WebSocket → H5 发起者</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="margin-top:16px">
|
||||
<h3>API 接口设计</h3>
|
||||
<table>
|
||||
<tr><th>接口</th><th>方法</th><th>说明</th></tr>
|
||||
<tr><td style="font-family:monospace;color:var(--cyan)">/api/conversations/:id/invite</td><td>POST</td><td>邀请人员加入会话</td></tr>
|
||||
<tr><td style="font-family:monospace;color:var(--cyan)">/api/conversations/:id/participants</td><td>GET</td><td>获取会话参与者列表</td></tr>
|
||||
<tr><td style="font-family:monospace;color:var(--cyan)">/api/conversations/:id/participants/:uid</td><td>DELETE</td><td>移除参与者(坐席/被邀请人退出)</td></tr>
|
||||
<tr><td style="font-family:monospace;color:var(--cyan)">/api/contacts/departments</td><td>GET</td><td>获取组织架构树(缓存自eHR/企微通讯录)</td></tr>
|
||||
<tr><td style="font-family:monospace;color:var(--cyan)">/api/contacts/search</td><td>GET</td><td>搜索人员(姓名/工号模糊匹配)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 6: 被邀请方通知 ===== -->
|
||||
<div class="scene" id="s6">
|
||||
<div class="card">
|
||||
<h3>步骤 6:被邀请人收到企微应用消息</h3>
|
||||
<p class="desc">王工在企微中收到一条<strong>应用消息卡片</strong>,点击卡片中的按钮即可跳转到 H5 会话页面。</p>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">企微消息卡片 — 通知样式</span></div>
|
||||
<div class="mockup-body" style="display:flex;align-items:center;justify-content:center;min-height:340px">
|
||||
<div class="wecom-notif">
|
||||
<div class="wecom-notif-header">
|
||||
<div class="avatar av-invited" style="width:24px;height:24px;font-size:0.65em">IT</div>
|
||||
<div class="wecom-notif-app">IT智能服务台</div>
|
||||
<div class="wecom-notif-time">刚刚</div>
|
||||
</div>
|
||||
<div class="wecom-notif-title">邀请你加入 IT 支持会话</div>
|
||||
<div class="wecom-notif-body">
|
||||
<div style="margin-bottom:6px"><strong>邀请人:</strong>小李(IT支持组)</div>
|
||||
<div style="margin-bottom:6px"><strong>发起人:</strong>张三(IT研发部)</div>
|
||||
<div style="margin-bottom:6px"><strong>问题摘要:</strong>VPN证书错误,ATrust客户端无法连接</div>
|
||||
<div style="margin-bottom:6px"><strong>邀请说明:</strong>需要你协助排查VPN证书问题</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;margin-top:10px">
|
||||
<div class="wecom-notif-btn" onclick="goScene('s7')">加入会话</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="card">
|
||||
<h3>消息卡片设计要点</h3>
|
||||
<ul style="color:var(--dim);font-size:0.88em">
|
||||
<li><strong style="color:var(--text)">使用交互式卡片消息</strong>(textcard 类型),而非纯文本</li>
|
||||
<li><strong style="color:var(--text)">包含问题摘要</strong>,让被邀请人判断是否需要立即加入</li>
|
||||
<li><strong style="color:var(--text)">「加入会话」按钮</strong>直接跳转 H5 页面(企微内置浏览器)</li>
|
||||
<li><strong style="color:var(--text)">如果共享了历史消息</strong>,摘要中自动包含最近 2-3 条关键消息</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>降级方案</h3>
|
||||
<p class="desc">如果被邀请人<strong>未安装企微</strong>或<strong>处于离线</strong>:后端记录邀请状态为「待加入」。被邀请人下次登录企微时会收到消息。同时,坐席工作台显示邀请状态:<span class="tag tag-amber">待加入</span> / <span class="tag tag-green">已加入</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 7: 加入会话 ===== -->
|
||||
<div class="scene" id="s7">
|
||||
<div class="card">
|
||||
<h3>步骤 7:被邀请人点击「加入会话」→ H5 页面</h3>
|
||||
<p class="desc">王工点击消息卡片按钮 → 企微内置浏览器打开 H5 页面 → 自动加入 WebSocket 会话 → 看到历史消息(如果被共享)→ 可直接发消息</p>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">H5 被邀请人视角 — 加入后</span></div>
|
||||
<div class="mockup-body">
|
||||
<div style="display:flex;align-items:center;gap:8px;padding-bottom:10px;border-bottom:1px solid var(--border);margin-bottom:12px">
|
||||
<div style="font-weight:600;font-size:0.92em;flex:1">IT支持会话</div>
|
||||
<span style="font-size:0.78em;color:var(--dim)">3人参与</span>
|
||||
</div>
|
||||
<div class="msg" style="justify-content:center">
|
||||
<div class="msg-bubble bubble-sys">── 小李 邀请了 王工 加入会话 ──</div>
|
||||
</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div class="msg-bubble bubble-user">VPN 连接不上,提示证书错误</div>
|
||||
</div>
|
||||
<div class="msg msg-right">
|
||||
<div class="avatar av-agent" style="width:28px;height:28px;font-size:0.7em">李</div>
|
||||
<div class="msg-bubble bubble-agent">请问您使用的是 ATrust 还是零信任客户端?</div>
|
||||
</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div class="msg-bubble bubble-user">ATrust,之前一直正常的,今天突然不行了</div>
|
||||
</div>
|
||||
<div class="msg" style="justify-content:center">
|
||||
<div class="msg-bubble bubble-sys">── 王工 已加入会话 ──</div>
|
||||
</div>
|
||||
<div class="msg msg-right">
|
||||
<div class="avatar av-invited" style="width:28px;height:28px;font-size:0.7em">王</div>
|
||||
<div class="msg-bubble bubble-invited">我来看看,先确认下证书服务状态</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="card">
|
||||
<h3>加入流程细节</h3>
|
||||
<div class="seq">
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-amber">1</div><div class="seq-text"><strong>点击「加入会话」</strong><br>企微内置浏览器打开 H5 URL(含 conversation_id + token)</div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-blue">2</div><div class="seq-text"><strong>H5 自动认证</strong><br>企微环境 → OAuth2 自动登录;非企微 → Mock 登录</div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-blue">3</div><div class="seq-text"><strong>建立 WebSocket 连接</strong><br>加入 conversation 的消息通道</div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-green">4</div><div class="seq-text"><strong>拉取历史消息</strong><br>根据共享设置,显示可查看的历史消息</div></div>
|
||||
<div class="seq-line"></div>
|
||||
<div class="seq-row"><div class="seq-dot seq-dot-green">5</div><div class="seq-text"><strong>广播「XX 已加入」</strong><br>所有参与者(坐席+用户+其他被邀请人)都能看到</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Scene 8: 多人会话 ===== -->
|
||||
<div class="scene" id="s8">
|
||||
<div class="card">
|
||||
<h3>步骤 8:多人会话持续进行</h3>
|
||||
<p class="desc">所有人通过 <strong>WebSocket</strong> 实时通信。坐席工作台和所有 H5 参与者共享同一个消息通道。坐席拥有<strong>管理权限</strong>(可移除参与者、结束会话)。</p>
|
||||
</div>
|
||||
<div class="row2">
|
||||
<!-- 坐席工作台视角 -->
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">坐席工作台 — 多人会话</span></div>
|
||||
<div class="mockup-body" style="min-height:400px">
|
||||
<!-- 参与者栏 -->
|
||||
<div style="display:flex;align-items:center;gap:8px;padding-bottom:10px;border-bottom:1px solid var(--border);margin-bottom:12px;flex-wrap:wrap">
|
||||
<div class="avatar av-user" style="width:32px;height:32px;font-size:0.7em">张</div>
|
||||
<div style="font-size:0.8em">张三<span style="color:var(--dim);margin-left:4px">发起</span></div>
|
||||
<div class="avatar av-agent" style="width:32px;height:32px;font-size:0.7em">李</div>
|
||||
<div style="font-size:0.8em">小李<span style="color:var(--dim);margin-left:4px">坐席</span></div>
|
||||
<div class="avatar av-invited" style="width:32px;height:32px;font-size:0.7em">王</div>
|
||||
<div style="font-size:0.8em">王工<span style="color:var(--dim);margin-left:4px">邀请</span></div>
|
||||
<button class="btn" style="font-size:0.75em;margin-left:auto" onclick="goScene('s2')">+ 邀请</button>
|
||||
</div>
|
||||
<!-- 消息 -->
|
||||
<div class="msg" style="justify-content:center"><div class="msg-bubble bubble-sys">── 小李 邀请了 王工 加入会话 ──</div></div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div style="font-size:0.72em;color:var(--dim);margin-bottom:2px">张三</div>
|
||||
<div class="msg-bubble bubble-user">VPN 连接不上,提示证书错误</div>
|
||||
</div>
|
||||
<div class="msg msg-right">
|
||||
<div class="avatar av-agent" style="width:28px;height:28px;font-size:0.7em">李</div>
|
||||
<div style="font-size:0.72em;color:var(--dim);margin-bottom:2px">小李(坐席)</div>
|
||||
<div class="msg-bubble bubble-agent">请问您使用的是 ATrust 还是零信任客户端?</div>
|
||||
</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div style="font-size:0.72em;color:var(--dim);margin-bottom:2px">张三</div>
|
||||
<div class="msg-bubble bubble-user">ATrust,今天突然不行了</div>
|
||||
</div>
|
||||
<div class="msg" style="justify-content:center"><div class="msg-bubble bubble-sys">── 王工 已加入会话 ──</div></div>
|
||||
<div class="msg msg-right">
|
||||
<div class="avatar av-invited" style="width:28px;height:28px;font-size:0.7em">王</div>
|
||||
<div style="font-size:0.72em;color:var(--dim);margin-bottom:2px">王工(受邀)</div>
|
||||
<div class="msg-bubble bubble-invited">证书服务器刚做了更新,我来检查下白名单配置</div>
|
||||
</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div style="font-size:0.72em;color:var(--dim);margin-bottom:2px">张三</div>
|
||||
<div class="msg-bubble bubble-user">好的,麻烦了</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- H5 被邀请人视角 -->
|
||||
<div class="mockup">
|
||||
<div class="mockup-bar"><div class="mockup-dot" style="background:#ef4444"></div><div class="mockup-dot" style="background:#f59e0b"></div><div class="mockup-dot" style="background:#22c55e"></div><span style="color:var(--dim);font-size:0.78em;margin-left:8px">H5 被邀请人视角 — 王工</span></div>
|
||||
<div class="mockup-body" style="min-height:400px">
|
||||
<div style="display:flex;align-items:center;gap:8px;padding-bottom:10px;border-bottom:1px solid var(--border);margin-bottom:12px">
|
||||
<div style="font-weight:600;font-size:0.92em;flex:1">IT支持会话</div>
|
||||
<span style="font-size:0.78em;color:var(--dim)">3人</span>
|
||||
<span class="tag tag-green">已加入</span>
|
||||
</div>
|
||||
<!-- 王工只能看到共享后的消息 -->
|
||||
<div style="font-size:0.78em;color:var(--dim);text-align:center;margin-bottom:8px;padding:6px;background:var(--bg3);border-radius:6px">你可以查看最近10条历史消息</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div style="font-size:0.72em;color:var(--dim);margin-bottom:2px">张三</div>
|
||||
<div class="msg-bubble bubble-user">ATrust,今天突然不行了</div>
|
||||
</div>
|
||||
<div class="msg" style="justify-content:center"><div class="msg-bubble bubble-sys">── 王工 已加入会话 ──</div></div>
|
||||
<div class="msg msg-right">
|
||||
<div class="avatar av-invited" style="width:28px;height:28px;font-size:0.7em">王</div>
|
||||
<div class="msg-bubble bubble-invited">证书服务器刚做了更新,我来检查下白名单配置</div>
|
||||
</div>
|
||||
<div class="msg msg-left">
|
||||
<div class="avatar av-user" style="width:28px;height:28px;font-size:0.7em">张</div>
|
||||
<div style="font-size:0.72em;color:var(--dim);margin-bottom:2px">张三</div>
|
||||
<div class="msg-bubble bubble-user">好的,麻烦了</div>
|
||||
</div>
|
||||
<!-- 输入区 -->
|
||||
<div style="margin-top:auto;padding-top:12px;border-top:1px solid var(--border)">
|
||||
<div style="display:flex;gap:8px">
|
||||
<input type="text" placeholder="输入消息..." style="flex:1;padding:8px 12px;border-radius:8px;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:0.88em;outline:none">
|
||||
<button class="btn btn-primary" style="font-size:0.85em">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top:16px">
|
||||
<h3>坐席管理能力</h3>
|
||||
<div class="row2" style="margin-bottom:0">
|
||||
<div>
|
||||
<table>
|
||||
<tr><th>操作</th><th>权限</th><th>说明</th></tr>
|
||||
<tr><td>邀请人员</td><td>坐席</td><td>选择人员加入会话</td></tr>
|
||||
<tr><td>移除参与者</td><td>坐席</td><td>将某人移出会话(不通知)</td></tr>
|
||||
<tr><td>主动退出</td><td>被邀请人</td><td>被邀请人可自行退出</td></tr>
|
||||
<tr><td>结束会话</td><td>坐席</td><td>关闭会话,所有人断开</td></tr>
|
||||
<tr><td>转让坐席</td><td>坐席</td><td>将坐席角色转给其他参与者</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<div class="callout warn"><strong>与企微群聊的核心区别</strong>:<br>1. 会话数据完全在后端控制,不依赖企微群聊 API<br>2. 被邀请人通过 H5 WebSocket 通信,无需加入企微群<br>3. 坐席拥有管理权限,可控制参与者和历史可见性<br>4. 外部联系人(供应商等)也可通过 H5 链接加入</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const steps = document.querySelectorAll('.step');
|
||||
const scenes = document.querySelectorAll('.scene');
|
||||
|
||||
function goScene(id) {
|
||||
steps.forEach(s => s.classList.remove('active'));
|
||||
scenes.forEach(s => s.classList.remove('show'));
|
||||
const idx = parseInt(id.replace('s',''));
|
||||
steps[idx].classList.add('active');
|
||||
document.getElementById(id).classList.add('show');
|
||||
window.scrollTo({top: 0, behavior: 'smooth'});
|
||||
}
|
||||
|
||||
steps.forEach(s => {
|
||||
s.addEventListener('click', () => goScene(s.dataset.scene));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,472 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>员工名片卡片原型</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
|
||||
background: #ededed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.phone-frame {
|
||||
width: 375px;
|
||||
background: #ededed;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.nav-bar {
|
||||
background: #f7f7f7;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
.nav-bar .title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
.nav-bar .back {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
font-size: 15px;
|
||||
color: #07C160;
|
||||
}
|
||||
|
||||
/* 聊天区域 */
|
||||
.chat-area {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
/* 时间戳 */
|
||||
.time-stamp {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/* 消息行 */
|
||||
.msg-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.msg-row.agent { flex-direction: row; }
|
||||
.msg-row.user { flex-direction: row-reverse; }
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.avatar.agent { background: #07C160; }
|
||||
.avatar.user { background: #5d9cec; }
|
||||
|
||||
.msg-content {
|
||||
max-width: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.msg-row.user .msg-content { align-items: flex-end; }
|
||||
|
||||
.sender-name {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 2px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
position: relative;
|
||||
}
|
||||
.agent .bubble {
|
||||
background: #fff;
|
||||
color: #1a1a1a;
|
||||
border-top-left-radius: 2px;
|
||||
}
|
||||
.user .bubble {
|
||||
background: #95EC69;
|
||||
color: #1a1a1a;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* === 员工名片卡片 === */
|
||||
.contact-card {
|
||||
width: 260px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border-top-left-radius: 2px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 14px 16px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.card-avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.card-avatar.wang { background: linear-gradient(135deg, #667eea, #764ba2); }
|
||||
.card-avatar.li { background: linear-gradient(135deg, #f093fb, #f5576c); }
|
||||
.card-avatar.zhao { background: linear-gradient(135deg, #4facfe, #00f2fe); }
|
||||
|
||||
.card-name-row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.card-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.card-name .gender {
|
||||
font-size: 12px;
|
||||
color: #4facfe;
|
||||
}
|
||||
.card-dept {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
.card-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
.card-info-item .label {
|
||||
color: #aaa;
|
||||
width: 56px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.card-info-item .value {
|
||||
color: #333;
|
||||
}
|
||||
.card-info-item .value.responsibility {
|
||||
color: #07C160;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 0 16px 12px;
|
||||
}
|
||||
.card-action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
background: #07C160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.card-action-btn:active { opacity: 0.85; }
|
||||
.card-action-btn .icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 名片类型标识 */
|
||||
.card-type-badge {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
background: #f0f0f0;
|
||||
color: #888;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 底部输入栏 */
|
||||
.input-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 375px;
|
||||
background: #f7f7f7;
|
||||
border-top: 1px solid #dcdcdc;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
z-index: 10;
|
||||
}
|
||||
.input-bar input {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
padding: 0 12px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
}
|
||||
.input-bar .send-btn {
|
||||
background: #07C160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 0 16px;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Toast 提示 */
|
||||
.toast {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(0,0,0,0.75);
|
||||
color: #fff;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
max-width: 280px;
|
||||
text-align: center;
|
||||
}
|
||||
.toast.show { opacity: 1; }
|
||||
|
||||
/* 系统消息 */
|
||||
.system-msg {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #b0b0b0;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.system-msg span {
|
||||
background: #e8e8e8;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="phone-frame">
|
||||
<!-- 顶部导航 -->
|
||||
<div class="nav-bar">
|
||||
<span class="back">← 返回</span>
|
||||
<span class="title">IT智能服务台</span>
|
||||
</div>
|
||||
|
||||
<!-- 聊天区域 -->
|
||||
<div class="chat-area">
|
||||
|
||||
<!-- 时间戳 -->
|
||||
<div class="time-stamp">今天 01:08</div>
|
||||
|
||||
<!-- 用户消息 -->
|
||||
<div class="msg-row user">
|
||||
<div class="avatar user">宋</div>
|
||||
<div class="msg-content">
|
||||
<div class="bubble">我的电脑连不上打印机了</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 坐席/AI回复 -->
|
||||
<div class="msg-row agent">
|
||||
<div class="avatar agent">AI</div>
|
||||
<div class="msg-content">
|
||||
<div class="sender-name">IT智能助手</div>
|
||||
<div class="bubble">
|
||||
您好,打印机问题属于行政设备范畴,不在IT服务台服务范围内 😊
|
||||
<br><br>
|
||||
为您推荐行政服务相关联系人,您可以直接点击名片联系TA:
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 名片卡片1 -->
|
||||
<div class="msg-row agent">
|
||||
<div class="avatar agent">AI</div>
|
||||
<div class="msg-content">
|
||||
<div class="contact-card" onclick="showToast('正在打开与 王芳 的企微聊天...')">
|
||||
<div class="card-header">
|
||||
<div class="card-avatar wang">王</div>
|
||||
<div class="card-name-row">
|
||||
<div class="card-name">
|
||||
王芳
|
||||
<span class="gender">♀</span>
|
||||
</div>
|
||||
<div class="card-dept">行政部 · 设备管理岗</div>
|
||||
</div>
|
||||
<span class="card-type-badge">员工名片</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-info-item">
|
||||
<span class="label">负责业务</span>
|
||||
<span class="value responsibility">打印机/复印机/扫描仪</span>
|
||||
</div>
|
||||
<div class="card-info-item">
|
||||
<span class="label">分机号</span>
|
||||
<span class="value">8002</span>
|
||||
</div>
|
||||
<div class="card-info-item">
|
||||
<span class="label">服务区域</span>
|
||||
<span class="value">滨江园区 3-5楼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="card-action-btn" onclick="event.stopPropagation(); showToast('调用 wx.invoke(\"openEnterpriseChat\", {userids: \"WangFang\"}) → 打开与王芳的企微单聊')">
|
||||
<span class="icon">💬</span>
|
||||
<span>联系TA</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统提示 -->
|
||||
<div class="system-msg">
|
||||
<span>以上为AI自动推荐,点击名片可直接发起企微聊天</span>
|
||||
</div>
|
||||
|
||||
<!-- 名片卡片2(多推荐场景) -->
|
||||
<div class="msg-row user">
|
||||
<div class="avatar user">宋</div>
|
||||
<div class="msg-content">
|
||||
<div class="bubble">我的工牌丢了,补办找谁?</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msg-row agent">
|
||||
<div class="avatar agent">AI</div>
|
||||
<div class="msg-content">
|
||||
<div class="sender-name">IT智能助手</div>
|
||||
<div class="bubble">
|
||||
工牌补办属于人力资源服务,为您推荐HR联系人:
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HR名片 -->
|
||||
<div class="msg-row agent">
|
||||
<div class="avatar agent">AI</div>
|
||||
<div class="msg-content">
|
||||
<div class="contact-card" onclick="showToast('正在打开与 李娜 的企微聊天...')">
|
||||
<div class="card-header">
|
||||
<div class="card-avatar li">李</div>
|
||||
<div class="card-name-row">
|
||||
<div class="card-name">
|
||||
李娜
|
||||
<span class="gender" style="color:#f5576c">♀</span>
|
||||
</div>
|
||||
<div class="card-dept">人力资源部 · 员工服务岗</div>
|
||||
</div>
|
||||
<span class="card-type-badge">员工名片</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-info-item">
|
||||
<span class="label">负责业务</span>
|
||||
<span class="value responsibility">工牌补办/考勤异常/入职手续</span>
|
||||
</div>
|
||||
<div class="card-info-item">
|
||||
<span class="label">分机号</span>
|
||||
<span class="value">8005</span>
|
||||
</div>
|
||||
<div class="card-info-item">
|
||||
<span class="label">办公地点</span>
|
||||
<span class="value">滨江园区 A栋3楼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="card-action-btn" onclick="event.stopPropagation(); showToast('调用 wx.invoke(\"openEnterpriseChat\", {userids: \"LiNa\"}) → 打开与李娜的企微单聊')">
|
||||
<span class="icon">💬</span>
|
||||
<span>联系TA</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 底部输入栏 -->
|
||||
<div class="input-bar">
|
||||
<input type="text" placeholder="输入消息..." disabled>
|
||||
<button class="send-btn">发送</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
function showToast(msg) {
|
||||
const toast = document.getElementById('toast');
|
||||
toast.textContent = msg;
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
}, 2500);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user