|
|
@@ -8,6 +8,10 @@ import { ProjectFileService } from '../../../services/project-file.service';
|
|
|
import { ProductSpaceService, Project } from '../../../services/product-space.service';
|
|
|
import { WxworkAuth } from 'fmode-ng/social';
|
|
|
import { DragUploadModalComponent, UploadResult } from '../../../components/drag-upload-modal/drag-upload-modal.component';
|
|
|
+import { RevisionTaskModalComponent } from '../../../components/revision-task-modal/revision-task-modal.component';
|
|
|
+import { RevisionTaskListComponent } from '../../../components/revision-task-list/revision-task-list.component';
|
|
|
+import { RevisionTaskService } from '../../../../../app/pages/services/revision-task.service';
|
|
|
+import { DeliveryMessageService, MESSAGE_TEMPLATES } from '../../../../../app/pages/services/delivery-message.service';
|
|
|
import { ImageAnalysisService } from '../../../services/image-analysis.service';
|
|
|
import { PhaseDeadlines, PhaseName } from '../../../../../app/models/project-phase.model';
|
|
|
import { ensurePhaseDeadlines, mapDeliveryTypeToPhase, markPhaseStatus, updatePhaseOnSubmission } from '../../../../../app/utils/phase-deadline.utils';
|
|
|
@@ -77,7 +81,7 @@ interface DeliveryFile {
|
|
|
@Component({
|
|
|
selector: 'app-stage-delivery',
|
|
|
standalone: true,
|
|
|
- imports: [CommonModule, FormsModule, DragUploadModalComponent],
|
|
|
+ imports: [CommonModule, FormsModule, DragUploadModalComponent, RevisionTaskModalComponent, RevisionTaskListComponent],
|
|
|
templateUrl: './stage-delivery-new.component.html',
|
|
|
styleUrls: ['./stage-delivery-new.component.scss'],
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
|
@@ -110,6 +114,30 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
// 当前选中的空间和阶段(用于查看图片)
|
|
|
selectedSpaceId: string = '';
|
|
|
selectedStageType: string = '';
|
|
|
+
|
|
|
+ // 改图工单相关
|
|
|
+ showRevisionTaskModal: boolean = false;
|
|
|
+ showRevisionTaskList: boolean = false;
|
|
|
+ revisionTaskCount: number = 0;
|
|
|
+
|
|
|
+ // 缓存的空间和阶段列表(避免频繁创建新数组)
|
|
|
+ cachedAvailableSpaces: Array<{ id: string; name: string }> = [];
|
|
|
+ cachedAvailableStages: Array<{ id: string; name: string }> = [];
|
|
|
+ cachedRevisionSpaces: Array<{ id: string; name: string; selected: boolean }> = [];
|
|
|
+
|
|
|
+ // 消息发送相关
|
|
|
+ showMessageModal: boolean = false;
|
|
|
+ messageModalConfig: {
|
|
|
+ spaceId: string;
|
|
|
+ spaceName: string;
|
|
|
+ stage: string;
|
|
|
+ stageName: string;
|
|
|
+ imageUrls: string[];
|
|
|
+ } | null = null;
|
|
|
+ selectedTemplate: string = '';
|
|
|
+ customMessage: string = '';
|
|
|
+ sendingMessage: boolean = false;
|
|
|
+ messageTemplates = MESSAGE_TEMPLATES;
|
|
|
|
|
|
// 交付类型定义
|
|
|
deliveryTypes = [
|
|
|
@@ -230,8 +258,12 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
// 视图上下文:管理员后台查看时隐藏操作按钮
|
|
|
isAdminView: boolean = false;
|
|
|
|
|
|
- // 状态自动刷新定时器
|
|
|
+ // 定时刷新器
|
|
|
private statusRefreshTimer: any = null;
|
|
|
+ private refreshInterval = 60000; // 每60秒刷新一次
|
|
|
+
|
|
|
+ // 产品更新事件监听器
|
|
|
+ private productUpdateListener: any = null;
|
|
|
|
|
|
// 图片占位(base64 SVG,避免外链失败)
|
|
|
private readonly fallbackImageDataUrl: string =
|
|
|
@@ -250,7 +282,9 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
private cdr: ChangeDetectorRef,
|
|
|
private projectFileService: ProjectFileService,
|
|
|
private productSpaceService: ProductSpaceService,
|
|
|
- private imageAnalysisService: ImageAnalysisService
|
|
|
+ private imageAnalysisService: ImageAnalysisService,
|
|
|
+ private revisionTaskService: RevisionTaskService,
|
|
|
+ public deliveryMessageService: DeliveryMessageService
|
|
|
) {}
|
|
|
|
|
|
async ngOnInit() {
|
|
|
@@ -274,6 +308,9 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
});
|
|
|
|
|
|
await this.loadData();
|
|
|
+
|
|
|
+ // 🔥 设置产品更新监听器
|
|
|
+ this.setupProductUpdateListener();
|
|
|
|
|
|
// 周期性刷新交付文件以同步组长端审批状态
|
|
|
if (!this.statusRefreshTimer) {
|
|
|
@@ -288,6 +325,12 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
clearInterval(this.statusRefreshTimer);
|
|
|
this.statusRefreshTimer = null;
|
|
|
}
|
|
|
+
|
|
|
+ // 🔥 清理产品更新监听器
|
|
|
+ if (this.productUpdateListener) {
|
|
|
+ document.removeEventListener('product-spaces-updated', this.productUpdateListener);
|
|
|
+ console.log('🧹 [交付执行] 已清理产品更新监听器');
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -418,6 +461,15 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
// 🔥 关键:初始化项目的 currentStage 字段(如果是交付执行阶段但没有具体子阶段)
|
|
|
await this.ensureDeliveryStageInitialized();
|
|
|
+
|
|
|
+ // 🔥 统一 currentStage 为"交付执行"
|
|
|
+ await this.unifyDeliveryStageForOldData();
|
|
|
+
|
|
|
+ // 加载改图工单数量
|
|
|
+ await this.loadRevisionTaskCount();
|
|
|
+
|
|
|
+ // 🔥 更新缓存列表(避免频繁创建新数组)
|
|
|
+ this.updateCachedLists();
|
|
|
}
|
|
|
|
|
|
this.cdr.markForCheck();
|
|
|
@@ -431,20 +483,37 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 🔥 确保交付执行阶段的 currentStage 已正确初始化
|
|
|
- * 如果项目处于"交付执行"阶段但没有具体的子阶段名称,则设置为第一个未完成的子阶段
|
|
|
+ * 🔥 设置产品更新监听器
|
|
|
+ */
|
|
|
+ private setupProductUpdateListener(): void {
|
|
|
+ this.productUpdateListener = (event: any) => {
|
|
|
+ const detail = event.detail || {};
|
|
|
+ if (detail.projectId === this.projectId) {
|
|
|
+ console.log(`🔄 [交付执行] 检测到产品${detail.action === 'add' ? '添加' : detail.action === 'edit' ? '编辑' : '删除'}事件,重新加载空间...`);
|
|
|
+ this.loadProjectProducts().then(() => {
|
|
|
+ this.syncProductsWithQuotation();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ document.addEventListener('product-spaces-updated', this.productUpdateListener);
|
|
|
+ console.log('✅ [交付执行] 已设置产品更新监听器');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 🔥 确保交付执行阶段的 deliverySubStage 已正确初始化
|
|
|
+ * currentStage 统一保持为"交付执行",子阶段信息存储在 data.deliverySubStage
|
|
|
*/
|
|
|
async ensureDeliveryStageInitialized(): Promise<void> {
|
|
|
if (!this.project) return;
|
|
|
|
|
|
const currentStage = this.project.get('currentStage');
|
|
|
- const validDeliveryStages = ['白模', '软装', '渲染', '后期'];
|
|
|
+ const data = this.project.get('data') || {};
|
|
|
|
|
|
- // 如果 currentStage 是"交付执行"或其他非具体子阶段,需要初始化
|
|
|
- if (currentStage === '交付执行' || !validDeliveryStages.includes(currentStage)) {
|
|
|
- console.log('🔥 检测到项目处于交付执行阶段但未设置具体子阶段,开始初始化...');
|
|
|
+ // 如果 currentStage 是"交付执行"且没有 deliverySubStage,需要初始化子阶段
|
|
|
+ if (currentStage === '交付执行' && !data.deliverySubStage) {
|
|
|
+ console.log('🔥 初始化交付执行子阶段信息...');
|
|
|
|
|
|
- const data = this.project.get('data') || {};
|
|
|
const deliveryStageStatus = data.deliveryStageStatus || {};
|
|
|
|
|
|
// 找到第一个未完成的子阶段
|
|
|
@@ -456,7 +525,7 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
'post_process': '后期'
|
|
|
};
|
|
|
|
|
|
- let targetStage = '白模'; // 默认从白模开始
|
|
|
+ let targetSubStage = '白模'; // 默认从白模开始
|
|
|
|
|
|
for (const stageId of stageOrder) {
|
|
|
const stageKey = stageId.replace('_', '');
|
|
|
@@ -464,22 +533,43 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
if (status !== 'approved') {
|
|
|
// 找到第一个未通过的阶段
|
|
|
- targetStage = stageNameMap[stageId];
|
|
|
+ targetSubStage = stageNameMap[stageId];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- console.log(`🔥 设置 currentStage 为: ${targetStage}`);
|
|
|
- this.project.set('currentStage', targetStage);
|
|
|
+ console.log(`🔥 设置 deliverySubStage 为: ${targetSubStage}`);
|
|
|
+ data.deliverySubStage = targetSubStage;
|
|
|
+ this.project.set('data', data);
|
|
|
+ // currentStage 保持为"交付执行",不修改
|
|
|
|
|
|
try {
|
|
|
await this.project.save();
|
|
|
- console.log('✅ currentStage 初始化成功');
|
|
|
+ console.log('✅ deliverySubStage 初始化成功');
|
|
|
} catch (e) {
|
|
|
- console.error('❌ currentStage 初始化失败:', e);
|
|
|
+ console.error('❌ deliverySubStage 初始化失败:', e);
|
|
|
}
|
|
|
} else {
|
|
|
- console.log(`✅ currentStage 已正确设置为: ${currentStage}`);
|
|
|
+ console.log(`✅ 交付执行阶段已正确初始化`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async unifyDeliveryStageForOldData(): Promise<void> {
|
|
|
+ if (!this.project) return;
|
|
|
+ const currentStage = this.project.get('currentStage');
|
|
|
+ const validDeliveryStages = ['白模', '软装', '渲染', '后期'];
|
|
|
+ if (validDeliveryStages.includes(currentStage)) {
|
|
|
+ console.log(`统一阶段: "${currentStage}" → "交付执行"`);
|
|
|
+ const data = this.project.get('data') || {};
|
|
|
+ data.deliverySubStage = currentStage;
|
|
|
+ this.project.set('currentStage', '交付执行');
|
|
|
+ this.project.set('data', data);
|
|
|
+ try {
|
|
|
+ await this.project.save();
|
|
|
+ console.log('阶段已统一为"交付执行"');
|
|
|
+ } catch (e) {
|
|
|
+ console.error('统一阶段失败:', e);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1695,19 +1785,21 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
this.project.set('currentStage', '尾款结算');
|
|
|
data.deliveryCompletedAt = now;
|
|
|
data.deliveryCompletedBy = this.currentUser.get('name');
|
|
|
+ delete data.deliverySubStage;
|
|
|
console.log('✅ 所有交付阶段已完成,推进到尾款结算');
|
|
|
} else {
|
|
|
- // 🔥 当前阶段通过后,更新 currentStage 为下一个阶段的名称
|
|
|
+ // 保持 currentStage 为"交付执行",更新 deliverySubStage 为下一个子阶段
|
|
|
const nextStage = this.getNextStage(currentType);
|
|
|
if (nextStage) {
|
|
|
- const nextStageName = stageNameMap[nextStage] || '交付执行';
|
|
|
- this.project.set('currentStage', nextStageName);
|
|
|
- console.log(`🔄 当前阶段审批通过,更新 currentStage 为: ${nextStageName}`);
|
|
|
+ const nextStageName = stageNameMap[nextStage] || '白模';
|
|
|
+ data.deliverySubStage = nextStageName;
|
|
|
+ this.project.set('currentStage', '交付执行');
|
|
|
+ console.log(`当前阶段审批通过,currentStage保持为"交付执行",deliverySubStage更新为: ${nextStageName}`);
|
|
|
} else {
|
|
|
- // 如果没有下一阶段(理论上不应该发生),设置为当前阶段名称
|
|
|
- const currentStageName = stageNameMap[currentType] || '交付执行';
|
|
|
- this.project.set('currentStage', currentStageName);
|
|
|
- console.log(`🔄 当前阶段审批通过,保持 currentStage 为: ${currentStageName}`);
|
|
|
+ const currentStageName = stageNameMap[currentType] || '白模';
|
|
|
+ data.deliverySubStage = currentStageName;
|
|
|
+ this.project.set('currentStage', '交付执行');
|
|
|
+ console.log(`当前阶段审批通过,currentStage保持为"交付执行",deliverySubStage保持为: ${currentStageName}`);
|
|
|
}
|
|
|
}
|
|
|
data.deliveryFileCount = this.getTotalFileCount();
|
|
|
@@ -2238,6 +2330,9 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
this.cdr.markForCheck();
|
|
|
|
|
|
+ // 🔥 关键:检查是否所有空间都已确认,如果是则自动进入售后归档
|
|
|
+ await this.checkAndAutoCompleteDelivery();
|
|
|
+
|
|
|
} catch (error) {
|
|
|
console.error('❌ 确认空间失败:', error);
|
|
|
window?.fmode?.alert?.('确认失败,请重试');
|
|
|
@@ -2247,6 +2342,124 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 🔥 检查并自动完成交付执行(当所有空间都已确认时)
|
|
|
+ */
|
|
|
+ private async checkAndAutoCompleteDelivery(): Promise<void> {
|
|
|
+ if (!this.project || !this.currentUser) return;
|
|
|
+
|
|
|
+ // 检查是否所有空间都已确认
|
|
|
+ const allSpacesConfirmed = this.projectProducts.every(space => this.isSpaceConfirmed(space.id));
|
|
|
+
|
|
|
+ if (!allSpacesConfirmed) {
|
|
|
+ const confirmedCount = this.projectProducts.filter(space => this.isSpaceConfirmed(space.id)).length;
|
|
|
+ console.log(`✅ 已确认 ${confirmedCount}/${this.projectProducts.length} 个空间`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('🎉 所有空间已确认,准备自动进入售后归档阶段...');
|
|
|
+
|
|
|
+ // 提示用户即将自动进入售后归档
|
|
|
+ const shouldProceed = await window?.fmode?.confirm?.(
|
|
|
+ `🎉 恭喜!所有空间的交付执行清单已确认完成。\n\n` +
|
|
|
+ `确认空间数:${this.projectProducts.length} 个\n` +
|
|
|
+ `交付文件总数:${this.getTotalFileCount()} 个\n\n` +
|
|
|
+ `是否立即进入售后归档阶段?`
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!shouldProceed) {
|
|
|
+ console.log('⏸️ 用户选择暂不进入售后归档阶段');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.saving = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+
|
|
|
+ console.log('🚀 开始自动完成交付执行,推进到售后归档阶段');
|
|
|
+
|
|
|
+ // 更新项目阶段为"售后归档"
|
|
|
+ this.project.set('currentStage', '售后归档');
|
|
|
+
|
|
|
+ // 更新 data 字段,记录完成时间和确认人信息
|
|
|
+ const data = this.project.get('data') || {};
|
|
|
+
|
|
|
+ // 记录交付完成信息
|
|
|
+ data.deliveryCompletedAt = new Date();
|
|
|
+ data.deliveryCompletedBy = this.currentUser.get('name') || '';
|
|
|
+ data.deliveryCompletedById = this.currentUser.id || '';
|
|
|
+ data.deliveryCompletedByRole = this.currentUser.get('roleName') || '';
|
|
|
+ data.deliveryFileCount = this.getTotalFileCount();
|
|
|
+ data.deliveryConfirmedSpacesCount = this.projectProducts.length;
|
|
|
+
|
|
|
+ // 保存所有空间的最终确认信息汇总
|
|
|
+ data.deliveryFinalConfirmations = this.projectProducts.map(space => {
|
|
|
+ const confirmation = this.getSpaceConfirmation(space.id);
|
|
|
+ return {
|
|
|
+ spaceId: space.id,
|
|
|
+ spaceName: space.name,
|
|
|
+ confirmedBy: confirmation?.confirmedByName || '',
|
|
|
+ confirmedByRole: confirmation?.confirmedByRole || '',
|
|
|
+ confirmedAt: confirmation?.confirmedAt || new Date(),
|
|
|
+ fileCount: this.getSpaceTotalFileCount(space.id)
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ this.project.set('data', data);
|
|
|
+
|
|
|
+ // 保存项目
|
|
|
+ await this.project.save();
|
|
|
+
|
|
|
+ console.log('✅ 项目阶段已更新为:售后归档');
|
|
|
+ console.log('📊 交付完成信息:', {
|
|
|
+ 完成时间: data.deliveryCompletedAt,
|
|
|
+ 完成人: data.deliveryCompletedBy,
|
|
|
+ 完成人角色: data.deliveryCompletedByRole,
|
|
|
+ 空间数: data.deliveryConfirmedSpacesCount,
|
|
|
+ 文件总数: data.deliveryFileCount
|
|
|
+ });
|
|
|
+
|
|
|
+ window?.fmode?.alert?.('✅ 交付执行完成!项目已自动进入售后归档阶段');
|
|
|
+
|
|
|
+ // 🔗 自动同步案例库:创建 Case 记录(避免重复创建)
|
|
|
+ try {
|
|
|
+ const { ProjectAutoCaseService } = await import('../../../../../app/pages/admin/services/project-auto-case.service');
|
|
|
+ const svc = new ProjectAutoCaseService(new (await import('../../../../../app/services/case.service')).CaseService());
|
|
|
+ const result = await svc.createCaseForProject(this.project.id!);
|
|
|
+ if (result.success) {
|
|
|
+ console.log('✅ 案例库已同步,CaseId:', result.caseId);
|
|
|
+ } else {
|
|
|
+ console.warn('⚠️ 案例库同步失败:', result.error);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('⚠️ 自动创建案例失败(忽略,不阻塞流程):', e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 派发阶段完成事件,通知父组件刷新
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log('📡 派发阶段完成事件: delivery');
|
|
|
+ try {
|
|
|
+ const event = new CustomEvent('stage:completed', {
|
|
|
+ detail: { stage: 'delivery' },
|
|
|
+ bubbles: true,
|
|
|
+ cancelable: true
|
|
|
+ });
|
|
|
+ document.dispatchEvent(event);
|
|
|
+ console.log('✅ 事件派发成功');
|
|
|
+ } catch (e) {
|
|
|
+ console.error('❌ 事件派发失败:', e);
|
|
|
+ }
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ } catch (err) {
|
|
|
+ console.error('❌ 自动完成交付失败:', err);
|
|
|
+ window?.fmode?.alert?.('自动进入售后归档失败,请稍后重试');
|
|
|
+ } finally {
|
|
|
+ this.saving = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取空间的确认信息
|
|
|
*/
|
|
|
@@ -2588,6 +2801,33 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 更新缓存的空间和阶段列表(避免频繁创建新数组导致ngOnChanges被调用)
|
|
|
+ */
|
|
|
+ private updateCachedLists(): void {
|
|
|
+ this.cachedAvailableSpaces = this.projectProducts.map(space => ({
|
|
|
+ id: space.id,
|
|
|
+ name: this.getSpaceDisplayName(space)
|
|
|
+ }));
|
|
|
+
|
|
|
+ this.cachedAvailableStages = this.deliveryTypes.map(stage => ({
|
|
|
+ id: stage.id,
|
|
|
+ name: stage.name
|
|
|
+ }));
|
|
|
+
|
|
|
+ this.cachedRevisionSpaces = this.projectProducts.map(space => ({
|
|
|
+ id: space.id,
|
|
|
+ name: this.getSpaceDisplayName(space),
|
|
|
+ selected: false
|
|
|
+ }));
|
|
|
+
|
|
|
+ console.log('✅ 缓存列表已更新:', {
|
|
|
+ 空间数量: this.cachedAvailableSpaces.length,
|
|
|
+ 阶段数量: this.cachedAvailableStages.length,
|
|
|
+ 工单空间数量: this.cachedRevisionSpaces.length
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 检查文件类型是否支持
|
|
|
*/
|
|
|
@@ -2799,4 +3039,208 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
return deliveryFile;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载改图工单数量
|
|
|
+ */
|
|
|
+ async loadRevisionTaskCount(): Promise<void> {
|
|
|
+ if (!this.project) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const tasks = await this.revisionTaskService.getProjectRevisionTasks(this.project.id!);
|
|
|
+ this.revisionTaskCount = tasks.filter(t =>
|
|
|
+ t.status !== 'completed' && t.status !== 'cancelled'
|
|
|
+ ).length;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载工单数量失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 打开创建改图工单弹窗
|
|
|
+ */
|
|
|
+ openRevisionTaskModal(): void {
|
|
|
+ this.showRevisionTaskModal = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关闭创建改图工单弹窗
|
|
|
+ */
|
|
|
+ closeRevisionTaskModal(): void {
|
|
|
+ this.showRevisionTaskModal = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 工单创建成功回调
|
|
|
+ */
|
|
|
+ onRevisionTaskCreated(taskId: string): void {
|
|
|
+ console.log('工单已创建:', taskId);
|
|
|
+ this.loadRevisionTaskCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 打开改图工单列表
|
|
|
+ */
|
|
|
+ openRevisionTaskList(): void {
|
|
|
+ this.showRevisionTaskList = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关闭改图工单列表
|
|
|
+ */
|
|
|
+ closeRevisionTaskList(): void {
|
|
|
+ this.showRevisionTaskList = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 工单列表刷新回调
|
|
|
+ */
|
|
|
+ onRevisionTaskListRefresh(): void {
|
|
|
+ this.loadRevisionTaskCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取可用空间列表(用于工单创建)
|
|
|
+ */
|
|
|
+ getAvailableSpacesForRevision(): Array<{id: string; name: string; selected: boolean}> {
|
|
|
+ return this.projectProducts.map(space => ({
|
|
|
+ id: space.id,
|
|
|
+ name: this.getSpaceDisplayName(space),
|
|
|
+ selected: false
|
|
|
+ }));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 打开消息发送弹窗(上传完成后)
|
|
|
+ */
|
|
|
+ openMessageModal(spaceId: string, stage: string, imageUrls: string[] = []): void {
|
|
|
+ const space = this.projectProducts.find(p => p.id === spaceId);
|
|
|
+ const stageInfo = this.deliveryTypes.find(t => t.id === stage);
|
|
|
+
|
|
|
+ if (!space || !stageInfo) return;
|
|
|
+
|
|
|
+ this.messageModalConfig = {
|
|
|
+ spaceId,
|
|
|
+ spaceName: this.getSpaceDisplayName(space),
|
|
|
+ stage,
|
|
|
+ stageName: stageInfo.name,
|
|
|
+ imageUrls
|
|
|
+ };
|
|
|
+
|
|
|
+ // 默认选择第一个模板
|
|
|
+ const templates = this.deliveryMessageService.getStageTemplates(stage);
|
|
|
+ this.selectedTemplate = templates[0] || '';
|
|
|
+ this.customMessage = '';
|
|
|
+ this.showMessageModal = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关闭消息弹窗
|
|
|
+ */
|
|
|
+ closeMessageModal(): void {
|
|
|
+ this.showMessageModal = false;
|
|
|
+ this.messageModalConfig = null;
|
|
|
+ this.selectedTemplate = '';
|
|
|
+ this.customMessage = '';
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选择话术模板
|
|
|
+ */
|
|
|
+ selectTemplate(template: string): void {
|
|
|
+ this.selectedTemplate = template;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送消息
|
|
|
+ */
|
|
|
+ async sendMessage(): Promise<void> {
|
|
|
+ if (!this.project || !this.currentUser || !this.messageModalConfig) return;
|
|
|
+
|
|
|
+ const content = this.customMessage.trim() || this.selectedTemplate;
|
|
|
+ if (!content && !this.messageModalConfig.imageUrls.length) {
|
|
|
+ window?.fmode?.alert?.('请输入消息内容或选择图片');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.sendingMessage = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+
|
|
|
+ if (this.messageModalConfig.imageUrls.length > 0) {
|
|
|
+ // 发送图文消息
|
|
|
+ await this.deliveryMessageService.createImageMessage(
|
|
|
+ this.project.id!,
|
|
|
+ this.messageModalConfig.stage,
|
|
|
+ this.messageModalConfig.imageUrls,
|
|
|
+ content,
|
|
|
+ this.currentUser
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ // 发送文本消息
|
|
|
+ await this.deliveryMessageService.createTextMessage(
|
|
|
+ this.project.id!,
|
|
|
+ this.messageModalConfig.stage,
|
|
|
+ content,
|
|
|
+ this.currentUser
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ window?.fmode?.toast?.success?.('✅ 消息已记录');
|
|
|
+ this.closeMessageModal();
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发送消息失败:', error);
|
|
|
+ window?.fmode?.alert?.('❌ 发送失败,请重试');
|
|
|
+ } finally {
|
|
|
+ this.sendingMessage = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送单张图片(快捷发送)
|
|
|
+ */
|
|
|
+ async sendSingleImage(spaceId: string, stage: string, imageUrl: string): Promise<void> {
|
|
|
+ if (!this.project || !this.currentUser) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.deliveryMessageService.createImageMessage(
|
|
|
+ this.project.id!,
|
|
|
+ stage,
|
|
|
+ [imageUrl],
|
|
|
+ '',
|
|
|
+ this.currentUser
|
|
|
+ );
|
|
|
+
|
|
|
+ window?.fmode?.toast?.success?.('✅ 图片已发送');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发送图片失败:', error);
|
|
|
+ window?.fmode?.alert?.('❌ 发送失败,请重试');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 阶段完成后提示发送消息
|
|
|
+ */
|
|
|
+ promptSendMessageAfterUpload(spaceId: string, stage: string): void {
|
|
|
+ // 获取该阶段的所有图片
|
|
|
+ const files = this.getProductDeliveryFiles(spaceId, stage);
|
|
|
+ const imageUrls = files.map(f => f.url);
|
|
|
+
|
|
|
+ if (imageUrls.length > 0) {
|
|
|
+ // 延迟显示,让用户看到上传成功的提示
|
|
|
+ setTimeout(() => {
|
|
|
+ this.openMessageModal(spaceId, stage, imageUrls);
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|