project-list.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import { Component, OnInit, signal, computed } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4. import { RouterModule } from '@angular/router';
  5. import { ProjectService } from '../../../services/project.service';
  6. import { Project, ProjectStatus, ProjectStage } from '../../../models/project.model';
  7. // 定义项目列表项接口,包含计算后的属性
  8. interface ProjectListItem extends Project {
  9. progress: number;
  10. daysUntilDeadline: number;
  11. isUrgent: boolean;
  12. tagDisplayText: string;
  13. }
  14. @Component({
  15. selector: 'app-project-list',
  16. standalone: true,
  17. imports: [CommonModule, FormsModule, RouterModule],
  18. templateUrl: './project-list.html',
  19. styleUrls: ['./project-list.scss', '../customer-service-styles.scss']
  20. })
  21. export class ProjectList implements OnInit {
  22. // 项目列表数据
  23. projects = signal<ProjectListItem[]>([]);
  24. // 原始项目数据(用于筛选)
  25. allProjects = signal<Project[]>([]);
  26. // 添加toggleSidebar方法
  27. toggleSidebar(): void {
  28. // 侧边栏切换逻辑
  29. console.log('Toggle sidebar');
  30. }
  31. // 筛选和排序状态
  32. searchTerm = signal('');
  33. statusFilter = signal<string>('all');
  34. stageFilter = signal<string>('all');
  35. sortBy = signal<string>('deadline');
  36. // 当前页码
  37. currentPage = signal(1);
  38. // 每页显示数量
  39. pageSize = 8;
  40. // 分页后的项目列表
  41. paginatedProjects = computed(() => {
  42. const filteredProjects = this.projects();
  43. const startIndex = (this.currentPage() - 1) * this.pageSize;
  44. return filteredProjects.slice(startIndex, startIndex + this.pageSize);
  45. });
  46. // 总页数
  47. totalPages = computed(() => {
  48. return Math.ceil(this.projects().length / this.pageSize);
  49. });
  50. // 筛选和排序选项
  51. statusOptions = [
  52. { value: 'all', label: '全部状态' },
  53. { value: '进行中', label: '进行中' },
  54. { value: '已完成', label: '已完成' },
  55. { value: '已暂停', label: '已暂停' },
  56. { value: '已延期', label: '已延期' }
  57. ];
  58. stageOptions = [
  59. { value: 'all', label: '全部阶段' },
  60. { value: '前期沟通', label: '前期沟通' },
  61. { value: '建模', label: '建模' },
  62. { value: '软装', label: '软装' },
  63. { value: '渲染', label: '渲染' },
  64. { value: '后期', label: '后期' },
  65. { value: '完成', label: '完成' }
  66. ];
  67. sortOptions = [
  68. { value: 'deadline', label: '截止日期' },
  69. { value: 'createdAt', label: '创建时间' },
  70. { value: 'name', label: '项目名称' }
  71. ];
  72. constructor(private projectService: ProjectService) {}
  73. ngOnInit(): void {
  74. this.loadProjects();
  75. }
  76. // 加载项目列表
  77. loadProjects(): void {
  78. this.projectService.getProjects().subscribe(projects => {
  79. this.allProjects.set(projects);
  80. // 添加模拟数据以丰富列表
  81. const enrichedProjects = [...projects, ...this.generateMockProjects()];
  82. this.processProjects(enrichedProjects);
  83. });
  84. }
  85. // 处理项目数据,添加计算属性
  86. processProjects(projects: Project[]): void {
  87. const processedProjects = projects.map(project => {
  88. // 计算项目进度(模拟)
  89. const progress = this.calculateProjectProgress(project);
  90. // 计算距离截止日期的天数
  91. const daysUntilDeadline = this.calculateDaysUntilDeadline(project.deadline);
  92. // 判断是否紧急(截止日期前3天或已逾期)
  93. const isUrgent = daysUntilDeadline <= 3 && project.status === '进行中';
  94. // 生成标签显示文本
  95. const tagDisplayText = this.generateTagDisplayText(project);
  96. return {
  97. ...project,
  98. progress,
  99. daysUntilDeadline,
  100. isUrgent,
  101. tagDisplayText
  102. };
  103. });
  104. this.projects.set(this.applyFiltersAndSorting(processedProjects));
  105. }
  106. // 应用筛选和排序
  107. applyFiltersAndSorting(projects: ProjectListItem[]): ProjectListItem[] {
  108. let filteredProjects = [...projects];
  109. // 搜索筛选
  110. if (this.searchTerm().trim()) {
  111. const searchLower = this.searchTerm().toLowerCase().trim();
  112. filteredProjects = filteredProjects.filter(project =>
  113. project.name.toLowerCase().includes(searchLower) ||
  114. project.customerName.toLowerCase().includes(searchLower)
  115. );
  116. }
  117. // 状态筛选
  118. if (this.statusFilter() !== 'all') {
  119. filteredProjects = filteredProjects.filter(project =>
  120. project.status === this.statusFilter()
  121. );
  122. }
  123. // 阶段筛选
  124. if (this.stageFilter() !== 'all') {
  125. filteredProjects = filteredProjects.filter(project =>
  126. project.currentStage === this.stageFilter()
  127. );
  128. }
  129. // 排序
  130. filteredProjects.sort((a, b) => {
  131. switch (this.sortBy()) {
  132. case 'deadline':
  133. return new Date(a.deadline).getTime() - new Date(b.deadline).getTime();
  134. case 'createdAt':
  135. return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
  136. case 'name':
  137. return a.name.localeCompare(b.name);
  138. default:
  139. return 0;
  140. }
  141. });
  142. return filteredProjects;
  143. }
  144. // 生成标签显示文本
  145. generateTagDisplayText(project: Project): string {
  146. if (!project.customerTags || project.customerTags.length === 0) {
  147. return '普通项目';
  148. }
  149. const tag = project.customerTags[0];
  150. return `${tag.preference}${tag.needType}`;
  151. }
  152. // 计算项目进度(模拟)
  153. calculateProjectProgress(project: Project): number {
  154. if (project.status === '已完成') return 100;
  155. if (project.status === '已暂停' || project.status === '已延期') return 0;
  156. // 基于当前阶段计算进度
  157. const stageProgress: Record<ProjectStage, number> = {
  158. '前期沟通': 20,
  159. '建模': 40,
  160. '软装': 60,
  161. '渲染': 80,
  162. '后期': 90,
  163. '完成': 100
  164. };
  165. return stageProgress[project.currentStage] || 0;
  166. }
  167. // 计算距离截止日期的天数
  168. calculateDaysUntilDeadline(deadline: Date): number {
  169. const now = new Date();
  170. const deadlineDate = new Date(deadline);
  171. const diffTime = deadlineDate.getTime() - now.getTime();
  172. return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  173. }
  174. // 生成模拟项目数据
  175. generateMockProjects(): Project[] {
  176. const statuses: ProjectStatus[] = ['进行中', '已完成', '已暂停', '已延期'];
  177. const stages: ProjectStage[] = ['前期沟通', '建模', '软装', '渲染', '后期', '完成'];
  178. const preferences: ('现代' | '宋式' | '欧式')[] = ['现代', '宋式', '欧式'];
  179. const needTypes: ('硬装' | '软装')[] = ['硬装', '软装'];
  180. const sources: ('朋友圈' | '信息流')[] = ['朋友圈', '信息流'];
  181. const mockProjects: Project[] = [];
  182. for (let i = 1; i <= 12; i++) {
  183. const baseDate = new Date();
  184. const createdDate = new Date(baseDate.setDate(baseDate.getDate() - Math.floor(Math.random() * 30)));
  185. baseDate.setDate(baseDate.getDate() + Math.floor(Math.random() * 15) + 5);
  186. const deadlineDate = new Date(baseDate);
  187. const preference = preferences[Math.floor(Math.random() * preferences.length)];
  188. const needType = needTypes[Math.floor(Math.random() * needTypes.length)];
  189. const source = sources[Math.floor(Math.random() * sources.length)];
  190. const stage = stages[Math.floor(Math.random() * stages.length)];
  191. const status = stage === '完成' ? '已完成' : statuses[Math.floor(Math.random() * 3)];
  192. mockProjects.push({
  193. id: `mock-${i}`,
  194. name: `${preference}风格${needType === '硬装' ? '全屋' : '客厅'}设计`,
  195. customerName: `客户${String.fromCharCode(64 + i)}`,
  196. customerTags: [
  197. {
  198. source: source,
  199. needType: needType,
  200. preference: preference,
  201. colorAtmosphere: i % 2 === 0 ? '简约明亮' : '温馨舒适'
  202. }
  203. ],
  204. highPriorityNeeds: i % 3 === 0 ? ['需快速交付', '重要客户'] : [],
  205. status: status,
  206. currentStage: stage,
  207. createdAt: createdDate,
  208. deadline: deadlineDate,
  209. assigneeId: `designer${i % 3 + 1}`,
  210. assigneeName: `设计师${String.fromCharCode(64 + (i % 3 + 1))}`,
  211. skillsRequired: [preference + '风格', needType]
  212. });
  213. }
  214. return mockProjects;
  215. }
  216. // 处理搜索
  217. onSearch(): void {
  218. this.currentPage.set(1);
  219. this.processProjects(this.allProjects());
  220. }
  221. // 处理状态筛选
  222. onStatusChange(event: Event): void {
  223. const selectElement = event.target as HTMLSelectElement;
  224. this.statusFilter.set(selectElement.value);
  225. this.currentPage.set(1);
  226. this.processProjects(this.allProjects());
  227. }
  228. // 处理阶段筛选
  229. onStageChange(event: Event): void {
  230. const selectElement = event.target as HTMLSelectElement;
  231. this.stageFilter.set(selectElement.value);
  232. this.currentPage.set(1);
  233. this.processProjects(this.allProjects());
  234. }
  235. // 处理排序变化
  236. onSortChange(event: Event): void {
  237. const selectElement = event.target as HTMLSelectElement;
  238. this.sortBy.set(selectElement.value);
  239. this.processProjects(this.allProjects());
  240. }
  241. // 分页导航
  242. goToPage(page: number): void {
  243. if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {
  244. this.currentPage.set(page);
  245. // 不需要重新加载整个项目列表,currentPage变更后computed属性会自动更新
  246. }
  247. }
  248. prevPage(): void {
  249. if (this.currentPage() > 1) {
  250. this.goToPage(this.currentPage() - 1);
  251. }
  252. }
  253. nextPage(): void {
  254. if (this.currentPage() < this.totalPages()) {
  255. this.goToPage(this.currentPage() + 1);
  256. }
  257. }
  258. // 计算当前显示的页码数组
  259. pageNumbers = computed(() => {
  260. const maxVisible = 5;
  261. const total = this.totalPages();
  262. const current = this.currentPage();
  263. const pages: number[] = [];
  264. if (total <= maxVisible) {
  265. // 如果总页数不超过最大可见页数,显示所有页码
  266. for (let i = 1; i <= total; i++) {
  267. pages.push(i);
  268. }
  269. } else {
  270. // 处理中间页码显示
  271. if (current <= Math.ceil(maxVisible / 2)) {
  272. // 当前页在前面部分
  273. for (let i = 1; i <= maxVisible; i++) {
  274. pages.push(i);
  275. }
  276. } else if (current >= total - Math.floor(maxVisible / 2)) {
  277. // 当前页在后面部分
  278. for (let i = total - (maxVisible - 1); i <= total; i++) {
  279. pages.push(i);
  280. }
  281. } else {
  282. // 当前页在中间部分
  283. for (let i = current - Math.floor(maxVisible / 2); i <= current + Math.floor(maxVisible / 2); i++) {
  284. pages.push(i);
  285. }
  286. }
  287. }
  288. return pages;
  289. });
  290. // 计算绝对值的辅助方法(用于模板中)
  291. getAbsValue(value: number): number {
  292. return Math.abs(value);
  293. }
  294. // 格式化日期
  295. formatDate(date: Date): string {
  296. return new Date(date).toLocaleDateString('zh-CN', {
  297. year: 'numeric',
  298. month: '2-digit',
  299. day: '2-digit'
  300. });
  301. }
  302. // 获取状态样式类
  303. getStatusClass(status: string): string {
  304. const statusClasses: Record<string, string> = {
  305. '进行中': 'status-active',
  306. '已完成': 'status-completed',
  307. '已暂停': 'status-paused',
  308. '已延期': 'status-overdue'
  309. };
  310. return statusClasses[status] || '';
  311. }
  312. // 获取阶段样式类
  313. getStageClass(stage: string): string {
  314. const stageClasses: Record<string, string> = {
  315. '前期沟通': 'stage-communication',
  316. '建模': 'stage-modeling',
  317. '软装': 'stage-decoration',
  318. '渲染': 'stage-rendering',
  319. '后期': 'stage-postproduction',
  320. '完成': 'stage-completed'
  321. };
  322. return stageClasses[stage] || '';
  323. }
  324. }