{code: 631, message: 'xhr request failed, code: 631; response: {"error":"no such bucket"}'}
原因:
NovaStorage.withCid(cid) 使用的公司ID对应的存储桶不存在原因:
ProjectFile {
project: Pointer<Project>, // 关联项目
attach: Pointer<Attachment>, // 关联附件(包含实际文件URL)
fileType: string, // 'delivery_white_model', 'delivery_soft_decor', etc.
fileUrl: string, // 文件URL(从Attachment复制)
fileName: string, // 文件名
fileSize: number, // 文件大小
stage: string, // 'delivery'
uploadedBy: Pointer<User>, // 上传人
data: { // 扩展数据
spaceId: string, // 空间ID(Product ID)
productId: string, // 同spaceId
deliveryType: string, // 'white_model', 'soft_decor', 'rendering', 'post_process'
uploadedFor: string, // 'delivery_execution'
approvalStatus: string, // 'unverified', 'approved', 'rejected'
aiAnalysis: { // 🔥 AI分析结果(点击确认后保存)
suggestedStage: string,
confidence: number,
category: string,
quality: {...},
technical: {...},
description: string,
tags: string[]
}
}
}
Attachment {
url: string, // OBS文件URL
name: string, // 文件名
size: number, // 文件大小
mime: string, // MIME类型
md5: string, // 文件MD5
metadata: { // 元数据
projectId: string,
fileType: string,
spaceId: string,
stage: string
}
}
文件: project-file.service.ts
问题:公司ID对应的存储桶不存在
解决:使用默认存储桶fallback
// 修复前
const cid = localStorage.getItem('company');
if (!cid) {
throw new Error('公司ID未找到');
}
const storage = await NovaStorage.withCid(cid);
// 修复后
let cid = localStorage.getItem('company');
if (!cid) {
console.warn('⚠️ 未找到公司ID,使用默认存储桶');
cid = 'cDL6R1hgSi'; // 默认公司ID
}
console.log(`📦 使用存储桶CID: ${cid}`);
const storage = await NovaStorage.withCid(cid);
用户拖拽图片 → drag-upload-modal
↓
AI分析图片(色彩+纹理检测)
↓
用户点击"确认交付清单"
↓
调用 confirmDragUpload(result)
↓
遍历 result.files
↓
对每个文件:
1. 调用 uploadDeliveryFile()
- 上传到OBS存储(3次重试)
- 创建Attachment记录
- 创建ProjectFile记录
- fileType: `delivery_${deliveryType}`
- stage: 'delivery'
- data.spaceId: 空间ID
- data.deliveryType: 阶段类型
↓
2. 保存AI分析结果
- 获取最新上传的ProjectFile
- 保存到 ProjectFile.data.aiAnalysis
- 保存到 Project.date.imageAnalysis
↓
刷新文件列表 loadDeliveryFiles()
↓
显示在对应阶段
loadDeliveryFiles()
↓
查询ProjectFile表
- 条件:project = 当前项目
- 条件:fileType = `delivery_${deliveryType}`
- 条件:stage = 'delivery'
↓
过滤:data.spaceId = 当前空间ID
↓
转换为DeliveryFile格式
- url: projectFile.get('fileUrl')
- name: projectFile.get('fileName')
- size: projectFile.get('fileSize')
- deliveryType: data.deliveryType
↓
显示在对应阶段
async confirmDragUpload(result: UploadResult): Promise<void> {
for (const fileItem of result.files) {
// 1. 上传文件
await this.uploadDeliveryFile(
mockEvent,
fileItem.spaceId, // 空间ID(Product ID)
fileItem.stageType, // 'white_model', 'soft_decor', etc.
true // silentMode
);
// 2. 保存AI分析结果(如果有)
if (uploadFile.analysisResult) {
const recentFiles = this.getProductDeliveryFiles(
fileItem.spaceId,
fileItem.stageType
);
const latestFile = recentFiles[recentFiles.length - 1];
if (latestFile && latestFile.projectFile) {
// 保存到ProjectFile.data.aiAnalysis
projectFile.set('data', {
...existingData,
aiAnalysis: uploadFile.analysisResult
});
await projectFile.save();
// 保存到Project.date.imageAnalysis
await this.imageAnalysisService.saveAnalysisResult(...);
}
}
}
// 3. 刷新文件列表
await this.loadDeliveryFiles();
}
async uploadDeliveryFile(
event: any,
productId: string, // 空间ID
deliveryType: string, // 阶段类型
silentMode: boolean = false
): Promise<void> {
// 上传文件并创建ProjectFile记录
const projectFile = await this.projectFileService.uploadProjectFileWithRecord(
file,
projectId,
`delivery_${deliveryType}`, // 🔥 fileType
productId, // 🔥 spaceId
'delivery', // 🔥 stage
{
deliveryType: deliveryType,
productId: productId,
spaceId: productId,
approvalStatus: 'unverified'
}
);
// 保存扩展数据
projectFile.set('data', {
spaceId: productId,
deliveryType: deliveryType,
approvalStatus: 'unverified'
});
await projectFile.save();
}
async loadDeliveryFiles(): Promise<void> {
for (const product of this.projectProducts) {
// 初始化
this.deliveryFiles[product.id] = {
white_model: [],
soft_decor: [],
rendering: [],
post_process: []
};
// 加载各类型的交付文件
for (const deliveryType of this.deliveryTypes) {
const files = await this.projectFileService.getProjectFiles(
projectId,
{
fileType: `delivery_${deliveryType.id}`, // 🔥 查询条件
stage: 'delivery'
}
);
// 过滤当前产品的文件
const productFiles = files.filter(file => {
const data = file.get('data');
return data?.productId === product.id || data?.spaceId === product.id;
});
// 转换为DeliveryFile格式
this.deliveryFiles[product.id][deliveryType.id] = productFiles.map(pf => ({
id: pf.id,
url: pf.get('fileUrl'), // 🔥 从ProjectFile读取
name: pf.get('fileName'),
size: pf.get('fileSize'),
deliveryType: deliveryType.id,
projectFile: pf
}));
}
}
}
【用户操作】
拖拽图片到弹窗
↓
【AI分析】
- 色彩检测
- 纹理检测
- 质量评估
- 阶段判定
↓
【显示结果】
在弹窗中显示分类
- 白模:无色彩+无纹理
- 软装:有色彩+有家具
- 渲染:有色彩+有灯光
- 后期:高质量+完整场景
↓
【用户确认】
点击"确认交付清单"
↓
【上传文件】
for each file:
1. 上传到OBS(3次重试)
2. 创建Attachment记录
3. 创建ProjectFile记录
- fileType: delivery_白模/软装/渲染/后期
- data.spaceId: 空间ID
- data.deliveryType: 阶段类型
4. 保存AI分析结果
- ProjectFile.data.aiAnalysis
- Project.date.imageAnalysis
↓
【刷新显示】
loadDeliveryFiles()
- 查询ProjectFile表
- 按spaceId和deliveryType分类
- 显示在对应阶段
1. 上传图片
2. 查看控制台日志:
📦 使用存储桶CID: xxx
📤 上传尝试 1/3: xxx.jpg
✅ 文件上传成功
3. 确认不再出现631错误
1. 上传纯白草图 → 应归类到"白模"
2. 上传有色彩图 → 应归类到"软装"/"渲染"/"后期"
3. 查看日志:
🎯 阶段判断依据:
有色彩: true/false
有纹理: true/false
1. 点击"确认交付清单"
2. 等待上传完成
3. 查看各阶段:
- 白模:显示白模图片
- 软装:显示软装图片
- 渲染:显示渲染图片
- 后期:显示后期图片
-- 查询ProjectFile表
SELECT * FROM ProjectFile
WHERE project = 'XB56jBlvkd'
AND fileType LIKE 'delivery_%'
AND stage = 'delivery'
ORDER BY createdAt DESC;
-- 验证字段
- fileUrl: 应该有值(OBS URL)
- fileName: 应该有值
- fileSize: 应该有值
- data.spaceId: 应该有值(Product ID)
- data.deliveryType: 应该有值(white_model/soft_decor/etc.)
- data.aiAnalysis: 应该有值(AI分析结果)
// 打开控制台
console.log('公司ID:', localStorage.getItem('company'));
console.log('使用的存储桶CID:', cid);
// 检查fmode-ng版本
import { NovaStorage } from 'fmode-ng/core';
const storage = await NovaStorage.withCid('cDL6R1hgSi');
console.log('存储桶配置:', storage);
联系管理员确认:
查看控制台日志:
✅ ProjectFile 创建成功: abc123
✅ AI分析结果已保存到ProjectFile
const query = new Parse.Query('ProjectFile');
query.equalTo('project', project);
query.equalTo('fileType', 'delivery_white_model');
const files = await query.find();
console.log('查询结果:', files.length, '个文件');
files.forEach(f => {
console.log({
id: f.id,
fileUrl: f.get('fileUrl'),
fileName: f.get('fileName'),
spaceId: f.get('data')?.spaceId,
deliveryType: f.get('data')?.deliveryType
});
});
查看控制台日志:
已加载交付文件: {...}
project-file.service.ts
stage-delivery.component.ts
image-analysis.service.ts
fileType: delivery_${deliveryType} (用于查询)data.spaceId: 空间ID (用于过滤)data.deliveryType: 阶段类型 (用于分类)data.aiAnalysis: AI分析结果 (用于显示)创建时间:2025-11-28