project-loader.component.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import { Component, OnInit } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { Router, ActivatedRoute } from '@angular/router';
  4. import { FormsModule } from '@angular/forms';
  5. import { WxworkSDK, WxworkCorp, WxworkCurrentChat } from 'fmode-ng/core';
  6. import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
  7. function wxdebug(...params:any[]){
  8. console.log(params)
  9. }
  10. const Parse = FmodeParse.with('nova');
  11. /**
  12. * 项目预加载页面
  13. *
  14. * 功能:
  15. * 1. 从企微会话获取上下文(群聊或联系人)
  16. * 2. 获取当前登录用户(Profile)
  17. * 3. 根据场景跳转到对应页面
  18. * - 群聊 → 项目详情 或 创建项目引导
  19. * - 联系人 → 客户画像
  20. *
  21. * 路由:/wxwork/:cid/project-loader
  22. *
  23. * 参考实现:nova-admin/projects/nova-crm/src/modules/chat/page-chat-context
  24. */
  25. @Component({
  26. selector: 'app-project-loader',
  27. standalone: true,
  28. imports: [CommonModule, FormsModule],
  29. templateUrl: './project-loader.component.html',
  30. styleUrls: ['./project-loader.component.scss']
  31. })
  32. export class ProjectLoaderComponent implements OnInit {
  33. // 基础数据
  34. cid: string = '';
  35. appId: string = 'crm';
  36. // 加载状态
  37. loading: boolean = true;
  38. loadingMessage: string = '正在加载...';
  39. error: string | null = null;
  40. // 企微SDK
  41. wxwork: WxworkSDK | null = null;
  42. wecorp: WxworkCorp | null = null;
  43. // 上下文数据
  44. currentUser: FmodeObject | null = null; // Profile 或 UserSocial
  45. currentChat: WxworkCurrentChat | null = null;
  46. chatType: 'group' | 'contact' | 'none' = 'none';
  47. groupChat: FmodeObject | null = null; // GroupChat
  48. contact: FmodeObject | null = null; // ContactInfo
  49. project: FmodeObject | null = null; // Project
  50. // 创建项目引导
  51. showCreateGuide: boolean = false;
  52. defaultProjectName: string = '';
  53. projectName: string = '';
  54. creating: boolean = false;
  55. // 历史项目(当前群聊无项目时展示)
  56. historyProjects: FmodeObject[] = [];
  57. constructor(
  58. private router: Router,
  59. private route: ActivatedRoute
  60. ) {}
  61. async ngOnInit() {
  62. // 获取路由参数
  63. this.route.paramMap.subscribe(async params => {
  64. this.cid = params.get('cid') || '';
  65. this.appId = params.get('appId') || 'crm';
  66. if (!this.cid) {
  67. this.error = '缺少企业ID参数';
  68. this.loading = false;
  69. return;
  70. }
  71. await this.loadData();
  72. });
  73. }
  74. /**
  75. * 加载数据主流程(参考 page-chat-context 实现)
  76. */
  77. async loadData() {
  78. try {
  79. this.loading = true;
  80. this.loadingMessage = '初始化企微SDK...';
  81. // 1️⃣ 初始化 SDK
  82. this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
  83. this.wecorp = new WxworkCorp(this.cid);
  84. wxdebug('1. SDK初始化完成', { cid: this.cid, appId: this.appId });
  85. // 2️⃣ 加载当前登录员工信息(由 WxworkAuthGuard 自动登录)
  86. this.loadingMessage = '获取用户信息...';
  87. try {
  88. this.currentUser = await this.wxwork.getCurrentUser();
  89. wxdebug('2. 获取当前用户成功', this.currentUser?.toJSON());
  90. } catch (err) {
  91. console.error('获取当前用户失败:', err);
  92. wxdebug('2. 获取当前用户失败', err);
  93. throw new Error('获取用户信息失败,请重试');
  94. }
  95. // 3️⃣ 加载当前聊天上下文
  96. this.loadingMessage = '获取会话信息...';
  97. try {
  98. this.currentChat = await this.wxwork.getCurrentChat();
  99. wxdebug('3. getCurrentChat返回', this.currentChat);
  100. } catch (err) {
  101. console.error('getCurrentChat失败:', err);
  102. wxdebug('3. getCurrentChat失败', err);
  103. }
  104. // 4️⃣ 根据场景同步数据
  105. if (this.currentChat?.type === "chatId" && this.currentChat?.group) {
  106. // 群聊场景
  107. wxdebug('4. 检测到群聊场景', this.currentChat.group);
  108. this.loadingMessage = '同步群聊信息...';
  109. try {
  110. this.chatType = 'group';
  111. this.groupChat = await this.wxwork.syncGroupChat(this.currentChat.group);
  112. wxdebug('5. 群聊同步完成', this.groupChat?.toJSON());
  113. // 处理群聊场景
  114. await this.handleGroupChatScene();
  115. } catch (err) {
  116. console.error('群聊同步失败:', err);
  117. wxdebug('5. 群聊同步失败', err);
  118. throw new Error('群聊信息同步失败');
  119. }
  120. } else if (this.currentChat?.type === "userId" && this.currentChat?.id) {
  121. // 联系人场景
  122. wxdebug('4. 检测到联系人场景', { id: this.currentChat.id });
  123. this.loadingMessage = '同步联系人信息...';
  124. try {
  125. this.chatType = 'contact';
  126. // 获取完整联系人信息
  127. const contactInfo = await this.wecorp!.externalContact.get(this.currentChat.id);
  128. wxdebug('5. 获取完整联系人信息', contactInfo);
  129. this.contact = await this.wxwork.syncContact(contactInfo);
  130. wxdebug('6. 联系人同步完成', this.contact?.toJSON());
  131. // 处理联系人场景
  132. await this.handleContactScene();
  133. } catch (err) {
  134. console.error('联系人同步失败:', err);
  135. wxdebug('联系人同步失败', err);
  136. throw new Error('联系人信息同步失败');
  137. }
  138. } else {
  139. // 未检测到有效场景
  140. wxdebug('4. 未检测到有效场景', {
  141. currentChat: this.currentChat,
  142. type: this.currentChat?.type,
  143. hasGroup: !!this.currentChat?.group,
  144. hasContact: !!this.currentChat?.contact,
  145. hasId: !!this.currentChat?.id
  146. });
  147. throw new Error('无法识别当前会话类型,请在群聊或联系人会话中打开');
  148. }
  149. wxdebug('加载完成', {
  150. chatType: this.chatType,
  151. hasGroupChat: !!this.groupChat,
  152. hasContact: !!this.contact,
  153. hasCurrentUser: !!this.currentUser
  154. });
  155. } catch (err: any) {
  156. console.error('加载失败:', err);
  157. this.error = err.message || '加载失败,请重试';
  158. } finally {
  159. this.loading = false;
  160. }
  161. }
  162. /**
  163. * 处理群聊场景
  164. */
  165. async handleGroupChatScene() {
  166. this.loadingMessage = '查询项目信息...';
  167. // 查询群聊关联的项目
  168. const projectPointer = this.groupChat!.get('project');
  169. if (projectPointer) {
  170. // 有项目,加载项目详情
  171. let pid = projectPointer.id || projectPointer.objectId
  172. try {
  173. const query = new Parse.Query('Project');
  174. query.include('customer', 'assignee');
  175. this.project = await query.get(pid);
  176. wxdebug('找到项目', this.project.toJSON());
  177. // 跳转项目详情
  178. await this.navigateToProjectDetail();
  179. } catch (err) {
  180. console.error('加载项目失败:', err);
  181. wxdebug('加载项目失败', err);
  182. this.error = '项目已删除或无权访问';
  183. }
  184. } else {
  185. // 无项目,查询历史项目并显示创建引导
  186. await this.loadHistoryProjects();
  187. this.showCreateProjectGuide();
  188. }
  189. }
  190. /**
  191. * 处理联系人场景
  192. */
  193. async handleContactScene() {
  194. wxdebug('联系人场景,跳转客户画像', {
  195. contactId: this.contact!.id,
  196. contactName: this.contact!.get('name')
  197. });
  198. // 跳转客户画像页面
  199. await this.router.navigate(['/wxwork', this.cid, 'contact', this.contact!.id], {
  200. queryParams: {
  201. profileId: this.currentUser!.id
  202. }
  203. });
  204. }
  205. /**
  206. * 加载历史项目(当前群聊相关的其他项目)
  207. */
  208. async loadHistoryProjects() {
  209. try {
  210. // 通过 ProjectGroup 查询该群聊的所有项目
  211. const pgQuery = new Parse.Query('ProjectGroup');
  212. pgQuery.equalTo('groupChat', this.groupChat!.toPointer());
  213. pgQuery.include('project');
  214. pgQuery.descending('createdAt');
  215. const projectGroups = await pgQuery.find();
  216. this.historyProjects = projectGroups
  217. .map((pg: any) => pg.get('project'))
  218. .filter((p: any) => p && !p.get('isDeleted'));
  219. wxdebug('找到历史项目', { count: this.historyProjects.length });
  220. } catch (err) {
  221. console.error('加载历史项目失败:', err);
  222. wxdebug('加载历史项目失败', err);
  223. }
  224. }
  225. /**
  226. * 显示创建项目引导
  227. */
  228. showCreateProjectGuide() {
  229. this.showCreateGuide = true;
  230. this.defaultProjectName = this.groupChat!.get('name') || '新项目';
  231. this.projectName = this.defaultProjectName;
  232. wxdebug('显示创建项目引导', {
  233. groupName: this.groupChat!.get('name'),
  234. historyProjectsCount: this.historyProjects.length
  235. });
  236. }
  237. /**
  238. * 创建项目
  239. */
  240. async createProject() {
  241. if (!this.projectName.trim()) {
  242. alert('请输入项目名称');
  243. return;
  244. }
  245. // 权限检查
  246. const role = this.currentUser!.get('roleName');
  247. if (!['客服', '组长', '管理员'].includes(role)) {
  248. alert('您没有权限创建项目');
  249. return;
  250. }
  251. try {
  252. this.creating = true;
  253. wxdebug('开始创建项目', {
  254. projectName: this.projectName,
  255. groupChatId: this.groupChat!.id,
  256. currentUserId: this.currentUser!.id,
  257. role: role
  258. });
  259. // 1. 创建项目
  260. const Project = Parse.Object.extend('Project');
  261. const project = new Project();
  262. project.set('title', this.projectName.trim());
  263. project.set('company', this.currentUser!.get('company'));
  264. project.set('status', '待分配');
  265. project.set('currentStage', '订单分配');
  266. project.set('data', {
  267. createdBy: this.currentUser!.id,
  268. createdFrom: 'wxwork_groupchat',
  269. groupChatId: this.groupChat!.id
  270. });
  271. await project.save();
  272. wxdebug('项目创建成功', { projectId: project.id });
  273. // 2. 关联群聊
  274. this.groupChat!.set('project', project.toPointer());
  275. await this.groupChat!.save();
  276. wxdebug('群聊关联项目成功');
  277. // 3. 创建 ProjectGroup 关联(支持多项目多群)
  278. const ProjectGroup = Parse.Object.extend('ProjectGroup');
  279. const pg = new ProjectGroup();
  280. pg.set('project', project.toPointer());
  281. pg.set('groupChat', this.groupChat!.toPointer());
  282. pg.set('isPrimary', true);
  283. pg.set('company', this.currentUser!.get('company'));
  284. await pg.save();
  285. wxdebug('ProjectGroup关联创建成功');
  286. // 4. 跳转项目详情
  287. this.project = project;
  288. await this.navigateToProjectDetail();
  289. } catch (err: any) {
  290. console.error('创建项目失败:', err);
  291. wxdebug('创建项目失败', err);
  292. alert('创建失败: ' + (err.message || '未知错误'));
  293. } finally {
  294. this.creating = false;
  295. }
  296. }
  297. /**
  298. * 选择历史项目
  299. */
  300. async selectHistoryProject(project: FmodeObject) {
  301. try {
  302. wxdebug('选择历史项目', {
  303. projectId: project.id,
  304. projectTitle: project.get('title')
  305. });
  306. // 更新群聊的当前项目
  307. this.groupChat!.set('project', project.toPointer());
  308. await this.groupChat!.save();
  309. // 跳转项目详情
  310. this.project = project;
  311. await this.navigateToProjectDetail();
  312. } catch (err: any) {
  313. console.error('关联项目失败:', err);
  314. alert('关联失败: ' + (err.message || '未知错误'));
  315. }
  316. }
  317. /**
  318. * 跳转项目详情
  319. */
  320. async navigateToProjectDetail() {
  321. wxdebug('跳转项目详情', {
  322. projectId: this.project!.id,
  323. cid: this.cid,
  324. groupChatId: this.groupChat?.id
  325. });
  326. await this.router.navigate(['/wxwork', this.cid, 'project', this.project!.id], {
  327. queryParams: {
  328. groupId: this.groupChat?.id,
  329. profileId: this.currentUser!.id
  330. }
  331. });
  332. }
  333. /**
  334. * 重新加载
  335. */
  336. async reload() {
  337. this.error = null;
  338. this.showCreateGuide = false;
  339. this.historyProjects = [];
  340. this.chatType = 'none';
  341. await this.loadData();
  342. }
  343. /**
  344. * 获取当前员工姓名
  345. */
  346. getCurrentUserName(): string {
  347. if (!this.currentUser) return '未知';
  348. return this.currentUser.get('name') || this.currentUser.get('userid') || '未知';
  349. }
  350. /**
  351. * 获取当前员工角色
  352. */
  353. getCurrentUserRole(): string {
  354. if (!this.currentUser) return '未知';
  355. return this.currentUser.get('roleName') || '未知';
  356. }
  357. /**
  358. * 获取项目状态的显示样式类
  359. */
  360. getProjectStatusClass(status: string): string {
  361. const classMap: any = {
  362. '待分配': 'status-pending',
  363. '进行中': 'status-active',
  364. '已完成': 'status-completed',
  365. '已暂停': 'status-paused',
  366. '已取消': 'status-cancelled'
  367. };
  368. return classMap[status] || 'status-default';
  369. }
  370. /**
  371. * 格式化日期
  372. */
  373. formatDate(date: Date): string {
  374. if (!date) return '';
  375. const d = new Date(date);
  376. return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
  377. }
  378. }