Forráskód Böngészése

Merge branch 'master' of http://git.fmode.cn:3000/nkkj/yss-project

ryanemax 1 napja
szülő
commit
6594b484e8

+ 4 - 4
package-lock.json

@@ -55,7 +55,7 @@
         "echarts": "^6.0.0",
         "esdk-obs-browserjs": "^3.25.6",
         "eventemitter3": "^5.0.1",
-        "fmode-ng": "^0.0.221",
+        "fmode-ng": "^0.0.222",
         "highlight.js": "^11.11.1",
         "jquery": "^3.7.1",
         "markdown-it": "^14.1.0",
@@ -9504,9 +9504,9 @@
       "license": "ISC"
     },
     "node_modules/fmode-ng": {
-      "version": "0.0.221",
-      "resolved": "https://registry.npmmirror.com/fmode-ng/-/fmode-ng-0.0.221.tgz",
-      "integrity": "sha512-Veafi8p8efJ2LDGPWZ2Gm6Zj6pDu2IBBNkPtV9WTxQVWCCLJg+6xiCI9KQAd17M84vhO4pQR0JFReMvAN1E1fQ==",
+      "version": "0.0.222",
+      "resolved": "https://registry.npmmirror.com/fmode-ng/-/fmode-ng-0.0.222.tgz",
+      "integrity": "sha512-GRB+eFjs08q+u9yF6tXvd8FQrfvIrSIEkbMiYoFC66kvG4Nw2ryRzLaMm62mph/j0o6VlBRumkrObuLBc+CSKQ==",
       "license": "COPYRIGHT © 未来飞马 未来全栈 www.fmode.cn All RIGHTS RESERVED",
       "dependencies": {
         "tslib": "^2.3.0"

+ 1 - 1
src/app/pages/auth/login/login.html

@@ -1,7 +1,7 @@
 <div class="login-container">
   <div class="login-card">
     <div class="brand">
-      <div class="logo"><img src="/assets/logo.jpg" width="100px" alt="" srcset=""></div>
+      <div class="logo"><img src="assets/logo.jpg" width="100px" alt="" srcset=""></div>
       <h1>欢迎使用 映三色 智慧项目系统</h1>
       
     </div>

+ 6 - 6
src/modules/project/pages/project-detail/stages/stage-delivery.component.html

@@ -73,7 +73,7 @@
                         @if (canEdit && isDesigner && deliverable.status === 'draft') {
                           <button
                             class="delete-button"
-                            (click)="deleteFile(group.spaceName, deliverable.processType, file?.id)">
+                            (click)="deleteFile(deliverable.spaceId, deliverable.processType, file?.id)">
                             <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                               <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144m224 0L144 368"/>
                             </svg>
@@ -88,7 +88,7 @@
                   <input
                     type="file"
                     accept="image/*,video/*,.pdf"
-                    (change)="uploadFile($event, group.spaceName, deliverable.processType)"
+                    (change)="uploadFile($event, deliverable.spaceId, deliverable.processType)"
                     [disabled]="uploading"
                     hidden
                     #fileInput />
@@ -113,7 +113,7 @@
                     @if (!deliverable.qualityCheck) {
                       <button
                         class="btn btn-outline btn-sm"
-                        (click)="performQualityCheck(group.spaceName, deliverable.processType)">
+                        (click)="performQualityCheck(deliverable.spaceId, deliverable.processType)">
                         开始自查
                       </button>
                     }
@@ -131,7 +131,7 @@
 
                     <button
                       class="btn btn-primary btn-block"
-                      (click)="deliverable.qualityCheck.checked = true; submitForReview(group.spaceName, deliverable.processType)"
+                      (click)="deliverable.qualityCheck.checked = true; submitForReview(deliverable.spaceId, deliverable.processType)"
                       [disabled]="!isQualityCheckAllPassed(deliverable)">
                       <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                         <path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/>
@@ -159,7 +159,7 @@
                   <div class="review-buttons">
                     <button
                       class="btn btn-success"
-                      (click)="reviewDeliverable(group.spaceName, deliverable.processType, 'approved', reviewComments.value || '')">
+                      (click)="reviewDeliverable(deliverable.spaceId, deliverable.processType, 'approved', reviewComments.value || '')">
                       <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                         <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M416 128L192 384l-96-96"/>
                       </svg>
@@ -167,7 +167,7 @@
                     </button>
                     <button
                       class="btn btn-danger"
-                      (click)="reviewDeliverable(group.spaceName, deliverable.processType, 'rejected', reviewComments.value || '')">
+                      (click)="reviewDeliverable(deliverable.spaceId, deliverable.processType, 'rejected', reviewComments.value || '')">
                       <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                         <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M368 368L144 144m224 0L144 368"/>
                       </svg>

+ 24 - 36
src/modules/project/pages/project-detail/stages/stage-delivery.component.ts

@@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
-import { NovaUploadService } from 'fmode-ng/storage';
 import { ProjectFileService } from '../../../services/project-file.service';
 import { ProductSpaceService, Project } from '../../../services/product-space.service';
 
@@ -166,9 +165,6 @@ export class StageDeliveryComponent implements OnInit {
   uploading: boolean = false;
   saving: boolean = false;
 
-  // 注入上传服务
-  private uploadService: NovaUploadService = inject(NovaUploadService);
-
   constructor(
     private route: ActivatedRoute,
     private projectFileService: ProjectFileService,
@@ -286,13 +282,13 @@ export class StageDeliveryComponent implements OnInit {
           url: file.get('fileUrl'),
           name: file.get('fileName'),
           type: this.getFileType(file.get('fileName')),
-          uploadTime: file.createdAt,
+          uploadTime: file.get('uploadedAt') || file.get('data')?.uploadedAt || file.createdAt,
           uploadBy: {
             id: file.get('uploadedBy')?.id,
             name: file.get('uploadedBy')?.get('name')
           },
           size: file.get('fileSize'),
-          metadata: file.get('metadata')
+          metadata: file.get('data')?.metadata
         }));
       }
 
@@ -505,7 +501,7 @@ export class StageDeliveryComponent implements OnInit {
     if (!files || files.length === 0) return;
 
     const deliverable = this.getDeliverable(spaceId, processType);
-    if (!deliverable) return;
+    if (!deliverable || !this.project || !this.currentUser) return;
 
     try {
       this.uploading = true;
@@ -519,8 +515,8 @@ export class StageDeliveryComponent implements OnInit {
           continue;
         }
 
-        // 使用ProjectFileService上传文件
-        const uploadedFile = await this.projectFileService.uploadProjectFile(
+        // 统一使用服务方法:上传并创建记录,返回 ProjectFile
+        const savedProjectFile = await this.projectFileService.uploadProjectFileWithRecord(
           file,
           this.projectId,
           'deliverable',
@@ -533,29 +529,26 @@ export class StageDeliveryComponent implements OnInit {
           }
         );
 
-        // 添加到交付物文件列表
+        // 添加到交付物文件列表,使用 ProjectFile 的真实ID以支持删除
         deliverable.files.push({
-          id: uploadedFile.md5,
-          url: uploadedFile.url,
-          name: uploadedFile.name,
-          type: this.getFileType(uploadedFile.name),
-          uploadTime: new Date(),
+          id: savedProjectFile.id,
+          url: savedProjectFile.get('fileUrl'),
+          name: savedProjectFile.get('fileName'),
+          type: this.getFileType(savedProjectFile.get('fileName') || ''),
+          uploadTime: savedProjectFile.get('uploadedAt') || new Date(),
           uploadBy: {
-            id: this.currentUser?.id,
-            name: this.currentUser?.get('name')
+            id: savedProjectFile.get('uploadedBy')?.id,
+            name: savedProjectFile.get('uploadedBy')?.get('name')
           },
-          size: uploadedFile.size,
-          metadata: uploadedFile.metadata
+          size: savedProjectFile.get('fileSize'),
+          metadata: savedProjectFile.get('data')?.metadata
         });
       }
 
       this.cdr.markForCheck();
-      // await this.saveDraft();
-      // alert('上传成功');
 
     } catch (err) {
       console.error('上传失败:', err);
-      // alert('上传失败');
     } finally {
       this.uploading = false;
     }
@@ -566,7 +559,7 @@ export class StageDeliveryComponent implements OnInit {
    */
   async deleteFile(spaceId: string, processType: string, fileId: string) {
     try {
-      // 从数据库删除
+      // 从数据库删除(参数为 ProjectFile 记录ID)
       await this.projectFileService.deleteProjectFile(fileId);
 
       const deliverable = this.getDeliverable(spaceId, processType);
@@ -574,8 +567,6 @@ export class StageDeliveryComponent implements OnInit {
         deliverable.files = deliverable.files.filter((file: any) => file.id !== fileId);
         this.cdr.markForCheck();
       }
-
-      // await this.saveDraft();
     } catch (error) {
       console.error('删除文件失败:', error);
     }
@@ -584,11 +575,10 @@ export class StageDeliveryComponent implements OnInit {
   /**
    * 进行质量自查
    */
-  performQualityCheck(spaceName: string, processType: string) {
-    const deliverable = this.getDeliverable(spaceName, processType);
+  performQualityCheck(spaceId: string, processType: string) {
+    const deliverable = this.getDeliverable(spaceId, processType);
     if (!deliverable) return;
 
-    // 初始化质量检查清单
     const template = this.qualityCheckTemplates[processType as keyof typeof this.qualityCheckTemplates];
     if (!deliverable.qualityCheck) {
       deliverable.qualityCheck = {
@@ -601,11 +591,10 @@ export class StageDeliveryComponent implements OnInit {
   /**
    * 提交交付物给组长审核
    */
-  async submitForReview(spaceName: string, processType: string) {
-    const deliverable = this.getDeliverable(spaceName, processType);
+  async submitForReview(spaceId: string, processType: string) {
+    const deliverable = this.getDeliverable(spaceId, processType);
     if (!deliverable) return;
 
-    // 验证
     if (deliverable.files.length === 0) {
       alert('请先上传交付物文件');
       return;
@@ -622,7 +611,6 @@ export class StageDeliveryComponent implements OnInit {
       deliverable.status = 'submitted';
       await this.saveDraft();
 
-      // TODO: 发送企微通知给组长
       alert('已提交审核');
 
     } catch (err) {
@@ -636,13 +624,13 @@ export class StageDeliveryComponent implements OnInit {
   /**
    * 组长审核(通过/驳回)
    */
-  async reviewDeliverable(spaceName: string, processType: string, result: 'approved' | 'rejected', comments: string) {
+  async reviewDeliverable(spaceId: string, processType: string, result: 'approved' | 'rejected', comments: string) {
     if (!this.isTeamLeader && !this.canEdit) {
       alert('您没有审核权限');
       return;
     }
 
-    const deliverable = this.getDeliverable(spaceName, processType);
+    const deliverable = this.getDeliverable(spaceId, processType);
     if (!deliverable) return;
 
     try {
@@ -675,8 +663,8 @@ export class StageDeliveryComponent implements OnInit {
   /**
    * 创建问题反馈
    */
-  async createIssue(spaceName: string, processType: string, issue: any) {
-    const deliverable = this.getDeliverable(spaceName, processType);
+  async createIssue(spaceId: string, processType: string, issue: any) {
+    const deliverable = this.getDeliverable(spaceId, processType);
     if (!deliverable || !deliverable.review) return;
 
     deliverable.review.issues.push(issue);

+ 64 - 1
src/modules/project/services/project-file.service.ts

@@ -148,11 +148,13 @@ export class ProjectFileService {
       projectFile.set('stage', stage);
     }
 
-    // 设置扩展数据
+    // 新增:从附件元数据中读取 deliverableId 并写入 ProjectFile.data
+    const deliverableId = attachment.get('metadata')?.deliverableId;
     const data = {
       spaceId,
       uploadedAt: new Date(),
       fileType,
+      deliverableId,
       metadata: attachment.get('metadata')
     };
     projectFile.set('data', data);
@@ -335,4 +337,65 @@ export class ProjectFileService {
 
     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) => {
+          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;
+    }
+  }
 }