Procházet zdrojové kódy

feat: new project schemas.md

ryanemax před 3 dny
rodič
revize
372fbe1c35
14 změnil soubory, kde provedl 4646 přidání a 34 odebrání
  1. 13 0
      CLAUDE.md
  2. 974 34
      package-lock.json
  3. 1 0
      package.json
  4. 455 0
      rules/agents.md
  5. 65 0
      rules/comp/asr.md
  6. 66 0
      rules/comp/loading.md
  7. 42 0
      rules/comp/markdown.md
  8. 12 0
      rules/frontend.md
  9. 54 0
      rules/parse.md
  10. 1552 0
      rules/schema/project.md
  11. 1398 0
      rules/schemas.md
  12. 2 0
      src/app/app.config.ts
  13. 12 0
      src/app/app.ts
  14. binární
      src/assets/logo.jpg

+ 13 - 0
CLAUDE.md

@@ -0,0 +1,13 @@
+# 项目开发规则
+- rules/ 规则目录
+    - comp/*.md 组件调用
+    - schema/*.md 数据范式
+    - agents.md 智能体开发
+    - frontend.md 前端开发
+    - parse.md 数据服务
+
+# 项目文档管理
+- docs/ 文档目录
+    - product.md 整体产品描述
+    - prd/<端>-<入口>-<页面>.md 各功能页产品结构
+    - schemas.md 数据范式文档 

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 974 - 34
package-lock.json


+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "@angular/router": "^20.1.0",
     "chart.js": "^4.5.0",
     "echarts": "^6.0.0",
+    "fmode-ng": "^0.0.212",
     "qrcode": "^1.5.4",
     "roboto-fontface": "^0.10.0",
     "rxjs": "~7.8.0",

+ 455 - 0
rules/agents.md

@@ -0,0 +1,455 @@
+
+# 核心概念
+- FmodeChatCompletion 文本补全(单次)
+    - 特点:简单易用,直接输入prompt,获得文本补全结果
+- FmodeChat 对话聊天(多轮)
+    - 特点:应对多轮对话复杂场景,与虚拟角色对话,获得消息列表
+
+# 项目结构
+- chat-class.ts 核心类
+    - interface FmodeChatMessage 飞码消息接口类型
+    - class FmodeChat 聊天会话类(多次消息)
+        - messageList 包含多条消息
+        - mask 面具信息(AI角色定义)
+    - class FmodeChatCompletion 消息补全类(单次消息)
+
+# 核心属性
+- isTalkMode:boolean 是否开启对话模式
+    - true 文本显示后,自动播放音频
+
+
+# AI问答:FmodeChatCompletion 文本补全(单次)
+## 调用方式
+> 注意:消息列表测试期间,保留关键字:FmodeAiTest测试问题,减少实际接口调用消耗,减少开发阶段的tokens支出。
+
+``` ts
+// 引用FmodeChatCompletion类
+import { FmodeChatCompletion } from 'fmode-ng/core/agent/chat/completion';
+
+class CompChatPage{
+
+    responseMessage:string = ""
+    createNewCompletion(){
+        // 创建上下文提示词消息列表
+        let messageList = [
+            {role:"user",content:"填写你发送过的历史消息:可用于Prompt定义AI扮演的角色细节"},
+            {role:"assistant",content:"填写AI回复的历史消息"},
+            {role:"user",content:"FmodeAiTest测试问题"}, // 填写你要发送的消息
+        ]
+
+        let options = {
+          model:"fmode-1.6-cn", 
+        }
+
+        // 创建并发起一条新的消息补全
+        let completion = new FmodeChatCompletion(messageList,options)
+        completion.model = "fmode-1.6-cn" 
+        // 持续更新事件推送的消息体内容绑定至消息变量
+        let send$ = completion.sendCompletion({
+          isDirect:true, // 直接返回不限速
+          temperature: 0.5,
+          presence_penalty: 0,
+          frequency_penalty: 0
+        }).subscribe(message=>{
+            if(typeof message?.content =="string"){
+                this.responseMessage = message?.content
+            }
+            if(message.complete){
+                // 完成消息推送,可执行其他业务逻辑
+
+                // 结束订阅
+                send$.unsubscribe();
+            }
+        })
+    }
+}
+```
+ 
+
+## AI生成:返回JSON格式可程序化调用的结果函数
+
+``` ts
+import { completionJSON } from 'fmode-ng/core/agent/chat/completion';
+
+// 定义 JSON 结构描述
+const clothingSchema = `{
+  "character": "string", // 角色名称
+  "outfit": {
+    "head": {
+      "item": "string", // 头部装饰名称
+      "color": "string" // 颜色色号,格式为 #RRGGBB
+    },
+    "top": {
+      "item": "string", // 上衣名称
+      "color": "string" // 颜色色号
+    },
+    "bottom": {
+      "item": "string", // 下装名称
+      "color": "string" // 颜色色号
+    },
+    "shoes": {
+      "item": "string", // 鞋子名称
+      "color": "string" // 颜色色号
+    },
+    "accessories": [
+      {
+        "item": "string", // 配饰名称
+        "color": "string" // 颜色色号
+      }
+    ]
+  }
+}`;
+
+// 使用函数生成哪吒主题的服装搭配
+async function generateNezhaOutfit() {
+  try {
+    const prompt = `
+      请为哪吒这个经典的中国神话角色设计一套现代服装搭配。
+      要求:
+      1. 保持哪吒的经典特色(红色为主色调,活泼动感)
+      2. 融入现代时尚元素
+      3. 适合年轻人穿着
+      4. 颜色搭配要协调
+      5. 每个部位都要指定具体的颜色色号
+    `;
+
+    const result = await completionJSON(
+      prompt,
+      clothingSchema,
+      (content) => {
+        // 实时显示生成过程
+        console.log('正在生成:', content);
+      }
+    );
+
+    console.log('生成的服装搭配:', result);
+    return result;
+  } catch (error) {
+    console.error('生成失败:', error);
+  }
+}
+
+```
+
+
+## AI对话:聊天弹窗组件完整用法
+
+```
+  // medical-consultation.component.ts
+import { CommonModule } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import { 
+  IonHeader, 
+  IonToolbar, 
+  IonTitle, 
+  IonContent, 
+  IonCard, 
+  IonCardHeader, 
+  IonCardTitle, 
+  IonCardSubtitle, 
+  IonCardContent, 
+  IonAvatar, 
+  IonList, 
+  IonItem, 
+  IonLabel, 
+  IonNote,
+  IonButton, 
+  IonIcon, 
+  IonBadge, 
+  IonListHeader,
+  ModalController,
+  InfiniteScrollCustomEvent,
+  IonInfiniteScroll,
+  IonInfiniteScrollContent,
+  IonSpinner,
+  IonGrid,
+  IonRow,
+  IonCol,
+  IonRefresher,
+  IonRefresherContent
+} from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { 
+  medicalOutline, 
+  medkitOutline, 
+  chatbubbleOutline, 
+  timeOutline, 
+  refreshOutline,
+  arrowForwardOutline
+} from 'ionicons/icons';
+import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
+import {FmodeParse,FmodeObject,FmodeQuery,FmodeUser} from "../../../../core/parse";const Parse = FmodeParse.with("nova");
+
+@Component({
+  selector: 'fm-test-fmode-chat-panel-lifecycle',
+  templateUrl: './test-fmode-chat-panel-lifecycle.component.html',
+  styleUrl: './test-fmode-chat-panel-lifecycle.component.scss',
+  standalone: true,
+  providers:[ModalController],
+  imports: [
+    CommonModule,
+    IonHeader,
+    IonToolbar,
+    IonTitle,
+    IonContent,
+    IonCard,
+    IonCardHeader,
+    IonCardTitle,
+    IonCardSubtitle,
+    IonCardContent,
+    IonAvatar,
+    IonList,
+    IonItem,
+    IonLabel,
+    IonNote,
+    IonButton,
+    IonIcon,
+    IonBadge,
+    IonListHeader,
+    IonInfiniteScroll,
+    IonInfiniteScrollContent,
+    IonSpinner,
+    IonGrid,
+    IonRow,
+    IonCol,
+    IonRefresher,
+    IonRefresherContent
+  ]
+})
+export class TestFmodeChatPanelLifecycleComponent implements OnInit {
+  doctors:FmodeObject[] = [];
+  consultations:FmodeObject[] = [];
+  isLoading = false;
+  isRefreshing = false;
+
+  constructor(private modalCtrl: ModalController) {
+    addIcons({ 
+      medicalOutline, 
+      medkitOutline, 
+      chatbubbleOutline, 
+      timeOutline, 
+      refreshOutline,
+      arrowForwardOutline
+    });
+  }
+
+  async ngOnInit() {
+    await this.loadDoctors();
+    await this.loadConsultations();
+  }
+
+  // 加载医生列表
+  async loadDoctors() {
+    this.isLoading = true;
+    try {
+      const query = new Parse.Query('Doctor');
+      query.include('depart');
+      query.equalTo('available', true);
+      query.descending('createdAt');
+      this.doctors = await query.find();
+    } catch (error) {
+      console.error('加载医生列表失败:', error);
+    } finally {
+      this.isLoading = false;
+    }
+  }
+
+  // 加载咨询记录
+  async loadConsultations(event?: InfiniteScrollCustomEvent) {
+    if (!event) {
+      this.isRefreshing = true;
+    }
+    
+    try {
+      const query = new Parse.Query('DoctorConsult');
+      query.include(['doctor', 'department']);
+      query.equalTo('patient', Parse.User.current());
+      query.descending('updatedAt');
+      query.limit(10);
+      
+      if (event) {
+        query.skip(this.consultations.length);
+      }
+      
+      const results = await query.find();
+      
+      if (event) {
+        this.consultations = [...this.consultations, ...results];
+        event.target.complete();
+        if (results.length < 10) {
+          event.target.disabled = true;
+        }
+      } else {
+        this.consultations = results;
+      }
+    } catch (error) {
+      console.error('加载咨询记录失败:', error);
+    } finally {
+      this.isRefreshing = false;
+    }
+  }
+
+  // 刷新数据
+  async handleRefresh(event: any) {
+    await this.loadDoctors();
+    await this.loadConsultations();
+    event.target.complete();
+  }
+
+ // 处理问诊(新建或恢复)
+async handleConsultation(doctor:FmodeObject, consultation?:FmodeObject) {
+  localStorage.setItem("company", "E4KpGvTEto");
+  
+  // 如果是新建咨询,创建记录
+  if (!consultation) {
+    consultation = new Parse.Object('DoctorConsult');
+    consultation.set('title', `${doctor.get('depart')?.get('name') || '未知科室'}问诊`);
+    consultation.set('doctor', doctor);
+    consultation.set('department', doctor.get('depart'));
+    consultation.set('patient', Parse.User.current());
+    consultation.set('status', '进行中');
+    consultation.set('consultationTime', new Date());
+    // await consultation.save();
+  }
+
+  const options: ChatPanelOptions = {
+    roleId: "2DXJkRsjXK",
+    chatId: consultation?.get('chatId'),
+    onChatInit: (chat: FmodeChat) => {
+      const currentDoctor = consultation?.get('doctor') || doctor;
+      
+      // 设置医生角色信息
+      chat.role.set("name", currentDoctor.get('name'));
+      chat.role.set("title", currentDoctor.get('title'));
+      chat.role.set("desc", `${currentDoctor.get('title')} ${currentDoctor.get('name')}`);
+      chat.role.set("tags", [
+        consultation?.get('department')?.get('name') || currentDoctor.get('depart')?.get('name'), 
+        currentDoctor.get('expertise')
+      ]);
+      chat.role.set("avatar", currentDoctor.get('avatar') || "assets/images/default-doctor.png");
+      
+      // 完整的角色提示词
+      chat.role.set("prompt", `
+# 角色设定
+您是一名亲切和蔼的专业的全科医生,${currentDoctor.get('name')},需要完成一次完整的门诊服务。
+
+# 对话环节
+## 1. 导诊环节:欢迎用户,请用户描述症状
+用户回答后,引导合适科室,开始问诊
+## 2. 问诊环节:询问症状细节、询问病史等关键信息
+用户回答后进入下阶段
+## 3. 检查环节:开检查单,并提醒用户检查后反馈结果
+用户反馈后进入下阶段
+## 4. 处方环节:给出处方内容,同时结尾携带[处方完成]"
+
+# 开始话语
+当您准备好了,可以以一个医生的身份,向来访的用户打招呼。并且每个阶段,主动一些。`);
+
+      // 对话灵感分类
+      const promptCates = [
+        {
+          "img": "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/r1ltv1023812146.png",
+          "name": "外科"
+        },
+        {
+          "img": "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fo81fg034154259.png",
+          "name": "内科"
+        },
+        {
+          "img": "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fc1nqi034201098.png",
+          "name": "心理"
+        }
+      ];
+      
+      // 对话灵感列表
+      const promptList = [
+        {
+          cate: "外科", 
+          img: "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/r1ltv1023812146.png",
+          messageList: [
+            "局部疼痛或肿胀", "伤口出血或感染", "关节活动受限", 
+            "体表肿块或结节", "外伤后活动障碍", "皮肤溃疡不愈合", 
+            "异物刺入或嵌顿", "术后并发症复查", "肢体麻木或无力", 
+            "运动损伤疼痛"
+          ]
+        },
+        {
+          cate: "内科", 
+          img: "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fo81fg034154259.png",
+          messageList: [
+            "反复发热或低热", "持续咳嗽咳痰", "胸闷气短心悸", 
+            "慢性腹痛腹泻", "头晕头痛乏力", "体重骤增或骤减", 
+            "食欲异常或消化不良", "尿频尿急尿痛", "睡眠障碍易醒", 
+            "异常出汗或怕冷"
+          ]
+        },
+        {
+          cate: "心理", 
+          img: "https://file-cloud.fmode.cn/UP2cStyjuk/20231211/fc1nqi034201098.png",
+          messageList: [
+            "持续情绪低落", "焦虑紧张不安", "失眠或睡眠过多", 
+            "注意力难以集中", "社交恐惧回避", "强迫思维或行为", 
+            "记忆减退疑虑", "躯体无器质性疼痛", "自杀倾向念头", 
+            "现实感丧失体验"
+          ]
+        },
+      ];
+      
+      setTimeout(() => {
+        chat.role.set("promptCates", promptCates);
+        
+        const ChatPrompt = Parse.Object.extend("ChatPrompt");
+        chat.promptList = promptList.map(item => {
+          const prompt = new ChatPrompt();
+          prompt.set(item);
+          prompt.img = item.img;
+          return prompt;
+        });
+      }, 500);
+
+      // 功能按钮区域预设
+      chat.leftButtons = [
+        { 
+          title: "话题灵感",
+          showTitle: true,
+          icon: "color-wand-outline",
+          onClick: () => {
+            chat.isPromptModalOpen = true;
+          },
+          show: () => {
+            return chat?.promptList?.length;
+          }
+        },
+        {
+          title: "门诊归档",
+          showTitle: true,
+          icon: "archive-outline",
+          onClick: () => {
+            console.log(chat?.chatSession);
+          },
+          show: () => true
+        }
+      ];
+    },
+    onMessage: (chat: FmodeChat, message: FmodeChatMessage) => {
+      const content = message?.content;
+      if (typeof content === 'string' && content.includes('[处方完成]')) {
+        console.log('处方完成,保存咨询记录');
+        consultation?.set('content', content);
+        consultation?.set('status', '已完成');
+        consultation?.save();
+      }
+    },
+    onChatSaved: (chat: FmodeChat) => {
+      console.log('咨询会话保存', chat?.chatSession?.id);
+      consultation?.set('chatId', chat?.chatSession?.id);
+      consultation?.save();
+    }
+  };
+  
+  openChatPanelModal(this.modalCtrl, options);
+}
+
+  // 格式化时间
+}
+```

+ 65 - 0
rules/comp/asr.md

@@ -0,0 +1,65 @@
+---
+category: agent
+title: LoaidngController
+subtitle: 加载状态区
+name: 'nova-waiting-loading'
+label: waiting
+---
+
+
+# 使用示例
+```
+import { FmodeLoadingController } from 'fmode-ng/core';
+
+const loader = new FmodeLoadingInstance({
+  message: "初始化任务",
+  progress: 0.2,
+  taskIndex: 1,
+  taskTotal: 5
+});
+
+loader.present();
+
+setTimeout(() => {
+  loader.message = "正在处理下一步...";
+  loader.taskIndex = 2;
+  loader.progress = 0.5;
+}, 2000);
+
+setTimeout(() => {
+  loader.message = "任务完成";
+  loader.taskIndex = 5;
+  loader.progress = 1;
+}, 4000);
+
+```
+
+```
+// 用法示例
+const loadingCtrl = new FmodeLoadingController();
+
+// 在组件或逻辑中
+(async () => {
+  const loading = await loadingCtrl.create({
+    message: "任务加载中...",
+    progress: 0,
+    taskIndex: 1,
+    taskTotal: 5,
+    position: "middle",
+  });
+
+  await loading.present();
+
+  // 示例:3秒后修改状态
+  setTimeout(() => {
+    loading.message = "任务2加载中...";
+    loading.taskIndex = 2;
+    loading.updateUI();
+  }, 3000);
+
+  // 10秒后销毁
+  setTimeout(() => {
+    loading.dismiss();
+  }, 10000);
+})();
+```

+ 66 - 0
rules/comp/loading.md

@@ -0,0 +1,66 @@
+---
+category: agent
+title: LoaidngController
+subtitle: 加载状态区
+name: 'nova-waiting-loading'
+label: waiting
+---
+
+
+# 使用示例
+```
+import { TipsController,FmodeLoadingController,FmodeLoadingInstance } from 'fmode-ng/lib/core/agent'; 
+
+const loader = new FmodeLoadingInstance({
+  message: "初始化任务",
+  progress: 0.2,
+  taskIndex: 1,
+  taskTotal: 5
+});
+
+loader.present();
+
+setTimeout(() => {
+  loader.message = "正在处理下一步...";
+  loader.taskIndex = 2;
+  loader.progress = 0.5;
+}, 2000);
+
+setTimeout(() => {
+  loader.message = "任务完成";
+  loader.taskIndex = 5;
+  loader.progress = 1;
+}, 4000);
+
+```
+
+```
+// 用法示例
+const loadingCtrl = new FmodeLoadingController();
+
+// 在组件或逻辑中
+(async () => {
+  const loading = await loadingCtrl.create({
+    message: "任务加载中...",
+    progress: 0,
+    taskIndex: 1,
+    taskTotal: 5,
+    position: "middle",
+  });
+
+  await loading.present();
+
+  // 示例:3秒后修改状态
+  setTimeout(() => {
+    loading.message = "任务2加载中...";
+    loading.taskIndex = 2;
+    loading.updateUI();
+  }, 3000);
+
+  // 10秒后销毁
+  setTimeout(() => {
+    loading.dismiss();
+  }, 10000);
+})();
+```
+

+ 42 - 0
rules/comp/markdown.md

@@ -0,0 +1,42 @@
+---
+category: aigc
+title: MarkdownPreview
+subtitle: 消息渲染组件
+name: 'fm-markdown-preview'
+---
+
+# MarkdownPreview 消息渲染组件
+
+``` html
+<fm-markdown-preview class="content-style" [content]="message?.content"></fm-markdown-preview>
+```
+
+
+# 依赖
+- 项目src/styles.scss必须引用font-awesome
+``` scss
+// styles.scss
+@import '@fortawesome/fontawesome-free/css/all.min.css'; // 字体图标
+@import 'highlight.js/scss/monokai.scss'; // 代码高亮 - monokai主题
+
+```
+
+# 示例
+<example name="fmode-ng-fm-markdown-preview-test-markdown-preview-example" />
+
+# 渲染方法
+- 普通渲染,将已经完整的Markdown文本加载渲染
+``` html
+<fm-markdown-preview [content]="content"></fm-markdown-preview>
+```
+
+- 流式渲染,将流式加载的消息段落渲染
+  - 加载过程,将[render]="false",只渲染纯文本信息
+  - 加载完成,将[render]默认为true,渲染完整的纯文本信息
+``` html
+<!-- 聊天气泡 -->
+    <div class="item-row bubble">
+        <fm-markdown-preview *ngIf="!message?.complete" class="content-style" [content]="message?.content" [render]="false"></fm-markdown-preview>
+        <fm-markdown-preview *ngIf="message?.complete" [content]="message?.content"></fm-markdown-preview>
+    </div>
+```

+ 12 - 0
rules/frontend.md

@@ -0,0 +1,12 @@
+前端开发与设计规则
+- Angular 20+ TypeScript
+    - 可使用各类支持TS的第三方库,如echarts等
+- 模块的划分目录:/src/modules 
+    - shared/ 作为共享组件和服务的模块
+    - xxx/ 各端入口需要独立模块
+- 独立组件优先:为各功能复用性,优先使用独立组件模式
+    - 但需要注意imports合理,补全引用所需的模块
+    - 例如用到管道或者路由需要引用对应模块
+    - 独立组件包含html/ts/scss,模板/控制/样式
+- Control Flow:优先使用@if @for 不要使用*ngFor *ngIf指令
+- DRY原则:共用的组件、数据统一写在shared模块,方便统一调用

+ 54 - 0
rules/parse.md

@@ -0,0 +1,54 @@
+# 数据服务调用规范
+## 配置初始化
+    - 请在app.component完成初始化
+    - 其他页面直接import使用,不必反复初始化
+```
+// 初始化配置(传统风格) 设置默认后端配置 替代原有Parse
+import { FmodeParse } from 'fmode-ng/core';
+const Parse = Fmode.Parse.with("nova")
+```
+
+## 注意 类型的引用
+- 不能直接用Parse.Query和Parse.Object
+- 需要引用新类型
+import { FmodeUser,FmodeObject,FmodeQuery } from 'fmode-ng/core';
+
+## 常用的方法
+
+- 查询 Parse.Query 对应类型 FmodeQuery
+    - 与Parse JS SDK用法一直
+- 对象 Parse.Object 对应类型 FmodeObject
+    - 注意此处和Parse原始extend的用法不同,需要通过构造函数直接构造新的类
+    - 其他用法相关
+``` ts
+// 查询
+let query = new Parse.Query("CrmCustomer");
+  query.startsWith("name","VIP");
+  console.log(await query.find())
+// 保存
+let customer = new Parse.Object("CrmCustormer");
+customer.set("name","TEST001")
+customer = await customer.save()
+customer.set("unit","单位001")
+customer = await customer.save()
+console.log(customer)
+// 删除
+customer.set("isDeleted",true) // 优先使用软删除
+console.log(customer)
+```
+
+- 用户 Parse.User 对应类型 FmodeUser
+    - 其他方法与Parse JS SDK相同
+``` ts
+// 新用户注册
+let user = await Parse.User.signUp("xuanshou1", "123456", {
+            email: "xuanshou@123.com",
+            name: "测试选手"
+});
+// 新用户登录
+let user = await Parse.User.logIn("xuanshou1","123456")
+// 获取当前用户(要求了必须await 调用)
+console.log(await Parse.User.current())
+// 当前用户指针
+console.log(await Parse.User.current()?.toPointer())
+```

+ 1552 - 0
rules/schema/project.md

@@ -0,0 +1,1552 @@
+---
+category: schema
+title: Parse Server 数据范式文档
+subtitle: 项目管理系统数据表结构
+name: 'project-schema'
+label: database
+---
+
+# Parse Server 数据范式 - 项目管理系统
+
+## 概述
+
+本文档详细描述了 nova-project 项目管理系统中使用的 Parse Server 数据表结构,包括表结构、字段说明、关系映射和使用场景。
+
+系统采用**多租户架构**,以 Company 为核心,支持项目管理、任务管理、产品需求管理、OKR目标管理和知识文档管理。
+
+---
+
+## 数据表关系图
+
+### 整体架构(PlantUML)
+
+```plantuml
+@startuml
+!define TABLE(name,desc) class name as "desc" << (T,#FFAAAA) >>
+!define FIELD(name,type) name : type
+
+skinparam classAttributeIconSize 0
+skinparam class {
+    BackgroundColor LightYellow
+    BorderColor Black
+    ArrowColor Black
+}
+
+' ============ 核心实体 ============
+TABLE(Company, "Company\n企业表") {
+    FIELD(objectId, String)
+    FIELD(name, String)
+    FIELD(project, Pointer)
+    FIELD(devModule, Array)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+TABLE(Profile, "Profile\n用户档案表") {
+    FIELD(objectId, String)
+    FIELD(name, String)
+    FIELD(mobile, String)
+    FIELD(email, String)
+    FIELD(company, Pointer→Company)
+    FIELD(department, Pointer→Department)
+    FIELD(user, Pointer→_User)
+    FIELD(identyType, String)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+TABLE(Department, "Department\n部门表") {
+    FIELD(objectId, String)
+    FIELD(name, String)
+    FIELD(type, String)
+    FIELD(company, Pointer→Company)
+    FIELD(parent, Pointer→Department)
+    FIELD(num, Number)
+    FIELD(index, Number)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+' ============ 项目模块 ============
+TABLE(Project, "Project\n项目表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(type, String)
+    FIELD(company, Pointer→Company)
+    FIELD(owner, Pointer→Profile)
+    FIELD(defaultTab, String)
+    FIELD(isClosed, Boolean)
+    FIELD(isStar, Boolean)
+    FIELD(isStarted, Boolean)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+TABLE(ProjectTask, "ProjectTask\n任务表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(project, Pointer→Project)
+    FIELD(company, Pointer→Company)
+    FIELD(assignee, Pointer→Profile)
+    FIELD(owner, Pointer→Profile)
+    FIELD(parent, Pointer→ProjectTask)
+    FIELD(priority, Number)
+    FIELD(stateList, String)
+    FIELD(stateLane, String)
+    FIELD(startDate, Date)
+    FIELD(endDate, Date)
+    FIELD(deadline, Date)
+    FIELD(duration, Number)
+    FIELD(estimate, Number)
+    FIELD(progress, Number)
+    FIELD(tag, Array)
+    FIELD(desc, String)
+    FIELD(cate, String)
+    FIELD(isClosed, Boolean)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+TABLE(ProjectTeam, "ProjectTeam\n项目团队表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(profile, Pointer→Profile)
+    FIELD(role, String)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+}
+
+TABLE(Product, "Product\n产品表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(project, Pointer→Project)
+    FIELD(company, Pointer→Company)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+TABLE(ProductRequire, "ProductRequire\n产品需求表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(type, String)
+    FIELD(project, Pointer→Project)
+    FIELD(company, Pointer→Company)
+    FIELD(product, Pointer→Product)
+    FIELD(module, Pointer→ProductRequire)
+    FIELD(parent, Pointer→ProductRequire)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+' ============ OKR模块 ============
+TABLE(OKR, "OKR\nOKR周期表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(company, Pointer→Company)
+    FIELD(startDate, Date)
+    FIELD(endDate, Date)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+TABLE(OKRObject, "OKRObject\nOKR对象表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(type, String)
+    FIELD(okr, Pointer→OKR)
+    FIELD(company, Pointer→Company)
+    FIELD(department, Pointer→Department)
+    FIELD(profile, Pointer→Profile)
+    FIELD(tree, Pointer→OKRObject)
+    FIELD(parent, Pointer→OKRObject)
+    FIELD(bsc, String)
+    FIELD(value, Number)
+    FIELD(unit, String)
+    FIELD(index, Number)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+' ============ 知识库模块 ============
+TABLE(NoteSpace, "NoteSpace\n知识空间表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(type, String)
+    FIELD(company, Pointer→Company)
+    FIELD(project, Pointer→Project)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+TABLE(NotePad, "NotePad\n文档笔记表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(space, Pointer→NoteSpace)
+    FIELD(profile, Pointer→Profile)
+    FIELD(parent, Pointer→NotePad)
+    FIELD(content, String)
+    FIELD(isDeleted, Boolean)
+    FIELD(createdAt, Date)
+    FIELD(updatedAt, Date)
+}
+
+' ============ 关系连线 ============
+
+' Company 一对多关系
+Company "1" --> "n" Profile : 企业员工
+Company "1" --> "n" Department : 企业部门
+Company "1" --> "n" Project : 企业项目
+Company "1" --> "n" OKR : OKR周期
+
+' Profile 关系
+Profile "n" --> "1" Department : 所属部门
+Profile "1" --> "n" ProjectTask : assignee\n执行任务
+Profile "1" --> "n" ProjectTask : owner\n创建任务
+Profile "1" --> "n" NotePad : 创建笔记
+
+' Department 树状结构
+Department "1" --> "n" Department : parent\n上下级
+
+' Project 相关
+Project "n" --> "1" Profile : owner\n项目负责人
+Project "1" --> "n" ProjectTask : 项目任务
+Project "1" --> "n" Product : 项目产品
+Project "1" --> "1" NoteSpace : Wiki空间
+Project "n" <--> "n" Profile : ProjectTeam\n团队成员
+
+' ProjectTask 树状结构
+ProjectTask "1" --> "n" ProjectTask : parent\n父子任务
+
+' Product 相关
+Product "1" --> "n" ProductRequire : 产品需求
+
+' ProductRequire 树状结构
+ProductRequire "1" --> "n" ProductRequire : module/parent\n层级结构
+
+' OKR 相关
+OKR "1" --> "n" OKRObject : OKR对象
+OKRObject "n" --> "1" Department : 部门OKR
+OKRObject "n" --> "1" Profile : 个人OKR
+OKRObject "1" --> "n" OKRObject : tree/parent\n指标树
+
+' NoteSpace 相关
+NoteSpace "1" --> "n" NotePad : 空间笔记
+
+' NotePad 树状结构
+NotePad "1" --> "n" NotePad : parent\n目录层级
+
+@enduml
+```
+
+---
+
+## 数据表详细说明
+
+### 1. Company(企业表)
+
+**用途**: 多租户系统的核心,所有数据通过 company 字段进行租户隔离。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "1AiWpTEDH9" |
+| name | String | 是 | 企业名称 | "科技有限公司" |
+| project | Pointer | 否 | 关联项目 | → Project |
+| devModule | Array | 否 | 开发模块配置 | ["module1", "module2"] |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**关系**:
+- 一对多: Company → Profile(企业员工)
+- 一对多: Company → Department(企业部门)
+- 一对多: Company → Project(企业项目)
+- 一对多: Company → OKR(OKR周期)
+
+**使用场景**:
+```typescript
+// 获取当前企业ID
+const companyId = localStorage.getItem("Parse/CompanyId") || localStorage.getItem("company");
+
+// 数据隔离查询
+const query = new Parse.Query("Project");
+query.equalTo("company", companyId);
+```
+
+**索引建议**:
+- `objectId` (主键,自动索引)
+- `name`
+
+---
+
+### 2. Profile(用户档案表)
+
+**用途**: 存储用户在企业内的档案信息,关联 Parse 内置 _User 表。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "abc123xyz" |
+| name | String | 是 | 用户姓名 | "张三" |
+| mobile | String | 否 | 手机号 | "13800138000" |
+| email | String | 否 | 邮箱 | "zhangsan@example.com" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| department | Pointer | 否 | 所属部门 | → Department |
+| user | Pointer | 否 | 关联Parse用户 | → _User |
+| identyType | String | 否 | 身份类型 | "admin" / "coders" |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**identyType 枚举值**:
+- `admin`: 管理员权限
+- `coders`: 开发者权限
+- 其他: 普通用户权限
+
+**关系**:
+- 多对一: Profile → Company(所属企业)
+- 多对一: Profile → Department(所属部门)
+- 多对一: Profile → _User(关联用户账号)
+- 一对多: Profile ← ProjectTask (assignee, 执行任务)
+- 一对多: Profile ← ProjectTask (owner, 创建任务)
+- 多对多: Profile ←→ Project(通过 ProjectTeam)
+
+**使用场景**:
+```typescript
+// 获取当前用户档案ID
+const profileId = localStorage.getItem("Parse/ProfileId");
+
+// 查询用户创建的任务
+const query = new Parse.Query("ProjectTask");
+query.equalTo("owner", profileId);
+
+// 查询用户执行的任务
+const query2 = new Parse.Query("ProjectTask");
+query2.equalTo("assignee", profileId);
+
+// 搜索用户(模糊查询)
+const query3 = new Parse.Query("Profile");
+query3.matches("name", keyword, "i"); // 不区分大小写
+query3.matches("mobile", keyword, "i");
+```
+
+**索引建议**:
+- `company + isDeleted`
+- `name + company`
+- `mobile + company`
+- `user`
+
+---
+
+### 3. Department(部门表)
+
+**用途**: 组织架构管理,支持树状结构(多层级部门)。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "dept001" |
+| name | String | 是 | 部门名称 | "技术部" |
+| type | String | 是 | 类型 | "depart" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| parent | Pointer | 否 | 父部门 | → Department |
+| num | Number | 否 | 排序号 | 1 |
+| index | Number | 否 | 索引 | 100 |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**关系**:
+- 多对一: Department → Company(所属企业)
+- 树状结构: Department → Department (parent, 支持多层级)
+- 一对多: Department ← Profile(部门员工)
+- 一对多: Department ← OKRObject(部门OKR)
+
+**使用场景**:
+```typescript
+// 查询企业所有部门
+const query = new Parse.Query("Department");
+query.equalTo("company", companyId);
+query.notEqualTo("isDeleted", true);
+query.ascending("num"); // 按排序号排序
+
+// 查询一级部门(没有父级)
+const rootQuery = new Parse.Query("Department");
+rootQuery.equalTo("company", companyId);
+rootQuery.doesNotExist("parent"); // parent 为空
+
+// 查询某部门的子部门
+const childQuery = new Parse.Query("Department");
+childQuery.equalTo("parent", parentDeptId);
+```
+
+**索引建议**:
+- `company + isDeleted`
+- `parent + company`
+- `num + company`
+
+---
+
+### 4. Project(项目表)
+
+**用途**: 项目管理的核心表,支持多种类型项目。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "proj001" |
+| title | String | 是 | 项目标题 | "CRM系统开发" |
+| type | String | 否 | 项目类型 | "project" / "book" / "plan" / "module" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| owner | Pointer | 是 | 项目负责人 | → Profile |
+| defaultTab | String | 否 | 默认Tab页 | "gantt" / "kanban" / "wiki" |
+| isClosed | Boolean | 否 | 是否已关闭 | false |
+| isStar | Boolean | 否 | 是否星标 | false |
+| isStarted | Boolean | 否 | 是否已启动 | true |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**type 枚举值**:
+- `project`: 项目平台
+- `book`: 电子书籍
+- `plan`: 行动策略
+- `module`: 功能模块
+
+**关系**:
+- 多对一: Project → Company(所属企业)
+- 多对一: Project → Profile (owner, 项目负责人)
+- 一对多: Project → ProjectTask(项目任务)
+- 一对多: Project → Product(项目产品)
+- 一对多: Project → ProductRequire(项目需求)
+- 一对一: Project → NoteSpace(Wiki空间)
+- 多对多: Project ←→ Profile(通过 ProjectTeam)
+
+**使用场景**:
+```typescript
+// 查询项目
+const query = new Parse.Query("Project");
+await query.get(projectId);
+
+// 查询用户创建的项目
+const myQuery = new Parse.Query("Project");
+myQuery.equalTo("owner", profileId);
+myQuery.notEqualTo("isDeleted", true);
+
+// 查询星标项目
+const starQuery = new Parse.Query("Project");
+starQuery.equalTo("isStar", true);
+starQuery.equalTo("company", companyId);
+
+// 按类型筛选
+const typeQuery = new Parse.Query("Project");
+typeQuery.containedIn("type", ["project", "module"]);
+
+// 查询用户参与的项目(通过 ProjectTeam)
+const teamQuery = new Parse.Query("ProjectTeam");
+teamQuery.equalTo("profile", profileId);
+teamQuery.include("project");
+const teams = await teamQuery.find();
+const projects = teams.map(team => team.get("project"));
+```
+
+**索引建议**:
+- `company + isDeleted`
+- `owner + company`
+- `isStar + company`
+- `isStarted + company`
+- `type + company`
+- `updatedAt` (降序)
+
+---
+
+### 5. ProjectTask(项目任务表)
+
+**用途**: 存储项目任务,支持甘特图、看板等多种视图,支持父子任务。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "task001" |
+| title | String | 是 | 任务标题 | "实现用户登录功能" |
+| desc | String | 否 | 任务描述 | "包括前端页面和后端API" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| company | Pointer | 是 | 所属企业 | → Company |
+| assignee | Pointer | 否 | 任务执行人 | → Profile |
+| owner | Pointer | 是 | 任务创建人 | → Profile |
+| parent | Pointer | 否 | 父任务 | → ProjectTask |
+| priority | Number | 否 | 优先级 | 1 / 50 / 70 / 90 / 100 |
+| stateList | String | 否 | 任务状态 | "待分配" / "进行中" / "测试中" / "已完成" / "已上线" |
+| stateLane | String | 否 | 看板泳道 | "默认泳道" |
+| startDate | Date | 是 | 开始时间 | 2024-01-01T00:00:00.000Z |
+| endDate | Date | 否 | 结束时间 | 2024-01-10T00:00:00.000Z |
+| deadline | Date | 否 | 截止时间 | 2024-01-15T00:00:00.000Z |
+| duration | Number | 否 | 实际工时(小时) | 8.5 |
+| estimate | Number | 否 | 预估工时(小时) | 10 |
+| progress | Number | 否 | 完成进度(0-100) | 75 |
+| tag | Array | 否 | 标签数组 | ["前端", "紧急"] |
+| cate | String | 否 | 工作分类 | "frontend" / "backend" / "design" / "testing" |
+| target | String | 否 | 排序目标ID | 用于甘特图排序 |
+| remark | String | 否 | 备注 | "需要先完成设计稿" |
+| approver | Pointer | 否 | 审批人 | → Profile |
+| approvalDate | Date | 否 | 审批日期 | 2024-01-10T00:00:00.000Z |
+| isClosed | Boolean | 否 | 是否已关闭 | false |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**priority 枚举值**:
+- `1`: 最低
+- `50`: 普通(默认)
+- `70`: 较高
+- `90`: 重要
+- `100`: 严重
+
+**stateList 枚举值**:
+```
+待分配 → 进行中 → 测试中 → 已完成 → 已上线
+```
+
+**cate 枚举值**:
+- `requirement`: 需求
+- `frontend`: 前端开发
+- `backend`: 后端开发
+- `design`: 设计
+- `testing`: 测试
+
+**关系**:
+- 多对一: ProjectTask → Project(所属项目)
+- 多对一: ProjectTask → Company(所属企业)
+- 多对一: ProjectTask → Profile (assignee, 执行人)
+- 多对一: ProjectTask → Profile (owner, 创建人)
+- 多对一: ProjectTask → Profile (approver, 审批人)
+- 树状结构: ProjectTask → ProjectTask (parent, 父子任务)
+
+**使用场景**:
+```typescript
+// 查询项目任务
+const query = new Parse.Query("ProjectTask");
+query.equalTo("project", projectId);
+query.notEqualTo("isDeleted", true);
+query.notEqualTo("isClosed", true);
+query.descending("startDate");
+query.include("assignee", "owner");
+
+// 查询用户的任务
+const myTaskQuery = new Parse.Query("ProjectTask");
+myTaskQuery.equalTo("assignee", profileId);
+myTaskQuery.notEqualTo("isDeleted", true);
+
+// 按状态筛选
+const stateQuery = new Parse.Query("ProjectTask");
+stateQuery.containedIn("stateList", ["进行中", "测试中"]);
+
+// 查询即将到期的任务
+const today = new Date();
+const endDate = new Date();
+endDate.setDate(endDate.getDate() + 7); // 7天内到期
+const deadlineQuery = new Parse.Query("ProjectTask");
+deadlineQuery.greaterThanOrEqualTo("deadline", today);
+deadlineQuery.lessThan("deadline", endDate);
+deadlineQuery.notContainedIn("stateList", ["已完成", "已上线"]);
+
+// 查询用户参与的所有项目(SQL方式)
+const sql = `
+SELECT DISTINCT(task."project"), pro."title" AS "name"
+FROM "ProjectTask" task
+LEFT JOIN "Project" pro ON pro."objectId" = task."project"
+WHERE task."isDeleted" IS NOT TRUE
+  AND task."isClosed" IS NOT TRUE
+  AND (task."assignee" = '${profileId}' OR task."owner" = '${profileId}')
+  AND task."project" IS NOT NULL
+`;
+```
+
+**工时统计(SQL)**:
+```sql
+-- 用户工时统计
+SELECT
+    pt."assignee",
+    SUM(pt."d") as duration,      -- 实际工时
+    SUM(pt."e") as estimate        -- 预估工时
+FROM (
+    SELECT *,
+    CASE WHEN "ProjectTask"."duration" IS NOT NULL
+         THEN "ProjectTask"."duration" ELSE 0 END as d,
+    CASE WHEN "ProjectTask"."estimate" IS NOT NULL
+         THEN "ProjectTask"."estimate" ELSE 0 END as e
+    FROM "ProjectTask"
+    WHERE "company" = $1
+      AND ("isDeleted" != true OR "isDeleted" IS NULL)
+      AND "startDate" >= $2
+      AND "startDate" < $3
+) as pt
+GROUP BY pt."assignee"
+```
+
+**索引建议**:
+- `project + isDeleted`
+- `assignee + isDeleted`
+- `owner + isDeleted`
+- `company + startDate`
+- `priority` (降序)
+- `stateList + project`
+- `deadline + stateList`
+
+---
+
+### 6. ProjectTeam(项目团队表)
+
+**用途**: 管理项目成员关系,实现项目与用户的多对多关联。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "team001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| profile | Pointer | 是 | 团队成员 | → Profile |
+| role | String | 否 | 成员角色 | "开发" / "测试" / "PM" |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 加入时间 | 2024-01-01T00:00:00.000Z |
+
+**关系**:
+- 多对一: ProjectTeam → Project
+- 多对一: ProjectTeam → Profile
+- 实现: Project ←→ Profile 的多对多关系
+
+**使用场景**:
+```typescript
+// 查询项目成员
+const query = new Parse.Query("ProjectTeam");
+query.equalTo("project", projectId);
+query.notEqualTo("isDeleted", true);
+query.include("profile");
+const members = await query.find();
+
+// 查询用户参与的项目
+const myProjectQuery = new Parse.Query("ProjectTeam");
+myProjectQuery.equalTo("profile", profileId);
+myProjectQuery.notEqualTo("isDeleted", true);
+myProjectQuery.include("project");
+myProjectQuery.select("project");
+const teams = await myProjectQuery.find();
+const projects = teams.map(team => team.get("project"));
+
+// 添加项目成员
+const ProjectTeam = Parse.Object.extend("ProjectTeam");
+const team = new ProjectTeam();
+team.set("project", project.toPointer());
+team.set("profile", profile.toPointer());
+team.set("role", "开发");
+await team.save();
+```
+
+**索引建议**:
+- `project + isDeleted`
+- `profile + isDeleted`
+- `project + profile` (联合唯一索引)
+
+---
+
+### 7. Product(产品表)
+
+**用途**: 项目下的产品管理。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "prod001" |
+| title | String | 是 | 产品名称 | "移动端APP" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| company | Pointer | 是 | 所属企业 | → Company |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**关系**:
+- 多对一: Product → Project(所属项目)
+- 多对一: Product → Company(所属企业)
+- 一对多: Product → ProductRequire(产品需求/模块)
+
+**使用场景**:
+```typescript
+// 查询项目的产品列表
+const query = new Parse.Query("Product");
+query.equalTo("project", projectId);
+query.addDescending("updatedAt");
+const products = await query.find();
+
+// 创建产品
+const Product = Parse.Object.extend("Product");
+const product = new Product();
+product.set("title", "Web管理后台");
+product.set("project", project.toPointer());
+product.set("company", company.toPointer());
+await product.save();
+```
+
+**索引建议**:
+- `project + isDeleted`
+- `company + isDeleted`
+- `updatedAt` (降序)
+
+---
+
+### 8. ProductRequire(产品需求表)
+
+**用途**: 支持模块化的需求管理,可构建树状结构(模块→子模块→需求)。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "req001" |
+| title | String | 是 | 需求/模块名称 | "用户管理模块" |
+| type | String | 是 | 类型 | "module" / "require" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| company | Pointer | 是 | 所属企业 | → Company |
+| product | Pointer | 否 | 所属产品 | → Product |
+| module | Pointer | 否 | 所属模块 | → ProductRequire |
+| parent | Pointer | 否 | 父级 | → ProductRequire |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**type 枚举值**:
+- `module`: 功能模块(可以有子模块)
+- `require`: 具体需求(归属于某个模块)
+
+**树状结构说明**:
+- 一级模块: type="module", module=null, parent=null
+- 二级模块: type="module", module=一级模块ID, parent=一级模块ID
+- 具体需求: type="require", module=所属模块ID
+
+**关系**:
+- 多对一: ProductRequire → Project(所属项目)
+- 多对一: ProductRequire → Company(所属企业)
+- 多对一: ProductRequire → Product(所属产品)
+- 树状结构: ProductRequire → ProductRequire (module/parent)
+
+**使用场景**:
+```typescript
+// 查询项目的模块列表
+const moduleQuery = new Parse.Query("ProductRequire");
+moduleQuery.equalTo("project", projectId);
+moduleQuery.equalTo("type", "module");
+moduleQuery.addDescending("updatedAt");
+const modules = await moduleQuery.find();
+
+// 查询某产品下的需求
+const requireQuery = new Parse.Query("ProductRequire");
+requireQuery.equalTo("project", projectId);
+requireQuery.equalTo("type", "require");
+requireQuery.equalTo("product", productId);
+requireQuery.limit(1000);
+const requires = await requireQuery.find();
+
+// 查询某模块及其子模块的所有需求
+function getModulePathIds(module, allModules) {
+  let ids = [module.id];
+  allModules.forEach(mitem => {
+    if (mitem.get("module")?.id == module.id) {
+      ids.push(mitem.id);
+    }
+    if (mitem.get("parent")?.id == module.id) {
+      ids.push(mitem.id);
+    }
+  });
+  return ids;
+}
+
+const query = new Parse.Query("ProductRequire");
+query.equalTo("type", "require");
+query.containedIn("module", modulePathIds);
+```
+
+**索引建议**:
+- `project + type + isDeleted`
+- `product + type`
+- `module + type`
+- `parent + type`
+- `updatedAt` (降序)
+
+---
+
+### 9. OKR(目标管理周期表)
+
+**用途**: 管理OKR周期(年度/季度),支持公司级目标管理。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "okr001" |
+| title | String | 是 | OKR名称 | "2024年度OKR" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| startDate | Date | 否 | 开始日期 | 2024-01-01T00:00:00.000Z |
+| endDate | Date | 否 | 结束日期 | 2024-12-31T23:59:59.999Z |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**关系**:
+- 多对一: OKR → Company(所属企业)
+- 一对多: OKR → OKRObject(OKR对象/目标)
+
+**使用场景**:
+```typescript
+// 查询企业的OKR列表
+const query = new Parse.Query("OKR");
+query.equalTo("company", companyId);
+query.notEqualTo("isDeleted", true);
+query.addDescending("updatedAt");
+const okrs = await query.find();
+
+// 创建年度OKR
+const OKR = Parse.Object.extend("OKR");
+const okr = new OKR();
+okr.set("title", "2024年度OKR");
+okr.set("company", company.toPointer());
+okr.set("startDate", new Date("2024-01-01"));
+okr.set("endDate", new Date("2024-12-31"));
+await okr.save();
+```
+
+**索引建议**:
+- `company + isDeleted`
+- `startDate + endDate`
+- `updatedAt` (降序)
+
+---
+
+### 10. OKRObject(OKR对象表)
+
+**用途**: 存储OKR的指标树和具体目标,支持公司/部门/个人三级架构,采用BSC平衡计分卡。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "okrobj001" |
+| title | String | 是 | 目标/指标树名称 | "年度营收目标" |
+| type | String | 是 | 对象类型 | "tree" / "objective" |
+| okr | Pointer | 是 | 所属OKR周期 | → OKR |
+| company | Pointer | 是 | 所属企业 | → Company |
+| department | Pointer | 否 | 所属部门 | → Department |
+| profile | Pointer | 否 | 所属个人 | → Profile |
+| tree | Pointer | 否 | 所属指标树 | → OKRObject |
+| parent | Pointer | 否 | 父级指标树 | → OKRObject |
+| bsc | String | 否 | 平衡计分卡维度 | "财务" / "客户" / "内部运营" / "学习成长" |
+| value | Number | 否 | 目标值 | 1000000 |
+| unit | String | 否 | 单位 | "万元" / "%" |
+| index | Number | 否 | 排序索引 | 1 |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**type 枚举值**:
+- `tree`: 指标树节点(用于分类组织)
+- `objective`: 具体目标(O或KR)
+
+**BSC 平衡计分卡维度**:
+- `财务`: 财务指标(如:营收、利润等)
+- `客户`: 客户相关指标(如:客户满意度、客户数等)
+- `内部运营`: 内部流程指标(如:交付效率、质量等)
+- `学习成长`: 学习与创新指标(如:培训时长、新技能等)
+
+**三级架构说明**:
+1. **公司级OKR**: department=null, profile=null
+2. **部门级OKR**: department不为空, profile=null
+3. **个人级OKR**: department可选, profile不为空
+
+**关系**:
+- 多对一: OKRObject → OKR(所属OKR周期)
+- 多对一: OKRObject → Company(所属企业)
+- 多对一: OKRObject → Department(部门OKR,可选)
+- 多对一: OKRObject → Profile(个人OKR,可选)
+- 树状结构: OKRObject → OKRObject (tree/parent, 指标树层级)
+
+**使用场景**:
+```typescript
+// 查询公司级指标树
+const treeQuery = new Parse.Query("OKRObject");
+treeQuery.equalTo("okr", okrId);
+treeQuery.equalTo("type", "tree");
+treeQuery.equalTo("department", null);
+treeQuery.equalTo("profile", null);
+treeQuery.ascending("index");
+const trees = await treeQuery.find();
+
+// 查询部门OKR
+const deptOKRQuery = new Parse.Query("OKRObject");
+deptOKRQuery.equalTo("okr", okrId);
+deptOKRQuery.equalTo("department", departmentId);
+deptOKRQuery.addDescending("updatedAt");
+const deptOKRs = await deptOKRQuery.find();
+
+// 查询个人OKR
+const personalOKRQuery = new Parse.Query("OKRObject");
+personalOKRQuery.equalTo("okr", okrId);
+personalOKRQuery.equalTo("profile", profileId);
+const personalOKRs = await personalOKRQuery.find();
+
+// 查询某指标树下的目标
+const objectiveQuery = new Parse.Query("OKRObject");
+objectiveQuery.equalTo("okr", okrId);
+objectiveQuery.equalTo("tree", treeId);
+objectiveQuery.equalTo("type", "objective");
+const objectives = await objectiveQuery.find();
+
+// 按BSC维度筛选
+const bscQuery = new Parse.Query("OKRObject");
+bscQuery.equalTo("bsc", "财务");
+bscQuery.equalTo("okr", okrId);
+```
+
+**创建目标示例**:
+```typescript
+// 创建公司级指标树
+const OKRObject = Parse.Object.extend("OKRObject");
+const tree = new OKRObject();
+tree.set("title", "财务指标");
+tree.set("type", "tree");
+tree.set("okr", okr.toPointer());
+tree.set("company", company.toPointer());
+tree.set("bsc", "财务");
+tree.set("index", 1);
+await tree.save();
+
+// 创建具体目标
+const objective = new OKRObject();
+objective.set("title", "实现营收1000万");
+objective.set("type", "objective");
+objective.set("okr", okr.toPointer());
+objective.set("company", company.toPointer());
+objective.set("tree", tree.toPointer());
+objective.set("bsc", "财务");
+objective.set("value", 1000);
+objective.set("unit", "万元");
+await objective.save();
+
+// 创建部门OKR(继承指标树)
+const deptOKR = new OKRObject();
+deptOKR.set("title", "技术部营收贡献500万");
+deptOKR.set("type", "objective");
+deptOKR.set("okr", okr.toPointer());
+deptOKR.set("company", company.toPointer());
+deptOKR.set("department", department.toPointer());
+deptOKR.set("tree", tree.toPointer());
+deptOKR.set("bsc", "财务");
+deptOKR.set("value", 500);
+deptOKR.set("unit", "万元");
+await deptOKR.save();
+```
+
+**索引建议**:
+- `okr + type + isDeleted`
+- `okr + department`
+- `okr + profile`
+- `tree + type`
+- `bsc + okr`
+- `index + okr`
+
+---
+
+### 11. NoteSpace(知识空间表)
+
+**用途**: 文档空间管理,通常与项目关联,作为项目Wiki。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "space001" |
+| title | String | 是 | 空间标题 | "CRM系统的Wiki空间" |
+| type | String | 是 | 空间类型 | "project" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| project | Pointer | 否 | 关联项目 | → Project |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**type 枚举值**:
+- `project`: 项目空间
+
+**关系**:
+- 多对一: NoteSpace → Company(所属企业)
+- 一对一: NoteSpace → Project(关联项目)
+- 一对多: NoteSpace → NotePad(空间笔记)
+
+**使用场景**:
+```typescript
+// 创建项目Wiki空间(自动)
+const NoteSpace = Parse.Object.extend("NoteSpace");
+const space = new NoteSpace();
+space.set("title", project.get("title") + "的Wiki空间");
+space.set("type", "project");
+space.set("company", company.toPointer());
+space.set("project", project.toPointer());
+await space.save();
+
+// 查询项目的Wiki空间
+const query = new Parse.Query("NoteSpace");
+query.equalTo("project", projectId);
+query.equalTo("type", "project");
+query.notEqualTo("isDeleted", true);
+const space = await query.first();
+
+// 查询用户有权访问的空间(通过项目团队)
+const teamQuery = new Parse.Query("ProjectTeam");
+teamQuery.equalTo("profile", profileId);
+const teams = await teamQuery.find();
+const projectIds = teams.map(t => t.get("project").id);
+
+const spaceQuery = new Parse.Query("NoteSpace");
+spaceQuery.containedIn("project", projectIds);
+const spaces = await spaceQuery.find();
+```
+
+**索引建议**:
+- `project + type`
+- `company + isDeleted`
+
+---
+
+### 12. NotePad(文档笔记表)
+
+**用途**: 存储具体的文档笔记,支持树状结构(目录层级)。
+
+**表结构**:
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "note001" |
+| title | String | 否 | 笔记标题 | "API接口设计" |
+| space | Pointer | 是 | 所属空间 | → NoteSpace |
+| profile | Pointer | 是 | 创建者 | → Profile |
+| parent | Pointer | 否 | 父笔记 | → NotePad |
+| content | String | 否 | 笔记内容 | Markdown格式 |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**关系**:
+- 多对一: NotePad → NoteSpace(所属空间)
+- 多对一: NotePad → Profile(创建者)
+- 树状结构: NotePad → NotePad (parent, 支持多层级目录)
+
+**使用场景**:
+```typescript
+// 查询空间的一级笔记(根目录)
+const query = new Parse.Query("NotePad");
+query.equalTo("space", spaceId);
+query.doesNotExist("parent"); // 或 query.equalTo("parent", null)
+query.notEqualTo("isDeleted", true);
+const rootNotes = await query.find();
+
+// 查询某笔记的子笔记
+const childQuery = new Parse.Query("NotePad");
+childQuery.equalTo("parent", parentNoteId);
+childQuery.notEqualTo("isDeleted", true);
+const children = await childQuery.find();
+
+// 创建笔记
+const NotePad = Parse.Object.extend("NotePad");
+const note = new NotePad();
+note.set("title", "接口文档");
+note.set("space", space.toPointer());
+note.set("profile", profile.toPointer());
+note.set("content", "# API接口设计\n\n...");
+await note.save();
+
+// 递归查询笔记层级结构(SQL方式)
+const sql = `
+WITH RECURSIVE notepad_hierarchy AS (
+  SELECT np."objectId", np."parent", np."title", 1 AS "level"
+  FROM "NotePad" np
+  WHERE np."parent" IS NULL
+    AND np."isDeleted" IS NOT TRUE
+    AND np."space" = $1
+  UNION ALL
+  SELECT np2."objectId", np2."parent", np2."title", nh."level" + 1
+  FROM "NotePad" np2
+  JOIN notepad_hierarchy nh ON nh."objectId" = np2."parent"
+  WHERE np2."isDeleted" IS NOT TRUE
+)
+SELECT * FROM notepad_hierarchy
+ORDER BY "level", "createdAt"
+`;
+```
+
+**索引建议**:
+- `space + isDeleted`
+- `parent + space`
+- `profile + space`
+- `updatedAt` (降序)
+
+---
+
+## 通用字段说明
+
+所有 Parse Server 表都包含以下系统字段:
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| objectId | String | 唯一标识符,自动生成,主键 |
+| createdAt | Date | 创建时间,自动生成 |
+| updatedAt | Date | 更新时间,自动维护 |
+| ACL | ACL | 访问控制列表(权限控制) |
+
+---
+
+## 软删除模式
+
+系统采用**软删除模式**,几乎所有表都包含 `isDeleted` 字段:
+
+```typescript
+// 标准查询模式(排除已删除数据)
+query.notEqualTo("isDeleted", true);
+
+// 或
+query.equalTo("isDeleted", null);
+```
+
+**优势**:
+- 数据可恢复
+- 保持关联完整性
+- 支持审计追踪
+- 避免级联删除问题
+
+---
+
+## 多租户隔离
+
+系统采用**多租户架构**,所有业务表都包含 `company` 字段:
+
+```typescript
+// 获取当前企业ID
+const companyId = localStorage.getItem("Parse/CompanyId") || localStorage.getItem("company");
+
+// 所有查询都需要加上企业隔离
+query.equalTo("company", companyId);
+```
+
+---
+
+## 关键查询模式
+
+### 1. 分页查询
+```typescript
+query.limit(pageSize);
+query.skip(pageSize * (pageIndex - 1));
+```
+
+### 2. 排序
+```typescript
+query.descending("createdAt");  // 降序
+query.ascending("index");       // 升序
+query.addDescending("priority"); // 添加额外排序
+```
+
+### 3. 关联查询(Include)
+```typescript
+// 单层级关联
+query.include("assignee", "owner");
+
+// 多层级关联
+query.include(["project", "assignee.user", "assignee.department"]);
+```
+
+### 4. 字段筛选
+```typescript
+// 只返回指定字段
+query.select("title", "startDate", "assignee");
+```
+
+### 5. 计数查询
+```typescript
+const count = await query.count();
+
+// 带计数的查询
+query.withCount();
+const result = await query.find();
+console.log(result.count); // 总数
+console.log(result.results); // 结果列表
+```
+
+### 6. 树状结构查询
+```typescript
+// 查询根节点
+const roots = list.filter(item => !item.get("parent"));
+
+// 查询子节点
+const children = list.filter(item => item.get("parent")?.id === parentId);
+
+// 递归构建树
+function buildTree(nodes, parentId = null) {
+  return nodes
+    .filter(node => node.get("parent")?.id === parentId)
+    .map(node => ({
+      ...node.toJSON(),
+      children: buildTree(nodes, node.id)
+    }));
+}
+```
+
+---
+
+## 性能优化建议
+
+### 1. 索引策略
+
+**必建索引**:
+- 所有表: `company + isDeleted`
+- 所有表: `updatedAt` (降序)
+- 关联字段: 所有 Pointer 字段
+- 查询字段: 经常用于筛选的字段
+
+**复合索引示例**:
+```javascript
+// Project表
+{ company: 1, isDeleted: 1, isStar: 1 }
+{ company: 1, owner: 1, isDeleted: 1 }
+
+// ProjectTask表
+{ project: 1, isDeleted: 1, stateList: 1 }
+{ assignee: 1, isDeleted: 1, deadline: 1 }
+{ company: 1, startDate: -1 }
+
+// OKRObject表
+{ okr: 1, type: 1, isDeleted: 1 }
+{ okr: 1, department: 1, profile: 1 }
+```
+
+### 2. 查询优化
+
+**避免N+1查询**:
+```typescript
+// ❌ 不好的做法
+const tasks = await query.find();
+for (const task of tasks) {
+  const assignee = await task.get("assignee").fetch(); // N次查询
+}
+
+// ✅ 好的做法
+query.include("assignee");
+const tasks = await query.find(); // 1次查询
+```
+
+**批量操作**:
+```typescript
+// 批量保存
+await Parse.Object.saveAll(objects);
+
+// 批量删除
+await Parse.Object.destroyAll(objects);
+```
+
+**使用 select 限制字段**:
+```typescript
+// ❌ 返回所有字段
+const tasks = await query.find();
+
+// ✅ 只返回需要的字段
+query.select("title", "startDate", "stateList");
+const tasks = await query.find();
+```
+
+### 3. 缓存策略
+
+项目中实现了简单的内存缓存:
+
+```typescript
+// 检查是否需要更新数据
+async hasUpdateObject(list: Parse.Object[], querySrc: Parse.Query, refresh = false) {
+  if (refresh) { return true; }
+
+  if (list?.length > 0) {
+    // 获取最新的一条数据
+    const query = Parse.Query.fromJSON(querySrc.className, querySrc.toJSON());
+    query.addDescending("updatedAt");
+    query.withCount();
+    query.limit(1);
+    const data = await query.find();
+
+    // 比较更新时间和数量
+    if (data.results[0]?.updatedAt > list[0].updatedAt) {
+      return true; // 有更新
+    }
+    if (data.count !== list.length) {
+      return true; // 数量变化
+    }
+    return false; // 无需更新
+  }
+
+  return true; // 首次加载
+}
+```
+
+---
+
+## 数据完整性约束
+
+### 必填字段(应用层校验)
+
+| 表名 | 必填字段 |
+|------|---------|
+| Company | name |
+| Profile | name, company |
+| Department | name, type, company |
+| Project | title, company, owner |
+| ProjectTask | title, project, company, owner, startDate |
+| ProjectTeam | project, profile |
+| Product | title, project, company |
+| ProductRequire | title, type, project, company |
+| OKR | title, company |
+| OKRObject | title, type, okr, company |
+| NoteSpace | title, type, company |
+| NotePad | space, profile |
+
+### 外键约束
+
+所有 Pointer 字段在应用层需要保证引用完整性:
+- 删除操作应采用**软删除**
+- 级联更新应谨慎处理
+- 关联查询前应检查对象是否存在
+
+---
+
+## 特殊业务逻辑
+
+### 1. 工时管理
+
+**基准工时**: 26天 × 7小时 = 182小时/月
+
+```typescript
+// ProjectTask 字段
+duration: Number  // 实际工时(小时)
+estimate: Number  // 预估工时(小时)
+```
+
+**工时统计SQL**:
+```sql
+SELECT
+    pt."assignee",
+    SUM(COALESCE(pt."duration", 0)) as actual_hours,
+    SUM(COALESCE(pt."estimate", 0)) as estimated_hours,
+    SUM(COALESCE(pt."duration", 0)) / 182.0 * 100 as completion_rate
+FROM "ProjectTask" pt
+WHERE pt."company" = $1
+  AND (pt."isDeleted" != true OR pt."isDeleted" IS NULL)
+  AND pt."startDate" >= $2
+  AND pt."startDate" < $3
+GROUP BY pt."assignee"
+```
+
+### 2. 任务状态流转
+
+```
+待分配 → 进行中 → 测试中 → 已完成 → 已上线
+```
+
+**状态映射**:
+```typescript
+const stateColorMap = {
+  '待分配': 'default',
+  '进行中': 'processing',
+  '测试中': 'warning',
+  '已完成': 'success',
+  '已上线': 'gold',
+};
+```
+
+### 3. 优先级映射
+
+```typescript
+const priorityMap = {
+  1:   { text: '最低', color: 'default' },
+  50:  { text: '普通', color: 'blue' },
+  70:  { text: '较高', color: 'orange' },
+  90:  { text: '重要', color: 'red' },
+  100: { text: '严重', color: '#f50' },
+};
+```
+
+### 4. OKR 三级架构
+
+```typescript
+// 公司级 OKR
+okrObject.set("department", null);
+okrObject.set("profile", null);
+
+// 部门级 OKR
+okrObject.set("department", department.toPointer());
+okrObject.set("profile", null);
+
+// 个人级 OKR
+okrObject.set("profile", profile.toPointer());
+// department 可选
+```
+
+### 5. 平衡计分卡(BSC)
+
+```typescript
+const bscList = [
+  { label: "财务", value: "财务" },
+  { label: "客户", value: "客户" },
+  { label: "内部运营", value: "内部运营" },
+  { label: "学习成长", value: "学习成长" },
+];
+```
+
+---
+
+## SQL 查询示例
+
+### 1. 用户参与项目统计
+
+```sql
+SELECT DISTINCT(task."project"), pro."title" AS "name"
+FROM "ProjectTask" task
+LEFT JOIN "Project" pro ON pro."objectId" = task."project"
+WHERE task."isDeleted" IS NOT TRUE
+  AND task."isClosed" IS NOT TRUE
+  AND (task."assignee" = $1 OR task."owner" = $1)
+  AND task."project" IS NOT NULL
+ORDER BY pro."updatedAt" DESC
+```
+
+### 2. 笔记层级结构查询
+
+```sql
+WITH RECURSIVE notepad_hierarchy AS (
+  -- 查询根节点
+  SELECT
+    np."objectId",
+    np."parent",
+    np."title",
+    np."space",
+    1 AS "level"
+  FROM "NotePad" np
+  WHERE np."parent" IS NULL
+    AND np."isDeleted" IS NOT TRUE
+    AND np."space" = $1
+
+  UNION ALL
+
+  -- 递归查询子节点
+  SELECT
+    np2."objectId",
+    np2."parent",
+    np2."title",
+    np2."space",
+    nh."level" + 1
+  FROM "NotePad" np2
+  JOIN notepad_hierarchy nh ON nh."objectId" = np2."parent"
+  WHERE np2."isDeleted" IS NOT TRUE
+)
+SELECT * FROM notepad_hierarchy
+ORDER BY "level", "createdAt"
+```
+
+### 3. 月度工时统计
+
+```sql
+SELECT
+    p."name",
+    p."objectId" as profile_id,
+    COALESCE(SUM(pt."duration"), 0) as actual_hours,
+    COALESCE(SUM(pt."estimate"), 0) as estimated_hours,
+    COUNT(pt."objectId") as task_count
+FROM "Profile" p
+LEFT JOIN "ProjectTask" pt ON pt."assignee" = p."objectId"
+    AND pt."isDeleted" IS NOT TRUE
+    AND pt."startDate" >= $1
+    AND pt."startDate" < $2
+WHERE p."company" = $3
+  AND p."isDeleted" IS NOT TRUE
+GROUP BY p."objectId", p."name"
+ORDER BY actual_hours DESC
+```
+
+---
+
+## 文件位置索引
+
+**核心服务文件**:
+- 项目服务: `src/modules/project/project.service.ts`
+- OKR服务: `src/modules/okr/okr.service.ts`
+- 部门服务: `src/modules/okr/depart.service.ts`
+
+**主要组件**:
+- 项目管理: `src/modules/project/project-manage/project-manage.component.ts`
+- 甘特图: `src/modules/project/project-gantt/project-gantt.component.ts`
+- 看板: `src/modules/project/project-kanban/project-kanban.component.ts`
+- 产品管理: `src/modules/project/project-product/project-product.component.ts`
+- OKR面板: `src/modules/okr/page-okr-panel/page-okr-panel.component.ts`
+- OKR仪表板: `src/modules/okr/okr-dashboard/okr-dashboard.component.ts`
+
+---
+
+## 附录
+
+### A. 数据表清单
+
+| 序号 | 表名 | 中文名 | 用途 |
+|------|------|--------|------|
+| 1 | Company | 企业表 | 多租户核心 |
+| 2 | Profile | 用户档案表 | 用户信息 |
+| 3 | Department | 部门表 | 组织架构 |
+| 4 | Project | 项目表 | 项目管理 |
+| 5 | ProjectTask | 任务表 | 任务管理 |
+| 6 | ProjectTeam | 项目团队表 | 成员关系 |
+| 7 | Product | 产品表 | 产品管理 |
+| 8 | ProductRequire | 产品需求表 | 需求管理 |
+| 9 | OKR | OKR周期表 | 目标周期 |
+| 10 | OKRObject | OKR对象表 | 目标管理 |
+| 11 | NoteSpace | 知识空间表 | 文档空间 |
+| 12 | NotePad | 文档笔记表 | 笔记管理 |
+
+### B. 关系类型说明
+
+- **一对多 (1:N)**: 一个父记录对应多个子记录
+- **多对多 (N:N)**: 通过中间表实现
+- **一对一 (1:1)**: 一个记录对应另一个记录
+- **树状结构**: 通过 parent 字段实现自引用
+
+### C. 数据类型说明
+
+| Parse 类型 | 说明 | 示例 |
+|-----------|------|------|
+| String | 字符串 | "项目名称" |
+| Number | 数字 | 100 |
+| Boolean | 布尔值 | true / false |
+| Date | 日期时间 | ISO 8601 格式 |
+| Array | 数组 | ["tag1", "tag2"] |
+| Object | JSON对象 | { key: "value" } |
+| Pointer | 关联引用 | { __type: "Pointer", className: "Profile", objectId: "abc" } |
+| Relation | 多对多关系 | Parse.Relation |
+| File | 文件 | Parse.File |
+
+---
+
+**文档版本**: v1.0
+**最后更新**: 2025-10-13
+**维护者**: Nova Project Team

+ 1398 - 0
rules/schemas.md

@@ -0,0 +1,1398 @@
+---
+category: schema
+title: YSS项目Parse Server数据范式
+subtitle: 映三色设计师项目管理系统完整数据表结构
+name: 'yss-schemas'
+label: database
+---
+
+# Parse Server 数据范式 - 映三色项目管理系统
+
+## 概述
+
+本文档定义了映三色(YSS)设计师项目管理系统的完整Parse Server数据范式。系统采用**多租户架构**,以Company为核心,支持客服、设计师、组长等多角色协作的全流程项目管理。
+
+**核心特性**:
+- 🏢 多租户架构,以Company为核心租户隔离
+- 👥 统一员工表(Profile)和客户表(ContactInfo)
+- 📋 项目与群聊灵活关联(Project ←→ GroupChat)
+- 🎨 设计师项目全生命周期管理
+- 💰 财务报价与结算流程
+- 📊 质量控制与客户反馈
+
+---
+
+## 数据表关系图
+
+```plantuml
+@startuml
+!define TABLE(name,desc) class name as "desc" << (T,#FFAAAA) >>
+!define FIELD(name,type) name : type
+
+skinparam classAttributeIconSize 0
+skinparam class {
+    BackgroundColor LightYellow
+    BorderColor Black
+    ArrowColor Black
+}
+
+' ============ 核心租户与人员 ============
+TABLE(Company, "Company\n企业表") {
+    FIELD(objectId, String)
+    FIELD(name, String)
+    FIELD(corpId, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(Profile, "Profile\n员工档案表") {
+    FIELD(objectId, String)
+    FIELD(name, String)
+    FIELD(mobile, String)
+    FIELD(company, Pointer→Company)
+    FIELD(userId, String)
+    FIELD(role, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ContactInfo, "ContactInfo\n客户信息表") {
+    FIELD(objectId, String)
+    FIELD(name, String)
+    FIELD(mobile, String)
+    FIELD(company, Pointer→Company)
+    FIELD(external_userid, String)
+    FIELD(source, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+' ============ 企微集成 ============
+TABLE(GroupChat, "GroupChat\n企微群聊表") {
+    FIELD(objectId, String)
+    FIELD(chat_id, String)
+    FIELD(name, String)
+    FIELD(company, Pointer→Company)
+    FIELD(project, Pointer→Project)
+    FIELD(member_list, Array)
+    FIELD(joinUrl, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ProjectGroup, "ProjectGroup\n项目群组关联表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(groupChat, Pointer→GroupChat)
+    FIELD(isPrimary, Boolean)
+    FIELD(createdAt, Date)
+}
+
+' ============ 项目模块 ============
+TABLE(Project, "Project\n项目表") {
+    FIELD(objectId, String)
+    FIELD(title, String)
+    FIELD(company, Pointer→Company)
+    FIELD(customer, Pointer→ContactInfo)
+    FIELD(assignee, Pointer→Profile)
+    FIELD(status, String)
+    FIELD(currentStage, String)
+    FIELD(deadline, Date)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ProjectRequirement, "ProjectRequirement\n需求信息表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(company, Pointer→Company)
+    FIELD(spaces, Array)
+    FIELD(designRequirements, Object)
+    FIELD(materialAnalysis, Object)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ProjectTeam, "ProjectTeam\n项目团队表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(profile, Pointer→Profile)
+    FIELD(role, String)
+    FIELD(workload, Number)
+    FIELD(isDeleted, Boolean)
+}
+
+' ============ 交付物与文件 ============
+TABLE(Product, "Product\n产品即交付物表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(company, Pointer→Company)
+    FIELD(stage, String)
+    FIELD(processType, String)
+    FIELD(space, String)
+    FIELD(fileUrl, String)
+    FIELD(reviewStatus, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ProjectFile, "ProjectFile\n项目文件表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(uploadedBy, Pointer→Profile)
+    FIELD(fileType, String)
+    FIELD(fileUrl, String)
+    FIELD(fileName, String)
+    FIELD(fileSize, Number)
+    FIELD(stage, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+' ============ 财务模块 ============
+
+
+TABLE(ProjectSettlement, "ProjectSettlement\n结算记录表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(company, Pointer→Company)
+    FIELD(stage, String)
+    FIELD(amount, Number)
+    FIELD(percentage, Number)
+    FIELD(status, String)
+    FIELD(dueDate, Date)
+    FIELD(settledAt, Date)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ProjectVoucher, "ProjectVoucher\n付款凭证表") {
+    FIELD(objectId, String)
+    FIELD(settlement, Pointer→ProjectSettlement)
+    FIELD(project, Pointer→Project)
+    FIELD(amount, Number)
+    FIELD(voucherUrl, String)
+    FIELD(recognizedInfo, Object)
+    FIELD(verifiedBy, Pointer→Profile)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+' ============ 质量与反馈 ============
+TABLE(ProjectFeedback, "ProjectFeedback\n客户反馈表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(customer, Pointer→ContactInfo)
+    FIELD(stage, String)
+    FIELD(feedbackType, String)
+    FIELD(content, String)
+    FIELD(rating, Number)
+    FIELD(status, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ProductCheck, "ProductCheck\n产品质量检查表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(checkType, String)
+    FIELD(checkedBy, Pointer→Profile)
+    FIELD(checkedAt, Date)
+    FIELD(isPassed, Boolean)
+    FIELD(items, Array)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+TABLE(ProjectIssue, "ProjectIssue\n异常记录表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(reportedBy, Pointer→Profile)
+    FIELD(exceptionType, String)
+    FIELD(severity, String)
+    FIELD(description, String)
+    FIELD(status, String)
+    FIELD(resolution, String)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+' ============ 沟通记录 ============
+TABLE(Communication, "Communication\n沟通记录表") {
+    FIELD(objectId, String)
+    FIELD(project, Pointer→Project)
+    FIELD(sender, Pointer→Profile/ContactInfo)
+    FIELD(content, String)
+    FIELD(communicationType, String)
+    FIELD(relatedStage, String)
+    FIELD(attachments, Array)
+    FIELD(data, Object)
+    FIELD(isDeleted, Boolean)
+}
+
+' ============ 关系连线 ============
+
+' Company 一对多关系
+Company "1" --> "n" Profile : 企业员工
+Company "1" --> "n" ContactInfo : 企业客户
+Company "1" --> "n" Project : 企业项目
+Company "1" --> "n" GroupChat : 企业群聊
+
+' 项目核心关系
+Project "n" --> "1" Company : 所属企业
+Project "n" --> "1" ContactInfo : 客户
+Project "n" --> "1" Profile : 负责人(assignee)
+Project "1" --> "1" ProjectRequirement : 需求信息
+Project "1" <--> "n" GroupChat : ProjectGroup\n群聊关联
+Project "1" --> "n" ProjectTeam : 项目团队
+
+' 交付与财务
+Project "1" --> "n" Product : 交付物
+Project "1" --> "n" ProjectFile : 项目文件
+Project "1" --> "n" ProjectSettlement : 结算记录
+ProjectSettlement "1" --> "n" ProjectVoucher : 付款凭证
+
+' 质量与沟通
+Project "1" --> "n" ProjectFeedback : 客户反馈
+Project "1" --> "n" ProductCheck : 质量检查
+Project "1" --> "n" ProjectIssue : 异常记录
+Project "1" --> "n" Communication : 沟通记录
+
+' 群聊关系
+GroupChat "n" --> "1" Company : 所属企业
+GroupChat "n" --> "1" Project : 关联项目(可选)
+
+@enduml
+```
+
+---
+
+## 核心数据表详解
+
+### 1. Company(企业表)
+
+**用途**: 多租户系统的核心,所有数据通过 company 字段进行租户隔离。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "cDL6R1hgSi" |
+| name | String | 是 | 企业名称 | "映三色设计" |
+| corpId | String | 否 | 企业微信CorpID | "ww1234567890" |
+| data | Object | 否 | 扩展数据 | { settings: {...}, modules: [...] } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**data字段扩展示例**:
+```json
+{
+  "settings": {
+    "timezone": "Asia/Shanghai",
+    "currency": "CNY",
+    "workingHours": { "start": "09:00", "end": "18:00" }
+  },
+  "modules": ["project", "crm", "finance", "hr"],
+  "wxwork": {
+    "agentId": "1000002",
+    "secret": "***"
+  }
+}
+```
+
+**索引建议**:
+- `objectId` (主键)
+- `corpId`
+- `name`
+
+---
+
+### 2. Profile(员工档案表)
+
+**用途**: 存储企业员工档案信息,统一管理客服、设计师、组长等所有角色。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "prof001" |
+| name | String | 是 | 员工姓名 | "张三" |
+| mobile | String | 否 | 手机号 | "13800138000" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| userId | String | 否 | 企微UserID | "zhangsan" |
+| role | String | 是 | 员工角色 | "客服" / "组员" / "组长" |
+| data | Object | 否 | 扩展数据 | { avatar, department, skills, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**role 枚举值**:
+- `客服`: 客户服务人员,负责接单、跟进
+- `组员`: 设计师,负责具体设计工作
+- `组长`: 团队负责人,负责审核、分配
+- `财务`: 财务人员
+- `人事`: 人事人员
+- `管理员`: 系统管理员
+
+**data字段扩展示例**:
+```json
+{
+  "avatar": "https://...",
+  "email": "zhangsan@example.com",
+  "department": "设计一组",
+  "position": "高级设计师",
+  "skills": ["建模", "渲染", "软装"],
+  "workload": {
+    "currentProjects": 5,
+    "maxCapacity": 8,
+    "utilizationRate": 0.625
+  },
+  "performance": {
+    "completedProjects": 120,
+    "averageRating": 4.8,
+    "onTimeRate": 0.95
+  }
+}
+```
+
+**使用场景**:
+```typescript
+// 获取当前登录员工
+const profileId = localStorage.getItem("Parse/ProfileId");
+const query = new Parse.Query("Profile");
+const profile = await query.get(profileId);
+
+// 查询设计师列表
+const designerQuery = new Parse.Query("Profile");
+designerQuery.equalTo("company", companyId);
+designerQuery.equalTo("role", "组员");
+designerQuery.notEqualTo("isDeleted", true);
+const designers = await designerQuery.find();
+
+// 查询客服人员
+const csQuery = new Parse.Query("Profile");
+csQuery.equalTo("role", "客服");
+csQuery.equalTo("company", companyId);
+```
+
+**索引建议**:
+- `company + isDeleted`
+- `userId + company`
+- `role + company`
+- `mobile`
+
+---
+
+### 3. ContactInfo(客户信息表)
+
+**用途**: 统一管理所有客户信息,支持企微外部联系人同步。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "contact001" |
+| name | String | 是 | 客户姓名 | "李四" |
+| mobile | String | 否 | 手机号 | "13900139000" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| external_userid | String | 否 | 企微外部联系人ID | "wmxxx" |
+| source | String | 否 | 来源渠道 | "朋友圈" / "信息流" / "转介绍" |
+| data | Object | 否 | 扩展数据 | { avatar, wechat, tags, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**source 枚举值**:
+- `朋友圈`: 微信朋友圈引流
+- `信息流`: 广告投放
+- `转介绍`: 老客户推荐
+- `其他`: 其他渠道
+
+**data字段扩展示例**:
+```json
+{
+  "avatar": "https://...",
+  "wechat": "lisi_wx",
+  "type": "new",
+  "tags": {
+    "needType": "全案",
+    "preference": "现代简约",
+    "colorAtmosphere": "明亮温馨",
+    "budget": { "min": 50000, "max": 100000 }
+  },
+  "demandType": "价格敏感",
+  "preferenceTags": ["环保材料", "智能家电", "大窗户"],
+  "followUpStatus": "待报价",
+  "remark": "要求使用环保材料,对价格比较敏感",
+  "follow_user": [{
+    "userid": "zhangsan",
+    "remark": "李总",
+    "createtime": 1605171726,
+    "tags": [...]
+  }]
+}
+```
+
+**使用场景**:
+```typescript
+// 搜索客户
+const searchQuery = new Parse.Query("ContactInfo");
+searchQuery.equalTo("company", companyId);
+searchQuery.matches("name", keyword, "i");
+searchQuery.matches("mobile", keyword, "i");
+
+// 同步企微外部联系人
+const contactInfo = await wxwork.syncContact(externalContact);
+
+// 查询客户的所有项目
+const projectQuery = new Parse.Query("Project");
+projectQuery.equalTo("customer", contactInfo.toPointer());
+projectQuery.notEqualTo("isDeleted", true);
+```
+
+**索引建议**:
+- `company + isDeleted`
+- `external_userid + company`
+- `mobile + company`
+- `source + company`
+
+---
+
+### 4. GroupChat(企微群聊表)
+
+**用途**: 存储企业微信群聊信息,支持与项目关联。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "group001" |
+| chat_id | String | 是 | 企微群聊ID | "wrxxx" |
+| name | String | 是 | 群聊名称 | "李总-现代简约全案" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| project | Pointer | 否 | 关联项目 | → Project |
+| member_list | Array | 否 | 成员列表 | [{userid: "zhangsan", ...}] |
+| joinUrl | String | 否 | 入群链接 | "https://..." |
+| data | Object | 否 | 扩展数据 | { owner, notice, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**data字段扩展示例**:
+```json
+{
+  "owner": "zhangsan",
+  "notice": "本群为李总项目专属群,请及时沟通设计需求",
+  "member_version": "1234567890",
+  "joinQrcode": "https://...",
+  "create_time": 1605171726
+}
+```
+
+**与Project的关系**:
+- **简单模式**: GroupChat.project 直接指向 Project(一个群当前进行中的项目)
+- **复杂模式**: 通过 ProjectGroup 中间表实现多对多(一个项目多个群,或多个项目一个群)
+
+**使用场景**:
+```typescript
+// 从企微会话中获取群聊并同步
+const { GroupChat } = await wxwork.getCurrentChatObject();
+console.log("当前群聊:", GroupChat.get("name"));
+console.log("关联项目:", GroupChat.get("project")?.id);
+
+// 查询项目的所有群聊
+const groupQuery = new Parse.Query("GroupChat");
+groupQuery.equalTo("project", projectId);
+groupQuery.notEqualTo("isDeleted", true);
+const groups = await groupQuery.find();
+
+// 创建项目群聊
+const GroupChat = Parse.Object.extend("GroupChat");
+const group = new GroupChat();
+group.set("chat_id", chatId);
+group.set("name", "项目群");
+group.set("company", company.toPointer());
+group.set("project", project.toPointer());
+await group.save();
+```
+
+**索引建议**:
+- `chat_id + company` (联合唯一)
+- `project + isDeleted`
+- `company + isDeleted`
+
+---
+
+### 5. ProjectGroup(项目群组关联表)
+
+**用途**: 实现项目与群聊的多对多关系(高级场景)。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "pg001" |
+| project | Pointer | 是 | 关联项目 | → Project |
+| groupChat | Pointer | 是 | 关联群聊 | → GroupChat |
+| isPrimary | Boolean | 否 | 是否主群 | true |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+
+**使用场景**:
+```typescript
+// 查询项目的所有群聊(通过中间表)
+const pgQuery = new Parse.Query("ProjectGroup");
+pgQuery.equalTo("project", projectId);
+pgQuery.include("groupChat");
+const pgs = await pgQuery.find();
+const groups = pgs.map(pg => pg.get("groupChat"));
+
+// 查询群聊关联的所有项目
+const pgQuery2 = new Parse.Query("ProjectGroup");
+pgQuery2.equalTo("groupChat", groupChatId);
+pgQuery2.include("project");
+const projects = (await pgQuery2.find()).map(pg => pg.get("project"));
+```
+
+**索引建议**:
+- `project + groupChat` (联合唯一)
+- `groupChat`
+- `isPrimary`
+
+---
+
+### 6. Project(项目表)
+
+**用途**: 项目管理的核心表,记录设计项目的全生命周期信息。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "proj001" |
+| title | String | 是 | 项目标题 | "李总现代简约全案" |
+| company | Pointer | 是 | 所属企业 | → Company |
+| customer | Pointer | 是 | 客户 | → ContactInfo |
+| assignee | Pointer | 否 | 负责设计师 | → Profile |
+| status | String | 是 | 项目状态 | "进行中" |
+| currentStage | String | 是 | 当前阶段 | "建模" |
+| deadline | Date | 否 | 截止时间 | 2024-12-31T00:00:00.000Z |
+| data | Object | 否 | 扩展数据 | { requirements, stageHistory, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**status 枚举值**:
+- `待分配`: 新建项目,等待分配设计师
+- `进行中`: 设计师已开始工作
+- `已完成`: 项目交付完成
+- `已暂停`: 客户要求暂停
+- `已延期`: 超过原定截止时间
+- `已取消`: 项目取消
+
+**currentStage 枚举值**:
+- `订单分配`: 客服下单,待分配设计师
+- `需求沟通`: 设计师与客户沟通需求
+- `方案确认`: 设计方案等待客户确认
+  - 并列任务
+    - `建模`: 3D建模阶段
+    - `软装`: 软装设计阶段
+    - `渲染`: 效果图渲染阶段
+    - `后期`: 后期处理与优化
+- `尾款结算`: 等待客户支付尾款
+- `客户评价`: 客户评价阶段
+- `投诉处理`: 客户投诉处理
+
+**data字段扩展示例**:
+```json
+{
+  "projectCode": "YSS-2024-001",
+  "estimatedCompletionDate": "2024-12-20T00:00:00.000Z",
+  "actualCompletionDate": null,
+  "customerServiceId": "prof002",
+  "priority": "high",
+  "tags": ["全案", "现代简约", "120平"],
+  "stageHistory": [
+    {
+      "stage": "订单分配",
+      "startTime": "2024-10-01T09:00:00.000Z",
+      "endTime": "2024-10-01T10:30:00.000Z",
+      "duration": 1.5,
+      "status": "completed",
+      "operator": { "id": "prof002", "name": "王客服", "role": "客服" }
+    }
+  ],
+  "workflow": {
+    "overallProgress": 45,
+    "estimatedHours": 160,
+    "actualHours": 72
+  }
+}
+```
+
+**使用场景**:
+```typescript
+// 创建项目
+const Project = Parse.Object.extend("Project");
+const project = new Project();
+project.set("title", "李总现代简约全案");
+project.set("company", company.toPointer());
+project.set("customer", customer.toPointer());
+project.set("status", "待分配");
+project.set("currentStage", "订单分配");
+await project.save();
+
+// 查询设计师的项目列表
+const query = new Parse.Query("Project");
+query.equalTo("assignee", profileId);
+query.containedIn("status", ["待分配", "进行中"]);
+query.notEqualTo("isDeleted", true);
+query.include("customer", "assignee");
+query.descending("updatedAt");
+
+// 更新项目阶段
+project.set("currentStage", "建模");
+project.set("status", "进行中");
+await project.save();
+```
+
+**索引建议**:
+- `company + isDeleted`
+- `assignee + status`
+- `customer + isDeleted`
+- `currentStage + status`
+- `deadline`
+- `updatedAt` (降序)
+
+---
+
+### 7. ProjectRequirement(需求信息表)
+
+**用途**: 存储项目的详细需求信息,包括空间信息、设计需求、素材分析等。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "req001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| company | Pointer | 是 | 所属企业 | → Company |
+| spaces | Array | 否 | 空间信息 | [{name: "客厅", area: 40, ...}] |
+| designRequirements | Object | 否 | 设计需求 | { style: [...], color: "...", ... } |
+| materialAnalysis | Object | 否 | 素材分析结果 | { colorAnalysis: {...}, ... } |
+| data | Object | 否 | 扩展数据 | { functionalRequirements, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**spaces 结构示例**:
+```json
+[
+  {
+    "id": "space001",
+    "name": "客厅",
+    "area": 40,
+    "dimensions": {
+      "length": 8,
+      "width": 5,
+      "height": 2.8
+    },
+    "existingConditions": ["采光好", "南向"],
+    "requirements": ["需要大储物空间", "开放式设计"]
+  }
+]
+```
+
+**designRequirements 结构示例**:
+```json
+{
+  "style": ["现代简约", "北欧"],
+  "colorPreference": "明亮温馨",
+  "materialPreference": ["实木", "环保材料"],
+  "specialRequirements": ["智能家电", "宠物友好设计"]
+}
+```
+
+**materialAnalysis 结构示例**:
+```json
+{
+  "colorAnalysis": {
+    "primaryHue": 180,
+    "saturation": 45,
+    "temperature": "冷色调",
+    "colorDistribution": [
+      { "hex": "#F5F5DC", "percentage": 40, "name": "米白色" },
+      { "hex": "#8B4513", "percentage": 30, "name": "原木色" }
+    ]
+  },
+  "formAnalysis": {
+    "lineType": "straight",
+    "complexity": 60,
+    "symmetry": "symmetric"
+  },
+  "textureAnalysis": {
+    "dominantTexture": "光滑",
+    "materials": ["实木", "大理石", "布艺"]
+  },
+  "lightingAnalysis": {
+    "naturalLight": "充足",
+    "lightColor": "暖白",
+    "lightIntensity": "中等"
+  }
+}
+```
+
+**索引建议**:
+- `project` (唯一)
+- `company + isDeleted`
+
+---
+
+### 8. ProjectTeam(项目团队表)
+
+**用途**: 管理项目团队成员及其角色分工。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "team001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| profile | Pointer | 是 | 团队成员 | → Profile |
+| role | String | 是 | 项目角色 | "主设计师" / "建模师" / "渲染师" |
+| workload | Number | 否 | 工作量占比 | 0.6 |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 加入时间 | 2024-01-01T00:00:00.000Z |
+
+**role 枚举值**:
+- `主设计师`: 项目负责人
+- `建模师`: 负责3D建模
+- `渲染师`: 负责效果图渲染
+- `软装师`: 负责软装设计
+- `助理`: 辅助工作
+
+**使用场景**:
+```typescript
+// 添加团队成员
+const ProjectTeam = Parse.Object.extend("ProjectTeam");
+const team = new ProjectTeam();
+team.set("project", project.toPointer());
+team.set("profile", designer.toPointer());
+team.set("role", "建模师");
+team.set("workload", 0.4);
+await team.save();
+
+// 查询项目团队
+const teamQuery = new Parse.Query("ProjectTeam");
+teamQuery.equalTo("project", projectId);
+teamQuery.include("profile");
+teamQuery.notEqualTo("isDeleted", true);
+```
+
+**索引建议**:
+- `project + isDeleted`
+- `profile + project` (联合唯一)
+
+---
+
+### 9. Product(交付物表)
+
+**用途**: 记录项目各阶段的交付物,如效果图、施工图等。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "deliv001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| company | Pointer | 是 | 所属企业 | → Company |
+| stage | String | 是 | 所属阶段 | "建模" / "渲染" / "后期" |
+| processType | String | 否 | 工序类型 | "modeling" / "rendering" |
+| space | String | 否 | 空间名称 | "客厅" / "主卧" |
+| fileUrl | String | 是 | 文件URL | "https://..." |
+| reviewStatus | String | 是 | 审核状态 | "pending" / "approved" |
+| data | Object | 否 | 扩展数据 | { thumbnailUrl, version, ... } |
+| quotation | Object | 否 | 产品报价 | { price, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**stage 枚举值**: 与 Project.currentStage 一致
+
+**processType 枚举值**:
+- `modeling`: 建模
+- `softDecor`: 软装
+- `rendering`: 渲染
+- `postProcess`: 后期
+
+**reviewStatus 枚举值**:
+- `pending`: 待审核
+- `approved`: 已通过
+- `rejected`: 已驳回
+- `revision_required`: 需要修改
+
+**data字段扩展示例**:
+```json
+{
+  "fileName": "客厅效果图v2.jpg",
+  "fileSize": 2048576,
+  "fileType": "image",
+  "format": "jpg",
+  "thumbnailUrl": "https://...",
+  "uploadedBy": "prof001",
+  "version": 2,
+  "reviewNotes": "色调需要再暖一些",
+  "progress": 100
+}
+```
+
+**quotation字段扩展说明**
+```
+Product.quotation 产品报价字段
+
+**用途**: 记录项目报价信息和审批流程。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| price | Number | 是 | 总金额 | 1200 |
+| currency | String | 是 | 货币单位 | "CNY" |
+| breakdown | Object | 否 | 费用明细 | { design: 30000, ... } |
+| status | String | 是 | 审核状态 | "待审核" / "已通过" |
+| approvedBy | Pointer | 否 | 审批人 | → Profile |
+| data | Object | 否 | 扩展数据 | { discount, notes, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**status 枚举值**:
+- `待审核`: 等待财务或管理员审核
+- `已通过`: 审核通过
+- `已驳回`: 审核未通过
+- `已修改`: 报价已修改,需重新审核
+
+**breakdown 结构示例**:
+``\`json
+{
+  "design": 30000,
+  "modeling": 20000,
+  "rendering": 15000,
+  "softDecor": 10000,
+  "postProcessing": 5000
+}
+``\`
+
+**data字段扩展示例**:
+``\`json
+{
+  "discount": 0.9,
+  "discountReason": "老客户优惠",
+  "notes": "含3次修改",
+  "validUntil": "2024-11-01T00:00:00.000Z",
+  "approvalHistory": [
+    {
+      "approver": "prof003",
+      "action": "approved",
+      "timestamp": "2024-10-15T10:00:00.000Z",
+      "comment": "价格合理"
+    }
+  ]
+}
+``\`
+
+**索引建议**:
+- `project + isDeleted`
+- `status + company`
+- `approvedBy`
+
+```
+
+**索引建议**:
+- `project + stage + isDeleted`
+- `project + space`
+- `reviewStatus + project`
+
+---
+
+### 10. ProjectFile(项目文件表)
+
+**用途**: 存储项目相关的所有文件,如CAD图纸、参考图等。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "file001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| uploadedBy | Pointer | 是 | 上传人 | → Profile |
+| fileType | String | 是 | 文件类型 | "cad" / "reference" / "document" |
+| fileUrl | String | 是 | 文件URL | "https://..." |
+| fileName | String | 是 | 文件名 | "户型图.dwg" |
+| fileSize | Number | 否 | 文件大小(字节) | 1024000 |
+| stage | String | 否 | 关联阶段 | "需求沟通" |
+| data | Object | 否 | 扩展数据 | { thumbnailUrl, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**fileType 枚举值**:
+- `cad`: CAD图纸
+- `reference`: 参考图片
+- `document`: 文档资料
+- `contract`: 合同文件
+- `voucher`: 付款凭证
+- `other`: 其他
+
+**索引建议**:
+- `project + fileType + isDeleted`
+- `uploadedBy + project`
+
+---
+
+### 12. ProjectSettlement (结算记录表)
+
+**用途**: 记录项目分阶段结算信息。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "settle001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| company | Pointer | 是 | 所属企业 | → Company |
+| stage | String | 是 | 结算阶段 | "定金" / "进度款" / "尾款" |
+| amount | Number | 是 | 结算金额 | 24000 |
+| percentage | Number | 否 | 占总额百分比 | 0.3 |
+| status | String | 是 | 结算状态 | "待结算" / "已结算" |
+| dueDate | Date | 否 | 应付日期 | 2024-10-20T00:00:00.000Z |
+| settledAt | Date | 否 | 实际结算时间 | 2024-10-18T14:30:00.000Z |
+| data | Object | 否 | 扩展数据 | { paymentMethod, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**stage 枚举值**:
+- `定金`: 项目启动定金(通常30%)
+- `进度款`: 中期进度款(通常40%)
+- `尾款`: 项目完成尾款(通常30%)
+
+**status 枚举值**:
+- `待结算`: 等待客户付款
+- `已结算`: 已收到款项
+- `逾期`: 超过应付日期未付款
+
+**data字段扩展示例**:
+```json
+{
+  "paymentMethod": "微信转账",
+  "transactionId": "WX20241018143000",
+  "notes": "已收到定金",
+  "invoiceRequired": true,
+  "invoiceIssued": false
+}
+```
+
+**索引建议**:
+- `project + stage`
+- `status + company`
+- `dueDate`
+
+---
+
+### 13. ProjectVoucher(付款凭证表)
+
+**用途**: 记录客户付款凭证及OCR识别结果。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "voucher001" |
+| settlement | Pointer | 是 | 关联结算记录 | → ProjectSettlement |
+| project | Pointer | 是 | 所属项目 | → Project |
+| amount | Number | 是 | 付款金额 | 24000 |
+| voucherUrl | String | 是 | 凭证图片URL | "https://..." |
+| recognizedInfo | Object | 否 | OCR识别结果 | { amount: 24000, ... } |
+| verifiedBy | Pointer | 否 | 核验人 | → Profile |
+| data | Object | 否 | 扩展数据 | { paymentTime, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**recognizedInfo 结构示例**:
+```json
+{
+  "amount": 24000,
+  "paymentTime": "2024-10-18 14:30:00",
+  "paymentMethod": "微信支付",
+  "transactionId": "WX20241018143000",
+  "payerName": "李四",
+  "confidence": 0.95
+}
+```
+
+**索引建议**:
+- `settlement + project`
+- `project + isDeleted`
+
+---
+
+### 14. ProjectFeedback(客户反馈表)
+
+**用途**: 记录客户在各阶段的反馈和评价。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "feedback001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| customer | Pointer | 是 | 反馈客户 | → ContactInfo |
+| stage | String | 是 | 反馈阶段 | "建模" / "渲染" |
+| feedbackType | String | 是 | 反馈类型 | "suggestion" / "complaint" |
+| content | String | 是 | 反馈内容 | "客厅颜色希望再暖一些" |
+| rating | Number | 否 | 评分(1-5) | 4 |
+| status | String | 是 | 处理状态 | "待处理" / "已解决" |
+| data | Object | 否 | 扩展数据 | { response, attachments, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**feedbackType 枚举值**:
+- `suggestion`: 建议
+- `complaint`: 投诉
+- `praise`: 表扬
+- `question`: 疑问
+
+**status 枚举值**:
+- `待处理`: 新反馈,待响应
+- `处理中`: 正在处理
+- `已解决`: 问题已解决
+- `已关闭`: 反馈已关闭
+
+**data字段扩展示例**:
+```json
+{
+  "isSatisfied": false,
+  "problemLocation": "客厅沙发区",
+  "expectedEffect": "希望更温馨舒适",
+  "referenceCase": "case123",
+  "response": "已调整色调,请查看最新版本",
+  "respondedBy": "prof001",
+  "respondedAt": "2024-10-16T10:00:00.000Z",
+  "attachments": ["https://..."]
+}
+```
+
+**索引建议**:
+- `project + status + isDeleted`
+- `customer + project`
+- `feedbackType + stage`
+
+---
+
+### 15. ProductCheck(产品质量检查表)
+
+**用途**: 记录项目质量检查结果,如模型检查、效果图审核等。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "check001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| checkType | String | 是 | 检查类型 | "model" / "render" / "final" |
+| checkedBy | Pointer | 是 | 检查人 | → Profile |
+| checkedAt | Date | 是 | 检查时间 | 2024-10-15T10:00:00.000Z |
+| isPassed | Boolean | 是 | 是否通过 | true |
+| items | Array | 否 | 检查项目 | [{name: "尺寸准确性", passed: true}] |
+| data | Object | 否 | 扩展数据 | { notes, images, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**checkType 枚举值**:
+- `model`: 模型检查
+- `render`: 渲染效果检查
+- `final`: 最终交付检查
+
+**items 结构示例**:
+```json
+[
+  {
+    "id": "item001",
+    "name": "尺寸准确性",
+    "category": "基础",
+    "isPassed": true,
+    "notes": "符合要求"
+  },
+  {
+    "id": "item002",
+    "name": "材质真实性",
+    "category": "渲染质量",
+    "isPassed": false,
+    "notes": "地板材质需要调整"
+  }
+]
+```
+
+**索引建议**:
+- `project + checkType + isDeleted`
+- `checkedBy + isPassed`
+
+---
+
+### 16. ProjectIssue(异常记录表)
+
+**用途**: 记录项目执行过程中的异常情况。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "except001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| reportedBy | Pointer | 是 | 报告人 | → Profile |
+| exceptionType | String | 是 | 异常类型 | "failed" / "stuck" / "quality" |
+| severity | String | 是 | 严重程度 | "low" / "high" / "critical" |
+| description | String | 是 | 异常描述 | "建模软件崩溃导致进度延误" |
+| status | String | 是 | 处理状态 | "待处理" / "已解决" |
+| resolution | String | 否 | 解决方案 | "重新建模,延期2天" |
+| data | Object | 否 | 扩展数据 | { assignedTo, impact, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**exceptionType 枚举值**:
+- `failed`: 任务失败
+- `stuck`: 进度停滞
+- `quality`: 质量问题
+- `timeline`: 进度延期
+- `resource`: 资源不足
+- `other`: 其他
+
+**severity 枚举值**:
+- `low`: 低,影响较小
+- `medium`: 中,需要关注
+- `high`: 高,严重影响进度
+- `critical`: 紧急,项目面临风险
+
+**data字段扩展示例**:
+```json
+{
+  "assignedTo": "prof003",
+  "relatedStage": "建模",
+  "impact": "预计延期2天",
+  "preventiveMeasures": ["增加备份频率", "升级建模软件"],
+  "resolvedAt": "2024-10-16T15:00:00.000Z"
+}
+```
+
+**索引建议**:
+- `project + status + isDeleted`
+- `exceptionType + severity`
+- `reportedBy`
+
+---
+
+### 17. Communication(沟通记录表)
+
+**用途**: 记录项目相关的沟通历史。
+
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| objectId | String | 是 | 主键ID | "comm001" |
+| project | Pointer | 是 | 所属项目 | → Project |
+| sender | Pointer | 是 | 发送人 | → Profile / ContactInfo |
+| content | String | 是 | 沟通内容 | "客厅效果图已发送,请查收" |
+| communicationType | String | 是 | 沟通类型 | "message" / "call" / "meeting" |
+| relatedStage | String | 否 | 关联阶段 | "渲染" |
+| attachments | Array | 否 | 附件列表 | ["https://..."] |
+| data | Object | 否 | 扩展数据 | { isRead, priority, ... } |
+| isDeleted | Boolean | 否 | 软删除标记 | false |
+| createdAt | Date | 自动 | 创建时间 | 2024-01-01T00:00:00.000Z |
+| updatedAt | Date | 自动 | 更新时间 | 2024-01-01T00:00:00.000Z |
+
+**communicationType 枚举值**:
+- `message`: 文字消息
+- `call`: 电话沟通
+- `meeting`: 会议
+- `email`: 邮件
+- `feedback`: 客户反馈
+
+**索引建议**:
+- `project + createdAt`
+- `sender + project`
+- `relatedStage`
+
+---
+
+## 数据范式设计原则
+
+### 1. 租户隔离
+所有业务表都包含 `company` 字段,确保多租户数据隔离:
+```typescript
+query.equalTo("company", companyId);
+```
+
+### 2. 软删除
+所有表都包含 `isDeleted` 字段,避免数据永久删除:
+```typescript
+query.notEqualTo("isDeleted", true);
+```
+
+### 3. 扩展性
+使用 `data` Object 字段存储灵活的扩展数据,避免频繁修改表结构:
+```typescript
+// 读取扩展数据
+const extraData = project.get("data");
+const workflow = extraData.workflow;
+
+// 写入扩展数据
+project.set("data", {
+  ...project.get("data"),
+  workflow: { overallProgress: 50 }
+});
+```
+
+### 4. 关联查询优化
+使用 `include` 减少查询次数:
+```typescript
+query.include("customer", "assignee", "project.customer");
+```
+
+### 5. 索引策略
+- 每个表的 `company + isDeleted` 必建复合索引
+- 所有 Pointer 字段必建索引
+- 高频查询字段建立索引
+- `updatedAt` / `createdAt` 建立降序索引
+
+---
+
+## 使用场景示例
+
+### 场景1: 从企微群聊快速加载项目
+
+```typescript
+// 1. 从企微会话获取群聊
+const { GroupChat } = await wxwork.getCurrentChatObject();
+
+// 2. 直接获取关联项目
+const project = GroupChat.get("project");
+if (project) {
+  await project.fetch({ include: ["customer", "assignee"] });
+  console.log("项目:", project.get("title"));
+  console.log("客户:", project.get("customer").get("name"));
+  console.log("设计师:", project.get("assignee").get("name"));
+}
+```
+
+### 场景2: 创建项目并关联群聊
+
+```typescript
+// 1. 创建客户
+const ContactInfo = Parse.Object.extend("ContactInfo");
+const customer = new ContactInfo();
+customer.set("name", "李总");
+customer.set("mobile", "13900139000");
+customer.set("company", company.toPointer());
+await customer.save();
+
+// 2. 创建项目
+const Project = Parse.Object.extend("Project");
+const project = new Project();
+project.set("title", "李总现代简约全案");
+project.set("company", company.toPointer());
+project.set("customer", customer.toPointer());
+project.set("status", "待分配");
+project.set("currentStage", "订单分配");
+await project.save();
+
+// 3. 关联群聊
+const groupChat = await wxwork.syncGroupChat(currentChat.group);
+groupChat.set("project", project.toPointer());
+await groupChat.save();
+```
+
+### 场景3: 查询设计师的工作负载
+
+```typescript
+// 查询设计师当前进行中的项目
+const query = new Parse.Query("Project");
+query.equalTo("assignee", designerId);
+query.containedIn("status", ["待分配", "进行中"]);
+query.notEqualTo("isDeleted", true);
+query.include("customer");
+const projects = await query.find();
+
+console.log(`当前负载: ${projects.length}个项目`);
+
+// 统计各阶段项目数
+const stageCount = {};
+projects.forEach(p => {
+  const stage = p.get("currentStage");
+  stageCount[stage] = (stageCount[stage] || 0) + 1;
+});
+```
+
+### 场景4: 项目阶段推进
+
+```typescript
+// 推进项目到下一阶段
+const project = await new Parse.Query("Project").get(projectId);
+const currentStage = project.get("currentStage");
+const data = project.get("data") || {};
+
+// 更新阶段历史
+const stageHistory = data.stageHistory || [];
+stageHistory.push({
+  stage: currentStage,
+  startTime: lastStartTime,
+  endTime: new Date(),
+  status: "completed",
+  operator: { id: profileId, name: "张设计师", role: "组员" }
+});
+
+// 更新当前阶段
+project.set("currentStage", "渲染");
+project.set("data", {
+  ...data,
+  stageHistory
+});
+await project.save();
+```
+
+---
+
+## 数据迁移与兼容性
+
+### 从现有系统迁移
+
+如果系统中已有 `Contact` 表(企微外部联系人),建议:
+
+1. **保留 Contact 表**用于企微原始数据同步
+2. **ContactInfo 表**作为业务客户表
+3. **数据同步脚本**:定期从 Contact 同步到 ContactInfo
+
+```typescript
+// 同步脚本示例
+const contacts = await new Parse.Query("Contact")
+  .equalTo("company", companyId)
+  .limit(1000)
+  .find();
+
+for (const contact of contacts) {
+  // 查询或创建 ContactInfo
+  let contactInfo = await new Parse.Query("ContactInfo")
+    .equalTo("external_userid", contact.get("external_userid"))
+    .equalTo("company", companyId)
+    .first();
+
+  if (!contactInfo) {
+    contactInfo = new Parse.Object("ContactInfo");
+    contactInfo.set("company", company.toPointer());
+    contactInfo.set("external_userid", contact.get("external_userid"));
+  }
+
+  contactInfo.set("name", contact.get("name"));
+  contactInfo.set("mobile", contact.get("mobile"));
+  contactInfo.set("data", contact.get("data"));
+  await contactInfo.save();
+}
+```
+
+---
+
+## 总结
+
+本数据范式具有以下特点:
+
+✅ **统一规范**: Profile(员工)、ContactInfo(客户)统一管理
+✅ **灵活关联**: Project ←→ GroupChat 支持简单和复杂场景
+✅ **角色清晰**: role 字段区分客服/组员/组长
+✅ **扩展性强**: data Object 字段支持灵活扩展
+✅ **多租户**: company 字段确保租户隔离
+✅ **软删除**: isDeleted 字段保护数据安全
+✅ **企微集成**: 与企业微信深度集成
+
+通过实施这个数据范式,可以实现:
+- 从企微群聊快速访问项目信息
+- 统一的员工和客户管理
+- 完整的项目生命周期跟踪
+- 灵活的角色权限控制
+- 高效的数据查询和统计
+
+---
+
+**文档版本**: v1.0
+**最后更新**: 2025-10-15
+**维护者**: YSS Development Team

+ 2 - 0
src/app/app.config.ts

@@ -3,6 +3,8 @@ import { provideRouter } from '@angular/router';
 import { provideHttpClient } from '@angular/common/http';
 import { provideAnimations } from '@angular/platform-browser/animations';
 
+localStorage.setItem("company","cDL6R1hgSi")
+
 import { routes } from './app.routes';
 
 export const appConfig: ApplicationConfig = {

+ 12 - 0
src/app/app.ts

@@ -1,5 +1,6 @@
 import { Component, signal } from '@angular/core';
 import { RouterOutlet } from '@angular/router';
+import { AuthService } from 'fmode-ng';
 
 @Component({
   selector: 'app-root',
@@ -10,4 +11,15 @@ import { RouterOutlet } from '@angular/router';
 })
 export class App {
   protected readonly title = signal('yss-project');
+  constructor(private authServ:AuthService){
+    this.initAuthServ();
+  }
+  initAuthServ(){
+    // this.authServ.LoginPage = "/pcuser/E4KpGvTEto/login" // 登录时默认为用户名增加飞码AI账套company前缀
+    this.authServ.init({
+      company:"E4KpGvTEto", // 登录时默认为用户名增加飞码AI账套company
+      guardType: "modal", // 设置登录守卫方式
+    })
+    this.authServ.logoUrl = document.baseURI+"/assets/logo.jpg"
+  }
 }

binární
src/assets/logo.jpg


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů