project-members-modal.component.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4. import { FmodeObject, FmodeQuery, FmodeParse } from 'fmode-ng/parse';
  5. import { WxworkCorp, WxworkSDK } from 'fmode-ng/core';
  6. import { getGroupChat } from '../get-group-joinway';
  7. const Parse = FmodeParse.with('nova');
  8. export interface ProjectMember {
  9. id: string;
  10. name: string;
  11. userid: string;
  12. avatar?: string;
  13. role: string;
  14. department?: string;
  15. isInGroupChat: boolean;
  16. isInProjectTeam: boolean;
  17. projectTeamId?: string;
  18. profileId?: string;
  19. }
  20. export interface GroupChatMember {
  21. userid: string;
  22. name: string;
  23. type: number; // 1: 内部成员 2: 外部联系人
  24. avatar?: string;
  25. }
  26. @Component({
  27. selector: 'app-project-members-modal',
  28. standalone: true,
  29. imports: [CommonModule, FormsModule],
  30. templateUrl: './project-members-modal.component.html',
  31. styleUrls: ['./project-members-modal.component.scss']
  32. })
  33. export class ProjectMembersModalComponent implements OnInit {
  34. @Input() project: FmodeObject | null = null;
  35. @Input() groupChat: FmodeObject | null = null;
  36. @Input() currentUser: FmodeObject | null = null;
  37. @Input() isVisible: boolean = false;
  38. @Input() cid: string = '';
  39. @Output() close = new EventEmitter<void>();
  40. members: ProjectMember[] = [];
  41. loading: boolean = false;
  42. error: string | null = null;
  43. isWxworkEnvironment: boolean = false;
  44. // 统计信息
  45. totalMembers: number = 0;
  46. groupChatMembers: number = 0;
  47. projectTeamMembers: number = 0;
  48. pendingAddMembers: number = 0;
  49. // 过滤和搜索
  50. searchQuery: string = '';
  51. memberFilter: 'all' | 'ingroup' | 'team' | 'pending' = 'all';
  52. // 企业微信API
  53. private wecorp: WxworkCorp | null = null;
  54. private wwsdk: WxworkSDK | null = null;
  55. isWechat:boolean = false;
  56. constructor() {
  57. let ua = navigator.userAgent.toLowerCase();
  58. this.isWechat = ua.indexOf('micromessenger') !== -1;
  59. this.checkWxworkEnvironment();
  60. }
  61. ngOnInit(): void {
  62. if (this.isVisible && this.project) {
  63. this.loadMembers();
  64. }
  65. }
  66. ngOnChanges(): void {
  67. if (this.isVisible && this.project) {
  68. this.loadMembers();
  69. }
  70. }
  71. private checkWxworkEnvironment(): void {
  72. // 检查是否在企业微信环境中
  73. let cid = this.project?.get("company")?.id || localStorage.getItem("company")
  74. this.wecorp = new WxworkCorp(cid);
  75. this.wwsdk = new WxworkSDK({cid:cid,appId:'crm'});
  76. console.log('✅ 企业微信环境检测成功');
  77. }
  78. async loadMembers(): Promise<void> {
  79. if (!this.project) return;
  80. try {
  81. this.loading = true;
  82. this.error = null;
  83. // 1. 加载项目团队成员
  84. const projectTeamMembers = await this.loadProjectTeamMembers();
  85. // 2. 加载群聊成员
  86. if(!this.groupChat?.id){
  87. const gcQuery2 = new Parse.Query('GroupChat');
  88. gcQuery2.equalTo('project', this.project?.id);
  89. this.groupChat = await gcQuery2.first();
  90. }
  91. if(this.groupChat?.id && !this.groupChat?.get("joinUrl")){
  92. this.groupChat = await getGroupChat(this.groupChat?.id)
  93. }
  94. const groupChatMembers = await this.loadGroupChatMembers();
  95. // 3. 合并成员数据
  96. console.log("999",projectTeamMembers, groupChatMembers)
  97. this.mergeMembersData(projectTeamMembers, groupChatMembers);
  98. this.calculateStats();
  99. console.log(`✅ 加载了 ${this.members.length} 个成员信息`);
  100. } catch (error) {
  101. console.error('❌ 加载成员失败:', error);
  102. this.error = error instanceof Error ? error.message : '加载成员失败';
  103. } finally {
  104. this.loading = false;
  105. }
  106. }
  107. private async loadProjectTeamMembers(): Promise<FmodeObject[]> {
  108. try {
  109. const query = new FmodeQuery('ProjectTeam');
  110. if (this.project) {
  111. query.equalTo('project', this.project.toPointer());
  112. }
  113. query.include('profile','department','profile.department');
  114. query.notEqualTo('isDeleted', true);
  115. return await query.find();
  116. } catch (error) {
  117. console.error('加载项目团队成员失败:', error);
  118. return [];
  119. }
  120. }
  121. private async loadGroupChatMembers(): Promise<GroupChatMember[]> {
  122. if (!this.groupChat) return [];
  123. try {
  124. const memberList = this.groupChat.get('member_list') || [];
  125. return memberList
  126. } catch (error) {
  127. console.error('加载群聊成员失败:', error);
  128. return [];
  129. }
  130. }
  131. private mergeMembersData(projectTeamMembers: FmodeObject[], groupChatMembers: GroupChatMember[]): void {
  132. const memberMap = new Map<string, ProjectMember>();
  133. // 1. 添加项目团队成员
  134. projectTeamMembers.forEach(team => {
  135. const profile = team.get('profile');
  136. if (profile) {
  137. const member: ProjectMember = {
  138. id: profile.id,
  139. name: profile.get('name') || '未知',
  140. userid: profile.get('userid') || '',
  141. avatar: profile.get('data')?.avatar,
  142. role: profile.get('roleName') || '未知',
  143. department: profile.get('department')?.get('name'),
  144. isInGroupChat: false,
  145. isInProjectTeam: true,
  146. projectTeamId: team.id,
  147. profileId: profile.id
  148. };
  149. memberMap.set(profile.id, member);
  150. }
  151. });
  152. // 2. 添加群聊成员(包括外部联系人)
  153. groupChatMembers.forEach(groupMember => {
  154. // 查找是否已在项目团队中
  155. const existingMember = Array.from(memberMap.values()).find(
  156. m => m.userid === groupMember.userid
  157. );
  158. if (existingMember) {
  159. existingMember.isInGroupChat = true;
  160. } else {
  161. // 添加仅存在于群聊中的成员
  162. const member: ProjectMember = {
  163. id: groupMember.userid,
  164. name: groupMember.name,
  165. userid: groupMember.userid,
  166. avatar: groupMember.avatar,
  167. role: groupMember.type === 1 ? '外部联系人' : '内部成员',
  168. isInGroupChat: true,
  169. isInProjectTeam: false,
  170. projectTeamId: undefined,
  171. profileId: undefined
  172. };
  173. memberMap.set(groupMember.userid, member);
  174. }
  175. });
  176. this.members = Array.from(memberMap.values());
  177. }
  178. calculateStats(): void {
  179. this.totalMembers = this.members.length;
  180. this.groupChatMembers = this.members.filter(m => m.isInGroupChat).length;
  181. this.projectTeamMembers = this.members.filter(m => m.isInProjectTeam).length;
  182. this.pendingAddMembers = this.members.filter(m => m.isInProjectTeam && !m.isInGroupChat).length;
  183. }
  184. onClose(): void {
  185. this.close.emit();
  186. }
  187. onBackdropClick(event: MouseEvent): void {
  188. if (event.target === event.currentTarget) {
  189. this.onClose();
  190. }
  191. }
  192. getFilteredMembers(): ProjectMember[] {
  193. let filtered = this.members;
  194. // 搜索过滤
  195. if (this.searchQuery) {
  196. const query = this.searchQuery.toLowerCase();
  197. filtered = filtered.filter(member =>
  198. member.name.toLowerCase().includes(query) ||
  199. member.role.toLowerCase().includes(query) ||
  200. member.department?.toLowerCase().includes(query)
  201. );
  202. }
  203. // 状态过滤
  204. switch (this.memberFilter) {
  205. case 'ingroup':
  206. filtered = filtered.filter(m => m.isInGroupChat);
  207. break;
  208. case 'team':
  209. filtered = filtered.filter(m => m.isInProjectTeam);
  210. break;
  211. case 'pending':
  212. filtered = filtered.filter(m => m.isInProjectTeam && !m.isInGroupChat);
  213. break;
  214. }
  215. return filtered.sort((a, b) => {
  216. // 优先级:项目团队成员 > 仅群聊成员
  217. if (a.isInProjectTeam && !b.isInProjectTeam) return -1;
  218. if (!a.isInProjectTeam && b.isInProjectTeam) return 1;
  219. // 按名称排序
  220. return a.name.localeCompare(b.name, 'zh-CN');
  221. });
  222. }
  223. async addMemberToGroupChat(member: ProjectMember): Promise<void> {
  224. if (!member.userid) {
  225. alert('该成员没有用户ID,无法添加到群聊');
  226. return;
  227. }
  228. try {
  229. const chatId = this.groupChat?.get('chat_id');
  230. if (!chatId) {
  231. alert('群聊ID不存在');
  232. return;
  233. }
  234. console.log(`🚀 开始添加成员 ${member.name} (${member.userid}) 到群聊 ${chatId}`);
  235. // TODO: 实现正确的企业微信API调用
  236. // console.log(this.groupChat?.get("joinUrl"),this.groupChat)
  237. this.wecorp?.message.sendNews({
  238. agentid:"1000017",
  239. touser: member.userid || '',
  240. articles:[
  241. {
  242. title: this.project?.get('title'),
  243. description: "点击加入项目群聊",
  244. url: this.groupChat?.get("joinUrl")?.qr_code,
  245. picurl: this.groupChat?.get("joinQrcode")?.qr_code,
  246. }
  247. ]
  248. });
  249. const md = `${this.project?.get('title')} 项目开始啦!\n就等你来啦!快点上面进群\n`;
  250. this.wecorp?.message.sendMarkdown({
  251. agentid:"1000017",
  252. touser: member.userid || '',
  253. content: md
  254. });
  255. let result = await this.wwsdk?.ww.updateEnterpriseChat({
  256. chatId: chatId,
  257. userIdsToAdd: [member.userid]
  258. });
  259. // 临时:直接更新本地状态用于演示
  260. if(result){
  261. member.isInGroupChat = true;
  262. this.calculateStats();
  263. }
  264. alert(`✅ 已将 ${member.name} 添加到群聊`);
  265. console.log(`✅ 成功添加成员 ${member.name} 到群聊`);
  266. } catch (error) {
  267. console.error('❌ 添加成员到群聊失败:', error);
  268. alert(`添加失败: ${error instanceof Error ? error.message : '未知错误'}`);
  269. }
  270. }
  271. // 新增:移出项目(软删除 ProjectTeam)
  272. async removeMemberFromProject(member: ProjectMember): Promise<void> {
  273. if (!member.isInProjectTeam || !member.projectTeamId) {
  274. alert('该成员未在项目团队中,无法移出项目');
  275. return;
  276. }
  277. if (!this.project) {
  278. alert('项目不存在,无法执行移出操作');
  279. return;
  280. }
  281. const confirmMsg = `确定将 \"${member.name}\" 移出项目吗?`;
  282. if (!confirm(confirmMsg)) return;
  283. try {
  284. this.loading = true;
  285. const query = new Parse.Query('ProjectTeam');
  286. const team = await query.get(member.projectTeamId);
  287. team.set('isDeleted', true);
  288. await team.save();
  289. // 重新加载成员数据,保持列表与统计一致
  290. await this.loadMembers();
  291. alert(`✅ 已将 ${member.name} 移出项目`);
  292. } catch (error) {
  293. console.error('❌ 移出项目失败:', error);
  294. alert(`移出失败: ${error instanceof Error ? error.message : '未知错误'}`);
  295. } finally {
  296. this.loading = false;
  297. }
  298. }
  299. getRoleBadgeClass(role: string): string {
  300. switch (role) {
  301. case '客服':
  302. return 'role-customer-service';
  303. case '组员':
  304. case '设计师':
  305. return 'role-designer';
  306. case '组长':
  307. return 'role-team-leader';
  308. case '管理员':
  309. return 'role-admin';
  310. case '外部联系人':
  311. return 'role-external';
  312. default:
  313. return 'role-default';
  314. }
  315. }
  316. getMemberStatusClass(member: ProjectMember): string {
  317. if (member.isInProjectTeam && member.isInGroupChat) {
  318. return 'status-active';
  319. } else if (member.isInProjectTeam) {
  320. return 'status-pending';
  321. } else {
  322. return 'status-group-only';
  323. }
  324. }
  325. getMemberStatusText(member: ProjectMember): string {
  326. if (member.isInProjectTeam && member.isInGroupChat) {
  327. return '已加入';
  328. } else if (member.isInProjectTeam) {
  329. return '待加入';
  330. } else {
  331. return '仅群聊';
  332. }
  333. }
  334. }