consultation-order-panel.component.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. import { Component, EventEmitter, OnInit, Output, Input, OnChanges, SimpleChanges } from '@angular/core';
  2. import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
  3. import { CommonModule } from '@angular/common';
  4. import { TeamAssignmentModalComponent, Designer } from '../team-assignment-modal/team-assignment-modal.component';
  5. import { FormsModule } from '@angular/forms';
  6. import { MatChipsModule } from '@angular/material/chips';
  7. import { MatIconModule } from '@angular/material/icon';
  8. // 定义客户信息接口
  9. interface Customer {
  10. id: string;
  11. name: string;
  12. phone: string;
  13. wechat?: string;
  14. avatar?: string;
  15. customerType?: string;
  16. source?: string;
  17. remark?: string;
  18. demandType?: string;
  19. preferenceTags?: string[];
  20. followUpStatus?: string;
  21. }
  22. @Component({
  23. selector: 'app-consultation-order-panel',
  24. standalone: true,
  25. imports: [
  26. CommonModule,
  27. ReactiveFormsModule,
  28. FormsModule,
  29. MatChipsModule,
  30. MatIconModule,
  31. TeamAssignmentModalComponent
  32. ],
  33. templateUrl: './consultation-order-panel.component.html',
  34. styleUrls: ['./consultation-order-panel.component.scss']
  35. })
  36. export class ConsultationOrderPanelComponent implements OnInit, OnChanges {
  37. @Output() orderCreated = new EventEmitter<any>();
  38. @Output() projectCreated = new EventEmitter<any>(); // 新增项目创建成功事件
  39. @Input() syncData: any = null; // 接收同步的订单数据
  40. // 搜索客户关键词
  41. searchKeyword = '';
  42. // 搜索结果列表
  43. searchResults: Customer[] = [];
  44. // 选中的客户
  45. selectedCustomer: Customer | null = null;
  46. // 表单提交状态
  47. isSubmitting = false;
  48. // 项目需求卡片展开状态 - 默认展开以便用户填写必填字段
  49. isRequirementCardExpanded = true;
  50. // 需求表单
  51. requirementForm: FormGroup;
  52. // 客户表单
  53. customerForm: FormGroup;
  54. // 团队分配弹窗相关
  55. showTeamAssignmentModal = false;
  56. selectedDesigner: Designer | null = null;
  57. // 新增:自动分配用的设计师池(与弹窗组件保持一致的数据结构)
  58. private allDesigners: Designer[] = [
  59. {
  60. id: '1',
  61. name: '张设计师',
  62. role: '高级室内设计师',
  63. avatar: '/assets/images/default-avatar.svg',
  64. skills: ['现代简约', '北欧风格', '工业风'],
  65. workload: { level: 'low', percentage: 30, text: '轻度' },
  66. recentTasks: [
  67. { id: '1', name: '海景别墅设计', projectName: '海景别墅设计', stage: 'modeling', deadline: '2024-01-15' },
  68. { id: '2', name: '现代公寓改造', projectName: '现代公寓改造', stage: 'rendering', deadline: '2024-01-20' }
  69. ]
  70. },
  71. {
  72. id: '2',
  73. name: '李设计师',
  74. role: '资深设计总监',
  75. avatar: '/assets/images/default-avatar.svg',
  76. skills: ['欧式古典', '美式乡村', '中式传统'],
  77. workload: { level: 'medium', percentage: 65, text: '中度' },
  78. recentTasks: [
  79. { id: '3', name: '豪华会所设计', projectName: '豪华会所设计', stage: 'soft-decoration', deadline: '2024-01-18' },
  80. { id: '4', name: '商业空间规划', projectName: '商业空间规划', stage: 'post-production', deadline: '2024-01-25' },
  81. { id: '5', name: '私人定制住宅', projectName: '私人定制住宅', stage: 'modeling', deadline: '2024-02-01' }
  82. ]
  83. },
  84. {
  85. id: '3',
  86. name: '王设计师',
  87. role: '创意设计师',
  88. avatar: '/assets/images/default-avatar.svg',
  89. skills: ['极简主义', '日式禅风', '斯堪的纳维亚'],
  90. workload: { level: 'high', percentage: 85, text: '重度' },
  91. recentTasks: [
  92. { id: '6', name: '艺术画廊设计', projectName: '艺术画廊设计', stage: 'rendering', deadline: '2024-01-12' },
  93. { id: '7', name: '精品酒店套房', projectName: '精品酒店套房', stage: 'soft-decoration', deadline: '2024-01-16' },
  94. { id: '8', name: '创意办公空间', projectName: '创意办公空间', stage: 'post-production', deadline: '2024-01-22' },
  95. { id: '9', name: '高端住宅项目', projectName: '高端住宅项目', stage: 'modeling', deadline: '2024-01-28' }
  96. ]
  97. },
  98. {
  99. id: '4',
  100. name: '陈设计师',
  101. role: '室内设计师',
  102. avatar: '/assets/images/default-avatar.svg',
  103. skills: ['新中式', '轻奢风格', '混搭风格'],
  104. workload: { level: 'low', percentage: 20, text: '轻度' },
  105. recentTasks: [
  106. { id: '10', name: '温馨家庭住宅', projectName: '温馨家庭住宅', stage: 'modeling', deadline: '2024-01-30' }
  107. ]
  108. },
  109. // 新增:完全空闲的设计师用于自动分配展示
  110. {
  111. id: '5',
  112. name: '赵设计师',
  113. role: '室内设计师',
  114. avatar: '/assets/images/default-avatar.svg',
  115. skills: ['现代简约', '北欧风格'],
  116. workload: { level: 'low', percentage: 0, text: '空闲' },
  117. recentTasks: []
  118. }
  119. ];
  120. // 样式选项
  121. styleOptions = [
  122. '现代简约', '北欧风', '工业风', '新中式', '法式轻奢', '日式', '美式', '混搭'
  123. ];
  124. // 项目小组选项
  125. projectGroupOptions = [
  126. '设计一组', '设计二组', '设计三组', '高端定制组', '软装设计组'
  127. ];
  128. // 偏好标签选项
  129. preferenceTagOptions = [
  130. '柔和色系', '明亮色系', '深色系', '中性色系',
  131. '环保材料', '实木', '大理石', '瓷砖', '地板', '墙纸',
  132. '现代简约', '北欧风格', '中式风格', '美式风格', '工业风',
  133. '智能家电', '收纳空间', '开放式厨房', '大窗户'
  134. ];
  135. preferenceTags: string[] = [];
  136. // 新标签输入
  137. newTag = '';
  138. // 角色上下文
  139. roleContext: 'customer-service' | 'designer' | 'team-leader' = 'customer-service';
  140. constructor(private fb: FormBuilder) {
  141. // 初始化需求表单
  142. this.requirementForm = this.fb.group({
  143. downPayment: ['', [Validators.required, Validators.min(0)]],
  144. budget: ['', Validators.required],
  145. area: ['', [Validators.required, Validators.min(1)]],
  146. smallImageTime: [''],
  147. largeImageTime: [''],
  148. houseType: [''],
  149. floor: ['', Validators.min(1)],
  150. preferredDesigner: [''],
  151. specialRequirements: [''],
  152. referenceCases: [[]],
  153. priceDetails: [''],
  154. // 新增字段
  155. spaceRequirements: [''],
  156. designAngles: [''],
  157. specialAreaHandling: [''],
  158. materialRequirements: [''],
  159. lightingRequirements: ['']
  160. });
  161. // 初始化客户表单
  162. this.customerForm = this.fb.group({
  163. wechat: [''],
  164. customerType: ['新客户'],
  165. source: [''],
  166. remark: [''],
  167. demandType: [''],
  168. followUpStatus: ['']
  169. });
  170. }
  171. ngOnInit(): void {
  172. // 监听表单变化,实时更新阶段状态
  173. this.customerForm.valueChanges.subscribe(() => {
  174. this.checkStageCompletion();
  175. });
  176. this.requirementForm.valueChanges.subscribe(() => {
  177. this.checkStageCompletion();
  178. });
  179. // 初始检查阶段状态
  180. this.checkStageCompletion();
  181. // 如果有同步数据,立即填充表单
  182. if (this.syncData) {
  183. this.fillFormWithSyncData(this.syncData);
  184. }
  185. }
  186. ngOnChanges(changes: SimpleChanges): void {
  187. // 当syncData发生变化时,填充表单数据
  188. if (changes['syncData'] && changes['syncData'].currentValue) {
  189. this.fillFormWithSyncData(changes['syncData'].currentValue);
  190. }
  191. }
  192. // 使用同步数据填充表单
  193. private fillFormWithSyncData(data: any): void {
  194. if (data.customerInfo) {
  195. this.customerForm.patchValue(data.customerInfo);
  196. this.selectedCustomer = data.customerInfo;
  197. }
  198. if (data.requirementInfo) {
  199. this.requirementForm.patchValue(data.requirementInfo);
  200. }
  201. if (data.preferenceTags && Array.isArray(data.preferenceTags)) {
  202. this.preferenceTags = [...data.preferenceTags];
  203. }
  204. // 更新阶段完成状态
  205. this.checkStageCompletion();
  206. }
  207. // 搜索客户
  208. searchCustomer() {
  209. if (this.searchKeyword.length >= 2) {
  210. // 模拟搜索结果
  211. this.searchResults = [
  212. {
  213. id: '1',
  214. name: '张先生',
  215. phone: '138****5678',
  216. customerType: '老客户',
  217. source: '官网咨询',
  218. avatar: "data:image/svg+xml,%3Csvg width='64' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23E6E6E6'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E"
  219. },
  220. {
  221. id: '2',
  222. name: '李女士',
  223. phone: '139****1234',
  224. customerType: 'VIP客户',
  225. source: '推荐介绍',
  226. avatar: "data:image/svg+xml,%3Csvg width='65' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23DCDCDC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E"
  227. }
  228. ];
  229. }
  230. }
  231. // 选择客户
  232. selectCustomer(customer: Customer) {
  233. this.selectedCustomer = customer;
  234. // 填充客户表单
  235. this.customerForm.patchValue({
  236. name: customer.name,
  237. phone: customer.phone,
  238. wechat: customer.wechat || '',
  239. customerType: customer.customerType || '新客户',
  240. source: customer.source || '',
  241. remark: customer.remark || ''
  242. });
  243. // 清空搜索结果
  244. this.searchResults = [];
  245. this.searchKeyword = '';
  246. }
  247. // 清除选中的客户
  248. clearSelectedCustomer() {
  249. this.selectedCustomer = null;
  250. this.customerForm.reset({
  251. customerType: '新客户'
  252. });
  253. }
  254. // 添加偏好标签
  255. addPreferenceTag(tag: string): void {
  256. if (tag && !this.preferenceTags.includes(tag)) {
  257. this.preferenceTags.push(tag);
  258. this.newTag = ''; // 清空输入框
  259. }
  260. }
  261. // 删除偏好标签
  262. removePreferenceTag(tag: string): void {
  263. const index = this.preferenceTags.indexOf(tag);
  264. if (index >= 0) {
  265. this.preferenceTags.splice(index, 1);
  266. }
  267. }
  268. // 流程状态管理
  269. stageCompletionStatus = {
  270. customerInfo: false,
  271. projectRequirements: false,
  272. teamAssignment: false,
  273. orderConfirmation: false
  274. };
  275. // 获取阶段状态文本
  276. getStageStatusText(stage: keyof typeof this.stageCompletionStatus): string {
  277. if (this.stageCompletionStatus[stage]) {
  278. return '已完成';
  279. } else {
  280. // 检查是否为未开始状态
  281. const stages = ['customerInfo', 'projectRequirements', 'teamAssignment', 'orderConfirmation'];
  282. const currentIndex = stages.indexOf(stage);
  283. // 检查前面的阶段是否都已完成
  284. let allPreviousCompleted = true;
  285. for (let i = 0; i < currentIndex; i++) {
  286. if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
  287. allPreviousCompleted = false;
  288. break;
  289. }
  290. }
  291. return allPreviousCompleted ? '进行中' : '未进行';
  292. }
  293. }
  294. // 获取阶段状态类名
  295. getStageStatusClass(stage: keyof typeof this.stageCompletionStatus): string {
  296. if (this.stageCompletionStatus[stage]) {
  297. return 'stage-completed';
  298. } else {
  299. // 检查是否为未开始状态
  300. const stages = ['customerInfo', 'projectRequirements', 'teamAssignment', 'orderConfirmation'];
  301. const currentIndex = stages.indexOf(stage);
  302. // 检查前面的阶段是否都已完成
  303. let allPreviousCompleted = true;
  304. for (let i = 0; i < currentIndex; i++) {
  305. if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
  306. allPreviousCompleted = false;
  307. break;
  308. }
  309. }
  310. return allPreviousCompleted ? 'stage-in-progress' : 'stage-pending';
  311. }
  312. }
  313. // 获取进度百分比
  314. getProgressPercentage(): number {
  315. const completedStages = Object.values(this.stageCompletionStatus).filter(status => status).length;
  316. return Math.round((completedStages / 4) * 100);
  317. }
  318. // 检查阶段完成状态
  319. private checkStageCompletion(): void {
  320. // 检查客户信息阶段 - 客户信息表单有效
  321. this.stageCompletionStatus.customerInfo = this.customerForm.valid;
  322. // 检查项目需求阶段 - 需求表单有效
  323. this.stageCompletionStatus.projectRequirements = this.requirementForm.valid;
  324. // 检查团队分配阶段 - 已选择设计师
  325. this.stageCompletionStatus.teamAssignment = !!this.selectedDesigner;
  326. // 检查订单确认阶段 - 所有前面阶段都完成
  327. this.stageCompletionStatus.orderConfirmation = this.stageCompletionStatus.customerInfo &&
  328. this.stageCompletionStatus.projectRequirements &&
  329. this.stageCompletionStatus.teamAssignment;
  330. }
  331. // 检查表单是否有效
  332. isFormValid(): boolean {
  333. return this.customerForm.valid && this.requirementForm.valid;
  334. }
  335. // 检查是否可以创建订单(放宽条件:仅需表单有效即可点击创建,设计师由系统自动分配或人工分配)
  336. canCreateOrder(): boolean {
  337. return this.isFormValid();
  338. }
  339. // 创建订单方法(支持自动分配空闲设计师)
  340. createOrder() {
  341. // 表单未通过校验则不允许创建
  342. if (!this.isFormValid()) {
  343. return;
  344. }
  345. // 若尚未选择设计师,尝试自动分配空闲设计师
  346. if (!this.selectedDesigner) {
  347. const assigned = this.autoAssignDesignerIfAvailable();
  348. if (!assigned) {
  349. // 无空闲设计师:阻止创建并弹出分配弹窗
  350. this.showTeamAssignmentModal = true;
  351. return;
  352. }
  353. }
  354. this.isSubmitting = true;
  355. // 构建完整的项目数据
  356. const formData = {
  357. customerInfo: this.customerForm.value,
  358. requirementInfo: this.requirementForm.value,
  359. preferenceTags: this.preferenceTags,
  360. roleContext: this.roleContext,
  361. createdAt: new Date()
  362. };
  363. const projectData = {
  364. customerInfo: this.customerForm.value,
  365. requirementInfo: this.requirementForm.value,
  366. preferenceTags: this.preferenceTags,
  367. assignedDesigner: this.selectedDesigner,
  368. createdAt: new Date()
  369. };
  370. // 先触发订单创建事件(用于数据同步)
  371. this.orderCreated.emit(formData);
  372. // 再触发项目创建成功事件(用于阶段推进)
  373. this.projectCreated.emit(projectData);
  374. // 重置表单
  375. this.customerForm.reset({
  376. customerType: '新客户'
  377. });
  378. this.requirementForm.reset();
  379. this.preferenceTags = [];
  380. this.selectedCustomer = null;
  381. this.selectedDesigner = null;
  382. this.isSubmitting = false;
  383. }
  384. // 提交表单 - 保留原有逻辑但简化
  385. submitForm() {
  386. this.createOrder();
  387. }
  388. // 关闭团队分配弹窗
  389. closeTeamAssignmentModal() {
  390. this.showTeamAssignmentModal = false;
  391. }
  392. // 确认团队分配 - 优化流程:同时触发订单创建和项目创建事件
  393. confirmTeamAssignment(designer: Designer) {
  394. this.selectedDesigner = designer;
  395. this.showTeamAssignmentModal = false;
  396. this.isSubmitting = false; // 重置提交状态
  397. // 更新阶段状态
  398. this.checkStageCompletion();
  399. // 构建完整的项目数据
  400. const formData = {
  401. customerInfo: this.customerForm.value,
  402. requirementInfo: this.requirementForm.value,
  403. preferenceTags: this.preferenceTags,
  404. roleContext: this.roleContext, // 添加角色上下文信息
  405. createdAt: new Date()
  406. };
  407. const projectData = {
  408. customerInfo: this.customerForm.value,
  409. requirementInfo: this.requirementForm.value,
  410. preferenceTags: this.preferenceTags,
  411. assignedDesigner: designer,
  412. createdAt: new Date()
  413. };
  414. // 先触发订单创建事件(用于数据同步)
  415. this.orderCreated.emit(formData);
  416. // 再触发项目创建成功事件(用于阶段推进)
  417. this.projectCreated.emit(projectData);
  418. // 重置表单
  419. this.customerForm.reset({
  420. customerType: '新客户'
  421. });
  422. this.requirementForm.reset();
  423. this.preferenceTags = [];
  424. this.selectedCustomer = null;
  425. }
  426. // 新增:查找空闲设计师(低负载且在项少于等于1)
  427. private findAvailableDesigners(): Designer[] {
  428. return this.allDesigners.filter(d => d.workload.level === 'low' && (d.recentTasks?.length || 0) === 0);
  429. }
  430. // 新增:自动随机分配空闲设计师
  431. private autoAssignDesignerIfAvailable(): boolean {
  432. const candidates = this.findAvailableDesigners();
  433. if (candidates.length === 0) {
  434. return false;
  435. }
  436. const randomIndex = Math.floor(Math.random() * candidates.length);
  437. this.selectedDesigner = candidates[randomIndex];
  438. // 更新阶段状态(选择了设计师)
  439. this.checkStageCompletion();
  440. return true;
  441. }
  442. }