--- 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 数据表关系图 ```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(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 | **索引配置**: ```javascript // 从 scripts/migration/create-schema.js indexes: [ { name: 'corpId_index', fields: { corpId: 1 } }, { name: 'name_index', fields: { name: 1 } } ] ``` **data字段结构示例**: ```json { "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" } } ``` **使用示例**: ```typescript // 获取当前企业 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`: 其他 **索引配置**: ```javascript indexes: [ { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } }, { name: 'leader_index', fields: { leader: 1 } } ] ``` **使用示例**: ```typescript // 创建项目组 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枚举值**: - `客服`: 客户服务人员,负责接单、跟进 - `组员`: 设计师,负责具体设计工作 - `组长`: 团队负责人,负责审核、分配 - `财务`: 财务人员 - `人事`: 人事人员 - `管理员`: 系统管理员 **索引配置**: ```javascript 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字段结构示例**: ```json { "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 } } ``` **使用示例**: ```typescript // 创建员工档案 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枚举值**: - `朋友圈`: 微信朋友圈广告 - `信息流`: 抖音/小红书等信息流广告 - `转介绍`: 老客户转介绍 - `其他`: 其他来源 **索引配置**: ```javascript 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字段结构示例**: ```json { "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": ["现代简约", "温馨舒适", "储物充足"] } ``` **使用示例**: ```typescript // 创建客户信息 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 | **索引配置**: ```javascript 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结构示例**: ```json [ { "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字段结构示例**: ```json { "owner": "zhangsan", "create_time": 1701234567, "notice": "本群用于项目沟通", "admin_list": [{ "userid": "zhangsan" }] } ``` **使用示例**: ```typescript // 创建群聊记录 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 | **索引配置**: ```javascript indexes: [ { name: 'project_groupChat', fields: { project: 1, groupChat: 1 }, unique: true }, { name: 'groupChat_index', fields: { groupChat: 1 } }, { name: 'isPrimary_index', fields: { isPrimary: 1 } } ] ``` **使用示例**: ```typescript // 创建项目群组关联 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枚举值**: - `订单分配`: 项目创建,分配设计师阶段 - `方案深化`: 需求沟通与方案确认阶段 - `交付执行`: 设计执行与交付阶段 - `售后归档`: 尾款结算与售后服务阶段 **索引配置**: ```javascript 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字段结构示例**: ```json { "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": "客户要求尽快完成" } ``` **使用示例**: ```typescript // 创建项目 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 | **索引配置**: ```javascript indexes: [ { name: 'project_unique', fields: { project: 1 }, unique: true }, { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } } ] ``` **spaces数组结构示例**: ```json [ { "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对象结构示例**: ```json { "style": ["现代简约", "北欧风"], "colorPreference": "暖色调,以米白色为主", "materialPreference": ["实木", "环保材料", "石材"], "specialRequirements": [ "需要大储物空间", "家有小孩,注意安全", "预留智能家居接口" ] } ``` **使用示例**: ```typescript // 创建项目需求 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枚举值**: - `设计师`: 负责设计工作 - `客服`: 负责沟通跟进 - `组长`: 负责审核把关 - `协作设计师`: 协助设计工作 **索引配置**: ```javascript indexes: [ { name: 'project_isDeleted', fields: { project: 1, isDeleted: 1 } }, { name: 'profile_project', fields: { profile: 1, project: 1 }, unique: true } ] ``` **使用示例**: ```typescript // 添加团队成员 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`: 需要修改 **索引配置**: ```javascript 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字段结构示例**: ```json { "spaceName": "主卧", "area": 18.5, "dimensions": { "length": 4.5, "width": 4.1, "height": 2.8 }, "features": ["朝南", "飘窗", "独立卫浴"], "constraints": ["承重墙不可动"], "priority": "high", "complexity": "medium" } ``` **quotation字段结构示例**: ```json { "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字段结构示例**: ```json { "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字段结构示例**: ```json [ { "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" } } ] ``` **使用示例**: ```typescript // 创建空间设计产品 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`: 售后归档阶段 **索引配置**: ```javascript 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模型分析结果): ```json { "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字段结构示例** (色彩分析插件结果): ```json { "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" } ] } ``` **使用示例**: ```typescript // 上传项目文件 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哈希值 | "5d41402abc4b2a76b9719d..." | | 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字段结构示例**: ```json { "width": 1920, "height": 1080, "duration": 120, "lastModified": 1701234567000, "projectId": "proj001", "fileType": "image", "spaceId": "space001", "stage": "delivery" } ``` **与NovaStorage集成**: NovaStorage是fmode-ng提供的文件存储服务,上传流程如下: ```typescript // 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(); ``` **使用示例**: ```typescript // 查询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 **使用示例**: ```typescript 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 **使用示例**: ```typescript 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: [...] }` | **使用示例**: ```typescript // 创建问题 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 **使用示例**: ```typescript 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表主要用于登录认证 **使用示例**: ```typescript // 查询当前登录用户 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 查询优化建议 #### 使用索引 系统为高频查询字段配置了索引,查询时尽量使用已索引的字段: ```typescript // ✅ 好 - 使用了索引字段 query.equalTo('company', companyId); query.equalTo('isDeleted', false); // ❌ 避免 - 未索引字段的复杂查询 query.matches('data.customField', /pattern/); ``` #### Include关联数据 查询时一次性include需要的关联数据,避免N+1查询: ```typescript // ✅ 好 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查询 } ``` #### 分页查询 对于大数据量,使用分页查询: ```typescript 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 **维护团队**: 后端开发团队