193 lines
4.3 KiB
Markdown
193 lines
4.3 KiB
Markdown
# 始祖标识功能说明(使用 isFounder 字段)
|
||
|
||
## 功能概述
|
||
|
||
系统使用专门的 `isFounder` 数据库字段来标识家族始祖,而不是使用 tags 标签。这样可以在业务逻辑中更准确地识别和处理始祖成员。
|
||
|
||
## 数据库Schema
|
||
|
||
### FamilyMember 模型新增字段
|
||
|
||
```prisma
|
||
model FamilyMember {
|
||
// ... 其他字段
|
||
isFounder Boolean @default(false) // 是否为始祖
|
||
// ... 其他字段
|
||
}
|
||
```
|
||
|
||
## 自动设置逻辑
|
||
|
||
### 第一个成员自动处理
|
||
|
||
当家族树中没有任何成员时,添加的第一个成员会自动:
|
||
|
||
1. **设置 isFounder**:`isFounder: true`
|
||
2. **设置世代**:`generation: 1`
|
||
3. **特殊提示**:页面标题显示"添加家族始祖"
|
||
|
||
### 代码实现
|
||
|
||
```typescript
|
||
// 检测是否是第一个成员
|
||
const existingMembers = Object.values(treeData.members)
|
||
const isFirstMember = existingMembers.length === 0
|
||
|
||
// 保存时自动设置
|
||
const memberData = isFirstMember ? {
|
||
...newMember,
|
||
generation: 1,
|
||
isFounder: true,
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
} : {
|
||
...newMember,
|
||
createdAt: new Date().toISOString(),
|
||
updatedAt: new Date().toISOString(),
|
||
}
|
||
```
|
||
|
||
## 视觉标识
|
||
|
||
### 成员列表页
|
||
|
||
始祖成员具有特殊的视觉标识:
|
||
|
||
1. **皇冠图标**:名字前显示 👑
|
||
2. **金色文字**:`text-amber-600`
|
||
3. **特殊标签**:显示"家族始祖"(`text-amber-700 font-semibold`)
|
||
|
||
### 代码实现
|
||
|
||
```typescript
|
||
// 检测始祖
|
||
const isFounder = member.isFounder === true
|
||
|
||
// 显示皇冠图标
|
||
{isFounder && <span className="text-amber-600">👑</span>}
|
||
|
||
// 显示特殊标签
|
||
{isFounder ? (
|
||
<p className="text-xs text-amber-700 font-semibold mt-0.5 truncate">
|
||
家族始祖
|
||
</p>
|
||
) : ...}
|
||
```
|
||
|
||
## 业务逻辑使用
|
||
|
||
### 成员分组
|
||
|
||
在按世代分组时,始祖成员会被优先识别:
|
||
|
||
```typescript
|
||
const familyMembers = members.filter(m => {
|
||
const isFounder = m.isFounder === true
|
||
const hasFamilyParents = m.fatherId || m.motherId
|
||
|
||
if (hasFamilyParents || isFounder) return true
|
||
// ...
|
||
})
|
||
```
|
||
|
||
### 排序逻辑
|
||
|
||
始祖成员在同世代中排在最前面:
|
||
|
||
```typescript
|
||
const sortedFamilyMembers = familyMembers.sort((a, b) => {
|
||
const aIsFounder = a.isFounder === true
|
||
const bIsFounder = b.isFounder === true
|
||
if (aIsFounder && !bIsFounder) return -1
|
||
if (!aIsFounder && bIsFounder) return 1
|
||
// ... 其他排序逻辑
|
||
})
|
||
```
|
||
|
||
## 与 tags 的区别
|
||
|
||
### 使用 isFounder 字段的优势
|
||
|
||
1. **类型安全**:Boolean 类型,不会有拼写错误
|
||
2. **查询效率**:数据库可以直接索引和查询
|
||
3. **业务清晰**:明确的语义,不会与其他标签混淆
|
||
4. **逻辑简单**:`member.isFounder === true` 比 `member.tags?.includes('始祖')` 更简洁
|
||
|
||
### tags 的用途
|
||
|
||
tags 字段保留用于其他标签,如:
|
||
- 职业标签:教书先生、医生、商人
|
||
- 成就标签:进士、举人、秀才
|
||
- 其他自定义标签
|
||
|
||
## 数据迁移
|
||
|
||
已创建数据库迁移:
|
||
```
|
||
migrations/20251124005530_add_is_founder_field/migration.sql
|
||
```
|
||
|
||
迁移内容:
|
||
```sql
|
||
ALTER TABLE "FamilyMember" ADD COLUMN "isFounder" BOOLEAN NOT NULL DEFAULT false;
|
||
```
|
||
|
||
## 示例数据
|
||
|
||
### 王氏家族始祖
|
||
|
||
```typescript
|
||
{
|
||
surname: '王',
|
||
givenName: '德厚',
|
||
fullName: '王德厚',
|
||
gender: 'MALE',
|
||
generation: 1,
|
||
isFounder: true, // 始祖标识
|
||
tags: ['教书先生'], // 其他标签
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 虞氏家族始祖
|
||
|
||
```typescript
|
||
{
|
||
surname: '虞',
|
||
givenName: '文昌',
|
||
fullName: '虞文昌',
|
||
gender: 'MALE',
|
||
generation: 1,
|
||
isFounder: true, // 始祖标识
|
||
tags: ['商人'], // 其他标签
|
||
// ...
|
||
}
|
||
```
|
||
|
||
## TypeScript 类型
|
||
|
||
```typescript
|
||
export interface FamilyMember {
|
||
// ... 其他字段
|
||
isFounder?: boolean // 是否为始祖
|
||
// ... 其他字段
|
||
}
|
||
```
|
||
|
||
## 注意事项
|
||
|
||
1. **唯一性**:每个家族树通常只有一个始祖(`isFounder: true`)
|
||
2. **不可自动撤销**:一旦设置为始祖,需要手动编辑才能修改
|
||
3. **世代关联**:始祖通常是第1代
|
||
4. **数据一致性**:确保 isFounder 和 generation 的一致性
|
||
|
||
## 未来扩展
|
||
|
||
可能的功能扩展:
|
||
|
||
- [ ] 支持多个始祖(如夫妻双方都标记为始祖)
|
||
- [ ] 始祖成员在族谱图中的特殊显示样式
|
||
- [ ] 始祖成员的专属统计信息
|
||
- [ ] 从始祖开始的完整世系追溯功能
|
||
- [ ] 始祖成员的特殊权限或保护机制
|