| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- import { Component, OnInit, signal } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { RouterModule } from '@angular/router';
- import { FormsModule } from '@angular/forms';
- import { MatButtonModule } from '@angular/material/button';
- import { MatIconModule } from '@angular/material/icon';
- import { MatTableModule } from '@angular/material/table';
- import { MatInputModule } from '@angular/material/input';
- import { MatSelectModule } from '@angular/material/select';
- import { MatPaginatorModule } from '@angular/material/paginator';
- import { MatDialogModule, MatDialog } from '@angular/material/dialog';
- import { MatSortModule } from '@angular/material/sort';
- import { ProjectDialogComponent } from './project-dialog/project-dialog'; // @ts-ignore: Component used in code but not in template
- import { ProjectService } from '../services/project.service';
- import { ProjectAutoCaseService } from '../services/project-auto-case.service';
- import { DesignerTeamAssignmentModalComponent, Designer, ProjectTeam } from '../../designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component';
- import { mapStageToCorePhase, getCorePhaseName, getProjectStatusByStage, normalizeStage } from '../../../utils/project-stage-mapper';
- interface Project {
- id: string;
- title: string;
- customer: string;
- customerId?: string;
- status: string;
- assignee: string;
- assigneeId?: string;
- createdAt?: Date;
- updatedAt?: Date;
- deadline?: Date;
- currentStage?: string;
- }
- @Component({
- selector: 'app-project-management',
- standalone: true,
- imports: [
- CommonModule,
- RouterModule,
- FormsModule,
- MatButtonModule,
- MatIconModule,
- MatTableModule,
- MatInputModule,
- MatSelectModule,
- MatPaginatorModule,
- MatDialogModule,
- MatSortModule,
- DesignerTeamAssignmentModalComponent
- ],
- templateUrl: './project-management.html',
- styleUrl: './project-management.scss'
- })
- export class ProjectManagement implements OnInit {
- projects = signal<Project[]>([]);
- filteredProjects = signal<Project[]>([]);
- searchTerm = '';
- statusFilter = '';
- sortColumn = 'updatedAt';
- sortDirection = 'desc';
- pageSize = 10;
- currentPage = 0;
- loading = signal(false);
- // 团队分配相关属性
- showTeamAssignmentModal = false;
- selectedProject: Project | null = null;
- projectTeams: ProjectTeam[] = [];
- currentTeamAssignment: any = {
- primaryTeamId: null,
- quotationAssignments: [],
- crossTeamCollaborators: []
- };
- currentQuotationItems: any[] = [];
- // 模拟项目团队数据
- private mockProjectTeams: ProjectTeam[] = [
- {
- id: 'team-1',
- name: '野生项目组',
- leaderId: 'designer-1',
- leaderName: '张佳乐',
- description: '专注于家庭装修设计,包括客厅、卧室、厨房等空间设计',
- members: [
- {
- id: 'designer-1',
- name: '张佳乐',
- avatar: '/assets/avatars/zhang.jpg',
- teamId: 'team-1',
- teamName: '野生项目组',
- isTeamLeader: true,
- status: 'busy',
- idleDays: 0,
- recentOrders: 3,
- lastOrderDate: '2024-10-20',
- reviewDates: ['2024-10-25', '2024-10-28'],
- workload: 85,
- skills: ['空间设计', '建模', '渲染'],
- isInStagnantProject: false,
- availableDates: [],
- groupId: 'team-1',
- groupName: '野生项目组',
- isLeader: true,
- currentProjects: 3
- },
- {
- id: 'designer-2',
- name: '未知设计师',
- avatar: '/assets/avatars/unknown.jpg',
- teamId: 'team-1',
- teamName: '野生项目组',
- isTeamLeader: false,
- status: 'idle',
- idleDays: 6,
- recentOrders: 0,
- lastOrderDate: '2024-10-14',
- reviewDates: [],
- workload: 0,
- skills: ['软装', '后期'],
- isInStagnantProject: false,
- availableDates: ['2024-10-25', '2024-10-26', '2024-10-27'],
- groupId: 'team-1',
- groupName: '野生项目组',
- isLeader: false,
- currentProjects: 0
- }
- ]
- },
- {
- id: 'team-2',
- name: '无常项目组',
- leaderId: 'designer-3',
- leaderName: '江集',
- description: '专业商业空间设计,办公室、店铺等',
- members: [
- {
- id: 'designer-3',
- name: '江集',
- avatar: '/assets/avatars/jiang.jpg',
- teamId: 'team-2',
- teamName: '无常项目组',
- isTeamLeader: true,
- status: 'busy',
- idleDays: 0,
- recentOrders: 2,
- lastOrderDate: '2024-10-21',
- reviewDates: ['2024-10-26'],
- workload: 70,
- skills: ['空间设计', '软装', '项目管理'],
- isInStagnantProject: false,
- availableDates: [],
- groupId: 'team-2',
- groupName: '无常项目组',
- isLeader: true,
- currentProjects: 2
- }
- ]
- }
- ];
- // 提供Math对象给模板使用
- readonly Math = Math;
- // 状态颜色映射
- statusColors: Record<string, string> = {
- '待分配': '#FFAA00',
- '进行中': '#165DFF',
- '已完成': '#00B42A',
- '已暂停': '#FF7D00',
- '已延期': '#F53F3F',
- '已取消': '#86909C'
- };
- // 状态文本映射
- statusTexts: Record<string, string> = {
- '待分配': '待分配',
- '进行中': '进行中',
- '已完成': '已完成',
- '已暂停': '已暂停',
- '已延期': '已延期',
- '已取消': '已取消'
- };
- constructor(
- private dialog: MatDialog,
- private projectService: ProjectService,
- private projectAutoCaseService: ProjectAutoCaseService
- ) {}
- ngOnInit(): void {
- this.loadProjects();
- }
- async loadProjects(): Promise<void> {
- this.loading.set(true);
- try {
- const projects = await this.projectService.findProjects({
- limit: 100
- });
- const projectList: Project[] = projects.map(p => {
- const json = this.projectService.toJSON(p);
-
- // 获取原始阶段
- const rawStage = json.currentStage || json.stage || '订单分配';
-
- // 🔄 规范化阶段名称(统一为四大核心阶段)
- const normalizedStage = normalizeStage(rawStage);
-
- // 🔄 根据阶段自动判断状态(与组长端逻辑保持一致)
- const autoStatus = getProjectStatusByStage(rawStage, json.status);
-
- console.log(`📊 [项目管理] "${json.title}":`, {
- 客户: json.customerName,
- 负责人: json.assigneeName,
- 角色: json.assigneeRole,
- 原始阶段: rawStage,
- 规范化阶段: normalizedStage,
- 状态: `${json.status} → ${autoStatus}`
- });
-
- return {
- id: json.objectId,
- title: json.title || '未命名项目',
- customer: json.customerName || '未知客户',
- customerId: json.customerId,
- status: autoStatus, // 使用根据阶段自动判断的状态
- assignee: json.assigneeName || '未分配',
- assigneeId: json.assigneeId,
- createdAt: json.createdAt?.iso || json.createdAt,
- updatedAt: json.updatedAt?.iso || json.updatedAt,
- deadline: json.deadline,
- currentStage: normalizedStage // 使用规范化后的阶段名称
- };
- });
- this.projects.set(projectList);
- this.applyFilters();
- } catch (error) {
- console.error('加载项目列表失败:', error);
- this.projects.set([]);
- } finally {
- this.loading.set(false);
- }
- }
- applyFilters(): void {
- let result = [...this.projects()];
- // 搜索过滤
- if (this.searchTerm) {
- const term = this.searchTerm.toLowerCase();
- result = result.filter(project =>
- project.title.toLowerCase().includes(term) ||
- project.customer.toLowerCase().includes(term) ||
- project.assignee.toLowerCase().includes(term)
- );
- }
-
- // 状态过滤
- if (this.statusFilter) {
- result = result.filter(project => project.status === this.statusFilter);
- }
-
- // 排序 - 按更新时间或创建时间
- result.sort((a, b) => {
- if (this.sortColumn === 'updatedAt' || this.sortColumn === 'createdAt') {
- const dateA = a[this.sortColumn] ? new Date(a[this.sortColumn] as Date).getTime() : 0;
- const dateB = b[this.sortColumn] ? new Date(b[this.sortColumn] as Date).getTime() : 0;
- return this.sortDirection === 'asc' ? dateA - dateB : dateB - dateA;
- } else {
- const valueA = String((a as any)[this.sortColumn] || '').toLowerCase();
- const valueB = String((b as any)[this.sortColumn] || '').toLowerCase();
- return this.sortDirection === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
- }
- });
-
- this.filteredProjects.set(result);
- }
- onSearch(): void {
- this.applyFilters();
- }
- onStatusFilterChange(): void {
- this.applyFilters();
- }
- onSort(column: string): void {
- if (this.sortColumn === column) {
- this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
- } else {
- this.sortColumn = column;
- this.sortDirection = 'asc';
- }
- this.applyFilters();
- }
- // 简化的编辑对话框(只允许修改名字和分配组员)
- openEditDialog(project: Project): void {
- // TODO: 实现简单的编辑对话框,使用设计师分配组件
- // 暂时使用alert提示
- window?.fmode?.alert('编辑功能将在设计师分配组件对接完成后实现');
- }
- // 打开团队分配弹窗
- openTeamAssignmentModal(project: Project): void {
- this.selectedProject = project;
-
- // 加载项目团队数据
- this.projectTeams = this.mockProjectTeams;
-
- // 初始化当前团队分配信息
- this.currentTeamAssignment = {
- primaryTeamId: project.assigneeId || null,
- quotationAssignments: [],
- crossTeamCollaborators: []
- };
-
- // TODO: 从Parse Server加载报价项数据
- this.currentQuotationItems = [];
-
- this.showTeamAssignmentModal = true;
- console.log('打开团队分配弹窗:', project, '团队数据:', this.projectTeams);
- }
- // 关闭团队分配弹窗
- closeTeamAssignmentModal(): void {
- this.showTeamAssignmentModal = false;
- this.selectedProject = null;
- this.currentTeamAssignment = {
- primaryTeamId: null,
- quotationAssignments: [],
- crossTeamCollaborators: []
- };
- this.currentQuotationItems = [];
- }
- // 确认团队分配
- confirmTeamAssignment(event: any): void {
- console.log('确认团队分配:', event);
- // TODO: 保存团队分配数据到Parse Server
- this.closeTeamAssignmentModal();
- // 重新加载项目列表
- this.loadProjects();
- }
- formatCurrency(amount: number): string {
- return new Intl.NumberFormat('zh-CN', {
- style: 'currency',
- currency: 'CNY',
- minimumFractionDigits: 0
- }).format(amount);
- }
- get paginatedProjects(): Project[] {
- const startIndex = this.currentPage * this.pageSize;
- return this.filteredProjects().slice(startIndex, startIndex + this.pageSize);
- }
- get totalPages(): number {
- return Math.ceil(this.filteredProjects().length / this.pageSize);
- }
- // 项目状态统计计算属性
- get inProgressProjectsCount(): number {
- return this.projects().filter(p => p.status === '进行中').length;
- }
- get completedProjectsCount(): number {
- return this.projects().filter(p => p.status === '已完成').length;
- }
- get pendingProjectsCount(): number {
- return this.projects().filter(p => p.status === '待分配').length;
- }
- // 项目总数
- get totalProjectsCount(): number {
- return this.projects().length;
- }
- onPageChange(page: number): void {
- this.currentPage = page;
- }
- // 生成页码数组
- getPageNumbers(): number[] {
- const totalPages = this.totalPages;
- const currentPage = this.currentPage;
- const pageNumbers: number[] = [];
- // 简单实现:显示所有页码
- for (let i = 0; i < totalPages; i++) {
- pageNumbers.push(i);
- }
- return pageNumbers;
- }
- }
|