designer-assignment.component.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4. export interface Designer {
  5. id: string;
  6. name: string;
  7. avatar?: string;
  8. teamId: string;
  9. teamName: string;
  10. isTeamLeader: boolean;
  11. status: 'idle' | 'busy' | 'reviewing';
  12. idleDays: number;
  13. recentOrders: number;
  14. lastOrderDate?: string;
  15. reviewDates: string[]; // 对图日期,这些日期不能安排其他工作
  16. workload: number; // 当前工作量 (0-100)
  17. skills: string[]; // 技能标签
  18. }
  19. export interface ProjectTeam {
  20. id: string;
  21. name: string;
  22. leaderId: string;
  23. leaderName: string;
  24. members: Designer[];
  25. }
  26. export interface QuotationAssignment {
  27. quotationItemId: string;
  28. quotationItemName: string;
  29. assignedDesigners: string[];
  30. estimatedHours?: number;
  31. }
  32. export interface DesignerAssignmentData {
  33. primaryTeamId: string;
  34. quotationAssignments: QuotationAssignment[];
  35. crossTeamCollaborators: string[]; // 跨组合作的设计师ID
  36. notes?: string;
  37. }
  38. @Component({
  39. selector: 'app-designer-assignment',
  40. standalone: true,
  41. imports: [CommonModule, FormsModule],
  42. templateUrl: './designer-assignment.component.html',
  43. styleUrls: ['./designer-assignment.component.scss']
  44. })
  45. export class DesignerAssignmentComponent implements OnInit {
  46. @Input() quotationItems: any[] = [];
  47. @Input() initialAssignment?: DesignerAssignmentData;
  48. @Output() assignmentChange = new EventEmitter<DesignerAssignmentData>();
  49. @Output() designerClick = new EventEmitter<Designer>();
  50. // 模拟数据 - 实际项目中应该从服务获取
  51. projectTeams: ProjectTeam[] = [
  52. {
  53. id: 'team-1',
  54. name: '家装设计组',
  55. leaderId: 'designer-1',
  56. leaderName: '张组长',
  57. members: [
  58. {
  59. id: 'designer-1',
  60. name: '张组长',
  61. teamId: 'team-1',
  62. teamName: '家装设计组',
  63. isTeamLeader: true,
  64. status: 'busy',
  65. idleDays: 0,
  66. recentOrders: 3,
  67. lastOrderDate: '2024-01-15',
  68. reviewDates: ['2024-01-20', '2024-01-25'],
  69. workload: 85,
  70. skills: ['家装设计', '软装搭配', '项目管理']
  71. },
  72. {
  73. id: 'designer-2',
  74. name: '李设计师',
  75. teamId: 'team-1',
  76. teamName: '家装设计组',
  77. isTeamLeader: false,
  78. status: 'idle',
  79. idleDays: 5,
  80. recentOrders: 1,
  81. lastOrderDate: '2024-01-10',
  82. reviewDates: [],
  83. workload: 30,
  84. skills: ['家装设计', '3D建模']
  85. }
  86. ]
  87. },
  88. {
  89. id: 'team-2',
  90. name: '工装设计组',
  91. leaderId: 'designer-3',
  92. leaderName: '王组长',
  93. members: [
  94. {
  95. id: 'designer-3',
  96. name: '王组长',
  97. teamId: 'team-2',
  98. teamName: '工装设计组',
  99. isTeamLeader: true,
  100. status: 'reviewing',
  101. idleDays: 0,
  102. recentOrders: 2,
  103. lastOrderDate: '2024-01-14',
  104. reviewDates: ['2024-01-18', '2024-01-22'],
  105. workload: 70,
  106. skills: ['工装设计', '商业空间', '项目管理']
  107. },
  108. {
  109. id: 'designer-4',
  110. name: '赵设计师',
  111. teamId: 'team-2',
  112. teamName: '工装设计组',
  113. isTeamLeader: false,
  114. status: 'idle',
  115. idleDays: 12,
  116. recentOrders: 0,
  117. lastOrderDate: '2024-01-03',
  118. reviewDates: [],
  119. workload: 10,
  120. skills: ['工装设计', '效果图制作']
  121. }
  122. ]
  123. }
  124. ];
  125. assignmentData: DesignerAssignmentData = {
  126. primaryTeamId: '',
  127. quotationAssignments: [],
  128. crossTeamCollaborators: [],
  129. notes: ''
  130. };
  131. selectedTeamId = '';
  132. showCrossTeamSelection = false;
  133. availableCrossTeamDesigners: Designer[] = [];
  134. constructor() {}
  135. ngOnInit() {
  136. if (this.initialAssignment) {
  137. this.assignmentData = { ...this.initialAssignment };
  138. this.selectedTeamId = this.assignmentData.primaryTeamId;
  139. }
  140. // 初始化报价分配
  141. this.initializeQuotationAssignments();
  142. }
  143. // 初始化报价分配
  144. initializeQuotationAssignments() {
  145. if (this.quotationItems.length > 0 && this.assignmentData.quotationAssignments.length === 0) {
  146. this.assignmentData.quotationAssignments = this.quotationItems.map(item => ({
  147. quotationItemId: item.id,
  148. quotationItemName: item.name,
  149. assignedDesigners: [],
  150. estimatedHours: 0
  151. }));
  152. }
  153. }
  154. // 选择主要项目组
  155. selectPrimaryTeam(teamId: string) {
  156. this.selectedTeamId = teamId;
  157. this.assignmentData.primaryTeamId = teamId;
  158. // 清空之前的分配
  159. this.assignmentData.quotationAssignments.forEach(assignment => {
  160. assignment.assignedDesigners = [];
  161. });
  162. this.updateAvailableCrossTeamDesigners();
  163. this.emitAssignmentChange();
  164. }
  165. // 获取选中的项目组
  166. getSelectedTeam(): ProjectTeam | undefined {
  167. return this.projectTeams.find(team => team.id === this.selectedTeamId);
  168. }
  169. // 分配设计师到报价项目
  170. assignDesignerToQuotation(quotationId: string, designerId: string) {
  171. const assignment = this.assignmentData.quotationAssignments.find(a => a.quotationItemId === quotationId);
  172. if (assignment) {
  173. if (!assignment.assignedDesigners.includes(designerId)) {
  174. assignment.assignedDesigners.push(designerId);
  175. this.emitAssignmentChange();
  176. }
  177. }
  178. }
  179. // 移除设计师分配
  180. removeDesignerFromQuotation(quotationId: string, designerId: string) {
  181. const assignment = this.assignmentData.quotationAssignments.find(a => a.quotationItemId === quotationId);
  182. if (assignment) {
  183. assignment.assignedDesigners = assignment.assignedDesigners.filter(id => id !== designerId);
  184. this.emitAssignmentChange();
  185. }
  186. }
  187. // 更新可用的跨组合作设计师
  188. updateAvailableCrossTeamDesigners() {
  189. this.availableCrossTeamDesigners = [];
  190. this.projectTeams.forEach(team => {
  191. if (team.id !== this.selectedTeamId) {
  192. this.availableCrossTeamDesigners.push(...team.members);
  193. }
  194. });
  195. }
  196. // 添加跨组合作设计师
  197. addCrossTeamCollaborator(designerId: string) {
  198. if (!this.assignmentData.crossTeamCollaborators.includes(designerId)) {
  199. this.assignmentData.crossTeamCollaborators.push(designerId);
  200. this.emitAssignmentChange();
  201. }
  202. }
  203. // 移除跨组合作设计师
  204. removeCrossTeamCollaborator(designerId: string) {
  205. this.assignmentData.crossTeamCollaborators = this.assignmentData.crossTeamCollaborators.filter(id => id !== designerId);
  206. this.emitAssignmentChange();
  207. }
  208. // 获取设计师信息
  209. getDesignerById(designerId: string): Designer | undefined {
  210. for (const team of this.projectTeams) {
  211. const designer = team.members.find(d => d.id === designerId);
  212. if (designer) return designer;
  213. }
  214. return undefined;
  215. }
  216. // 获取设计师状态颜色
  217. getDesignerStatusColor(status: string): string {
  218. switch (status) {
  219. case 'idle': return '#52c41a';
  220. case 'busy': return '#faad14';
  221. case 'reviewing': return '#1890ff';
  222. default: return '#d9d9d9';
  223. }
  224. }
  225. // 获取设计师状态文本
  226. getDesignerStatusText(status: string): string {
  227. switch (status) {
  228. case 'idle': return '空闲';
  229. case 'busy': return '忙碌';
  230. case 'reviewing': return '对图中';
  231. default: return '未知';
  232. }
  233. }
  234. // 获取工作量状态
  235. getWorkloadStatus(workload: number): 'low' | 'medium' | 'high' {
  236. if (workload < 30) return 'low';
  237. if (workload < 70) return 'medium';
  238. return 'high';
  239. }
  240. // 点击设计师
  241. onDesignerClick(designer: Designer) {
  242. this.designerClick.emit(designer);
  243. }
  244. // 发送分配变化事件
  245. emitAssignmentChange() {
  246. this.assignmentChange.emit({ ...this.assignmentData });
  247. }
  248. // 自动分配建议
  249. getAutoAssignmentSuggestion(): string {
  250. const selectedTeam = this.getSelectedTeam();
  251. if (!selectedTeam) return '';
  252. const idleDesigners = selectedTeam.members.filter(d => d.status === 'idle' && d.workload < 50);
  253. const busyDesigners = selectedTeam.members.filter(d => d.workload >= 70);
  254. let suggestion = '';
  255. if (idleDesigners.length > 0) {
  256. suggestion += `建议优先分配给空闲设计师:${idleDesigners.map(d => d.name).join('、')}。`;
  257. }
  258. if (busyDesigners.length > 0) {
  259. suggestion += `注意:${busyDesigners.map(d => d.name).join('、')} 工作量较重。`;
  260. }
  261. return suggestion;
  262. }
  263. // 检查是否有冲突的对图日期
  264. hasReviewDateConflict(designerId: string, projectStartDate?: string): boolean {
  265. const designer = this.getDesignerById(designerId);
  266. if (!designer || !projectStartDate) return false;
  267. // 简单的日期冲突检查逻辑
  268. const startDate = new Date(projectStartDate);
  269. const endDate = new Date(startDate.getTime() + 30 * 24 * 60 * 60 * 1000); // 假设项目周期30天
  270. return designer.reviewDates.some(reviewDate => {
  271. const review = new Date(reviewDate);
  272. return review >= startDate && review <= endDate;
  273. });
  274. }
  275. // 获取团队空闲人数的安全方法
  276. getIdleCount(team: ProjectTeam): number {
  277. if (!team || !team.members) return 0;
  278. return team.members.filter(m => m && m.status === 'idle').length;
  279. }
  280. }