project-detail.component.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import { Component, OnInit, Input } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { Router, ActivatedRoute, RouterModule } from '@angular/router';
  4. import { IonicModule } from '@ionic/angular';
  5. import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
  6. import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
  7. const Parse = FmodeParse.with('nova');
  8. /**
  9. * 项目详情核心组件
  10. *
  11. * 功能:
  12. * 1. 展示四阶段导航(订单分配、确认需求、交付执行、售后归档)
  13. * 2. 根据角色控制权限
  14. * 3. 子路由切换阶段内容
  15. * 4. 支持@Input和路由参数两种数据加载方式
  16. *
  17. * 路由:/wxwork/:cid/project/:projectId
  18. */
  19. @Component({
  20. selector: 'app-project-detail',
  21. standalone: true,
  22. imports: [CommonModule, IonicModule, RouterModule],
  23. templateUrl: './project-detail.component.html',
  24. styleUrls: ['./project-detail.component.scss']
  25. })
  26. export class ProjectDetailComponent implements OnInit {
  27. // 输入参数(支持组件复用)
  28. @Input() project: FmodeObject | null = null;
  29. @Input() groupChat: FmodeObject | null = null;
  30. @Input() currentUser: FmodeObject | null = null;
  31. // 路由参数
  32. cid: string = '';
  33. projectId: string = '';
  34. groupId: string = '';
  35. profileId: string = '';
  36. // 企微SDK
  37. wxwork: WxworkSDK | null = null;
  38. wecorp: WxworkCorp | null = null;
  39. // 加载状态
  40. loading: boolean = true;
  41. error: string | null = null;
  42. // 项目数据
  43. customer: FmodeObject | null = null;
  44. assignee: FmodeObject | null = null;
  45. // 当前阶段
  46. currentStage: string = 'order'; // order | requirements | delivery | aftercare
  47. stages = [
  48. { id: 'order', name: '订单分配', icon: 'document-text-outline', number: 1 },
  49. { id: 'requirements', name: '确认需求', icon: 'checkmark-circle-outline', number: 2 },
  50. { id: 'delivery', name: '交付执行', icon: 'rocket-outline', number: 3 },
  51. { id: 'aftercare', name: '售后归档', icon: 'archive-outline', number: 4 }
  52. ];
  53. // 权限
  54. canEdit: boolean = false;
  55. canViewCustomerPhone: boolean = false;
  56. role: string = '';
  57. constructor(
  58. private router: Router,
  59. private route: ActivatedRoute
  60. ) {}
  61. async ngOnInit() {
  62. // 获取路由参数
  63. this.cid = this.route.snapshot.paramMap.get('cid') || '';
  64. this.projectId = this.route.snapshot.paramMap.get('projectId') || '';
  65. this.groupId = this.route.snapshot.queryParamMap.get('groupId') || '';
  66. this.profileId = this.route.snapshot.queryParamMap.get('profileId') || '';
  67. // 监听路由变化
  68. this.route.firstChild?.url.subscribe((segments) => {
  69. if (segments.length > 0) {
  70. this.currentStage = segments[0].path;
  71. }
  72. });
  73. await this.loadData();
  74. }
  75. /**
  76. * 加载数据
  77. */
  78. async loadData() {
  79. try {
  80. this.loading = true;
  81. // 1. 初始化SDK
  82. if (!this.wxwork) {
  83. this.wxwork = new WxworkSDK({ cid: this.cid, appId: 'crm' });
  84. this.wecorp = new WxworkCorp(this.cid);
  85. }
  86. // 2. 获取当前用户(如果没有传入)
  87. if (!this.currentUser) {
  88. if (this.profileId) {
  89. const query = new Parse.Query('Profile');
  90. this.currentUser = await query.get(this.profileId);
  91. } else {
  92. this.currentUser = await this.wxwork.getCurrentUser();
  93. }
  94. }
  95. // 设置权限
  96. this.role = this.currentUser?.get('role') || '';
  97. this.canEdit = ['客服', '组员', '组长', '管理员'].includes(this.role);
  98. this.canViewCustomerPhone = ['客服', '组长', '管理员'].includes(this.role);
  99. // 3. 加载项目(如果没有传入)
  100. if (!this.project) {
  101. const query = new Parse.Query('Project');
  102. query.include('customer', 'assignee');
  103. this.project = await query.get(this.projectId);
  104. }
  105. this.customer = this.project.get('customer');
  106. this.assignee = this.project.get('assignee');
  107. // 4. 加载群聊(如果没有传入)
  108. if (!this.groupChat && this.groupId) {
  109. const gcQuery = new Parse.Query('GroupChat');
  110. this.groupChat = await gcQuery.get(this.groupId);
  111. }
  112. // 5. 根据项目当前阶段设置默认路由
  113. const projectStage = this.project.get('currentStage');
  114. const stageMap: any = {
  115. '订单分配': 'order',
  116. '确认需求': 'requirements',
  117. '方案确认': 'requirements',
  118. '建模': 'delivery',
  119. '软装': 'delivery',
  120. '渲染': 'delivery',
  121. '后期': 'delivery',
  122. '尾款结算': 'aftercare',
  123. '客户评价': 'aftercare',
  124. '投诉处理': 'aftercare'
  125. };
  126. const targetStage = stageMap[projectStage] || 'order';
  127. // 如果当前没有子路由,跳转到对应阶段
  128. if (!this.route.firstChild) {
  129. this.router.navigate([targetStage], { relativeTo: this.route, replaceUrl: true });
  130. }
  131. } catch (err: any) {
  132. console.error('加载失败:', err);
  133. this.error = err.message || '加载失败';
  134. } finally {
  135. this.loading = false;
  136. }
  137. }
  138. /**
  139. * 切换阶段
  140. */
  141. switchStage(stageId: string) {
  142. this.currentStage = stageId;
  143. this.router.navigate([stageId], { relativeTo: this.route });
  144. }
  145. /**
  146. * 获取阶段状态
  147. */
  148. getStageStatus(stageId: string): 'completed' | 'active' | 'pending' {
  149. const projectStage = this.project?.get('currentStage') || '';
  150. const stageOrder = ['订单分配', '确认需求', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价'];
  151. const currentIndex = stageOrder.indexOf(projectStage);
  152. const stageIndexMap: any = {
  153. 'order': 0,
  154. 'requirements': 1,
  155. 'delivery': 3,
  156. 'aftercare': 6
  157. };
  158. const targetIndex = stageIndexMap[stageId];
  159. if (currentIndex > targetIndex) {
  160. return 'completed';
  161. } else if (this.currentStage === stageId) {
  162. return 'active';
  163. } else {
  164. return 'pending';
  165. }
  166. }
  167. /**
  168. * 返回
  169. */
  170. goBack() {
  171. this.router.navigate(['/wxwork', this.cid, 'project-loader']);
  172. }
  173. /**
  174. * 更新项目阶段
  175. */
  176. async updateProjectStage(stage: string) {
  177. if (!this.project || !this.canEdit) return;
  178. try {
  179. this.project.set('currentStage', stage);
  180. await this.project.save();
  181. // 添加阶段历史
  182. const data = this.project.get('data') || {};
  183. const stageHistory = data.stageHistory || [];
  184. stageHistory.push({
  185. stage,
  186. startTime: new Date(),
  187. status: 'current',
  188. operator: {
  189. id: this.currentUser!.id,
  190. name: this.currentUser!.get('name'),
  191. role: this.role
  192. }
  193. });
  194. this.project.set('data', { ...data, stageHistory });
  195. await this.project.save();
  196. } catch (err) {
  197. console.error('更新阶段失败:', err);
  198. alert('更新失败');
  199. }
  200. }
  201. /**
  202. * 发送企微消息
  203. */
  204. async sendWxMessage(message: string) {
  205. if (!this.groupChat || !this.wecorp) return;
  206. try {
  207. const chatId = this.groupChat.get('chat_id');
  208. await this.wecorp.appchat.sendText(chatId, message);
  209. } catch (err) {
  210. console.error('发送消息失败:', err);
  211. }
  212. }
  213. }