123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- import { Component, OnInit, Input } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { Router, ActivatedRoute, RouterModule } from '@angular/router';
- import { IonicModule } from '@ionic/angular';
- import { WxworkSDK, WxworkCorp, WxworkAuth } from 'fmode-ng/core';
- import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
- import { ProfileService } from '../../../../app/services/profile.service';
- import { ProjectBottomCardComponent } from '../../components/project-bottom-card/project-bottom-card.component';
- import { ProjectFilesModalComponent } from '../../components/project-files-modal/project-files-modal.component';
- import { ProjectMembersModalComponent } from '../../components/project-members-modal/project-members-modal.component';
- import { ProjectIssuesModalComponent } from '../../components/project-issues-modal/project-issues-modal.component';
- import { ProjectIssueService } from '../../services/project-issue.service';
- const Parse = FmodeParse.with('nova');
- /**
- * 项目详情核心组件
- *
- * 功能:
- * 1. 展示四阶段导航(订单分配、确认需求、交付执行、售后归档)
- * 2. 根据角色控制权限
- * 3. 子路由切换阶段内容
- * 4. 支持@Input和路由参数两种数据加载方式
- *
- * 路由:/wxwork/:cid/project/:projectId
- */
- @Component({
- selector: 'app-project-detail',
- standalone: true,
- imports: [CommonModule, IonicModule, RouterModule, ProjectBottomCardComponent, ProjectFilesModalComponent, ProjectMembersModalComponent, ProjectIssuesModalComponent],
- templateUrl: './project-detail.component.html',
- styleUrls: ['./project-detail.component.scss']
- })
- export class ProjectDetailComponent implements OnInit {
- // 输入参数(支持组件复用)
- @Input() project: FmodeObject | null = null;
- @Input() groupChat: FmodeObject | null = null;
- @Input() currentUser: FmodeObject | null = null;
- // 问题统计
- issueCount: number = 0;
- // 路由参数
- cid: string = '';
- projectId: string = '';
- groupId: string = '';
- profileId: string = '';
- chatId: string = ''; // 从企微进入时的 chat_id
- // 企微SDK
- wxwork: WxworkSDK | null = null;
- wecorp: WxworkCorp | null = null;
- wxAuth: WxworkAuth | null = null; // WxworkAuth 实例
- // 加载状态
- loading: boolean = true;
- error: string | null = null;
- // 项目数据
- contact: FmodeObject | null = null;
- assignee: FmodeObject | null = null;
- // 当前阶段
- currentStage: string = 'order'; // order | requirements | delivery | aftercare
- stages = [
- { id: 'order', name: '订单分配', icon: 'document-text-outline', number: 1 },
- { id: 'requirements', name: '确认需求', icon: 'checkmark-circle-outline', number: 2 },
- { id: 'delivery', name: '交付执行', icon: 'rocket-outline', number: 3 },
- { id: 'aftercare', name: '售后归档', icon: 'archive-outline', number: 4 }
- ];
- // 权限
- canEdit: boolean = false;
- canViewCustomerPhone: boolean = false;
- role: string = '';
- // 模态框状态
- showFilesModal: boolean = false;
- showMembersModal: boolean = false;
- showIssuesModal: boolean = false;
- constructor(
- private router: Router,
- private route: ActivatedRoute,
- private profileService: ProfileService,
- private issueService: ProjectIssueService
- ) {}
- async ngOnInit() {
- // 获取路由参数
- this.cid = this.route.snapshot.paramMap.get('cid') || '';
- this.projectId = this.route.snapshot.paramMap.get('projectId') || '';
- this.groupId = this.route.snapshot.queryParamMap.get('groupId') || '';
- this.profileId = this.route.snapshot.queryParamMap.get('profileId') || '';
- this.chatId = this.route.snapshot.queryParamMap.get('chatId') || '';
- // 监听路由变化
- this.route.firstChild?.url.subscribe((segments) => {
- if (segments.length > 0) {
- this.currentStage = segments[0].path;
- }
- });
- // 初始化企微授权(不阻塞页面加载)
- await this.initWxworkAuth();
- await this.loadData();
- }
- /**
- * 初始化企微授权(不阻塞页面)
- */
- async initWxworkAuth() {
- let cid = this.cid || localStorage.getItem("company") || "";
- this.wxAuth = new WxworkAuth({ cid: cid});
- this.wxwork = new WxworkSDK({ cid: cid, appId: 'crm' });
- this.wecorp = new WxworkCorp(cid);
- }
- /**
- * 加载数据
- */
- async loadData() {
- try {
- this.loading = true;
- // 2. 获取当前用户(优先从全局服务获取)
- if (!this.currentUser?.id) {
- this.currentUser = await this.wxAuth?.currentProfile();
- }
- console.log("777",this.currentUser)
- // 设置权限
- console.log(this.currentUser)
- this.role = this.currentUser?.get('roleName') || '';
- this.canEdit = ['客服', '组员', '组长', '管理员'].includes(this.role);
- this.canViewCustomerPhone = ['客服', '组长', '管理员'].includes(this.role);
- const companyId = this.currentUser?.get('company')?.id || localStorage?.getItem("company");
- // 3. 加载项目
- if (!this.project) {
- if (this.projectId) {
- // 通过 projectId 加载(从后台进入)
- const query = new Parse.Query('Project');
- query.include('contact', 'assignee','department','department.leader');
- this.project = await query.get(this.projectId);
- } else if (this.chatId) {
- // 通过 chat_id 查找项目(从企微群聊进入)
- if (companyId) {
- // 先查找 GroupChat
- const gcQuery = new Parse.Query('GroupChat');
- gcQuery.equalTo('chat_id', this.chatId);
- gcQuery.equalTo('company', companyId);
- let groupChat = await gcQuery.first();
- if (groupChat) {
- this.groupChat = groupChat;
- const projectPointer = groupChat.get('project');
- if (projectPointer) {
- const pQuery = new Parse.Query('Project');
- pQuery.include('contact', 'assignee','department','department.leader');
- this.project = await pQuery.get(projectPointer.id);
- }
- }
- if (!this.project) {
- throw new Error('该群聊尚未关联项目,请先在后台创建项目');
- }
- }
- }
- }
- if(!this.groupChat?.id){
- const gcQuery2 = new Parse.Query('GroupChat');
- gcQuery2.equalTo('project', this.projectId);
- gcQuery2.equalTo('company', companyId);
- this.groupChat = await gcQuery2.first();
- }
- if (!this.project) {
- throw new Error('无法加载项目信息');
- }
- this.contact = this.project.get('contact');
- this.assignee = this.project.get('assignee');
- // 更新问题计数
- try {
- if (this.project?.id) {
- this.issueService.seed(this.project.id!);
- const counts = this.issueService.getCounts(this.project.id!);
- this.issueCount = counts.total;
- }
- } catch (e) {
- console.warn('统计问题数量失败:', e);
- }
- // 4. 加载群聊(如果没有传入且有groupId)
- if (!this.groupChat && this.groupId) {
- try {
- const gcQuery = new Parse.Query('GroupChat');
- this.groupChat = await gcQuery.get(this.groupId);
- } catch (err) {
- console.warn('加载群聊失败:', err);
- }
- }
- // 5. 根据项目当前阶段设置默认路由
- const projectStage = this.project.get('currentStage');
- const stageMap: any = {
- '订单分配': 'order',
- '确认需求': 'requirements',
- '方案确认': 'requirements',
- '方案深化': 'requirements',
- '交付执行': 'delivery',
- '建模': 'delivery',
- '软装': 'delivery',
- '渲染': 'delivery',
- '后期': 'delivery',
- '尾款结算': 'aftercare',
- '客户评价': 'aftercare',
- '投诉处理': 'aftercare'
- };
- const targetStage = stageMap[projectStage] || 'order';
- // 如果当前没有子路由,跳转到对应阶段
- if (!this.route.firstChild) {
- this.router.navigate([targetStage], { relativeTo: this.route, replaceUrl: true });
- }
- } catch (err: any) {
- console.error('加载失败:', err);
- this.error = err.message || '加载失败';
- } finally {
- this.loading = false;
- }
- }
- /**
- * 切换阶段
- */
- switchStage(stageId: string) {
- this.currentStage = stageId;
- this.router.navigate([stageId], { relativeTo: this.route });
- }
- /**
- * 获取阶段状态
- */
- getStageStatus(stageId: string): 'completed' | 'active' | 'pending' {
- const projectStage = this.project?.get('currentStage') || '';
- const stageOrder = ['订单分配', '确认需求', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价'];
- const currentIndex = stageOrder.indexOf(projectStage);
- const stageIndexMap: any = {
- 'order': 0,
- 'requirements': 1,
- 'delivery': 3,
- 'aftercare': 6
- };
- const targetIndex = stageIndexMap[stageId];
- if (currentIndex > targetIndex) {
- return 'completed';
- } else if (this.currentStage === stageId) {
- return 'active';
- } else {
- return 'pending';
- }
- }
- /**
- * 返回
- */
- goBack() {
- let ua = navigator.userAgent.toLowerCase();
- let isWeixin = ua.indexOf("micromessenger") != -1;
- if(isWeixin){
- this.router.navigate(['/wxwork', this.cid, 'project-loader']);
- }else{
- history.back();
- }
- }
- /**
- * 更新项目阶段
- */
- async updateProjectStage(stage: string) {
- if (!this.project || !this.canEdit) return;
- try {
- this.project.set('currentStage', stage);
- await this.project.save();
- // 添加阶段历史
- const data = this.project.get('data') || {};
- const stageHistory = data.stageHistory || [];
- stageHistory.push({
- stage,
- startTime: new Date(),
- status: 'current',
- operator: {
- id: this.currentUser!.id,
- name: this.currentUser!.get('name'),
- role: this.role
- }
- });
- this.project.set('data', { ...data, stageHistory });
- await this.project.save();
- } catch (err) {
- console.error('更新阶段失败:', err);
- alert('更新失败');
- }
- }
- /**
- * 发送企微消息
- */
- async sendWxMessage(message: string) {
- if (!this.groupChat || !this.wecorp) return;
- try {
- const chatId = this.groupChat.get('chat_id');
- await this.wecorp.appchat.sendText(chatId, message);
- } catch (err) {
- console.error('发送消息失败:', err);
- }
- }
- /**
- * 选择客户(从群聊成员中选择外部联系人)
- */
- async selectCustomer() {
- console.log(this.canEdit, this.groupChat)
- if (!this.groupChat) return;
- try {
- const memberList = this.groupChat.get('member_list') || [];
- const externalMembers = memberList.filter((m: any) => m.type === 2);
- if (externalMembers.length === 0) {
- alert('当前群聊中没有外部联系人');
- return;
- }
- console.log(externalMembers)
- // 简单实现:选择第一个外部联系人
- // TODO: 实现选择器UI
- const selectedMember = externalMembers[0];
- await this.setCustomerFromMember(selectedMember);
- } catch (err) {
- console.error('选择客户失败:', err);
- alert('选择客户失败');
- }
- }
- /**
- * 从群成员设置客户
- */
- async setCustomerFromMember(member: any) {
- if (!this.wecorp) return;
- try {
- const companyId = this.currentUser?.get('company')?.id || localStorage.getItem("company");
- if (!companyId) throw new Error('无法获取企业信息');
- // 1. 查询是否已存在 ContactInfo
- const query = new Parse.Query('ContactInfo');
- query.equalTo('external_userid', member.userid);
- query.equalTo('company', companyId);
- let contactInfo = await query.first();
- // 2. 如果不存在,通过企微API获取并创建
- if (!contactInfo) {
- contactInfo = new Parse.Object("ContactInfo");
- }
- const externalContactData = await this.wecorp.externalContact.get(member.userid);
- console.log("externalContactData",externalContactData)
- const ContactInfo = Parse.Object.extend('ContactInfo');
- contactInfo.set('name', externalContactData.name);
- contactInfo.set('external_userid', member.userid);
- const company = new Parse.Object('Company');
- company.id = companyId;
- const companyPointer = company.toPointer();
- contactInfo.set('company', companyPointer);
- contactInfo.set('data', externalContactData);
- await contactInfo.save();
- // 3. 设置为项目客户
- if (this.project) {
- this.project.set('contact', contactInfo.toPointer());
- await this.project.save();
- this.contact = contactInfo;
- alert('客户设置成功');
- }
- } catch (err) {
- console.error('设置客户失败:', err);
- throw err;
- }
- }
- /**
- * 显示文件模态框
- */
- showFiles() {
- this.showFilesModal = true;
- }
- /**
- * 显示成员模态框
- */
- showMembers() {
- this.showMembersModal = true;
- }
- /** 显示问题模态框 */
- showIssues() {
- this.showIssuesModal = true;
- }
- /**
- * 关闭文件模态框
- */
- closeFilesModal() {
- this.showFilesModal = false;
- }
- /**
- * 关闭成员模态框
- */
- closeMembersModal() {
- this.showMembersModal = false;
- }
- /** 关闭问题模态框 */
- closeIssuesModal() {
- this.showIssuesModal = false;
- // 关闭后更新计数(避免列表操作后的计数不一致)
- if (this.project?.id) {
- const counts = this.issueService.getCounts(this.project.id!);
- this.issueCount = counts.total;
- }
- }
- }
|