| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- import { Component, Input, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { FormsModule } from '@angular/forms';
- import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
- import { ProductSpaceService, Project } from '../../services/product-space.service';
- import { DesignerTeamAssignmentModalComponent } from '../../../../app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component';
- import type {
- ProjectTeam,
- Designer,
- SpaceScene,
- DesignerAssignmentResult
- } from '../../../../app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component';
- const Parse = FmodeParse.with('nova');
- @Component({
- selector: 'app-team-assign',
- standalone: true,
- imports: [CommonModule, FormsModule, DesignerTeamAssignmentModalComponent],
- templateUrl: './team-assign.component.html',
- styleUrls: ['./team-assign.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush
- })
- export class TeamAssignComponent implements OnInit {
- @Input() project: FmodeObject | null = null;
- @Input() canEdit: boolean = true; // 可选:未传入时默认允许编辑
- @Input() currentUser: FmodeObject | null = null; // 可选:未传入时为 null
- // 项目组(Department)列表
- departments: FmodeObject[] = [];
- selectedDepartment: FmodeObject | null = null;
- // 项目组成员(Profile)列表
- departmentMembers: FmodeObject[] = [];
- selectedDesigner: FmodeObject | null = null;
- // 已分配的项目团队成员
- projectTeams: FmodeObject[] = [];
- // 设计师分配对话框(旧的,保留以兼容)
- showAssignDialog: boolean = false;
- assigningDesigner: FmodeObject | null = null;
- selectedSpaces: string[] = [];
- editingTeam: FmodeObject | null = null; // 当前正在编辑的团队对象
- // 统一的设计师分配弹窗
- showDesignerModal: boolean = false;
- modalProjectTeams: ProjectTeam[] = [];
- modalSpaceScenes: SpaceScene[] = [];
- modalSelectedTeamId: string = '';
- // 加载状态
- loadingMembers: boolean = false;
- loadingTeams: boolean = false;
- loadingSpaces: boolean = false;
- saving: boolean = false;
- // 空间数据
- projectSpaces: Project[] = [];
- constructor(
- private productSpaceService: ProductSpaceService,
- private cdr: ChangeDetectorRef
- ) {}
- async ngOnInit() {
- await this.loadData();
- }
- async loadData() {
- if (!this.project) return;
- try {
- // 初始化已选择的项目组与设计师(若项目已有)
- const department = this.project.get('department');
- if (department) {
- this.selectedDepartment = department;
- await this.loadDepartmentMembers(department);
- }
- const assignee = this.project.get('assignee');
- if (assignee) {
- this.selectedDesigner = assignee;
- }
- // 加载项目组列表
- const deptQuery = new Parse.Query('Department');
- deptQuery.include('leader');
- deptQuery.equalTo('type', 'project');
- deptQuery.equalTo('company', localStorage.getItem('company'));
- deptQuery.notEqualTo('isDeleted', true);
- deptQuery.ascending('name');
- this.departments = await deptQuery.find();
- console.log("this.departments",this.departments)
- // 加载项目团队
- await this.loadProjectTeams();
- // 加载项目空间
- await this.loadProjectSpaces();
- } catch (err) {
- console.error('加载团队分配数据失败:', err);
- } finally {
- this.cdr.markForCheck();
- }
- }
- async loadProjectSpaces(): Promise<void> {
- if (!this.project) return;
- try {
- this.loadingSpaces = true;
- const projectId = this.project.id || '';
- this.projectSpaces = await this.productSpaceService.getProjectProductSpaces(projectId);
- } catch (err) {
- console.error('加载项目空间失败:', err);
- } finally {
- this.loadingSpaces = false;
- this.cdr.markForCheck();
- }
- }
- async loadProjectTeams() {
- if (!this.project) return;
- try {
- this.loadingTeams = true;
- const query = new Parse.Query('ProjectTeam');
- query.equalTo('project', this.project.toPointer());
- query.include('profile');
- query.notEqualTo('isDeleted', true);
- this.projectTeams = await query.find();
- } catch (err) {
- console.error('加载项目团队失败:', err);
- } finally {
- this.loadingTeams = false;
- }
- }
- async selectDepartment(department: FmodeObject) {
- this.selectedDepartment = department;
- this.selectedDesigner = null;
- this.departmentMembers = [];
- // ✅ 自动设置组长为项目负责人
- const leader = department.get('leader');
- if (leader && this.project) {
- try {
- // 更新项目的assignee字段为组长
- this.project.set('assignee', leader);
- this.project.set('department', department);
- await this.project.save();
- console.log('✅ 项目负责人已设置为组长:', leader.get('name'));
-
- // 触发界面更新
- this.cdr.markForCheck();
- } catch (error) {
- console.error('❌ 设置项目负责人失败:', error);
- }
- }
- await this.loadDepartmentMembers(department);
- }
- async loadDepartmentMembers(department: FmodeObject) {
- const departmentId = department?.id;
- if (!departmentId) return [];
- try {
- this.loadingMembers = true;
- const query = new Parse.Query('Profile');
- query.equalTo('department', departmentId);
- query.equalTo('roleName', '组员');
- query.notEqualTo('isDeleted', true);
- query.ascending('name');
- this.departmentMembers = await query.find();
- // 将组长置顶展示
- const leader = department?.get('leader');
- if (leader) {
- this.departmentMembers.unshift(leader);
- }
- this.loadingMembers = false;
- } catch (err) {
- console.error('加载项目组成员失败:', err);
- } finally {
- this.loadingMembers = false;
- }
- this.cdr.detectChanges()
- return this.departmentMembers;
- }
- selectDesigner(designer: FmodeObject) {
- // 检查是否已分配
- const isAssigned = this.projectTeams.some(team => team.get('profile')?.id === designer.id);
- if (isAssigned) {
- alert('该设计师已分配到此项目');
- return;
- }
- this.assigningDesigner = designer;
- this.selectedSpaces = [];
- // 如果只有一个空间,默认选中
- if (this.projectSpaces.length === 1) {
- const only = this.projectSpaces[0];
- this.selectedSpaces = [only.name];
- }
- this.showAssignDialog = true;
- }
- editAssignedDesigner(team: FmodeObject) {
- const designer = team.get('profile');
- if (!designer) return;
- this.assigningDesigner = designer;
- this.editingTeam = team;
- const currentSpaces = team.get('data')?.spaces || [];
- this.selectedSpaces = [...currentSpaces];
- this.showAssignDialog = true;
- }
- toggleSpaceSelection(spaceName: string) {
- const index = this.selectedSpaces.indexOf(spaceName);
- if (index > -1) {
- this.selectedSpaces.splice(index, 1);
- } else {
- this.selectedSpaces.push(spaceName);
- }
- }
- async confirmAssignDesigner() {
- if (!this.assigningDesigner || !this.project) return;
- if (this.selectedSpaces.length === 0) {
- alert('请至少选择一个空间场景');
- return;
- }
- try {
- this.saving = true;
- if (this.editingTeam) {
- // 更新现有团队成员的空间分配
- const data = this.editingTeam.get('data') || {};
- data.spaces = this.selectedSpaces;
- data.updatedAt = new Date();
- data.updatedBy = this.currentUser?.id;
- this.editingTeam.set('data', data);
- await this.editingTeam.save();
- alert('更新成功');
- } else {
- // 创建新的 ProjectTeam
- const ProjectTeam = Parse.Object.extend('ProjectTeam');
- const team = new ProjectTeam();
- team.set('project', this.project.toPointer());
- team.set('profile', this.assigningDesigner.toPointer());
- team.set('department', this.assigningDesigner.get("department"));
- team.set('role', '组员');
- team.set('data', {
- spaces: this.selectedSpaces,
- assignedAt: new Date(),
- assignedBy: this.currentUser?.id
- });
- await team.save();
- // 加入群聊(静默执行)
- await this.addMemberToGroupChat(this.assigningDesigner.get('userId'));
- alert('分配成功');
- }
- await this.loadProjectTeams();
- this.showAssignDialog = false;
- this.assigningDesigner = null;
- this.selectedSpaces = [];
- this.editingTeam = null;
- } catch (err) {
- console.error(this.editingTeam ? '更新失败:' : '分配设计师失败:', err);
- alert(this.editingTeam ? '更新失败' : '分配失败');
- } finally {
- this.saving = false;
- }
- }
- cancelAssignDialog() {
- this.showAssignDialog = false;
- this.assigningDesigner = null;
- this.selectedSpaces = [];
- this.editingTeam = null;
- }
- async addMemberToGroupChat(userId: string) {
- if (!userId) return;
- try {
- const groupChat = (this as any).groupChat;
- if (!groupChat) return;
- const chatId = groupChat.get('chat_id');
- if (!chatId) return;
- if (typeof (window as any).ww !== 'undefined') {
- await (window as any).ww.updateEnterpriseChat({
- chatId: chatId,
- userIdsToAdd: [userId]
- });
- }
- } catch (err) {
- console.warn('添加群成员失败:', err);
- }
- }
- async confirmDeleteMember() {
- if (!this.editingTeam) return;
- const ok = window.confirm('确定要删除该成员的项目分配吗?');
- if (!ok) return;
- try {
- this.saving = true;
- // 软删除 ProjectTeam
- this.editingTeam.set('isDeleted', true);
- const data = this.editingTeam.get('data') || {};
- data.deletedAt = new Date();
- data.deletedBy = this.currentUser?.id;
- this.editingTeam.set('data', data);
- await this.editingTeam.save();
- // 从群聊移除(静默尝试)
- const profile = this.editingTeam.get('profile');
- const userId = profile?.get?.('userId');
- if (userId) {
- await this.removeMemberFromGroupChat(userId);
- }
- alert('成员已删除');
- // 刷新列表并收起弹窗
- await this.loadProjectTeams();
- this.showAssignDialog = false;
- this.assigningDesigner = null;
- this.selectedSpaces = [];
- this.editingTeam = null;
- } catch (err) {
- console.error('删除成员失败:', err);
- alert('删除失败');
- } finally {
- this.saving = false;
- this.cdr.markForCheck();
- }
- }
- async removeMemberFromGroupChat(userId: string) {
- if (!userId) return;
- try {
- const groupChat = (this as any).groupChat;
- if (!groupChat) return;
- const chatId = groupChat.get('chat_id');
- if (!chatId) return;
- if (typeof (window as any).ww !== 'undefined') {
- await (window as any).ww.updateEnterpriseChat({
- chatId: chatId,
- userIdsToRemove: [userId]
- });
- }
- } catch (err) {
- console.warn('移除群成员失败或不支持:', err);
- }
- }
- getMemberSpaces(team: FmodeObject): string {
- const spaces = team.get('data')?.spaces || [];
- return spaces.join('、') || '未分配';
- }
- getDesignerWorkload(designer: FmodeObject): string {
- return '3个项目';
- }
- /**
- * 移除团队成员
- */
- async removeMember(team: FmodeObject) {
- if (!confirm(`确定要移除 ${team.get('profile')?.get('name')} 吗?`)) {
- return;
- }
- try {
- this.saving = true;
-
- // 删除ProjectTeam记录
- await team.destroy();
-
- // 重新加载项目团队
- await this.loadProjectTeams();
-
- this.cdr.markForCheck();
- } catch (err) {
- console.error('移除成员失败:', err);
- alert('移除失败');
- } finally {
- this.saving = false;
- this.cdr.markForCheck();
- }
- }
- // ===== 统一设计师分配弹窗相关方法 =====
- /**
- * 打开统一的设计师分配弹窗
- */
- openDesignerAssignmentModal() {
- // 设置当前选中的项目组
- // 项目组数据和空间数据都由弹窗自己加载真实数据
- this.modalSelectedTeamId = this.selectedDepartment?.id || '';
-
- this.showDesignerModal = true;
- this.cdr.markForCheck();
- }
- /**
- * 将Parse的Department对象转换为ProjectTeam
- */
- private convertToProjectTeam(dept: FmodeObject): ProjectTeam {
- const leader = dept.get('leader');
- const members = this.departments.find(d => d.id === dept.id)?.get('members') || [];
-
- return {
- id: dept.id,
- name: dept.get('name') || '',
- leaderId: leader?.id || '',
- leaderName: leader?.get('name') || '未指定',
- description: dept.get('description') || '',
- members: this.convertDepartmentMembers(dept.id)
- };
- }
- /**
- * 转换项目组成员为Designer格式
- */
- private convertDepartmentMembers(deptId: string): Designer[] {
- // 如果是当前选中的项目组,使用已加载的成员数据
- if (this.selectedDepartment?.id === deptId && this.departmentMembers.length > 0) {
- return this.departmentMembers
- .filter(member => member?.get)
- .map(member => this.convertToDesigner(member, deptId));
- }
- return [];
- }
- /**
- * 将Parse的Profile对象转换为Designer
- */
- private convertToDesigner(profile: FmodeObject, teamId: string): Designer {
- const data = profile.get('data') || {};
- const dept = this.departments.find(d => d.id === teamId);
-
- return {
- id: profile.id,
- name: profile.get('name') || '',
- avatar: data.avatar,
- teamId: teamId,
- teamName: dept?.get('name') || '',
- isTeamLeader: false, // 可以根据实际情况判断
- status: 'idle', // 默认空闲,可以根据实际工作量判断
- idleDays: 0,
- recentOrders: 0,
- lastOrderDate: undefined,
- reviewDates: [],
- workload: 0,
- skills: data.skills || [],
- isInStagnantProject: false,
- availableDates: [],
- groupId: teamId,
- groupName: dept?.get('name') || '',
- isLeader: false,
- currentProjects: 0
- };
- }
- /**
- * 关闭设计师分配弹窗
- */
- closeDesignerModal() {
- this.showDesignerModal = false;
- this.cdr.markForCheck();
- }
- /**
- * 确认设计师分配
- */
- async handleDesignerAssignment(result: DesignerAssignmentResult) {
- console.log('设计师分配结果:', result);
-
- try {
- this.saving = true;
- // 保存选中的设计师到项目团队
- for (const designer of result.selectedDesigners) {
- // 查找该设计师负责的空间
- const spaceAssignment = result.spaceAssignments.find(
- sa => sa.designerId === designer.id
- );
-
- await this.saveDesignerToTeam(designer, spaceAssignment?.spaceIds || []);
- }
- // 保存跨组合作者
- for (const collaborator of result.crossTeamCollaborators) {
- const spaceAssignment = result.spaceAssignments.find(
- sa => sa.designerId === collaborator.id
- );
-
- await this.saveDesignerToTeam(collaborator, spaceAssignment?.spaceIds || [], true);
- }
- // 重新加载项目团队数据
- await this.loadProjectTeams();
-
- // 关闭弹窗
- this.closeDesignerModal();
-
- alert('设计师分配成功!');
- } catch (err) {
- console.error('保存设计师分配失败:', err);
- alert('保存失败,请重试');
- } finally {
- this.saving = false;
- this.cdr.markForCheck();
- }
- }
- /**
- * 保存设计师到项目团队
- */
- private async saveDesignerToTeam(
- designer: Designer,
- spaceIds: string[],
- isCrossTeam: boolean = false
- ): Promise<void> {
- if (!this.project) return;
- // 查找对应的Profile对象
- const profileQuery = new Parse.Query('Profile');
- profileQuery.equalTo('objectId', designer.id);
- const profile = await profileQuery.first();
- if (!profile) {
- console.error('未找到设计师Profile:', designer.id);
- return;
- }
- // 查找是否已存在团队记录
- const existingTeamQuery = new Parse.Query('ProjectTeam');
- existingTeamQuery.equalTo('project', this.project);
- existingTeamQuery.equalTo('profile', profile);
- let teamObj = await existingTeamQuery.first();
- if (!teamObj) {
- // 创建新的团队记录
- const ProjectTeam = Parse.Object.extend('ProjectTeam');
- teamObj = new ProjectTeam();
- teamObj.set('project', this.project);
- teamObj.set('profile', profile);
- teamObj.set('role', 'designer');
- }
- // 转换空间ID为空间名称
- const spaceNames = spaceIds.map(id => {
- const space = this.projectSpaces.find(s => s.id === id);
- return space?.name || '';
- }).filter(name => name !== '');
- // 保存空间分配信息
- teamObj.set('data', {
- ...teamObj.get('data'),
- spaces: spaceNames,
- isCrossTeam: isCrossTeam,
- assignedAt: new Date().toISOString()
- });
- await teamObj.save();
- }
- }
|