Răsfoiți Sursa

feat: project member with inGroup check

ryanemax 1 zi în urmă
părinte
comite
e0343c22f3

+ 2 - 2
src/modules/project/components/project-members-modal/project-members-modal.component.html

@@ -166,7 +166,7 @@
 
               <!-- 操作按钮 -->
               <div class="member-actions">
-                @if (member.isInProjectTeam && !member.isInGroupChat && isWxworkEnvironment) {
+                @if (member.isInProjectTeam && !member.isInGroupChat ) {
                   <button
                     class="action-btn add-btn"
                     (click)="addMemberToGroupChat(member)"
@@ -176,7 +176,7 @@
                       <line x1="12" y1="8" x2="12" y2="16"></line>
                       <path d="M8 12h8"></path>
                     </svg>
-                    <span>添加</span>
+                    <span>邀请进群</span>
                   </button>
                 }
 

+ 34 - 40
src/modules/project/components/project-members-modal/project-members-modal.component.ts

@@ -2,14 +2,14 @@ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { FmodeObject, FmodeQuery, FmodeParse } from 'fmode-ng/parse';
-import { WxworkCorp } from 'fmode-ng/core';
+import { WxworkCorp, WxworkSDK } from 'fmode-ng/core';
 
 const Parse = FmodeParse.with('nova');
 
 export interface ProjectMember {
   id: string;
   name: string;
-  userId: string;
+  userid: string;
   avatar?: string;
   role: string;
   department?: string;
@@ -58,8 +58,12 @@ export class ProjectMembersModalComponent implements OnInit {
 
   // 企业微信API
   private wecorp: WxworkCorp | null = null;
+  private wwsdk: WxworkSDK | null = null;
 
+  isWechat:boolean = false;
   constructor() {
+    let ua = navigator.userAgent.toLowerCase();
+    this.isWechat = ua.indexOf('micromessenger') !== -1;
     this.checkWxworkEnvironment();
   }
 
@@ -77,20 +81,9 @@ export class ProjectMembersModalComponent implements OnInit {
 
   private checkWxworkEnvironment(): void {
     // 检查是否在企业微信环境中
-    this.isWxworkEnvironment = typeof window !== 'undefined' &&
-                              ((window as any).wx !== undefined ||
-                               (window as any).WWOpenData !== undefined ||
-                               location.hostname.includes('work.weixin.qq.com'));
-
-    if (this.isWxworkEnvironment && this.cid) {
-      try {
-        this.wecorp = new WxworkCorp(this.cid);
-        console.log('✅ 企业微信环境检测成功');
-      } catch (error) {
-        console.warn('⚠️ 企业微信SDK初始化失败:', error);
-        this.isWxworkEnvironment = false;
-      }
-    }
+    this.wecorp = new WxworkCorp(this.cid);
+    this.wwsdk = new WxworkSDK({cid:this.cid,appId:'crm'});
+    console.log('✅ 企业微信环境检测成功');
   }
 
   async loadMembers(): Promise<void> {
@@ -104,9 +97,15 @@ export class ProjectMembersModalComponent implements OnInit {
       const projectTeamMembers = await this.loadProjectTeamMembers();
 
       // 2. 加载群聊成员
+      if(!this.groupChat?.id){
+        const gcQuery2 = new Parse.Query('GroupChat');
+        gcQuery2.equalTo('project', this.project?.id);
+        this.groupChat = await gcQuery2.first();
+      }
       const groupChatMembers = await this.loadGroupChatMembers();
 
       // 3. 合并成员数据
+      console.log("999",projectTeamMembers, groupChatMembers)
       this.mergeMembersData(projectTeamMembers, groupChatMembers);
 
       this.calculateStats();
@@ -125,8 +124,7 @@ export class ProjectMembersModalComponent implements OnInit {
       if (this.project) {
         query.equalTo('project', this.project.toPointer());
       }
-      query.include('profile');
-      query.include('department');
+      query.include('profile','department','profile.department');
       query.notEqualTo('isDeleted', true);
 
       return await query.find();
@@ -141,12 +139,7 @@ export class ProjectMembersModalComponent implements OnInit {
 
     try {
       const memberList = this.groupChat.get('member_list') || [];
-      return memberList.map((member: any) => ({
-        userid: member.userid,
-        name: member.name,
-        type: member.type || 1,
-        avatar: member.avatar
-      }));
+      return memberList
     } catch (error) {
       console.error('加载群聊成员失败:', error);
       return [];
@@ -163,7 +156,7 @@ export class ProjectMembersModalComponent implements OnInit {
         const member: ProjectMember = {
           id: profile.id,
           name: profile.get('name') || '未知',
-          userId: profile.get('userid') || '',
+          userid: profile.get('userid') || '',
           avatar: profile.get('data')?.avatar,
           role: profile.get('roleName') || '未知',
           department: profile.get('department')?.get('name'),
@@ -180,7 +173,7 @@ export class ProjectMembersModalComponent implements OnInit {
     groupChatMembers.forEach(groupMember => {
       // 查找是否已在项目团队中
       const existingMember = Array.from(memberMap.values()).find(
-        m => m.userId === groupMember.userid || m.name === groupMember.name
+        m => m.userid === groupMember.userid || m.name === groupMember.name
       );
 
       if (existingMember) {
@@ -190,7 +183,7 @@ export class ProjectMembersModalComponent implements OnInit {
         const member: ProjectMember = {
           id: groupMember.userid,
           name: groupMember.name,
-          userId: groupMember.userid,
+          userid: groupMember.userid,
           avatar: groupMember.avatar,
           role: groupMember.type === 1 ? '外部联系人' : '内部成员',
           isInGroupChat: true,
@@ -259,34 +252,35 @@ export class ProjectMembersModalComponent implements OnInit {
   }
 
   async addMemberToGroupChat(member: ProjectMember): Promise<void> {
-    if (!this.isWxworkEnvironment || !this.wecorp || !this.groupChat) {
-      alert('当前环境不支持此操作');
-      return;
+   
+    if(!this.isWechat){
+      alert("请在企业微信客户端添加")
     }
-
-    if (!member.userId) {
+    if (!member.userid) {
       alert('该成员没有用户ID,无法添加到群聊');
       return;
     }
 
     try {
-      const chatId = this.groupChat.get('chat_id');
+      const chatId = this.groupChat?.get('chat_id');
       if (!chatId) {
         alert('群聊ID不存在');
         return;
       }
 
-      console.log(`🚀 开始添加成员 ${member.name} (${member.userId}) 到群聊 ${chatId}`);
+      console.log(`🚀 开始添加成员 ${member.name} (${member.userid}) 到群聊 ${chatId}`);
 
       // TODO: 实现正确的企业微信API调用
-      // await this.wecorp.appchat.updateEnterpriseChat({
-      //   chatId: chatId,
-      //   userIdsToAdd: [member.userId]
-      // });
+      let result = await this.wwsdk?.ww.updateEnterpriseChat({
+        chatId: chatId,
+        userIdsToAdd: [member.userid]
+      });
 
       // 临时:直接更新本地状态用于演示
-      member.isInGroupChat = true;
-      this.calculateStats();
+      if(result){
+        member.isInGroupChat = true;
+        this.calculateStats();
+      }
 
       alert(`✅ 已将 ${member.name} 添加到群聊`);
       console.log(`✅ 成功添加成员 ${member.name} 到群聊`);

+ 7 - 9
src/modules/project/components/team-assign/team-assign.component.html

@@ -2,10 +2,6 @@
 <div class="card designer-card">
   <div class="card-header">
     <h3 class="card-title">
-      <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-        <path fill="currentColor" d="M402 168c-2.93 40.67-33.1 72-66 72s-63.12-31.32-66-72c-3-42.31 26.37-72 66-72s69 30.46 66 72z"/>
-        <path fill="currentColor" d="M336 304c-65.17 0-127.84 32.37-143.54 95.41-2.08 8.34 3.15 16.59 11.72 16.59h263.65c8.57 0 13.77-8.25 11.72-16.59C463.85 335.36 401.18 304 336 304z"/>
-      </svg>
       设计师分配
     </h3>
     <p class="card-subtitle">先选择项目组,再选择组员</p>
@@ -91,13 +87,14 @@
         } @else {
           <div class="designer-grid">
             @for (designer of departmentMembers; track designer.id) {
+             @if(designer?.get){
               <div
                 class="designer-item"
-                [class.selected]="selectedDesigner?.id === designer.id"
+                [class.selected]="selectedDesigner?.id === designer?.id"
                 (click)="selectDesigner(designer)">
                 <div class="designer-avatar">
-                  @if (designer.get('data')?.avatar) {
-                    <img [src]="designer.get('data').avatar" alt="设计师头像" />
+                  @if (designer?.get('data')?.avatar) {
+                    <img [src]="designer?.get('data').avatar" alt="设计师头像" />
                   } @else {
                     <svg class="icon avatar-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 48zm0 60a60 60 0 11-60 60 60 60 0 0160-60zm0 336c-63.6 0-119.92-36.47-146.39-89.68C109.74 329.09 176.24 296 256 296s146.26 33.09 146.39 58.32C376.92 407.53 319.6 444 256 444z"/>
@@ -105,15 +102,16 @@
                   }
                 </div>
                 <div class="designer-info">
-                  <h4>{{ designer.get('name') }}</h4>
+                  <h4>{{ designer?.get('name') }}</h4>
                   <p>{{ getDesignerWorkload(designer) }}</p>
                 </div>
-                @if (selectedDesigner?.id === designer.id) {
+                @if (selectedDesigner?.id === designer?.id) {
                   <svg class="icon selected-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>
                 }
               </div>
+              }
             }
           </div>
         }

+ 8 - 0
src/modules/project/components/team-assign/team-assign.component.scss

@@ -1,5 +1,13 @@
 /* 设计师分配组件样式(复用 StageOrder 风格) */
 
+
+.card{
+  background: var(--white);
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  padding: 15px;
+}
+
 .designer-card {
   .section-title {
     font-size: 15px;

+ 5 - 3
src/modules/project/components/team-assign/team-assign.component.ts

@@ -78,7 +78,7 @@ export class TeamAssignComponent implements OnInit {
       deptQuery.notEqualTo('isDeleted', true);
       deptQuery.ascending('name');
       this.departments = await deptQuery.find();
-
+      console.log("this.departments",this.departments)
       // 加载项目团队
       await this.loadProjectTeams();
 
@@ -152,13 +152,14 @@ export class TeamAssignComponent implements OnInit {
       if (leader) {
         this.departmentMembers.unshift(leader);
       }
-      return this.departmentMembers;
+      this.loadingMembers = false;
     } catch (err) {
       console.error('加载项目组成员失败:', err);
     } finally {
       this.loadingMembers = false;
     }
-    return [];
+    this.cdr.detectChanges()
+    return this.departmentMembers;
   }
 
   selectDesigner(designer: FmodeObject) {
@@ -231,6 +232,7 @@ export class TeamAssignComponent implements OnInit {
         const team = new ProjectTeam();
         team.set('project', this.project.toPointer());
         team.set('profile', this.assigningDesigner.toPointer());
+        team.set('department', this.assigningDesigner.get("department"));
         team.set('role', '组员');
         team.set('data', {
           spaces: this.selectedSpaces,

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

@@ -119,12 +119,12 @@
   </app-project-files-modal>
 
   <!-- 成员模态框 -->
-  <app-project-members-modal
-    [project]="project"
-    [groupChat]="groupChat"
-    [currentUser]="currentUser"
-    [cid]="cid"
-    [isVisible]="showMembersModal"
-    (close)="closeMembersModal()">
-  </app-project-members-modal>
+     <app-project-members-modal
+     [project]="project"
+     [groupChat]="groupChat"
+     [currentUser]="currentUser"
+     [cid]="cid"
+     [isVisible]="showMembersModal"
+     (close)="closeMembersModal()">
+    </app-project-members-modal>
 </div>

+ 10 - 3
src/modules/project/pages/project-detail/project-detail.component.ts

@@ -168,7 +168,7 @@ export class ProjectDetailComponent implements OnInit {
         if (this.projectId) {
           // 通过 projectId 加载(从后台进入)
           const query = new Parse.Query('Project');
-          query.include('customer', 'assignee');
+          query.include('customer', 'assignee','department','department.leader');
           this.project = await query.get(this.projectId);
         } else if (this.chatId) {
           // 通过 chat_id 查找项目(从企微群聊进入)
@@ -178,7 +178,14 @@ export class ProjectDetailComponent implements OnInit {
             const gcQuery = new Parse.Query('GroupChat');
             gcQuery.equalTo('chat_id', this.chatId);
             gcQuery.equalTo('company', companyId);
-            const groupChat = await gcQuery.first();
+            let groupChat = await gcQuery.first();
+
+            if(!groupChat?.id){
+              const gcQuery2 = new Parse.Query('GroupChat');
+              gcQuery2.equalTo('project', this.projectId);
+              gcQuery2.equalTo('company', companyId);
+              groupChat = await gcQuery2.first();
+            }
 
             if (groupChat) {
               this.groupChat = groupChat;
@@ -186,7 +193,7 @@ export class ProjectDetailComponent implements OnInit {
 
               if (projectPointer) {
                 const pQuery = new Parse.Query('Project');
-                pQuery.include('customer', 'assignee');
+                pQuery.include('customer', 'assignee','department','department.leader');
                 this.project = await pQuery.get(projectPointer.id);
               }
             }

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

@@ -168,160 +168,7 @@
       [currentUser]="currentUser">
     </app-team-assign>
 
-    <!-- 6. 项目文件管理 -->
-    <div class="card files-card">
-      <div class="card-header">
-        <h3 class="card-title">
-          <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-            <path fill="currentColor" d="M416 221.25V416a48 48 0 01-48 48H144a48 48 0 01-48-48V96a48 48 0 0148-48h98.75a32 32 0 0122.62 9.37l141.26 141.26a32 32 0 019.37 22.62z"/>
-            <path fill="currentColor" d="M256 56v120a32 32 0 0032 32h120"/>
-          </svg>
-          项目文件
-        </h3>
-        <p class="card-subtitle">
-          支持手动上传和企业微信拖拽上传,文件存储在项目目录中
-          @if (wxFileDropSupported) {
-            <span class="wx-support-indicator">📱 支持企业微信拖拽</span>
-          }
-        </p>
-      </div>
-      <div class="card-content">
-        <!-- 上传区域 -->
-        @if (canEdit) {
-          <div class="upload-section">
-            <div
-              #dropZone
-              class="drop-zone"
-              [class.drag-over]="dragOver"
-              [class.uploading]="isUploading"
-              (click)="triggerFileSelect()"
-              (dragover)="onDragOver($event)"
-              (dragleave)="onDragLeave($event)"
-              (drop)="onDrop($event)">
-
-              <!-- 上传图标和提示 -->
-              @if (!isUploading) {
-                <div class="upload-content">
-                  <div class="upload-icon">
-                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none" stroke="currentColor" stroke-width="2">
-                      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
-                      <polyline points="17,8 12,3 7,8"></polyline>
-                      <line x1="12" y1="3" x2="12" y2="15"></line>
-                    </svg>
-                  </div>
-                  <div class="upload-text">
-                    <p class="upload-title">
-                      拖拽文件到此处或 <span class="upload-link">点击选择文件</span>
-                    </p>
-                    <p class="upload-hint">
-                      @if (wxFileDropSupported) {
-                        支持从企业微信拖拽文件 •
-                      }
-                      支持多文件上传
-                    </p>
-                  </div>
-                </div>
-              }
-
-              <!-- 上传进度 -->
-              @if (isUploading) {
-                <div class="upload-progress">
-                  <div class="progress-circle">
-                    <svg class="progress-svg" viewBox="0 0 36 36">
-                      <path class="progress-bg"
-                        d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
-                      />
-                      <path class="progress-bar"
-                        d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
-                        [style.stroke-dasharray]="uploadProgress + ', 100'"
-                      />
-                    </svg>
-                    <div class="progress-text">{{ uploadProgress | number:'1.0-0' }}%</div>
-                  </div>
-                  <p class="progress-title">正在上传文件...</p>
-                </div>
-              }
-
-              <!-- 隐藏的文件输入 -->
-              <input #fileInput
-                     type="file"
-                     multiple
-                     [disabled]="isUploading"
-                     (change)="onFileSelect($event)"
-                     class="file-input">
-            </div>
-          </div>
-        }
-
-        <!-- 文件列表 -->
-        @if (projectFiles.length > 0) {
-          <div class="files-list">
-            <h4 class="section-title">
-              项目文件 ({{ projectFiles.length }})
-            </h4>
-            <div class="files-grid">
-              @for (file of projectFiles; track file.id) {
-                <div class="file-item">
-                  <div class="file-preview">
-                    @if (isImageFile(file.type)) {
-                      <img [src]="file.url" [alt]="file.name" class="file-image" />
-                    } @else {
-                      <div class="file-icon-large">
-                        {{ getFileIcon(file.type) }}
-                      </div>
-                    }
-                  </div>
-                  <div class="file-info">
-                    <h5 class="file-name" [title]="file.name">{{ file.name }}</h5>
-                    <div class="file-meta">
-                      <span class="file-size">{{ formatFileSize(file.size) }}</span>
-                      <span class="file-date">{{ file.uploadedAt | date:'MM-dd HH:mm' }}</span>
-                      <span class="file-uploader">{{ file.uploadedBy }}</span>
-                    </div>
-                  </div>
-                  <div class="file-actions">
-                    <button
-                      class="action-btn"
-                      (click)="downloadFile(file.url, file.name)"
-                      title="下载文件">
-                      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor">
-                        <path d="M376 232H216V72c0-13.3-10.7-24-24-24s-24 10.7-24 24v160H8c-13.3 0-24 10.7-24 24s10.7 24 24 24h160v160c0 13.3 10.7 24 24 24s24-10.7 24-24V280h160c13.3 0 24-10.7 24-24s-10.7-24-24-24z"/>
-                      </svg>
-                    </button>
-                    @if (canEdit) {
-                      <button
-                        class="action-btn delete-btn"
-                        (click)="deleteProjectFile(file.id)"
-                        title="删除文件">
-                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor">
-                          <path d="M296 64h-80a7.91 7.91 0 00-8 8v56H136a56.16 56.16 0 00-56 56v208a56.16 56.16 0 0056 56h240a56.16 56.16 0 0056-56V184a56.16 56.16 0 00-56-56h-72V72a7.91 7.91 0 00-8-8zm-72 264h96a8 8 0 018 8v16a8 8 0 01-8 8h-96a8 8 0 01-8-8v-16a8 8 0 018-8z"/>
-                        </svg>
-                      </button>
-                    }
-                  </div>
-                </div>
-              }
-            </div>
-          </div>
-        } @else {
-          <div class="empty-files">
-            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" class="empty-icon">
-              <path d="M64 464V48a48 48 0 0148-48h192a48 48 0 0133.94 13.94l83.05 83.05A48 48 0 01384 176v288a48 48 0 01-48 48H112a48 48 0 01-48-48zm176-304h144a16 16 0 0016-16v-16a16 16 0 00-16-16H240a16 16 0 00-16 16v16a16 16 0 0016 16zm48 0h96a16 16 0 0016-16v-16a16 16 0 00-16-16H288a16 16 0 00-16 16v16a16 16 0 0016 16zm-48 96h144a16 16 0 0016-16v-16a16 16 0 00-16-16H240a16 16 0 00-16 16v16a16 16 0 0016 16zm48 0h96a16 16 0 0016-16v-16a16 16 0 00-16-16H288a16 16 0 00-16 16v16a16 16 0 0016 16z"/>
-            </svg>
-            <p class="empty-text">暂无项目文件</p>
-            <p class="empty-hint">
-              @if (wxFileDropSupported) {
-                尝试从企业微信拖拽文件,或点击上方按钮上传
-              } @else {
-                点击上方按钮上传项目文件
-              }
-            </p>
-          </div>
-        }
-      </div>
-    </div>
-
-    <!-- 7. 操作按钮 -->
+    <!-- 6. 操作按钮 -->
     @if (canEdit) {
       <div class="action-buttons">
         <button