database-tables-overview.md 62 KB


title: YSS项目管理系统数据库表结构文档 version: v1.0 date: 2025-10-21

category: database

YSS项目管理系统 - Parse Server 数据库表结构文档

文档信息

  • 项目名称: 映三色(YSS)项目管理系统
  • 数据库类型: Parse Server
  • 文档版本: v1.0
  • 创建日期: 2025-10-21
  • 维护团队: 后端开发团队

一、系统概述

1.1 项目简介

映三色项目管理系统是一个基于企业微信的设计师项目全流程管理平台,支持从客户咨询、订单分配、需求确认、交付执行到售后归档的完整生命周期管理。系统采用多租户架构,以 Company(企业)为核心进行数据隔离,支持客服、设计师、组长等多角色协作。

1.2 技术架构

  • 后端服务: Parse Server(开源BaaS平台)
  • 数据存储: MongoDB(Parse Server底层)
  • 前端框架: Angular 17 + Ionic Framework
  • 企微集成: fmode-ng/core(WxworkSDK)
  • 数据服务: fmode-ng/parse(FmodeParse)
  • 文件存储: fmode-ng NovaStorage(对象存储)

1.3 多租户架构

系统采用单库多租户设计模式:

  • 所有数据表通过 company 字段关联到企业(Company表)
  • 每个企业的数据完全隔离,互不干扰
  • 统一的用户认证系统(_User表)
  • 灵活的权限控制(基于角色和部门)

1.4 核心特性

  • 🏢 多租户隔离: 以Company为核心的数据隔离
  • 👥 统一人员管理: Profile(员工)和ContactInfo(客户)统一管理
  • 📋 灵活项目关联: Project与GroupChat灵活关联
  • 🏠 Product表空间管理: 通过Product表实现多空间设计产品管理
  • 💰 完整财务流程: 从报价、结算到付款凭证的完整闭环
  • 📊 质量控制体系: 客户反馈、质量检查、问题追踪完整流程
  • 🔍 AI辅助设计: 图片分析、色彩提取、方案生成

二、数据表总览

2.1 数据表分类

系统共包含 18个核心数据表 + 1个系统表(_User),按功能模块分类如下:

分类 表数量 表名列表
基础表 4 Company, Department, Profile, ContactInfo
企微集成 2 GroupChat, ProjectGroup
项目管理 3 Project, ProjectRequirement, ProjectTeam
产品空间 1 Product
文件管理 2 ProjectFile, Attachment
财务管理 1 ProjectPayment
质量反馈 3 ProjectFeedback, ProductCheck, ProjectIssue
沟通跟进 1 ContactFollow
系统认证 1 _User

2.2 数据表关系图

@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(Department, "Department\n部门表") {
    FIELD(objectId, String)
    FIELD(name, String)
    FIELD(type, String)
    FIELD(leader, Pointer→Profile)
    FIELD(company, Pointer→Company)
    FIELD(isDeleted, Boolean)
}

TABLE(Profile, "Profile\n员工档案表") {
    FIELD(objectId, String)
    FIELD(name, String)
    FIELD(mobile, String)
    FIELD(department, Pointer→Department)
    FIELD(company, Pointer→Company)
    FIELD(userId, String)
    FIELD(roleName, 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)
}

' ============ 项目模块 ============
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(profile, Pointer→Profile)
    FIELD(stage, String)
    FIELD(processType, String)
    FIELD(productName, String)
    FIELD(productType, String)
    FIELD(status, String)
    FIELD(fileUrl, String)
    FIELD(reviewStatus, String)
    FIELD(space, Object)
    FIELD(quotation, Object)
    FIELD(requirements, Object)
    FIELD(reviews, Array)
    FIELD(estimatedBudget, Number)
    FIELD(estimatedDuration, Number)
    FIELD(order, Number)
    FIELD(data, Object)
    FIELD(isDeleted, Boolean)
}

' ============ 文件管理 ============
TABLE(ProjectFile, "ProjectFile\n项目文件表") {
    FIELD(objectId, String)
    FIELD(project, Pointer→Project)
    FIELD(product, Pointer→Product)
    FIELD(attach, Pointer→Attachment)
    FIELD(uploadedBy, Pointer→Profile)
    FIELD(stage, String)
    FIELD(category, String)
    FIELD(data, Object)
    FIELD(analysis, Object)
    FIELD(isDeleted, Boolean)
}

TABLE(Attachment, "Attachment\n附件表") {
    FIELD(objectId, String)
    FIELD(size, Number)
    FIELD(url, String)
    FIELD(name, String)
    FIELD(mime, String)
    FIELD(md5, String)
    FIELD(metadata, Object)
    FIELD(company, Pointer→Company)
    FIELD(user, Pointer→_User)
}

' ============ 财务模块 ============
TABLE(ProjectPayment, "ProjectPayment\n项目付款表") {
    FIELD(objectId, String)
    FIELD(project, Pointer→Project)
    FIELD(company, Pointer→Company)
    FIELD(type, String)
    FIELD(stage, String)
    FIELD(method, String)
    FIELD(amount, Number)
    FIELD(currency, String)
    FIELD(percentage, Number)
    FIELD(paymentDate, Date)
    FIELD(dueDate, Date)
    FIELD(recordedDate, Date)
    FIELD(status, String)
    FIELD(voucherFile, Pointer→ProjectFile)
    FIELD(voucherUrl, String)
    FIELD(transactionId, String)
    FIELD(paymentReference, String)
    FIELD(paidBy, Pointer→ContactInfo)
    FIELD(recordedBy, Pointer→Profile)
    FIELD(verifiedBy, Pointer→Profile)
    FIELD(description, String)
    FIELD(notes, String)
    FIELD(relatedStage, String)
    FIELD(product, Pointer→Product)
    FIELD(autoReminderSent, Boolean)
    FIELD(reminderCount, Number)
    FIELD(data, Object)
    FIELD(isDeleted, Boolean)
}

' ============ 质量与反馈 ============
TABLE(ProjectFeedback, "ProjectFeedback\n客户反馈表") {
    FIELD(objectId, String)
    FIELD(project, Pointer→Project)
    FIELD(customer, Pointer→ContactInfo)
    FIELD(product, Pointer→Product)
    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(product, Pointer→Product)
    FIELD(creator, Pointer→Profile)
    FIELD(assignee, Pointer→Profile)
    FIELD(title, String)
    FIELD(description, String)
    FIELD(relatedSpace, String)
    FIELD(relatedStage, String)
    FIELD(relatedContentType, String)
    FIELD(relatedFiles, Array)
    FIELD(priority, String)
    FIELD(issueType, String)
    FIELD(dueDate, Date)
    FIELD(status, String)
    FIELD(resolution, String)
    FIELD(lastReminderAt, Date)
    FIELD(reminderCount, Number)
    FIELD(data, Object)
    FIELD(isDeleted, Boolean)
}

' ============ 跟进记录 ============
TABLE(ContactFollow, "ContactFollow\n跟进记录表") {
    FIELD(objectId, String)
    FIELD(project, Pointer→Project)
    FIELD(profile, Pointer→Profile)
    FIELD(contact, Pointer→ContactInfo)
    FIELD(content, String)
    FIELD(type, String)
    FIELD(stage, 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 : 负责人
Project "1" --> "1" ProjectRequirement : 需求信息
Project "1" <--> "n" GroupChat : ProjectGroup群聊关联
Project "1" --> "n" ProjectTeam : 项目团队

' Product表统一空间管理关系
Project "1" --> "n" Product : 空间设计产品
Product "n" --> "1" Profile : 负责设计师
Product "1" --> "n" ProjectFile : 产品文件
Product "1" --> "n" ProjectFeedback : 产品反馈
Product "1" --> "n" ProjectIssue : 产品异常
Product "1" --> "n" ContactFollow : 产品跟进

' 交付与财务
Project "1" --> "n" ProjectFile : 项目文件
Project "1" --> "n" ProjectPayment : 项目付款
ProjectPayment "1" --> "1" ProjectFile : 付款凭证
ProjectPayment "1" --> "1" ContactInfo : 付款人
ProjectPayment "1" --> "1" Profile : 记录人/验证人
Product "1" --> "n" ProjectPayment : 产品级付款

' 质量与沟通
Project "1" --> "n" ProjectFeedback : 客户反馈
Project "1" --> "n" ProjectIssue : 异常记录
Project "1" --> "n" ContactFollow : 跟进记录

' 群聊关系
GroupChat "n" --> "1" Company : 所属企业
GroupChat "n" --> "1" Project : 关联项目

' 文件关系
ProjectFile "n" --> "1" Attachment : 附件引用
Attachment "n" --> "1" Company : 所属企业

@enduml

三、核心数据表详解

3.1 基础表

3.1.1 Company(企业表)

用途: 多租户系统的核心表,所有业务数据通过 company 字段进行租户隔离。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "cDL6R1hgSi"
name String - 企业名称 "映三色设计"
corpId String - 企业微信CorpID "ww1234567890abcdef"
data Object {} 扩展数据(配置、模块等) { settings: {...}, modules: [...] }
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

索引配置:

// 从 scripts/migration/create-schema.js
indexes: [
  { name: 'corpId_index', fields: { corpId: 1 } },
  { name: 'name_index', fields: { name: 1 } }
]

data字段结构示例:

{
  "settings": {
    "timezone": "Asia/Shanghai",
    "currency": "CNY",
    "workingHours": {
      "start": "09:00",
      "end": "18:00"
    }
  },
  "modules": ["project", "customer", "finance"],
  "features": {
    "aiAnalysis": true,
    "wxworkIntegration": true
  },
  "branding": {
    "logo": "https://...",
    "primaryColor": "#3880FF"
  }
}

使用示例:

// 获取当前企业
const cid = localStorage.getItem('company');
const companyQuery = new Parse.Query('Company');
const company = await companyQuery.get(cid);

// 创建新企业
const Company = Parse.Object.extend('Company');
const newCompany = new Company();
newCompany.set('name', '映三色设计');
newCompany.set('corpId', 'ww1234567890abcdef');
newCompany.set('data', {
  settings: { timezone: 'Asia/Shanghai' },
  modules: ['project', 'customer']
});
await newCompany.save();

// 查询所有未删除的企业
const query = new Parse.Query('Company');
query.equalTo('isDeleted', false);
const companies = await query.find();

3.1.2 Department(部门表)

用途: 存储企业的组织架构,主要用于项目组管理。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "dept001"
name String - 部门名称 "设计一组"
type String "project" 部门类型 "project"
leader Pointer - 组长 → Profile
company Pointer - 所属企业 → Company
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

type枚举值:

  • project: 项目组
  • sales: 销售部
  • finance: 财务部
  • hr: 人事部
  • other: 其他

索引配置:

indexes: [
  { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  { name: 'leader_index', fields: { leader: 1 } }
]

使用示例:

// 创建项目组
const Department = Parse.Object.extend('Department');
const dept = new Department();
dept.set('name', '设计一组');
dept.set('type', 'project');
dept.set('company', company.toPointer());
dept.set('leader', leaderProfile.toPointer());
await dept.save();

// 查询企业的所有项目组
const deptQuery = new Parse.Query('Department');
deptQuery.equalTo('company', company.toPointer());
deptQuery.equalTo('type', 'project');
deptQuery.equalTo('isDeleted', false);
deptQuery.include('leader');
const departments = await deptQuery.find();

// 查询组长负责的部门
const leaderDeptQuery = new Parse.Query('Department');
leaderDeptQuery.equalTo('leader', profileId);
const leaderDepts = await leaderDeptQuery.find();

3.1.3 Profile(员工档案表)

用途: 统一管理企业员工信息,支持客服、设计师(组员)、组长等多种角色。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "prof001"
name String - 员工姓名 "张三"
mobile String - 手机号 "13800138000"
department Pointer - 所属部门 → Department
company Pointer - 所属企业 → Company
userId String - 企微UserID "zhangsan"
roleName String - 员工角色 "客服" / "组员" / "组长"
data Object {} 扩展数据 { avatar, skills, ... }
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

roleName枚举值:

  • 客服: 客户服务人员,负责接单、跟进
  • 组员: 设计师,负责具体设计工作
  • 组长: 团队负责人,负责审核、分配
  • 财务: 财务人员
  • 人事: 人事人员
  • 管理员: 系统管理员

索引配置:

indexes: [
  { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  { name: 'userId_company', fields: { userId: 1, company: 1 }, unique: true },
  { name: 'roleName_company', fields: { roleName: 1, company: 1 } },
  { name: 'mobile_company', fields: { mobile: 1, company: 1 } }
]

data字段结构示例:

{
  "avatar": "https://...",
  "gender": "male",
  "email": "zhangsan@example.com",
  "skills": ["建模", "渲染", "软装"],
  "level": "中级设计师",
  "joinDate": "2024-01-01",
  "wxworkInfo": {
    "userid": "zhangsan",
    "department": [1, 2],
    "position": "设计师",
    "mobile": "13800138000",
    "avatar": "https://..."
  },
  "workload": {
    "currentProjects": 3,
    "completedProjects": 15,
    "averageQuality": 4.5
  }
}

使用示例:

// 创建员工档案
const Profile = Parse.Object.extend('Profile');
const profile = new Profile();
profile.set('name', '张三');
profile.set('mobile', '13800138000');
profile.set('department', department.toPointer());
profile.set('company', company.toPointer());
profile.set('userId', 'zhangsan');
profile.set('roleName', '组员');
profile.set('data', {
  avatar: 'https://...',
  skills: ['建模', '渲染']
});
await profile.save();

// 查询企业的所有设计师
const designerQuery = new Parse.Query('Profile');
designerQuery.equalTo('company', company.toPointer());
designerQuery.equalTo('roleName', '组员');
designerQuery.equalTo('isDeleted', false);
designerQuery.include('department');
const designers = await designerQuery.find();

// 根据企微UserID查询员工
const userQuery = new Parse.Query('Profile');
userQuery.equalTo('userId', 'zhangsan');
userQuery.equalTo('company', company.toPointer());
const user = await userQuery.first();

3.1.4 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 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

source枚举值:

  • 朋友圈: 微信朋友圈广告
  • 信息流: 抖音/小红书等信息流广告
  • 转介绍: 老客户转介绍
  • 其他: 其他来源

索引配置:

indexes: [
  { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  { name: 'external_userid_company', fields: { external_userid: 1, company: 1 }, unique: true },
  { name: 'mobile_company', fields: { mobile: 1, company: 1 } },
  { name: 'source_company', fields: { source: 1, company: 1 } }
]

data字段结构示例:

{
  "avatar": "https://...",
  "wechat": "lisi_wechat",
  "gender": "female",
  "age": 28,
  "tags": {
    "needType": "硬装",
    "preference": "现代",
    "budget": { "min": 50000, "max": 100000 },
    "colorAtmosphere": "暖色调"
  },
  "wxworkInfo": {
    "external_userid": "wmxxx",
    "name": "李四",
    "avatar": "https://...",
    "type": 1,
    "gender": 2,
    "unionid": "xxx"
  },
  "followUpStatus": "confirm",
  "demandType": "value-sensitive",
  "preferenceTags": ["现代简约", "温馨舒适", "储物充足"]
}

使用示例:

// 创建客户信息
const ContactInfo = Parse.Object.extend('ContactInfo');
const contact = new ContactInfo();
contact.set('name', '李四');
contact.set('mobile', '13900139000');
contact.set('company', company.toPointer());
contact.set('external_userid', 'wmxxx');
contact.set('source', '朋友圈');
contact.set('data', {
  wechat: 'lisi_wechat',
  tags: {
    needType: '硬装',
    preference: '现代',
    budget: { min: 50000, max: 100000 }
  }
});
await contact.save();

// 根据企微external_userid查询客户
const externalQuery = new Parse.Query('ContactInfo');
externalQuery.equalTo('external_userid', 'wmxxx');
externalQuery.equalTo('company', company.toPointer());
const customer = await externalQuery.first();

// 查询某来源渠道的所有客户
const sourceQuery = new Parse.Query('ContactInfo');
sourceQuery.equalTo('company', company.toPointer());
sourceQuery.equalTo('source', '朋友圈');
sourceQuery.equalTo('isDeleted', false);
const customers = await sourceQuery.find();

3.2 企微集成表

3.2.1 GroupChat(企微群聊表)

用途: 存储企业微信群聊信息,支持与项目的灵活关联。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "gc001"
chat_id String - 企微群聊ID "wrxxxxxx"
name String - 群聊名称 "李总-现代简约全案设计"
company Pointer - 所属企业 → Company
project Pointer - 关联项目 → Project
member_list Array [] 群成员列表 [{ userid: "zhangsan", type: 1 }]
joinUrl String - 群聊加入链接 "https://work.weixin.qq.com/..."
data Object {} 扩展数据 { owner, create_time, ... }
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

索引配置:

indexes: [
  { name: 'chat_id_company', fields: { chat_id: 1, company: 1 }, unique: true },
  { name: 'project_isDeleted', fields: { project: 1, isDeleted: 1 } },
  { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } }
]

member_list结构示例:

[
  {
    "userid": "zhangsan",
    "type": 1,
    "join_time": 1701234567,
    "join_scene": 3,
    "invitor": { "userid": "lisi" }
  },
  {
    "userid": "wmxxx",
    "type": 2,
    "join_time": 1701234567,
    "join_scene": 3,
    "unionid": "xxx"
  }
]

data字段结构示例:

{
  "owner": "zhangsan",
  "create_time": 1701234567,
  "notice": "本群用于项目沟通",
  "admin_list": [{ "userid": "zhangsan" }]
}

使用示例:

// 创建群聊记录
const GroupChat = Parse.Object.extend('GroupChat');
const groupChat = new GroupChat();
groupChat.set('chat_id', 'wrxxxxxx');
groupChat.set('name', '李总-现代简约全案设计');
groupChat.set('company', company.toPointer());
groupChat.set('project', project.toPointer());
groupChat.set('member_list', [
  { userid: 'zhangsan', type: 1, join_time: Date.now() },
  { userid: 'wmxxx', type: 2, join_time: Date.now() }
]);
await groupChat.save();

// 根据chat_id查询群聊
const chatQuery = new Parse.Query('GroupChat');
chatQuery.equalTo('chat_id', 'wrxxxxxx');
chatQuery.equalTo('company', company.toPointer());
chatQuery.include('project');
const chat = await chatQuery.first();

// 查询项目关联的所有群聊
const projectChatQuery = new Parse.Query('GroupChat');
projectChatQuery.equalTo('project', project.toPointer());
projectChatQuery.equalTo('isDeleted', false);
const projectChats = await projectChatQuery.find();

3.2.2 ProjectGroup(项目群组关联表)

用途: 管理项目与群聊的多对多关联关系,支持一个项目关联多个群聊。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "pg001"
project Pointer - 关联项目 → Project
groupChat Pointer - 关联群聊 → GroupChat
isPrimary Boolean false 是否主群 true
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z

索引配置:

indexes: [
  { name: 'project_groupChat', fields: { project: 1, groupChat: 1 }, unique: true },
  { name: 'groupChat_index', fields: { groupChat: 1 } },
  { name: 'isPrimary_index', fields: { isPrimary: 1 } }
]

使用示例:

// 创建项目群组关联
const ProjectGroup = Parse.Object.extend('ProjectGroup');
const pg = new ProjectGroup();
pg.set('project', project.toPointer());
pg.set('groupChat', groupChat.toPointer());
pg.set('isPrimary', true); // 标记为主群
await pg.save();

// 查询项目的所有群聊
const pgQuery = new Parse.Query('ProjectGroup');
pgQuery.equalTo('project', project.toPointer());
pgQuery.include('groupChat');
const groups = await pgQuery.find();

// 查询项目的主群
const primaryGroupQuery = new Parse.Query('ProjectGroup');
primaryGroupQuery.equalTo('project', project.toPointer());
primaryGroupQuery.equalTo('isPrimary', true);
primaryGroupQuery.include('groupChat');
const primaryGroup = await primaryGroupQuery.first();

3.3 项目管理表

3.3.1 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 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

status枚举值:

  • 待分配: 项目已创建,等待分配设计师
  • 进行中: 项目正在进行
  • 已完成: 项目已完成
  • 已暂停: 项目已暂停
  • 已延期: 项目已延期
  • 已取消: 项目已取消

currentStage枚举值:

  • 订单分配: 项目创建,分配设计师阶段
  • 方案深化: 需求沟通与方案确认阶段
  • 交付执行: 设计执行与交付阶段
  • 售后归档: 尾款结算与售后服务阶段

索引配置:

indexes: [
  { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  { name: 'assignee_status', fields: { assignee: 1, status: 1 } },
  { name: 'customer_isDeleted', fields: { customer: 1, isDeleted: 1 } },
  { name: 'currentStage_status', fields: { currentStage: 1, status: 1 } },
  { name: 'deadline_index', fields: { deadline: 1 } },
  { name: 'updatedAt_desc', fields: { updatedAt: -1 } }
]

data字段结构示例:

{
  "stageHistory": [
    {
      "stage": "订单分配",
      "startTime": "2024-01-01T00:00:00.000Z",
      "endTime": "2024-01-02T00:00:00.000Z",
      "status": "completed"
    },
    {
      "stage": "方案深化",
      "startTime": "2024-01-02T00:00:00.000Z",
      "status": "in_progress"
    }
  ],
  "totalBudget": 120000,
  "estimatedDuration": 30,
  "priority": "high",
  "tags": ["全案设计", "现代简约"],
  "notes": "客户要求尽快完成"
}

使用示例:

// 创建项目
const Project = Parse.Object.extend('Project');
const project = new Project();
project.set('title', '李总现代简约全案');
project.set('company', company.toPointer());
project.set('customer', contact.toPointer());
project.set('assignee', designer.toPointer());
project.set('status', '进行中');
project.set('currentStage', '方案深化');
project.set('deadline', new Date('2024-12-31'));
project.set('data', {
  totalBudget: 120000,
  priority: 'high',
  tags: ['全案设计', '现代简约']
});
await project.save();

// 查询设计师负责的项目
const designerProjectQuery = new Parse.Query('Project');
designerProjectQuery.equalTo('assignee', designerId);
designerProjectQuery.equalTo('status', '进行中');
designerProjectQuery.equalTo('isDeleted', false);
designerProjectQuery.include('customer');
designerProjectQuery.descending('updatedAt');
const projects = await designerProjectQuery.find();

// 更新项目阶段
project.set('currentStage', '交付执行');
const stageHistory = project.get('data').stageHistory || [];
stageHistory.push({
  stage: '交付执行',
  startTime: new Date(),
  status: 'in_progress'
});
project.set('data', { ...project.get('data'), stageHistory });
await project.save();

3.3.2 ProjectRequirement(需求信息表)

用途: 存储项目的详细需求信息,包括空间信息和设计需求。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "req001"
project Pointer - 关联项目 → Project
company Pointer - 所属企业 → Company
spaces Array [] 空间列表 [{ name: "客厅", area: 25 }]
designRequirements Object {} 设计需求 { style: [...], color: "..." }
materialAnalysis Object {} 材料分析 { preferred: [...], budget: {...} }
data Object {} 扩展数据 { functionalNeeds, ... }
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

索引配置:

indexes: [
  { name: 'project_unique', fields: { project: 1 }, unique: true },
  { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } }
]

spaces数组结构示例:

[
  {
    "spaceName": "客厅",
    "area": 25,
    "dimensions": {
      "length": 5.0,
      "width": 5.0,
      "height": 2.8
    },
    "features": ["朝南", "采光好"],
    "requirements": ["需要电视墙", "储物空间充足"]
  },
  {
    "spaceName": "主卧",
    "area": 18.5,
    "dimensions": {
      "length": 4.5,
      "width": 4.1,
      "height": 2.8
    },
    "features": ["朝南", "飘窗"],
    "requirements": ["独立卫浴", "衣柜空间"]
  }
]

designRequirements对象结构示例:

{
  "style": ["现代简约", "北欧风"],
  "colorPreference": "暖色调,以米白色为主",
  "materialPreference": ["实木", "环保材料", "石材"],
  "specialRequirements": [
    "需要大储物空间",
    "家有小孩,注意安全",
    "预留智能家居接口"
  ]
}

使用示例:

// 创建项目需求
const ProjectRequirement = Parse.Object.extend('ProjectRequirement');
const requirement = new ProjectRequirement();
requirement.set('project', project.toPointer());
requirement.set('company', company.toPointer());
requirement.set('spaces', [
  {
    spaceName: '客厅',
    area: 25,
    dimensions: { length: 5.0, width: 5.0, height: 2.8 },
    features: ['朝南', '采光好']
  }
]);
requirement.set('designRequirements', {
  style: ['现代简约'],
  colorPreference: '暖色调',
  materialPreference: ['实木', '环保材料']
});
await requirement.save();

// 查询项目需求
const reqQuery = new Parse.Query('ProjectRequirement');
reqQuery.equalTo('project', project.toPointer());
const projectReq = await reqQuery.first();

3.3.3 ProjectTeam(项目团队表)

用途: 管理项目团队成员及其角色。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "team001"
project Pointer - 关联项目 → Project
profile Pointer - 团队成员 → Profile
role String - 成员角色 "设计师" / "客服"
workload Number 0 工作量占比 50
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

role枚举值:

  • 设计师: 负责设计工作
  • 客服: 负责沟通跟进
  • 组长: 负责审核把关
  • 协作设计师: 协助设计工作

索引配置:

indexes: [
  { name: 'project_isDeleted', fields: { project: 1, isDeleted: 1 } },
  { name: 'profile_project', fields: { profile: 1, project: 1 }, unique: true }
]

使用示例:

// 添加团队成员
const ProjectTeam = Parse.Object.extend('ProjectTeam');
const teamMember = new ProjectTeam();
teamMember.set('project', project.toPointer());
teamMember.set('profile', designer.toPointer());
teamMember.set('role', '设计师');
teamMember.set('workload', 100);
await teamMember.save();

// 查询项目团队
const teamQuery = new Parse.Query('ProjectTeam');
teamQuery.equalTo('project', project.toPointer());
teamQuery.equalTo('isDeleted', false);
teamQuery.include('profile');
const team = await teamQuery.find();

// 查询设计师参与的所有项目
const designerTeamQuery = new Parse.Query('ProjectTeam');
designerTeamQuery.equalTo('profile', designerId);
designerTeamQuery.include('project');
const designerProjects = await designerTeamQuery.find();

3.4 产品空间管理表

3.4.1 Product(空间设计产品表)⭐核心表

用途: 核心创新表 - 统一管理空间设计产品,每个Product代表一个空间的设计产品(如"李总主卧设计"),包含空间信息、报价、需求、评价等全生命周期数据。

设计理念:

  • 🎯 Product即空间: 每个Product代表一个空间的设计产品
  • 💰 产品化报价: 通过Product.quotation管理空间级报价
  • 📁 灵活文件分类: ProjectFile通过category区分panorama、delivery等
  • 👥 直连设计师: 通过Product.profile直接关联负责设计师

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "prod001"
project Pointer - 所属项目 → Project
company Pointer - 所属企业 → Company
profile Pointer - 负责设计师 → Profile
stage String "not_started" 设计阶段 "modeling" / "rendering"
processType String - 工序类型 "modeling" / "rendering"
productName String - 产品名称 "李总主卧设计"
productType String - 空间类型 "bedroom"
status String "not_started" 产品状态 "in_progress"
fileUrl String - 主要效果图URL "https://..."
reviewStatus String "pending" 审核状态 "approved"
space Object {} 空间信息 {name, area, dimensions}
quotation Object {} 产品报价 {price, breakdown}
requirements Object {} 设计需求 {color, material}
reviews Array [] 产品评价 [{rating, comments}]
estimatedBudget Number - 预估预算 35000
estimatedDuration Number - 预估工期(天) 7
order Number 0 排序顺序 1
data Object {} 扩展数据 { version, progress, ... }
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

productType枚举值:

  • living_room: 客厅
  • bedroom: 卧室
  • master_bedroom: 主卧
  • kitchen: 厨房
  • bathroom: 卫生间
  • dining_room: 餐厅
  • study: 书房
  • balcony: 阳台
  • corridor: 走廊
  • storage: 储物间
  • entrance: 玄关
  • other: 其他

status枚举值:

  • not_started: 未开始
  • in_progress: 进行中
  • awaiting_review: 待审核
  • completed: 已完成
  • blocked: 已阻塞
  • delayed: 已延期

stage枚举值:

  • modeling: 建模阶段
  • softDecor: 软装阶段
  • rendering: 渲染阶段
  • postProcess: 后期阶段

reviewStatus枚举值:

  • pending: 待审核
  • approved: 已通过
  • rejected: 已驳回
  • revision_required: 需要修改

索引配置:

indexes: [
  { name: 'project_company_isDeleted', fields: { project: 1, company: 1, isDeleted: 1 } },
  { name: 'profile_company', fields: { profile: 1, company: 1 } },
  { name: 'productType_order', fields: { productType: 1, order: 1 } },
  { name: 'stage_status', fields: { stage: 1, status: 1 } },
  { name: 'reviewStatus', fields: { reviewStatus: 1 } }
]

space字段结构示例:

{
  "spaceName": "主卧",
  "area": 18.5,
  "dimensions": {
    "length": 4.5,
    "width": 4.1,
    "height": 2.8
  },
  "features": ["朝南", "飘窗", "独立卫浴"],
  "constraints": ["承重墙不可动"],
  "priority": "high",
  "complexity": "medium"
}

quotation字段结构示例:

{
  "price": 35000,
  "currency": "CNY",
  "breakdown": {
    "design": 15000,
    "modeling": 10000,
    "rendering": 8000,
    "softDecor": 2000
  },
  "unitPrice": 1891,
  "estimatedDays": 7,
  "status": "approved",
  "approvedBy": {
    "__type": "Pointer",
    "className": "Profile",
    "objectId": "prof001"
  },
  "approvedAt": "2024-01-05T10:00:00.000Z",
  "validUntil": "2024-12-31T00:00:00.000Z",
  "notes": "包含全套效果图和施工图"
}

requirements字段结构示例:

{
  "colorRequirement": {
    "primaryHue": 180,
    "saturation": 45,
    "temperature": "暖色调",
    "colorDistribution": [
      {
        "hex": "#F5F5DC",
        "percentage": 40,
        "name": "米白色"
      },
      {
        "hex": "#8B4513",
        "percentage": 30,
        "name": "原木色"
      }
    ]
  },
  "materialRequirement": {
    "preferred": ["实木", "环保材料", "石材"],
    "avoid": ["塑料", "合成材料"],
    "budget": {
      "min": 20000,
      "max": 40000
    }
  },
  "lightingRequirement": {
    "naturalLight": "充足",
    "lightColor": "暖白",
    "specialRequirements": ["床头阅读灯", "氛围灯"]
  },
  "specificRequirements": [
    "需要大储物空间",
    "独立卫浴",
    "飘窗设计"
  ],
  "referenceImages": ["https://...", "https://..."]
}

reviews字段结构示例:

[
  {
    "reviewId": "review001",
    "satisfactionScore": 4.5,
    "spaceSpecificRatings": {
      "design": 5,
      "functionality": 4,
      "material": 4,
      "lighting": 5
    },
    "usageFeedback": {
      "positive": ["储物空间充足", "光线舒适"],
      "improvements": ["可以增加插座数量"]
    },
    "comments": "整体设计很满意,储物功能强大",
    "afterPhotos": ["https://...", "https://..."],
    "submittedAt": "2024-11-15T10:00:00.000Z",
    "submittedBy": {
      "__type": "Pointer",
      "className": "ContactInfo",
      "objectId": "contact001"
    }
  }
]

使用示例:

// 创建空间设计产品
const Product = Parse.Object.extend('Product');
const product = new Product();
product.set('project', project.toPointer());
product.set('company', company.toPointer());
product.set('profile', designer.toPointer());
product.set('productName', '李总主卧设计');
product.set('productType', 'master_bedroom');
product.set('stage', 'modeling');
product.set('status', 'in_progress');

// 设置空间信息
product.set('space', {
  spaceName: '主卧',
  area: 18.5,
  dimensions: { length: 4.5, width: 4.1, height: 2.8 },
  features: ['朝南', '飘窗', '独立卫浴'],
  priority: 'high',
  complexity: 'medium'
});

// 设置产品报价
product.set('quotation', {
  price: 35000,
  currency: 'CNY',
  breakdown: {
    design: 15000,
    modeling: 10000,
    rendering: 8000,
    softDecor: 2000
  },
  status: 'pending',
  validUntil: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000)
});

// 设置设计需求
product.set('requirements', {
  colorRequirement: {
    temperature: '暖色调',
    colorDistribution: [
      { hex: '#F5F5DC', percentage: 40, name: '米白色' },
      { hex: '#8B4513', percentage: 30, name: '原木色' }
    ]
  },
  materialRequirement: {
    preferred: ['实木', '环保材料'],
    budget: { min: 20000, max: 40000 }
  },
  specificRequirements: ['需要大储物空间', '独立卫浴']
});

await product.save();

// 查询项目的所有空间设计产品
const productQuery = new Parse.Query('Product');
productQuery.equalTo('project', projectId);
productQuery.equalTo('isDeleted', false);
productQuery.include('profile');
productQuery.ascending('order');
const products = await productQuery.find();

// 查询设计师负责的空间产品
const designerProductQuery = new Parse.Query('Product');
designerProductQuery.equalTo('profile', designerId);
designerProductQuery.equalTo('status', 'in_progress');
designerProductQuery.include('project');
const designerProducts = await designerProductQuery.find();

// 更新产品状态和审核
product.set('status', 'awaiting_review');
product.set('reviewStatus', 'pending');
product.set('fileUrl', 'https://file.example.com/render.jpg');
await product.save();

// 添加客户评价
const reviews = product.get('reviews') || [];
reviews.push({
  reviewId: uuidv4(),
  satisfactionScore: 4.5,
  spaceSpecificRatings: {
    design: 5,
    functionality: 4,
    material: 4,
    lighting: 5
  },
  comments: '整体设计很满意',
  submittedAt: new Date(),
  submittedBy: customer.toPointer()
});
product.set('reviews', reviews);
await product.save();

3.5 文件管理表

3.5.1 ProjectFile(项目文件表)

用途: 存储项目相关的所有文件,通过category字段区分文件类型,支持AI分析结果存储。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "file001"
project Pointer - 所属项目 → Project
product Pointer - 关联空间产品 → Product
attach Pointer - 附件文件 → Attachment
uploadedBy Pointer - 上传人 → Profile
stage String - 关联阶段 "requirements"
category String "other" 文件分类 "panorama" / "delivery"
data Object {} 扩展数据 { thumbnailUrl, ... }
analysis Object {} AI分析结果 { ai: {...}, color: {...} }
isDeleted Boolean false 软删除标记 false
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

category枚举值:

  • quotation: 财务凭据(报价单等)
  • panorama: 全景素材(720全景图)
  • delivery: 交付文件(效果图、施工图)
  • reference: 参考文件(客户提供的参考图)
  • requirement: 需求文件(需求说明文档)
  • other: 其他文件

stage枚举值:

  • order: 订单分配阶段
  • requirements: 方案深化阶段
  • delivery: 交付执行阶段
  • aftercare: 售后归档阶段

索引配置:

indexes: [
  { name: 'project_product_stage_isDeleted', fields: { project: 1, product: 1, stage: 1, isDeleted: 1 } },
  { name: 'attach', fields: { attach: 1 } },
  { name: 'uploadedBy_project', fields: { uploadedBy: 1, project: 1 } },
  { name: 'category_index', fields: { category: 1 } }
]

analysis.ai字段结构示例 (AI模型分析结果):

{
  "styleElements": ["现代简约", "线条流畅", "留白设计"],
  "colorPalette": ["#FFFFFF", "#F5F5F5", "#3880FF"],
  "materialAnalysis": ["实木地板", "布艺沙发", "金属装饰"],
  "layoutFeatures": ["开放式布局", "功能分区明确", "采光良好"],
  "mood": "简洁明亮,温馨舒适",
  "confidence": 0.92,
  "analyzedAt": "2024-10-21T12:00:00.000Z",
  "version": "1.0",
  "source": "image_analysis"
}

analysis.color字段结构示例 (色彩分析插件结果):

{
  "version": "1.0",
  "source": "color-get",
  "pixelSize": 100,
  "createdAt": "2024-10-20T12:00:00.000Z",
  "palette": [
    {
      "rgb": { "r": 240, "g": 200, "b": 160 },
      "hex": "#F0C8A0",
      "percentage": 28.5
    },
    {
      "rgb": { "r": 139, "g": 69, "b": 19 },
      "hex": "#8B4513",
      "percentage": 22.3
    }
  ],
  "mosaicUrl": "data:image/png;base64,...",
  "metrics": {
    "warmCoolBalance": 12.0,
    "averageBrightness": 56.0,
    "averageSaturation": 42.5,
    "diversity": 18
  },
  "histogram": {
    "brightnessBins": [0, 1, 3, 5, 8, 12, 9, 6, 3, 1],
    "saturationBins": [0, 0, 2, 4, 7, 10, 9, 5, 2, 1]
  },
  "splitPoints": [
    {
      "temp": 14.2,
      "brightness": 62.1,
      "size": 3.5,
      "color": "#F0C8A0"
    }
  ]
}

使用示例:

// 上传项目文件
const ProjectFile = Parse.Object.extend('ProjectFile');
const projectFile = new ProjectFile();
projectFile.set('project', project.toPointer());
projectFile.set('product', product.toPointer());
projectFile.set('attach', attachment.toPointer());
projectFile.set('uploadedBy', profile.toPointer());
projectFile.set('stage', 'delivery');
projectFile.set('category', 'panorama');
projectFile.set('data', {
  thumbnailUrl: 'https://...',
  description: '主卧720全景图'
});
await projectFile.save();

// 保存AI分析结果
projectFile.set('analysis', {
  ai: {
    styleElements: ['现代简约', '线条流畅'],
    colorPalette: ['#FFFFFF', '#F5F5F5'],
    confidence: 0.92
  }
});
await projectFile.save();

// 保存色彩分析结果
projectFile.set('analysis', {
  ...projectFile.get('analysis'),
  color: {
    version: '1.0',
    source: 'color-get',
    palette: [
      { hex: '#F0C8A0', percentage: 28.5 }
    ],
    metrics: {
      warmCoolBalance: 12.0,
      averageBrightness: 56.0
    }
  }
});
await projectFile.save();

// 查询项目的全景图文件
const panoramaQuery = new Parse.Query('ProjectFile');
panoramaQuery.equalTo('project', projectId);
panoramaQuery.equalTo('category', 'panorama');
panoramaQuery.equalTo('isDeleted', false);
panoramaQuery.include('attach', 'uploadedBy', 'product');
const panoramaFiles = await panoramaQuery.find();

// 查询已进行色彩分析的文件
const analyzedQuery = new Parse.Query('ProjectFile');
analyzedQuery.exists('analysis.color');
analyzedQuery.equalTo('project', projectId);
const analyzedFiles = await analyzedQuery.find();

3.5.2 Attachment(附件表)

用途: 存储文件的元数据信息,由fmode-ng NovaStorage服务管理,与ProjectFile表关联使用。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID(NovaFile.id) "attach001"
size Number - 文件大小(字节) 1024000
url String - 文件访问URL "https://file.fmode.cn/..."
name String - 文件名 "主卧效果图.jpg"
mime String - MIME类型 "image/jpeg"
md5 String - 文件MD5哈希值 "5d41402abc..."
metadata Object - 文件元数据 { width, height, duration }
company Pointer - 所属企业 → Company
user Pointer - 上传用户 → _User
createdAt Date 自动 当前时间 创建时间 2024-01-01T00:00:00.000Z
updatedAt Date 自动 当前时间 更新时间 2024-01-01T00:00:00.000Z

metadata字段结构示例:

{
  "width": 1920,
  "height": 1080,
  "duration": 120,
  "lastModified": 1701234567000,
  "projectId": "proj001",
  "fileType": "image",
  "spaceId": "space001",
  "stage": "delivery"
}

与NovaStorage集成:

NovaStorage是fmode-ng提供的文件存储服务,上传流程如下:

// 1. 通过NovaStorage上传文件
import { NovaStorage, NovaFile } from 'fmode-ng/core';

const cid = localStorage.getItem('company');
const storage = await NovaStorage.withCid(cid);

const uploadedFile: NovaFile = await storage.upload(file, {
  prefixKey: `project/${projectId}`,
  onProgress: (progress) => {
    console.log('上传进度:', progress.total.percent);
  }
});

// 2. NovaFile自动保存到Attachment表
// uploadedFile.id 即为 Attachment.objectId

// 3. 创建ProjectFile记录关联Attachment
const ProjectFile = Parse.Object.extend('ProjectFile');
const projectFile = new ProjectFile();
projectFile.set('project', project.toPointer());

// 使用NovaFile.id创建Attachment指针
const Attachment = Parse.Object.extend('Attachment');
const attachment = new Attachment();
attachment.id = uploadedFile.id; // NovaFile.id
projectFile.set('attach', attachment.toPointer());

projectFile.set('uploadedBy', profile.toPointer());
projectFile.set('category', 'delivery');
await projectFile.save();

使用示例:

// 查询Attachment信息
const Attachment = Parse.Object.extend('Attachment');
const attachQuery = new Parse.Query('Attachment');
const attachment = await attachQuery.get('attach001');

console.log('文件名:', attachment.get('name'));
console.log('文件大小:', attachment.get('size'));
console.log('文件URL:', attachment.get('url'));
console.log('文件MD5:', attachment.get('md5'));

// 通过ProjectFile查询关联的Attachment
const fileQuery = new Parse.Query('ProjectFile');
fileQuery.include('attach');
const projectFile = await fileQuery.get('file001');
const relatedAttach = projectFile.get('attach');
console.log('附件URL:', relatedAttach.get('url'));

3.6 财务管理表

3.6.1 ProjectPayment(项目付款表)

用途: 统一的项目付款管理表,整合了原有的ProjectSettlement和ProjectVoucher功能,每条记录代表一次具体的付款行为。

字段说明:

字段名 类型 必填 默认值 说明 示例值
objectId String 自动生成 主键ID "payment001"
project Pointer - 所属项目 → Project
company Pointer - 所属企业 → Company
type String - 付款类型 "advance" / "milestone" / "final"
stage String - 付款阶段 "order" / "delivery"
method String - 支付方式 "bank_transfer" / "wechat"
amount Number - 付款金额 35000
currency String "CNY" 货币类型 "CNY"
percentage Number 0 占总价百分比 30
paymentDate Date - 实际付款时间 2024-12-01T10:00:00.000Z
dueDate Date - 应付款时间 2024-12-01T00:00:00.000Z
recordedDate Date - 记录时间 2024-11-30T15:30:00.000Z
status String "pending" 付款状态 "paid" / "overdue"
voucherFile Pointer - 付款凭证文件 → ProjectFile
voucherUrl String - 凭证URL "https://..."
transactionId String - 第三方交易ID "wx_123456789"
paymentReference String - 付款参考号/发票号 "INV-2024-001"
paidBy Pointer - 付款人 → ContactInfo
recordedBy Pointer - 记录人 → Profile
verifiedBy Pointer - 验证人 → Profile
description String - 付款描述 "首期款"
notes String - 备注信息 "客户通过银行转账付款"
relatedStage String - 关联执行阶段 "modeling"
product Pointer - 关联产品 → Product
autoReminderSent Boolean false 是否已发送提醒 false
reminderCount Number 0 提醒次数 0
data Object {} 扩展数据 {bankInfo, approvalFlow}
isDeleted Boolean false 软删除标记 false

枚举值: 参考rules/schemas.md中的详细定义(type: advance/milestone/final/refund, method: cash/bank_transfer/alipay/wechat等)


3.7 质量与反馈表

3.7.1 ProjectFeedback(客户反馈表)

用途: 记录客户在各阶段的反馈和评价。

关键字段: objectId, project, customer, product, stage, feedbackType, content, rating, status, data

使用示例:

const ProjectFeedback = Parse.Object.extend('ProjectFeedback');
const feedback = new ProjectFeedback();
feedback.set('project', project.toPointer());
feedback.set('customer', customer.toPointer());
feedback.set('product', product.toPointer());
feedback.set('stage', '渲染');
feedback.set('feedbackType', 'suggestion');
feedback.set('content', '客厅颜色希望再暖一些');
feedback.set('rating', 4);
feedback.set('status', '待处理');
await feedback.save();

3.7.2 ProductCheck(产品质量检查表)

用途: 记录产品的质量检查结果。

关键字段: objectId, project, checkType, checkedBy, checkedAt, isPassed, items, data

使用示例:

const ProductCheck = Parse.Object.extend('ProductCheck');
const check = new ProductCheck();
check.set('project', project.toPointer());
check.set('checkType', 'modeling');
check.set('checkedBy', leader.toPointer());
check.set('checkedAt', new Date());
check.set('isPassed', true);
check.set('items', [
  { name: '建模精度', passed: true },
  { name: '材质贴图', passed: true }
]);
await check.save();

3.7.3 ProjectIssue(项目问题追踪表)

用途: 记录项目中的问题、投诉、改图需求等。

字段说明:

字段名 类型 必填 说明 示例值
objectId String 主键ID "issue001"
project Pointer 所属项目 → Project
product Pointer 关联产品 → Product
creator Pointer 创建人 → Profile
assignee Pointer 责任人 → Profile
title String 问题标题 "客厅灯光需要调整"
description String 问题描述 "客户反馈灯光太暗..."
relatedSpace String 相关空间 "客厅"
relatedStage String 相关阶段 "渲染"
relatedContentType String 相关内容类型 "白模/软装/渲染/后期"
relatedFiles Array 相关项目文件 [fileId1, fileId2]
priority String 优先程度 "low/medium/high/urgent"
issueType String 问题类型 "bug/task/feedback/risk"
dueDate Date 截止时间 2024-12-31T00:00:00.000Z
status String 状态 "待处理/处理中/已解决/已关闭"
resolution String 解决方案 "已调整灯光亮度"
lastReminderAt Date 最后催单时间 2024-11-15T10:00:00.000Z
reminderCount Number 催单次数 2
data Object 扩展数据 { comments: [...] }

使用示例:

// 创建问题
const ProjectIssue = Parse.Object.extend('ProjectIssue');
const issue = new ProjectIssue();
issue.set('project', project.toPointer());
issue.set('product', product.toPointer());
issue.set('creator', profile.toPointer());
issue.set('assignee', designer.toPointer());
issue.set('title', '客厅灯光需要调整');
issue.set('description', '客户反馈客厅灯光太暗,需要增加亮度');
issue.set('relatedSpace', '客厅');
issue.set('relatedStage', '渲染');
issue.set('priority', 'high');
issue.set('issueType', 'feedback');
issue.set('status', '待处理');
issue.set('data', {
  comments: [
    { author: '客服小王', content: '已转达给设计师', time: new Date() }
  ]
});
await issue.save();

// 查询待处理的问题
const issueQuery = new Parse.Query('ProjectIssue');
issueQuery.equalTo('project', projectId);
issueQuery.equalTo('status', '待处理');
issueQuery.include('assignee', 'creator');
const issues = await issueQuery.find();

3.8 沟通跟进表

3.8.1 ContactFollow(跟进记录表)

用途: 记录项目沟通跟进历史。

关键字段: objectId, project, profile, contact, content, type, stage, attachments, data

使用示例:

const ContactFollow = Parse.Object.extend('ContactFollow');
const follow = new ContactFollow();
follow.set('project', project.toPointer());
follow.set('profile', profile.toPointer());
follow.set('contact', contact.toPointer());
follow.set('content', '与客户沟通确认了主卧设计方案');
follow.set('type', 'call');
follow.set('stage', '方案深化');
follow.set('attachments', ['fileId1', 'fileId2']);
await follow.save();

3.9 系统认证表

3.9.1 _User(用户认证表)

用途: Parse Server内置的用户认证表,用于系统登录和权限管理。

关键字段:

  • username (String): 用户名
  • password (String): 加密密码
  • email (String): 邮箱
  • emailVerified (Boolean): 邮箱是否已验证
  • authData (Object): 第三方登录数据(企微、微信等)

与Profile关系:

  • Profile.userId 存储企微UserID
  • Profile表通过userId与企微用户关联
  • _User表主要用于登录认证

使用示例:

// 查询当前登录用户
const currentUser = Parse.User.current();
if (currentUser) {
  console.log('当前用户:', currentUser.get('username'));
  
  // 查询用户的Profile
  const profileQuery = new Parse.Query('Profile');
  profileQuery.equalTo('userId', currentUser.get('username'));
  const profile = await profileQuery.first();
}

四、附录

4.1 枚举值总览

Project.status(项目状态)

中文 说明
待分配 - 项目已创建,等待分配设计师
进行中 - 项目正在进行
已完成 - 项目已完成
已暂停 - 项目已暂停
已延期 - 项目已延期
已取消 - 项目已取消

Project.currentStage(项目阶段)

说明
订单分配 项目创建,分配设计师阶段
方案深化 需求沟通与方案确认阶段
交付执行 设计执行与交付阶段
售后归档 尾款结算与售后服务阶段

Profile.roleName(员工角色)

说明
客服 客户服务人员,负责接单、跟进
组员 设计师,负责具体设计工作
组长 团队负责人,负责审核、分配
财务 财务人员
人事 人事人员
管理员 系统管理员

Product.productType(空间类型)

中文
living_room 客厅
bedroom 卧室
master_bedroom 主卧
kitchen 厨房
bathroom 卫生间
dining_room 餐厅
study 书房
balcony 阳台
corridor 走廊
storage 储物间
entrance 玄关
other 其他

4.2 Parse数据类型说明

Parse类型 JavaScript类型 说明 示例
String string 字符串 "项目名称"
Number number 数字(整数或浮点数) 100, 3.14
Boolean boolean 布尔值 true, false
Date Date 日期时间 new Date()
Array Array 数组 ["tag1", "tag2"]
Object Object JSON对象 { key: "value" }
Pointer Parse.Object 关联引用 → OtherTable
File Parse.File 文件 Parse.File

4.3 查询优化建议

使用索引

系统为高频查询字段配置了索引,查询时尽量使用已索引的字段:

// ✅ 好 - 使用了索引字段
query.equalTo('company', companyId);
query.equalTo('isDeleted', false);

// ❌ 避免 - 未索引字段的复杂查询
query.matches('data.customField', /pattern/);

Include关联数据

查询时一次性include需要的关联数据,避免N+1查询:

// ✅ 好
const query = new Parse.Query('Project');
query.include('customer', 'assignee', 'company');
const projects = await query.find();

// ❌ 避免
const projects = await projectQuery.find();
for (const project of projects) {
  const customer = await project.get('customer').fetch(); // N+1查询
}

分页查询

对于大数据量,使用分页查询:

const query = new Parse.Query('Project');
query.limit(20);
query.skip(page * 20);
query.descending('createdAt');
const projects = await query.find();

4.4 数据迁移参考

完整的数据表结构定义和迁移脚本参考:

  • Schema定义: scripts/migration/create-schema.js
  • 表依赖顺序: Company → Department → Profile → ContactInfo → Project → ...
  • 执行方式: node scripts/migration/create-schema.js

4.5 相关文档

  • 详细Schema文档: rules/schemas.md
  • 存储服务文档: rules/storage.md
  • Parse使用文档: rules/parse.md
  • 业务PRD文档: docs/prd/wxwork-project-management.md
  • 数据范式文档: docs/Database/customer-designer-normalization.md

五、总结

本文档详细描述了YSS项目管理系统的完整数据库表结构,共包含18个核心业务表1个系统认证表

核心特点

  1. 多租户架构: 以Company为核心的数据隔离
  2. 灵活扩展: 所有表都有data字段用于扩展
  3. 软删除设计: 使用isDeleted字段实现软删除
  4. 完善索引: 针对高频查询场景优化索引配置
  5. Product表创新: 统一管理空间设计产品的完整生命周期

使用建议

  • 查询时始终过滤isDeleted字段
  • 充分利用已配置的复合索引
  • 使用include减少查询次数
  • 合理使用data字段存储扩展信息
  • 遵循软删除原则,避免物理删除数据

文档版本: v1.0
最后更新: 2024-10-21
维护团队: 后端开发团队