Browse Source

feat: stage order task

ryanemax 12 hours ago
parent
commit
33b38016fa

+ 45 - 0
docs/task/2025101713-project.md

@@ -0,0 +1,45 @@
+# 任务:订单分配阶段开发
+
+项目详情一级路由组件是:src/modules/project/pages/project-detail/project-detail.component.ts
+项目详情二级订单分配阶段组件为:src/modules/project/pages/project-detail/stages/stage-order.component.ts
+
+1. 项目详情中,customer,对应的是当前群组客户联系人ContactInfo
+- 待设置状态,客服身份,允许点击选择客户,从当前GroupChat中加载member_list中type为2的外部用户作为联系人
+    - 查询该外部用户的ContactInfo,若存在将customer设置为查到的ContactInfo
+    - 若不存在,则通过this.wecorp!.externalContact.get(member.userid)获取用户信息,并同步创建ContactInfo,并设置为该数据
+- 设置后,则显示客户名称和信息,但是仅客服能看到联系方式和微信,其他成员只允许看到头像和名字
+
+2. 订单分配中
+- 报价工具
+    - 报价区域,需要封装成组件,针对quotation Object json格式进行编辑
+        - 无报价,提示选择场景,引导选择空间,生成报价表
+        - 有报价,直接展示报价明细,其他折叠,当用户点击后再展开编辑
+    - 报价明细,每个场景,可以用表格展示,显示更多信息,点击后展开对应场景细项选择和价格填写
+    - 生成报价表,按钮样式有问题,需要修复
+- 设计师分配
+    - 增加展示已分配组员 ProjectTeam
+        - 展示组员和其负责的空间
+    - 选择组员,弹出是否分配给该设计师
+        - 指派空间场景(报价信息中获取)
+            - 仅有一个空间时候,默认填写好该条
+        - 确认分配
+            - 创建ProjectTeam
+                - .data属性,给其分配的空间场景,方便交付环节匹配和展示
+        - try catch 静默执行加入群聊
+        ```
+            ww.updateEnterpriseChat({
+                chatId: 'CHATID',
+                userIdsToAdd: [
+                    'zhangsan',
+                    'lisi'
+                ]
+            })
+        ```
+
+## 参考文档
+- 数据范式 ./rules/schemas.md
+- 企微调用 ./docs/wxwork/*.md (请仅使用WxworkCorp,避免使用前端SDK)
+    - 仅组员分配,加入群聊部分,静默调用sdk中ww库能力变更添加群成员
+- 开发过程,如果更新了schemas.md内容请同步更新文档
+    - 例如:ProjectTeam,识别项目组员,和.data中负责空间信息
+        - .data也需要预留到后续,记录该组员在该项目的整体表现评估复盘的内容

+ 1 - 1
package.json

@@ -68,7 +68,7 @@
     "echarts": "^6.0.0",
     "esdk-obs-browserjs": "^3.25.6",
     "eventemitter3": "^5.0.1",
-    "fmode-ng": "^0.0.218",
+    "fmode-ng": "^0.0.219",
     "highlight.js": "^11.11.1",
     "jquery": "^3.7.1",
     "markdown-it": "^14.1.0",

+ 2 - 1
src/app/services/profile.service.ts

@@ -1,6 +1,5 @@
 import { Injectable } from '@angular/core';
 import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
-import { WxworkAuth } from 'fmode-ng/core';
 
 const Parse = FmodeParse.with('nova');
 
@@ -90,6 +89,8 @@ export class ProfileService {
    */
   private async syncFromWxwork(cid: string): Promise<FmodeObject | null> {
     try {
+      // 动态导入 WxworkAuth
+      const { WxworkAuth } = await import('fmode-ng/core');
       const wxAuth = new WxworkAuth({ cid, appId: 'crm' });
 
       // 获取用户信息并同步

+ 4 - 2
src/modules/project/pages/contact/contact.component.ts

@@ -2,7 +2,7 @@ import { Component, OnInit, Input } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { Router, ActivatedRoute } from '@angular/router';
 import { IonicModule } from '@ionic/angular';
-import { WxworkSDK, WxworkCorp, WxworkAuth } from 'fmode-ng/core';
+import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
 import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
 import { WxworkSDKService } from '../../services/wxwork-sdk.service';
 import { ProfileService } from '../../../../app/services/profile.service';
@@ -42,7 +42,7 @@ export class CustomerProfileComponent implements OnInit {
   // 企微SDK
   wxwork: WxworkSDK | null = null;
   wecorp: WxworkCorp | null = null;
-  wxAuth: WxworkAuth | null = null;
+  wxAuth: any = null; // WxworkAuth 实例
 
   // 加载状态
   loading: boolean = true;
@@ -133,6 +133,8 @@ export class CustomerProfileComponent implements OnInit {
     if (!this.cid) return;
 
     try {
+      // 动态导入 WxworkAuth 避免导入错误
+      const { WxworkAuth } = await import('fmode-ng/core');
       this.wxAuth = new WxworkAuth({ cid: this.cid, appId: 'crm' });
 
       // 静默授权并同步 Profile,不阻塞页面

+ 4 - 4
src/modules/project/pages/project-detail/project-detail.component.html

@@ -1,20 +1,20 @@
 <div class="header">
   <div class="toolbar">
-    <div class="buttons-start">
+    <!-- <div class="buttons-start">
       <button class="back-button" (click)="goBack()">
         <svg class="icon" viewBox="0 0 512 512">
           <path fill="currentColor" d="M256 48C141.13 48 48 141.13 48 256s93.13 208 208 208 208-93.13 208-208S370.87 48 256 48zm35.31 292.69a16 16 0 11-22.62 22.62l-96-96a16 16 0 010-22.62l96-96a16 16 0 0122.62 22.62L206.63 256z"/>
         </svg>
       </button>
-    </div>
+    </div> -->
     <div class="title">{{ project?.get('title') || '项目详情' }}</div>
-    <div class="buttons-end">
+    <!-- <div class="buttons-end">
       <button class="icon-button">
         <svg class="icon" viewBox="0 0 512 512">
           <path fill="currentColor" d="M256 176a32 32 0 11-32 32 32 32 0 0132-32zM256 80a32 32 0 11-32 32 32 32 0 0132-32zM256 272a32 32 0 11-32 32 32 32 0 0132-32z"/>
         </svg>
       </button>
-    </div>
+    </div> -->
   </div>
 
   <!-- 四阶段导航 -->

+ 4 - 2
src/modules/project/pages/project-detail/project-detail.component.ts

@@ -2,7 +2,7 @@ import { Component, OnInit, Input } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { Router, ActivatedRoute, RouterModule } from '@angular/router';
 import { IonicModule } from '@ionic/angular';
-import { WxworkSDK, WxworkCorp, WxworkAuth } from 'fmode-ng/core';
+import { WxworkSDK, WxworkCorp } from 'fmode-ng/core';
 import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
 import { ProfileService } from '../../../../app/services/profile.service';
 
@@ -42,7 +42,7 @@ export class ProjectDetailComponent implements OnInit {
   // 企微SDK
   wxwork: WxworkSDK | null = null;
   wecorp: WxworkCorp | null = null;
-  wxAuth: WxworkAuth | null = null;
+  wxAuth: any = null; // WxworkAuth 实例
 
   // 加载状态
   loading: boolean = true;
@@ -100,6 +100,8 @@ export class ProjectDetailComponent implements OnInit {
     if (!this.cid) return;
 
     try {
+      // 动态导入 WxworkAuth 避免导入错误
+      const { WxworkAuth } = await import('fmode-ng/core');
       this.wxAuth = new WxworkAuth({ cid: this.cid, appId: 'crm' });
 
       // 静默授权并同步 Profile,不阻塞页面

+ 9 - 5
src/modules/project/pages/project-detail/stages/stage-aftercare.component.ts

@@ -1,9 +1,9 @@
-import { Component, OnInit, Input } from '@angular/core';
+import { Component, OnInit, Input, inject } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
-import { ProjectUploadService } from '../../../services/upload.service';
+import { NovaUploadService } from 'fmode-ng/storage';
 
 const Parse = FmodeParse.with('nova');
 
@@ -84,9 +84,11 @@ export class StageAftercareComponent implements OnInit {
   generating: boolean = false;
   saving: boolean = false;
 
+  // 注入上传服务
+  private uploadService = inject(NovaUploadService);
+
   constructor(
-    private route: ActivatedRoute,
-    private uploadService:ProjectUploadService
+    private route: ActivatedRoute
   ) {}
 
   async ngOnInit() {
@@ -182,7 +184,9 @@ export class StageAftercareComponent implements OnInit {
     try {
       this.uploading = true;
 
-      let url = await this.uploadService?.uploadFile(file)
+      // 使用 NovaUploadService 上传文件
+      const fileResult = await this.uploadService.upload(file);
+      const url = fileResult.url;
 
       // 暂时不使用OCR,需要手动输入金额和支付方式
       this.finalPayment.paymentVouchers.push({

+ 9 - 5
src/modules/project/pages/project-detail/stages/stage-delivery.component.ts

@@ -1,9 +1,9 @@
-import { Component, OnInit, Input } from '@angular/core';
+import { Component, OnInit, Input, inject } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
-import { ProjectUploadService } from '../../../services/upload.service';
+import { NovaUploadService } from 'fmode-ng/storage';
 
 const Parse = FmodeParse.with('nova');
 
@@ -115,9 +115,11 @@ export class StageDeliveryComponent implements OnInit {
   uploading: boolean = false;
   saving: boolean = false;
 
+  // 注入上传服务
+  private uploadService = inject(NovaUploadService);
+
   constructor(
-    private route: ActivatedRoute,
-    private uploadService: ProjectUploadService
+    private route: ActivatedRoute
   ) {}
 
   async ngOnInit() {
@@ -235,7 +237,9 @@ export class StageDeliveryComponent implements OnInit {
     try {
       this.uploading = true;
 
-      let url = await this.uploadService?.uploadFile(file)
+      // 使用 NovaUploadService 上传文件
+      const fileResult = await this.uploadService.upload(file);
+      const url = fileResult.url;
 
       const deliverable = this.getDeliverable(spaceName, processType);
       if (deliverable) {

+ 2 - 2
src/modules/project/pages/project-detail/stages/stage-order.component.html

@@ -564,12 +564,12 @@
 
         <button
           class="btn btn-primary"
-          (click)="submitForApproval()"
+          (click)="submitForOrder()"
           [disabled]="saving">
           <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
             <path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm-38 312.38L137.4 280.8a24 24 0 0133.94-33.94l50.2 50.2 95.74-95.74a24 24 0 0133.94 33.94z"/>
           </svg>
-          提交审批
+          确认订单
         </button>
       </div>
     }

+ 4 - 5
src/modules/project/pages/project-detail/stages/stage-order.component.ts

@@ -530,9 +530,9 @@ export class StageOrderComponent implements OnInit {
   }
 
   /**
-   * 提交审批
+   * 提交订单分配
    */
-  async submitForApproval() {
+  async submitForOrder() {
     if (!this.project || !this.canEdit) return;
 
     // 验证
@@ -571,8 +571,7 @@ export class StageOrderComponent implements OnInit {
 
       await this.saveDraft();
 
-      this.project.set('status', '待审核');
-      this.project.set('currentStage', '订单分配');
+      this.project.set('currentStage', '确认需求');
 
       const data = this.project.get('data') || {};
       const approvalHistory = data.approvalHistory || [];
@@ -585,7 +584,7 @@ export class StageOrderComponent implements OnInit {
           role: this.currentUser!.get('roleName')
         },
         submitTime: new Date(),
-        status: 'pending',
+        status: 'confirm',
         quotationTotal: this.quotation.total,
         department: {
           id: this.selectedDepartment.id,