| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- import { Injectable } from '@angular/core';
- import { NovaStorage, NovaFile } from 'fmode-ng/core';
- import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
- const Parse = FmodeParse.with('nova');
- @Injectable({
- providedIn: 'root'
- })
- export class ProjectFileService {
- /**
- * 上传项目文件并保存到ProjectFile表
- * @param file 要上传的文件
- * @param projectId 项目ID
- * @param fileType 文件类型
- * @param spaceId 空间ID(可选)
- * @param stage 项目阶段(可选)
- * @param additionalMetadata 额外元数据(可选)
- * @param onProgress 上传进度回调
- * @returns 上传后的NovaFile对象
- */
- async uploadProjectFile(
- file: File,
- projectId: string,
- fileType: string,
- spaceId?: string,
- stage?: string,
- additionalMetadata?: any,
- onProgress?: (progress: number) => void
- ): Promise<NovaFile> {
- try {
- // 获取公司ID
- const cid = localStorage.getItem('company');
- if (!cid) {
- throw new Error('公司ID未找到');
- }
- // 初始化存储
- const storage = await NovaStorage.withCid(cid);
- // 构建prefixKey
- let prefixKey = `project/${projectId}`;
- if (spaceId) {
- prefixKey += `/space/${spaceId}`;
- }
- if (stage) {
- prefixKey += `/stage/${stage}`;
- }
- // 上传文件
- const uploadedFile: NovaFile = await storage.upload(file, {
- prefixKey,
- onProgress: (progress: { total: { percent: number } }) => {
- if (onProgress) {
- onProgress(progress.total.percent);
- }
- }
- });
- // 保存到Attachment表
- await this.saveToAttachmentTable(uploadedFile, projectId, fileType, spaceId, stage, additionalMetadata);
- return uploadedFile;
- } catch (error) {
- console.error('项目文件上传失败:', error);
- throw error;
- }
- }
- /**
- * 保存文件信息到Attachment表
- */
- private async saveToAttachmentTable(
- file: NovaFile,
- projectId: string,
- fileType: string,
- spaceId?: string,
- stage?: string,
- additionalMetadata?: any
- ): Promise<FmodeObject> {
- const attachment = new Parse.Object('Attachment');
- // 设置基本字段
- attachment.set('size', file.size);
- attachment.set('url', file.url);
- attachment.set('name', file.name);
- attachment.set('mime', file.type);
- attachment.set('md5', file.md5);
- attachment.set('metadata', {
- ...file.metadata,
- projectId,
- fileType,
- spaceId,
- stage,
- ...additionalMetadata
- });
- // 设置关联关系
- const cid = localStorage.getItem('company');
- if (cid) {
- let company = new Parse.Object('Company');
- company.id = cid
- if (company) {
- attachment.set('company', company.toPointer());
- }
- }
- // 设置当前用户
- const currentUser = Parse.User.current();
- if (currentUser) {
- attachment.set('user', currentUser);
- }
- const savedAttachment = await attachment.save();
- return savedAttachment;
- }
- /**
- * 保存到ProjectFile表
- */
- async saveToProjectFile(
- attachment: FmodeObject,
- projectId: string,
- fileType: string,
- spaceId?: string,
- stage?: string
- ): Promise<FmodeObject> {
- const projectFile = new Parse.Object('ProjectFile');
- // 获取项目
- const projectQuery = new Parse.Query("Project");
- const project = await projectQuery.get(projectId);
- // 设置字段
- projectFile.set('project', project);
- projectFile.set('attach', attachment);
- projectFile.set('fileType', fileType);
- projectFile.set('fileUrl', attachment.get('url'));
- projectFile.set('fileName', attachment.get('name'));
- projectFile.set('fileSize', attachment.get('size'));
- if (stage) {
- projectFile.set('stage', stage);
- }
- // ✨ 增强:完整保存元数据到 ProjectFile.data,包括审批状态等
- const attachmentMetadata = attachment.get('metadata') || {};
- const deliverableId = attachmentMetadata.deliverableId;
-
- const data = {
- spaceId,
- uploadedAt: new Date(),
- fileType,
- deliverableId,
- // ✨ 保存所有元数据(包含 approvalStatus, uploadedByName, uploadedById 等)
- ...attachmentMetadata
- };
- projectFile.set('data', data);
- // 设置上传者
- const currentUser = Parse.User.current();
- if (currentUser) {
- projectFile.set('uploadedBy', currentUser);
- }
- const savedProjectFile = await projectFile.save();
- console.log('✅ ProjectFile已保存,data字段:', savedProjectFile.get('data'));
- return savedProjectFile;
- }
- /**
- * 删除项目文件
- */
- async deleteProjectFile(projectFileId: string): Promise<void> {
- try {
- // 删除ProjectFile记录
- const ProjectFile = new Parse.Object('ProjectFile');
- const query = new Parse.Query("ProjectFile");
- const projectFile = await query.get(projectFileId);
- // 删除Attachment记录
- const attachment = projectFile.get('attach');
- if (attachment) {
- await attachment.destroy();
- }
- // 删除ProjectFile记录
- await projectFile.destroy();
- } catch (error) {
- console.error('删除项目文件失败:', error);
- throw error;
- }
- }
- /**
- * 获取项目文件列表
- */
- async getProjectFiles(
- projectId: string,
- filters?: {
- fileType?: string;
- spaceId?: string;
- stage?: string;
- }
- ): Promise<FmodeObject[]> {
- try {
- const ProjectFile = new Parse.Object('ProjectFile');
- const query = new Parse.Query("ProjectFile");
- // 关联项目查询
- const Project = new Parse.Object('Project');
- const projectQuery = new Parse.Query("Project");
- projectQuery.equalTo('objectId', projectId);
- query.matchesQuery('project', projectQuery);
- query.include('attach', 'uploadedBy');
- query.descending('createdAt');
- // 应用过滤器
- if (filters?.fileType) {
- query.equalTo('fileType', filters.fileType);
- }
- if (filters?.stage) {
- query.equalTo('stage', filters.stage);
- }
- const results = await query.find();
- // 如果有空间ID过滤,从data中筛选
- if (filters?.spaceId) {
- return results.filter(result => {
- const data = result.get('data');
- return data?.spaceId === filters.spaceId;
- });
- }
- return results;
- } catch (error) {
- console.error('获取项目文件列表失败:', error);
- throw error;
- }
- }
- /**
- * 批量上传文件
- */
- async uploadMultipleFiles(
- files: File[],
- projectId: string,
- fileType: string,
- spaceId?: string,
- stage?: string,
- onProgress?: (fileIndex: number, progress: number) => void
- ): Promise<NovaFile[]> {
- const results: NovaFile[] = [];
- for (let i = 0; i < files.length; i++) {
- const file = files[i];
- try {
- const uploadedFile = await this.uploadProjectFile(
- file,
- projectId,
- fileType,
- spaceId,
- stage,
- undefined,
- (progress) => {
- if (onProgress) {
- onProgress(i, progress);
- }
- }
- );
- results.push(uploadedFile);
- } catch (error) {
- console.error(`文件 ${file.name} 上传失败:`, error);
- // 继续上传其他文件
- }
- }
- return results;
- }
- /**
- * 验证文件
- */
- validateFile(file: File, maxSize: number = 50 * 1024 * 1024, allowedTypes?: string[]): boolean {
- // 检查文件大小
- if (file.size > maxSize) {
- return false;
- }
- // 检查文件类型
- if (allowedTypes && !allowedTypes.includes(file.type)) {
- return false;
- }
- return true;
- }
- /**
- * 获取文件类型标签
- */
- getFileTypeLabel(fileType: string): string {
- const typeMap: Record<string, string> = {
- 'image/jpeg': '图片',
- 'image/png': '图片',
- 'image/gif': '图片',
- 'image/webp': '图片',
- 'video/mp4': '视频',
- 'video/mov': '视频',
- 'video/avi': '视频',
- 'application/pdf': 'PDF',
- 'application/msword': 'Word',
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Word',
- 'application/vnd.ms-excel': 'Excel',
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Excel',
- 'application/vnd.ms-powerpoint': 'PPT',
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'PPT'
- };
- return typeMap[fileType] || '其他';
- }
- /**
- * 格式化文件大小
- */
- formatFileSize(bytes: number): string {
- if (bytes === 0) return '0 B';
- const k = 1024;
- const sizes = ['B', 'KB', 'MB', 'GB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
- }
- /**
- * 上传文件并创建 Attachment 与 ProjectFile 记录,返回 ProjectFile
- */
- async uploadProjectFileWithRecord(
- file: File,
- projectId: string,
- fileType: string,
- spaceId?: string,
- stage?: string,
- additionalMetadata?: any,
- onProgress?: (progress: number) => void
- ): Promise<FmodeObject> {
- try {
- const cid = localStorage.getItem('company');
- if (!cid) {
- throw new Error('公司ID未找到');
- }
- const storage = await NovaStorage.withCid(cid);
- let prefixKey = `project/${projectId}`;
- if (spaceId) {
- prefixKey += `/space/${spaceId}`;
- }
- if (stage) {
- prefixKey += `/stage/${stage}`;
- }
- const uploadedFile = await storage.upload(file, {
- prefixKey,
- onProgress: (progress: { total: { percent: number } }) => {
- if (onProgress) {
- onProgress(progress.total.percent);
- }
- }
- });
- const attachment = await this.saveToAttachmentTable(
- uploadedFile,
- projectId,
- fileType,
- spaceId,
- stage,
- additionalMetadata
- );
- const projectFile = await this.saveToProjectFile(
- attachment,
- projectId,
- fileType,
- spaceId,
- stage
- );
- return projectFile;
- } catch (error) {
- console.error('上传并创建ProjectFile失败:', error);
- throw error;
- }
- }
- }
|