|
|
@@ -1,4 +1,4 @@
|
|
|
-import { Component, OnInit, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
|
|
+import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { ActivatedRoute } from '@angular/router';
|
|
|
@@ -9,6 +9,11 @@ import { WxworkAuth } from 'fmode-ng/social';
|
|
|
|
|
|
const Parse = FmodeParse.with('nova');
|
|
|
|
|
|
+/**
|
|
|
+ * 审批状态类型
|
|
|
+ */
|
|
|
+type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'unverified';
|
|
|
+
|
|
|
/**
|
|
|
* 交付文件接口
|
|
|
*/
|
|
|
@@ -23,6 +28,11 @@ interface DeliveryFile {
|
|
|
deliveryType: 'white_model' | 'soft_decor' | 'rendering' | 'post_process';
|
|
|
stage: string;
|
|
|
projectFile?: FmodeObject;
|
|
|
+ // 审批相关字段
|
|
|
+ approvalStatus: ApprovalStatus; // 审批状态
|
|
|
+ approvedBy?: string; // 审批人
|
|
|
+ approvedAt?: Date; // 审批时间
|
|
|
+ rejectionReason?: string; // 驳回原因
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -42,7 +52,7 @@ interface DeliveryFile {
|
|
|
styleUrls: ['./stage-delivery.component.scss'],
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
|
})
|
|
|
-export class StageDeliveryComponent implements OnInit {
|
|
|
+export class StageDeliveryComponent implements OnInit, OnDestroy {
|
|
|
@Input() project: FmodeObject | null = null;
|
|
|
@Input() customer: FmodeObject | null = null;
|
|
|
@Input() currentUser: FmodeObject | null = null;
|
|
|
@@ -112,6 +122,24 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
loading: boolean = true;
|
|
|
saving: boolean = false;
|
|
|
|
|
|
+ // 视图上下文:管理员后台查看时隐藏操作按钮
|
|
|
+ isAdminView: boolean = false;
|
|
|
+
|
|
|
+ // 状态自动刷新定时器
|
|
|
+ private statusRefreshTimer: any = null;
|
|
|
+
|
|
|
+ // 图片占位(base64 SVG,避免外链失败)
|
|
|
+ private readonly fallbackImageDataUrl: string =
|
|
|
+ 'data:image/svg+xml;utf8,' +
|
|
|
+ encodeURIComponent(
|
|
|
+ '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">' +
|
|
|
+ '<defs><linearGradient id="g" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#eef2ff"/><stop offset="1" stop-color="#e9ecff"/></linearGradient></defs>' +
|
|
|
+ '<rect width="400" height="300" fill="url(#g)"/>' +
|
|
|
+ '<g fill="#667eea" opacity="0.7"><circle cx="70" cy="60" r="6"/><circle cx="120" cy="80" r="4"/><circle cx="340" cy="210" r="5"/></g>' +
|
|
|
+ '<text x="200" y="160" text-anchor="middle" font-size="18" fill="#64748b">暂无预览</text>' +
|
|
|
+ '</svg>'
|
|
|
+ );
|
|
|
+
|
|
|
constructor(
|
|
|
private route: ActivatedRoute,
|
|
|
private cdr: ChangeDetectorRef,
|
|
|
@@ -122,9 +150,28 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
async ngOnInit() {
|
|
|
// 从路由或Input获取参数
|
|
|
this.cid = this.route.parent?.snapshot.paramMap.get('cid') || '';
|
|
|
+ // 判断是否为管理员后台视图(/admin 路由)
|
|
|
+ try {
|
|
|
+ const path = (typeof window !== 'undefined') ? window.location.pathname : '';
|
|
|
+ this.isAdminView = path.includes('/admin/');
|
|
|
+ } catch {}
|
|
|
this.projectId = this.route.parent?.snapshot.paramMap.get('projectId') || '';
|
|
|
|
|
|
await this.loadData();
|
|
|
+
|
|
|
+ // 周期性刷新交付文件以同步组长端审批状态
|
|
|
+ if (!this.statusRefreshTimer) {
|
|
|
+ this.statusRefreshTimer = setInterval(() => {
|
|
|
+ this.loadDeliveryFiles();
|
|
|
+ }, 15000); // 15s 刷新一次
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ngOnDestroy(): void {
|
|
|
+ if (this.statusRefreshTimer) {
|
|
|
+ clearInterval(this.statusRefreshTimer);
|
|
|
+ this.statusRefreshTimer = null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -190,12 +237,25 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
this.activeProductId = this.projectProducts[0].id;
|
|
|
}
|
|
|
|
|
|
- console.log(`已加载 ${this.projectProducts.length} 个场景Product`);
|
|
|
+ console.log(`✅ 已加载 ${this.projectProducts.length} 个场景Product`);
|
|
|
+ console.log('📊 canEdit:', this.canEdit);
|
|
|
+ console.log('📊 projectProducts.length:', this.projectProducts.length);
|
|
|
+ console.log('📊 显示完成交付按钮条件:', this.canEdit && this.projectProducts.length > 0);
|
|
|
} catch (error) {
|
|
|
console.error('加载项目场景失败:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 图片加载失败时使用内置占位图,避免外链报错
|
|
|
+ */
|
|
|
+ onImageError(event: Event): void {
|
|
|
+ const img = event.target as HTMLImageElement;
|
|
|
+ if (img && img.src !== this.fallbackImageDataUrl) {
|
|
|
+ img.src = this.fallbackImageDataUrl;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 加载交付文件 (从ProjectFile表按category查询)
|
|
|
*/
|
|
|
@@ -231,18 +291,26 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
});
|
|
|
|
|
|
// 转换为DeliveryFile格式
|
|
|
- this.deliveryFiles[product.id][deliveryType.id as keyof typeof this.deliveryFiles[typeof product.id]] = productFiles.map(projectFile => ({
|
|
|
- id: projectFile.id || '',
|
|
|
- url: projectFile.get('fileUrl') || '',
|
|
|
- name: projectFile.get('fileName') || '',
|
|
|
- size: projectFile.get('fileSize') || 0,
|
|
|
- uploadTime: projectFile.createdAt || new Date(),
|
|
|
- uploadedBy: projectFile.get('uploadedBy')?.get('name') || '',
|
|
|
- productId: product.id,
|
|
|
- deliveryType: deliveryType.id as any,
|
|
|
- stage: 'delivery',
|
|
|
- projectFile: projectFile
|
|
|
- }));
|
|
|
+ this.deliveryFiles[product.id][deliveryType.id as keyof typeof this.deliveryFiles[typeof product.id]] = productFiles.map(projectFile => {
|
|
|
+ const fileData = projectFile.get('data') || {};
|
|
|
+ return {
|
|
|
+ id: projectFile.id || '',
|
|
|
+ url: projectFile.get('fileUrl') || '',
|
|
|
+ name: projectFile.get('fileName') || '',
|
|
|
+ size: projectFile.get('fileSize') || 0,
|
|
|
+ uploadTime: projectFile.createdAt || new Date(),
|
|
|
+ uploadedBy: projectFile.get('uploadedBy')?.get('name') || '',
|
|
|
+ productId: product.id,
|
|
|
+ deliveryType: deliveryType.id as any,
|
|
|
+ stage: 'delivery',
|
|
|
+ projectFile: projectFile,
|
|
|
+ // 审批状态信息
|
|
|
+ approvalStatus: fileData.approvalStatus || 'unverified',
|
|
|
+ approvedBy: fileData.approvedBy,
|
|
|
+ approvedAt: fileData.approvedAt ? new Date(fileData.approvedAt) : undefined,
|
|
|
+ rejectionReason: fileData.rejectionReason
|
|
|
+ };
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -319,7 +387,11 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
{
|
|
|
deliveryType: deliveryType,
|
|
|
productId: productId,
|
|
|
- uploadedFor: 'delivery_execution'
|
|
|
+ uploadedFor: 'delivery_execution',
|
|
|
+ // ✨ 新增:设置初始审批状态为"未验证"
|
|
|
+ approvalStatus: 'unverified',
|
|
|
+ uploadedByName: this.currentUser?.get('name') || '',
|
|
|
+ uploadedById: this.currentUser?.id || ''
|
|
|
},
|
|
|
(progress) => {
|
|
|
// 计算总体进度
|
|
|
@@ -340,7 +412,9 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
productId: productId,
|
|
|
deliveryType: deliveryType as any,
|
|
|
stage: 'delivery',
|
|
|
- projectFile: projectFile
|
|
|
+ projectFile: projectFile,
|
|
|
+ // ✨ 新增:初始审批状态
|
|
|
+ approvalStatus: 'unverified'
|
|
|
};
|
|
|
|
|
|
// 添加到对应类型的数组中
|
|
|
@@ -355,6 +429,11 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
this.cdr.markForCheck();
|
|
|
console.log(`成功上传 ${uploadedFiles} 个交付文件`);
|
|
|
|
|
|
+ // ✨ 上传成功后通知组长审批
|
|
|
+ if (uploadedFiles > 0) {
|
|
|
+ await this.notifyTeamLeaderForApproval(uploadedFiles, deliveryType);
|
|
|
+ }
|
|
|
+
|
|
|
} catch (error) {
|
|
|
console.error('上传交付文件失败:', error);
|
|
|
window?.fmode?.alert('文件上传失败,请重试');
|
|
|
@@ -365,6 +444,73 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 通知组长审批交付文件
|
|
|
+ */
|
|
|
+ async notifyTeamLeaderForApproval(fileCount: number, deliveryType: string): Promise<void> {
|
|
|
+ try {
|
|
|
+ if (!this.project || !this.cid) return;
|
|
|
+
|
|
|
+ const projectTitle = this.project.get('title') || '项目';
|
|
|
+ const typeName = this.getDeliveryTypeName(deliveryType);
|
|
|
+ const uploaderName = this.currentUser?.get('name') || '设计师';
|
|
|
+
|
|
|
+ // 构建审批页面URL(组长端)
|
|
|
+ const approvalUrl = `https://yinsanse.fmode.cn/wxwork/${this.cid}/team-leader/delivery-approval?projectId=${this.project.id}&type=${deliveryType}`;
|
|
|
+
|
|
|
+ // 发送企业微信消息
|
|
|
+ const WxworkSDK = (window as any).WxworkSDK;
|
|
|
+ if (WxworkSDK) {
|
|
|
+ const sdk = new WxworkSDK(this.cid);
|
|
|
+
|
|
|
+ // 查询设计师组长
|
|
|
+ const teamLeaderQuery = new Parse.Query('Profile');
|
|
|
+ teamLeaderQuery.equalTo('roleName', '设计组长');
|
|
|
+ teamLeaderQuery.limit(10);
|
|
|
+ const teamLeaders = await teamLeaderQuery.find();
|
|
|
+
|
|
|
+ for (const leader of teamLeaders) {
|
|
|
+ const userId = leader.get('wxworkUserId');
|
|
|
+ if (!userId) continue;
|
|
|
+
|
|
|
+ await sdk.sendTextMessage(
|
|
|
+ userId,
|
|
|
+ `📦 交付物待审批提醒\n\n` +
|
|
|
+ `项目:${projectTitle}\n` +
|
|
|
+ `类型:${typeName}\n` +
|
|
|
+ `上传人:${uploaderName}\n` +
|
|
|
+ `文件数:${fileCount} 个\n` +
|
|
|
+ `状态:待审批\n\n` +
|
|
|
+ `请点击查看并审批:\n${approvalUrl}`
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`✅ 已通知 ${teamLeaders.length} 位组长审批`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在项目data中添加待审批标记
|
|
|
+ const data = this.project.get('data') || {};
|
|
|
+ if (!data.pendingDeliveryApprovals) {
|
|
|
+ data.pendingDeliveryApprovals = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ data.pendingDeliveryApprovals.push({
|
|
|
+ type: deliveryType,
|
|
|
+ fileCount: fileCount,
|
|
|
+ uploadedBy: uploaderName,
|
|
|
+ uploadedAt: new Date().toISOString(),
|
|
|
+ status: 'pending'
|
|
|
+ });
|
|
|
+
|
|
|
+ this.project.set('data', data);
|
|
|
+ await this.project.save();
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('通知组长失败:', error);
|
|
|
+ // 通知失败不影响主流程,只记录错误
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 删除交付文件
|
|
|
*/
|
|
|
@@ -440,15 +586,6 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
return type?.description || '';
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 触发文件输入点击
|
|
|
- */
|
|
|
- triggerFileInput(inputId: string): void {
|
|
|
- const element = document.getElementById(inputId) as HTMLInputElement;
|
|
|
- if (element) {
|
|
|
- element.click();
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
/**
|
|
|
* 格式化文件大小
|
|
|
@@ -494,7 +631,53 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 完成交付
|
|
|
+ * 保存草稿
|
|
|
+ */
|
|
|
+ async saveDraft(): Promise<void> {
|
|
|
+ if (!this.project || !this.canEdit) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.saving = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+
|
|
|
+ console.log('💾 保存交付执行阶段草稿');
|
|
|
+
|
|
|
+ // 更新项目的 data 字段,保存交付文件信息
|
|
|
+ const data = this.project.get('data') || {};
|
|
|
+ data.deliveryDraft = {
|
|
|
+ products: this.projectProducts.map(p => p.id),
|
|
|
+ lastSavedAt: new Date(),
|
|
|
+ fileCount: this.getTotalFileCount()
|
|
|
+ };
|
|
|
+
|
|
|
+ this.project.set('data', data);
|
|
|
+ await this.project.save();
|
|
|
+
|
|
|
+ window?.fmode?.alert('草稿保存成功');
|
|
|
+ console.log('✅ 交付执行草稿已保存');
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存草稿失败:', error);
|
|
|
+ window?.fmode?.alert('保存失败,请重试');
|
|
|
+ } finally {
|
|
|
+ this.saving = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取总文件数
|
|
|
+ */
|
|
|
+ private getTotalFileCount(): number {
|
|
|
+ let count = 0;
|
|
|
+ for (const product of this.projectProducts) {
|
|
|
+ count += this.getTotalDeliveryFileCount(product.id);
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 完成交付(推进到售后归档阶段)
|
|
|
*/
|
|
|
async completeDelivery(): Promise<void> {
|
|
|
if (!this.project || !this.canEdit) return;
|
|
|
@@ -518,15 +701,49 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (!await window?.fmode?.confirm('确定要完成交付执行并进入售后归档阶段吗?')) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
try {
|
|
|
this.saving = true;
|
|
|
this.cdr.markForCheck();
|
|
|
|
|
|
- console.log('完成交付执行阶段');
|
|
|
+ console.log('🚀 完成交付执行,推进到售后归档阶段');
|
|
|
|
|
|
- window?.fmode?.alert('交付执行完成');
|
|
|
+ // ✨ 更新项目阶段为"售后归档"
|
|
|
+ this.project.set('currentStage', '尾款结算');
|
|
|
+
|
|
|
+ // 更新 data 字段,记录完成时间
|
|
|
+ const data = this.project.get('data') || {};
|
|
|
+ data.deliveryCompletedAt = new Date();
|
|
|
+ data.deliveryCompletedBy = this.currentUser?.get('name') || '';
|
|
|
+ data.deliveryFileCount = this.getTotalFileCount();
|
|
|
+ this.project.set('data', data);
|
|
|
+
|
|
|
+ // 保存项目
|
|
|
+ await this.project.save();
|
|
|
+
|
|
|
+ console.log('✅ 项目阶段已更新为:尾款结算');
|
|
|
+ console.log('📊 交付文件总数:', 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 {
|
|
|
@@ -551,6 +768,73 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 提交交付执行审批(组长端看板显示待审批)
|
|
|
+ */
|
|
|
+ async submitDeliveryForApproval(): Promise<void> {
|
|
|
+ if (!this.project || !this.canEdit) return;
|
|
|
+
|
|
|
+ // 至少需要有文件
|
|
|
+ const total = this.getTotalFileCount();
|
|
|
+ if (total === 0) {
|
|
|
+ window?.fmode?.alert('请先上传交付文件后再提交审批');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.saving = true;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+
|
|
|
+ // 写入项目 data 的交付审批条目,供组长端看板识别
|
|
|
+ const data = this.project.get('data') || {};
|
|
|
+ const now = new Date();
|
|
|
+ const submitter = this.currentUser?.get('name') || '';
|
|
|
+ const submitterId = this.currentUser?.id || '';
|
|
|
+
|
|
|
+ // 汇总各类型数量
|
|
|
+ const summary: Record<string, number> = {} as any;
|
|
|
+ for (const type of this.deliveryTypes) {
|
|
|
+ summary[type.id] = this.getCurrentTypeFileCount(this.activeProductId || this.projectProducts[0]?.id || '', type.id);
|
|
|
+ }
|
|
|
+
|
|
|
+ data.deliveryApproval = {
|
|
|
+ status: 'pending', // 供看板过滤
|
|
|
+ stage: 'delivery',
|
|
|
+ totalFiles: total,
|
|
|
+ types: summary,
|
|
|
+ submittedAt: now,
|
|
|
+ submittedByName: submitter,
|
|
|
+ submittedById: submitterId
|
|
|
+ };
|
|
|
+
|
|
|
+ // 兼容:看板可能读取 pendingDeliveryApprovals
|
|
|
+ const pendingList = Array.isArray(data.pendingDeliveryApprovals) ? data.pendingDeliveryApprovals : [];
|
|
|
+ pendingList.push({
|
|
|
+ status: 'pending',
|
|
|
+ productId: this.activeProductId || (this.projectProducts[0]?.id || ''),
|
|
|
+ submittedAt: now,
|
|
|
+ submittedBy: submitter,
|
|
|
+ totalFiles: total
|
|
|
+ });
|
|
|
+ data.pendingDeliveryApprovals = pendingList;
|
|
|
+
|
|
|
+ this.project.set('data', JSON.parse(JSON.stringify(data)));
|
|
|
+ await this.project.save();
|
|
|
+
|
|
|
+ // 发送通知
|
|
|
+ await this.notifyTeamLeaderForApproval(total, 'all');
|
|
|
+
|
|
|
+ window?.fmode?.toast?.success?.('已提交审批,等待组长审核');
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ } catch (e) {
|
|
|
+ console.error('提交交付审批失败:', e);
|
|
|
+ window?.fmode?.alert('提交失败,请稍后再试');
|
|
|
+ } finally {
|
|
|
+ this.saving = false;
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 加载审批历史记录
|
|
|
*/
|
|
|
@@ -571,4 +855,51 @@ export class StageDeliveryComponent implements OnInit {
|
|
|
console.error('❌ 加载审批历史失败:', error);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取审批状态显示文本
|
|
|
+ */
|
|
|
+ getApprovalStatusText(status: ApprovalStatus): string {
|
|
|
+ const statusMap: Record<ApprovalStatus, string> = {
|
|
|
+ 'unverified': '未验证',
|
|
|
+ 'pending': '待审批',
|
|
|
+ 'approved': '已通过',
|
|
|
+ 'rejected': '已驳回'
|
|
|
+ };
|
|
|
+ return statusMap[status] || '未知';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取审批状态颜色类
|
|
|
+ */
|
|
|
+ getApprovalStatusClass(status: ApprovalStatus): string {
|
|
|
+ const classMap: Record<ApprovalStatus, string> = {
|
|
|
+ 'unverified': 'status-unverified',
|
|
|
+ 'pending': 'status-pending',
|
|
|
+ 'approved': 'status-approved',
|
|
|
+ 'rejected': 'status-rejected'
|
|
|
+ };
|
|
|
+ return classMap[status] || '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取未验证文件数量
|
|
|
+ */
|
|
|
+ getUnverifiedFileCount(productId: string): number {
|
|
|
+ if (!this.deliveryFiles[productId]) return 0;
|
|
|
+
|
|
|
+ return this.deliveryTypes.reduce((total, type) => {
|
|
|
+ const files = this.getProductDeliveryFiles(productId, type.id);
|
|
|
+ const unverified = files.filter(f => f.approvalStatus === 'unverified').length;
|
|
|
+ return total + unverified;
|
|
|
+ }, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取指定类型未验证文件数量
|
|
|
+ */
|
|
|
+ getTypeUnverifiedFileCount(productId: string, typeId: string): number {
|
|
|
+ const files = this.getProductDeliveryFiles(productId, typeId);
|
|
|
+ return files.filter(f => f.approvalStatus === 'unverified').length;
|
|
|
+ }
|
|
|
}
|