project.md 16 KB

项目管理模块路由文档

概述

本文档描述映三色设计师项目管理系统的企微项目管理模块路由规则,支持从企微端和网页端两种方式进入。

核心特性

  1. 双端入口支持

    • 企微端:通过企微 external_useridchat_id 自动查找关联数据
    • 网页端:通过 contactId/projectId 直接加载,配合 profileId 参数
  2. 智能授权

    • 企微端自动静默授权并同步 Profile
    • 网页端通过 localStorage 缓存的 Parse/ProfileId 加载
    • 授权过程不阻塞页面加载
  3. 灵活加载

    • 优先使用路由参数
    • 回退到全局 ProfileService
    • 最后尝试企微 SDK 获取

路由规则

1. 客户画像页

路由路径: /wxwork/:cid/contact/:contactId

企微端进入

/wxwork/cDL6R1hgSi/contact/placeholder?externalUserId=wmKkHgAAF1W7xjKUCcPVdG92Mxxxxxxx

参数说明:

  • :cid - 公司帐套ID(必填)
  • :contactId - ContactInfo 的 objectId,企微端可使用占位符(如 placeholder
  • externalUserId - 企微外部联系人ID(查询参数,企微端使用)

加载逻辑:

  1. 通过 externalUserIdContactInfo 表中查找 external_userid 字段匹配的记录
  2. 如果找不到,提示"未找到客户信息,请先在企微中添加该客户"
  3. 自动触发 WxworkAuth 静默授权,同步当前员工 Profile

数据要求:

  • ContactInfo 必须有 external_userid 字段(企微端进入后台才会创建)

网页端进入

/wxwork/cDL6R1hgSi/contact/abc123xyz?profileId=prof001

参数说明:

  • :cid - 公司帐套ID(必填)
  • :contactId - ContactInfo 的 objectId(必填,真实ID)
  • profileId - 当前员工 Profile ID(查询参数,可选)

加载逻辑:

  1. 直接通过 contactIdContactInfo 表加载数据
  2. 优先使用 profileId 参数加载当前员工
  3. 如果没有 profileId,从 localStorageParse/ProfileId 加载

数据要求:

  • ContactInfo 必须存在且未软删除(isDeleted != true

2. 项目详情页

路由路径: /wxwork/:cid/project/:projectId

企微端进入

/wxwork/cDL6R1hgSi/project/placeholder?chatId=wrOtiJDAAAcwMTB7YmDxxxxx

参数说明:

  • :cid - 公司帐套ID(必填)
  • :projectId - Project 的 objectId,企微端可使用占位符(如 placeholder
  • chatId - 企微群聊 chat_id(查询参数,企微端使用)

加载逻辑:

  1. 通过 chatIdGroupChat 表中查找 chat_id 字段匹配的记录
  2. GroupChat.project 指针获取关联的 Project
  3. 如果找不到项目,提示"该群聊尚未关联项目,请先在后台创建项目"
  4. 自动触发 WxworkAuth 静默授权,同步当前员工 Profile

数据要求:

  • GroupChat 必须有 chat_id 字段(企微群聊同步后创建)
  • GroupChat.project 指针必须指向有效的 Project(后台管理员配置)

网页端进入

/wxwork/cDL6R1hgSi/project/proj001?profileId=prof001

参数说明:

  • :cid - 公司帐套ID(必填)
  • :projectId - Project 的 objectId(必填,真实ID)
  • profileId - 当前员工 Profile ID(查询参数,可选)

加载逻辑:

  1. 直接通过 projectIdProject 表加载数据
  2. 优先使用 profileId 参数加载当前员工
  3. 如果没有 profileId,从 localStorageParse/ProfileId 加载
  4. 自动加载 Project.customerProject.assignee 关联对象

数据要求:

  • Project 必须存在且未软删除(isDeleted != true

子路由(项目详情四阶段)

项目详情页包含四个子路由,对应项目管理的四个阶段:

路径 组件 标题 说明
order StageOrderComponent 订单分配 客服下单、分配设计师
requirements StageRequirementsComponent 确认需求 需求沟通、方案确认
delivery StageDeliveryComponent 交付执行 建模、软装、渲染、后期
aftercare StageAftercareComponent 售后归档 尾款结算、客户评价、投诉处理

默认路由:

  • 根据 Project.currentStage 自动跳转到对应阶段
  • 例如:currentStage = "建模" → 跳转到 delivery

阶段映射:

const stageMap = {
  '订单分配': 'order',
  '确认需求': 'requirements',
  '方案确认': 'requirements',
  '建模': 'delivery',
  '软装': 'delivery',
  '渲染': 'delivery',
  '后期': 'delivery',
  '尾款结算': 'aftercare',
  '客户评价': 'aftercare',
  '投诉处理': 'aftercare'
};

Profile 获取逻辑

两个组件都使用统一的 ProfileService 来获取当前员工信息,优先级如下:

优先级 1: 路由参数 profileId

if (this.profileId) {
  this.currentUser = await this.profileService.getProfileById(this.profileId);
}

优先级 2: 全局服务(从 localStorage 缓存)

if (!this.currentUser) {
  this.currentUser = await this.profileService.getCurrentProfile(this.cid);
}

缓存逻辑:

  1. 检查 localStorage.getItem("Parse/ProfileId")
  2. 如果存在,从数据库加载 Profile
  3. 如果不存在,尝试通过企微授权获取

优先级 3: 企微 SDK(企微环境)

if (!this.currentUser && this.wxwork) {
  try {
    this.currentUser = await this.wxwork.getCurrentUser();
  } catch (err) {
    console.warn('无法从企微SDK获取用户:', err);
  }
}

企微授权集成

WxworkAuth 静默授权

两个组件都在 ngOnInit 中调用 initWxworkAuth() 方法,实现不阻塞页面的静默授权:

async initWxworkAuth() {
  if (!this.cid) return;

  try {
    this.wxAuth = new WxworkAuth({ cid: this.cid, appId: 'crm' });

    // 静默授权并同步 Profile,不阻塞页面
    const { profile } = await this.wxAuth.authenticateAndLogin();

    if (profile) {
      this.profileService.setCurrentProfile(profile);
    }
  } catch (error) {
    console.warn('企微授权失败:', error);
    // 授权失败不影响页面加载,继续使用其他方式加载数据
  }
}

特点:

  • 异步执行,不阻塞 loadData()
  • 授权成功后自动缓存到 localStorage
  • 授权失败不影响页面正常加载
  • 支持自动注册和登录

授权流程:

  1. 获取企微用户信息(getUserInfo
  2. 同步到 ProfileUserSocial 表(syncUserInfo
  3. 自动登录/注册(autoLogin
  4. 缓存到 localStorage.setItem("Parse/ProfileId", profile.id)

数据表字段要求

ContactInfo(客户信息表)

字段名 类型 必填 说明 用途
objectId String 主键ID 网页端路由参数
external_userid String 企微外部联系人ID 企微端查找依据
name String 客户姓名 显示使用
company Pointer 所属企业 租户隔离
isDeleted Boolean 软删除标记 数据过滤

GroupChat(企微群聊表)

字段名 类型 必填 说明 用途
objectId String 主键ID 唯一标识
chat_id String 企微群聊ID 企微端查找依据
name String 群聊名称 显示使用
company Pointer 所属企业 租户隔离
project Pointer 关联项目 项目加载
isDeleted Boolean 软删除标记 数据过滤

重要: GroupChat 必须有 chat_id 属性才能支持企微端进入!


Project(项目表)

字段名 类型 必填 说明 用途
objectId String 主键ID 网页端路由参数
title String 项目标题 显示使用
customer Pointer 客户 关联 ContactInfo
assignee Pointer 负责设计师 关联 Profile
currentStage String 当前阶段 默认路由跳转
company Pointer 所属企业 租户隔离
isDeleted Boolean 软删除标记 数据过滤

Profile(员工档案表)

字段名 类型 必填 说明 用途
objectId String 主键ID 缓存和查询
name String 员工姓名 显示使用
mobile String 手机号 联系方式
company Pointer 所属企业 租户隔离
userId String 企微UserID 企微同步
roleName String 员工角色 权限控制
isDeleted Boolean 软删除标记 数据过滤

使用示例

1. 从企微群聊进入项目详情

场景: 用户在企微群聊中点击应用卡片

// 步骤1: 企微 SDK 获取当前群聊
const { GroupChat } = await wxwork.getCurrentChatObject();
const chatId = GroupChat.get('chat_id'); // 例如: "wrOtiJDAAAcwMTB7YmDxxxxx"

// 步骤2: 构造路由
const url = `/wxwork/${cid}/project/placeholder?chatId=${chatId}`;

// 步骤3: 跳转
this.router.navigateByUrl(url);

后台数据准备:

  1. 确保 GroupChat 表中有对应 chat_id 的记录
  2. 确保 GroupChat.project 指针指向有效的 Project
  3. 确保 Project.customerProject.assignee 已关联

2. 从后台管理页面进入客户画像

场景: 管理员在客户列表中点击查看客户详情

// 步骤1: 获取客户 objectId 和当前员工 profileId
const contactId = customer.id; // 例如: "abc123xyz"
const profileId = localStorage.getItem('Parse/ProfileId'); // 例如: "prof001"

// 步骤2: 构造路由
const url = `/wxwork/${cid}/contact/${contactId}?profileId=${profileId}`;

// 步骤3: 跳转
this.router.navigateByUrl(url);

3. 从企微外部联系人进入客户画像

场景: 用户在企微中查看外部联系人详情

// 步骤1: 企微 SDK 获取外部联系人
const { Contact } = await wxwork.getCurrentChatObject();
const externalUserId = Contact.get('external_userid'); // 例如: "wmKkHgAAF1W7xjKUCcPVdG92Mxxxxxxx"

// 步骤2: 构造路由(使用占位符)
const url = `/wxwork/${cid}/contact/placeholder?externalUserId=${externalUserId}`;

// 步骤3: 跳转
this.router.navigateByUrl(url);

后台数据准备:

  1. 确保已通过企微 API 同步外部联系人到 ContactInfo
  2. 确保 ContactInfo.external_userid 字段已填充

错误处理

客户画像页

错误场景 错误信息 解决方案
企微端找不到客户 "未找到客户信息,请先在企微中添加该客户" 在企微中添加外部联系人,等待同步
网页端 contactId 不存在 "加载失败" 检查 contactId 是否正确
没有权限查看敏感信息 手机号显示为 *** 切换到客服/组长/管理员账号

项目详情页

错误场景 错误信息 解决方案
企微端群聊未关联项目 "该群聊尚未关联项目,请先在后台创建项目" 在后台管理页面将群聊关联到项目
网页端 projectId 不存在 "加载失败" 检查 projectId 是否正确
授权失败 控制台警告 检查企微配置,或手动传入 profileId

权限控制

客户画像页

权限 角色 说明
查看基本信息 所有角色 姓名、来源、画像标签等
查看敏感信息 客服、组长、管理员 手机号、微信号

项目详情页

权限 角色 说明
查看项目信息 所有角色 项目标题、阶段、进度等
编辑项目信息 客服、组员、组长、管理员 更新阶段、上传文件等
查看客户手机号 客服、组长、管理员 客户联系方式

技术实现细节

ProfileService 全局服务

位置: src/app/services/profile.service.ts

核心方法:

  1. getCurrentProfile(cid?, forceRefresh?) - 获取当前 Profile
  2. getProfileById(profileId, useCache?) - 根据 ID 获取 Profile
  3. setCurrentProfile(profile) - 设置当前 Profile 并缓存
  4. clearCurrentProfile() - 清除缓存
  5. getCompanyProfiles(companyId, roleName?) - 获取公司所有员工

WxworkAuth 授权工具

来源: fmode-ng/core

核心方法:

  1. getUserInfo(code?) - 获取企微用户信息
  2. syncUserInfo(userInfo?) - 同步到 Profile 表
  3. autoLogin(userInfo?) - 自动登录/注册
  4. authenticateAndLogin(code?) - 一站式授权(推荐使用)

特点:

  • 静默授权(snsapi_base
  • 自动注册用户(用户名=userid,密码=userid后6位)
  • 自动同步 Profile 数据
  • 缓存到 localStorage

最佳实践

1. 企微端开发

// 始终通过企微 SDK 获取 chat_id 或 external_userid
const { GroupChat, Contact } = await wxwork.getCurrentChatObject();

// 使用占位符作为路由参数,避免提前查询
const url = `/wxwork/${cid}/project/placeholder?chatId=${GroupChat.get('chat_id')}`;

// 依赖组件内部的 WxworkAuth 自动授权
this.router.navigateByUrl(url);

2. 网页端开发

// 始终传递 profileId 参数,避免依赖全局缓存
const profileId = localStorage.getItem('Parse/ProfileId');
const url = `/wxwork/${cid}/contact/${contactId}?profileId=${profileId}`;

this.router.navigateByUrl(url);

3. 数据准备

企微端:

  1. 同步企微外部联系人到 ContactInfo 表(external_userid 必填)
  2. 同步企微群聊到 GroupChat 表(chat_id 必填)
  3. 在后台管理页面将群聊关联到项目(GroupChat.project

网页端:

  1. 确保 ContactInfoProject 数据完整
  2. 确保当前员工已登录并缓存 Parse/ProfileId
  3. 确保员工有权限访问对应数据

调试技巧

1. 检查路由参数

console.log('cid:', this.cid);
console.log('contactId:', this.contactId);
console.log('externalUserId:', this.externalUserId);
console.log('profileId:', this.profileId);

2. 检查 Profile 加载

console.log('currentUser:', this.currentUser?.toJSON());
console.log('role:', this.role);
console.log('canEdit:', this.canEdit);

3. 检查数据加载

// 客户画像页
console.log('contactInfo:', this.contactInfo?.toJSON());

// 项目详情页
console.log('project:', this.project?.toJSON());
console.log('groupChat:', this.groupChat?.toJSON());
console.log('customer:', this.customer?.toJSON());

4. 检查企微授权

// 在浏览器控制台查看缓存
console.log('ProfileId:', localStorage.getItem('Parse/ProfileId'));

// 清除缓存重新授权
localStorage.removeItem('Parse/ProfileId');
location.reload();

常见问题

Q1: 企微端进入提示"未找到客户信息"

原因: ContactInfo 表中没有对应 external_userid 的记录

解决:

  1. 检查企微外部联系人同步是否成功
  2. 检查 ContactInfo.external_userid 字段是否填充
  3. 检查查询条件是否正确(company、isDeleted)

Q2: 企微端进入提示"该群聊尚未关联项目"

原因: GroupChat.project 指针为空或指向无效项目

解决:

  1. 在后台管理页面找到对应群聊
  2. 将群聊关联到有效的项目
  3. 确保项目未软删除

Q3: 网页端进入后看不到客户手机号

原因: 当前员工角色没有权限

解决:

  1. 检查 Profile.roleName 字段
  2. 确保角色是"客服"、"组长"或"管理员"
  3. 刷新页面重新加载权限

Q4: 授权失败但页面正常加载

原因: 授权过程是异步的,不会阻塞页面加载

说明:

  • 这是正常行为,授权失败会回退到其他加载方式
  • 如果需要强制授权,可以使用路由守卫 WxworkAuthGuard

参考文档


文档版本: v1.0 最后更新: 2025-10-17 维护者: YSS Development Team