Skip to content

guangshu100/KindredTree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

跨平台家族谱系管理应用 - 支持桌面端 (Tauri 2) 和移动端 (Capacitor 6)


目录

  1. 项目概述
  2. 整体架构
  3. 技术栈详解
  4. 核心库架构
  5. 桌面端架构
  6. 移动端架构
  7. 数据结构设计
  8. 快速开始
  9. 构建部署
  10. API 参考

项目概述

KindredTree 是一个现代化的家族谱系管理应用,采用 Monorepo 架构,支持多平台部署:

平台 技术方案 输出格式
Windows Tauri 2 .exe / .msi
macOS Tauri 2 .dmg / .app
Linux Tauri 2 .deb / .AppImage
Android Capacitor 6 .apk
iOS Capacitor 6 .ipa

核心特性

  • 🌳 交互式家族树 - D3.js 驱动的可视化
  • 👨‍👩‍👧‍👦 完整关系支持 - 父母、配偶、子女、兄弟姐妹
  • 💾 本地数据存储 - 数据完全存储在本地
  • 📱 跨平台一致 - 共享核心代码,平台适配
  • 🎨 现代化 UI - Tailwind CSS 样式
  • 🔄 实时更新 - Zustand 响应式状态管理

整体架构

┌─────────────────────────────────────────────────────────────────────────┐
│                           KindredTree Monorepo                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │                     @kindred-tree/core (共享核心)                  │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐    │  │
│  │  │   types     │  │   utils     │  │      layout/kinship     │    │  │
│  │  │  类型定义    │  │  工具函数    │  │   布局计算/亲属关系      │    │  │
│  │  └─────────────┘  └─────────────┘  └─────────────────────────┘    │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                    │                                     │
│                    ┌───────────────┴───────────────┐                    │
│                    ▼                               ▼                    │
│  ┌─────────────────────────────┐  ┌─────────────────────────────────┐  │
│  │   @kindred-tree/desktop     │  │      @kindred-tree/mobile       │  │
│  │   (Tauri 桌面端)             │  │      (Capacitor 移动端)          │  │
│  │                             │  │                                 │  │
│  │  ┌───────────────────────┐  │  │  ┌───────────────────────────┐  │  │
│  │  │   React 18 前端       │  │  │  │    React 18 前端          │  │  │
│  │  │   - Zustand 状态管理  │  │  │  │    - Zustand 状态管理     │  │  │
│  │  │   - D3.js 可视化      │  │  │  │    - D3.js 可视化         │  │  │
│  │  │   - Tailwind CSS      │  │  │  │    - Tailwind CSS         │  │  │
│  │  └───────────────────────┘  │  │  └───────────────────────────┘  │  │
│  │             │               │  │              │                  │  │
│  │             ▼               │  │              ▼                  │  │
│  │  ┌───────────────────────┐  │  │  ┌───────────────────────────┐  │  │
│  │  │   Tauri 2 后端        │  │  │  │    Capacitor 插件         │  │  │
│  │  │   - Rust 文件系统     │  │  │  │    - Filesystem           │  │  │
│  │  │   - 本地存储 API      │  │  │  │    - Preferences          │  │  │
│  │  └───────────────────────┘  │  │  └───────────────────────────┘  │  │
│  │             │               │  │              │                  │  │
│  │             ▼               │  │              ▼                  │  │
│  │  ┌───────────────────────┐  │  │  ┌───────────────────────────┐  │  │
│  │  │   操作系统原生 API    │  │  │  │    平台原生 API           │  │  │
│  │  │   - Windows/macOS/Linux│  │  │    - Android/iOS           │  │  │
│  │  └───────────────────────┘  │  │  └───────────────────────────┘  │  │
│  └─────────────────────────────┘  └─────────────────────────────────┘  │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

技术栈详解

核心库 (@kindred-tree/core)

技术 版本 用途
TypeScript ^5.3.0 类型安全开发
D3.js ^7.9.0 树形布局计算
UUID ^9.0.0 唯一标识生成
tsup ^8.0.0 库打包工具

桌面端 (@kindred-tree/desktop)

技术 版本 用途
Tauri 2.x 桌面应用框架
React ^18.2.0 UI 框架
Zustand ^4.5.0 状态管理
D3.js ^7.9.0 SVG 可视化
Tailwind CSS ^3.4.0 样式框架
Vite ^5.0.0 构建工具
Rust 1.70+ 后端逻辑

移动端 (@kindred-tree/mobile)

技术 版本 用途
Capacitor 6.x 跨平台移动框架
React ^18.2.0 UI 框架
Zustand ^4.5.0 状态管理
D3.js ^7.9.0 SVG 可视化
Tailwind CSS ^3.4.0 样式框架
@capacitor/filesystem ^6.0.0 文件存储
@capacitor/preferences ^6.0.0 键值存储

核心库架构

模块结构

packages/core/src/
├── types.ts          # 类型定义
├── utils.ts          # 工具函数
├── layout.ts         # 树形布局计算
├── kinship.ts        # 亲属关系计算
└── index.ts          # 导出入口

类型系统

// 核心类型定义
export type Gender = 'male' | 'female';
export type RelationType = 'biological' | 'adopted' | 'step' | 'married-in';
export type SpouseType = 'primary' | 'secondary' | 'concubine';

// 人员关系结构
export interface PersonRels {
  fathers: { parentId: string; relationType: RelationType }[];
  mothers: { parentId: string; relationType: RelationType }[];
  spouses: { spouseId: string; spouseType: SpouseType }[];
  children: string[];
}

// 人员信息
export interface Person {
  id: string;
  name: string;
  gender?: Gender;
  birthDate?: string;
  deathDate?: string;
  rels: PersonRels;
  // ... 其他字段
}

// 树节点
export interface TreeNode {
  id: string;
  x: number;
  y: number;
  depth: number;
  person: Person;
  spouse?: Person;
  children: TreeNode[];
}

布局算法

// 使用 D3.js 层次布局
export const calculateTreeLayout = (
  persons: Person[],
  mainPersonId: string,
  options: TreeLayoutOptions
): TreeNode | null => {
  // 1. 构建后代树
  const root = buildNode(mainPerson, 0);
  
  // 2. 构建祖先树
  const ancestors = buildAncestors(mainPerson, persons);
  
  // 3. 合并树结构
  if (ancestors) {
    return mergeAncestorsWithTree(ancestors, root);
  }
  
  // 4. 应用 D3.js 布局
  applyD3Layout(root, options);
  
  return root;
};

// D3.js 布局配置
const treeLayout = d3.tree<any>()
  .nodeSize([nodeSeparation, levelSeparation])
  .separation((a, b) => {
    const aWidth = a.data.spouse ? 1.5 : 1;
    const bWidth = b.data.spouse ? 1.5 : 1;
    return (aWidth + bWidth) / 2;
  });

亲属关系计算

// BFS 搜索最短路径
export const calculateKinship = (
  personId: string,
  targetId: string,
  allPersons: Person[]
): KinshipResult | null => {
  // 1. BFS 查找最短路径
  const path = findShortestPath(personId, targetId, allPersons);
  
  // 2. 根据路径确定关系
  const relation = determineKinship(path, allPersons);
  
  return { relation, distance, path };
};

// 关系判断逻辑
const determineKinship = (path: string[], persons: Person[]): string => {
  // 直系亲属判断
  if (path.length === 2) {
    // 配偶、父母、子女、兄弟姐妹
  }
  
  // 旁系亲属判断
  if (path.length === 3) {
    // 祖父母、孙辈
  }
  
  return '亲属';
};

桌面端架构

架构图

┌─────────────────────────────────────────────────────────────┐
│                      React 前端层                            │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   App.tsx   │  │  FamilyTree │  │     PersonPanel     │  │
│  │   主应用     │  │   树形组件   │  │      人员面板        │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                      状态管理层                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                    Zustand Store                     │    │
│  │  families[] | persons[] | selectedPersonId | ...    │    │
│  └─────────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────────┤
│                      Tauri 桥接层                            │
│  ┌─────────────────────────────────────────────────────┐    │
│  │              @tauri-apps/api                        │    │
│  │  invoke() | fs | dialog | notification             │    │
│  └─────────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────────┤
│                      Rust 后端层                             │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                   lib.rs                            │    │
│  │  save_family | load_family | list_families | ...   │    │
│  └─────────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────────┤
│                      文件系统层                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  {DATA_DIR}/KindredTree/{family_id}.json           │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

Rust 后端实现

// 数据结构定义
#[derive(Serialize, Deserialize)]
pub struct Person {
    pub id: String,
    pub name: String,
    pub gender: Option<String>,
    pub rels: PersonRels,
    // ...
}

// Tauri 命令
#[tauri::command]
fn save_family(
    state: State<Mutex<AppState>>,
    family: Family,
    persons: Vec<Person>,
) -> Result<(), String> {
    let path = state.get_family_path(&family.id);
    let data = FamilyData { family, persons };
    let json = serde_json::to_string_pretty(&data)?;
    fs::write(path, json)?;
    Ok(())
}

// 应用入口
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_dialog::init())
        .manage(Mutex::new(AppState::new()))
        .invoke_handler(tauri::generate_handler![
            save_family,
            load_family,
            list_families,
            delete_family
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

React 前端实现

// Zustand Store
export const useFamilyStore = create<FamilyState>((set, get) => ({
  families: [],
  persons: [],
  selectedPersonId: null,

  // 创建族谱
  createNewFamily: async (name) => {
    const family = createFamily({ name });
    const mainPerson = createPerson({ name: '本人', gender: 'male' });
    
    await invoke('save_family', { family, persons: [mainPerson] });
    
    set({ families: [...get().families, family], persons: [mainPerson] });
  },

  // 添加亲属
  addSpouse: (personId, spouseData) => {
    const spouse = createPerson(spouseData);
    // 更新双方关系...
    get().saveFamily();
  },
}));

// D3.js 可视化
const FamilyTree = () => {
  const { persons, currentFamily } = useFamilyStore();
  
  useEffect(() => {
    const root = calculateTreeLayout(persons, currentFamily.mainPersonId, options);
    
    // D3.js 渲染
    const svg = d3.select(svgRef.current);
    const container = svg.append('g');
    
    // 绘制连接线
    container.selectAll('.link')
      .data(getLinks(root))
      .enter()
      .append('path');
    
    // 绘制节点
    container.selectAll('.node')
      .data(getAllNodes(root))
      .enter()
      .append('g')
      .each(renderCard);
  }, [persons]);
};

移动端架构

架构图

┌─────────────────────────────────────────────────────────────┐
│                      React 前端层                            │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   App.tsx   │  │  FamilyTree │  │     PersonSheet     │  │
│  │   主应用     │  │   树形组件   │  │     底部弹出面板     │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                      状态管理层                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                    Zustand Store                     │    │
│  └─────────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────────┤
│                      存储适配层                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                   storage.ts                        │    │
│  │  saveFamily | loadFamily | listFamilies | ...      │    │
│  └─────────────────────────────────────────────────────┘    │
├─────────────────────────────────────────────────────────────┤
│                    Capacitor 插件层                          │
│  ┌──────────────────┐  ┌───────────────────────────────┐    │
│  │   Filesystem     │  │       Preferences            │    │
│  │   文件存储        │  │       键值存储                │    │
│  └──────────────────┘  └───────────────────────────────┘    │
├─────────────────────────────────────────────────────────────┤
│                      平台原生层                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Android: Internal Storage                          │    │
│  │  iOS: App Documents Directory                      │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

Capacitor 存储实现

import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';

export const storage = {
  // 保存族谱
  async saveFamily(family: Family, persons: Person[]): Promise<void> {
    const fileName = `family_${family.id}.json`;
    const data = JSON.stringify({ family, persons }, null, 2);
    
    // 写入文件
    await Filesystem.writeFile({
      path: fileName,
      data: data,
      directory: Directory.Data,
      encoding: Encoding.UTF8,
    });

    // 更新索引
    const { value } = await Preferences.get({ key: FAMILY_LIST_KEY });
    const familyList: string[] = value ? JSON.parse(value) : [];
    
    if (!familyList.includes(family.id)) {
      familyList.push(family.id);
      await Preferences.set({
        key: FAMILY_LIST_KEY,
        value: JSON.stringify(familyList),
      });
    }
  },

  // 加载族谱
  async loadFamily(id: string): Promise<FamilyData | null> {
    const result = await Filesystem.readFile({
      path: `family_${id}.json`,
      directory: Directory.Data,
      encoding: Encoding.UTF8,
    });
    
    return JSON.parse(result.data as string);
  },
};

移动端 UI 适配

// 底部弹出面板
const PersonSheet: React.FC<PersonSheetProps> = ({ person, onClose }) => {
  return (
    <div className="fixed inset-0 bg-black/50 z-50" onClick={onClose}>
      <div 
        className="absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4 safe-area-bottom"
        onClick={e => e.stopPropagation()}
      >
        {/* 表单内容 */}
      </div>
    </div>
  );
};

// 安全区域适配
.safe-area-bottom {
  padding-bottom: env(safe-area-inset-bottom);
}

数据结构设计

关系数据模型

┌─────────────────────────────────────────────────────────────┐
│                        Person                                │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  id: string                    // 唯一标识           │    │
│  │  name: string                  // 姓名               │    │
│  │  gender: 'male' | 'female'     // 性别               │    │
│  │  birthDate?: string            // 出生日期           │    │
│  │  deathDate?: string            // 逝世日期           │    │
│  │  ...                                                │    │
│  └─────────────────────────────────────────────────────┘    │
│                              │                               │
│                              ▼                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                      rels                           │    │
│  │  ┌─────────────────────────────────────────────┐    │    │
│  │  │  fathers: [{ parentId, relationType }]      │    │    │
│  │  │  mothers: [{ parentId, relationType }]      │    │    │
│  │  │  spouses: [{ spouseId, spouseType }]        │    │    │
│  │  │  children: [id1, id2, ...]                  │    │    │
│  │  └─────────────────────────────────────────────┘    │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

关系类型

// 父母关系类型
type RelationType = 
  | 'biological'   // 亲生
  | 'adopted'      // 收养
  | 'step'         // 继父母
  | 'married-in';  // 入赘/嫁入

// 配偶类型
type SpouseType = 
  | 'primary'      // 元配
  | 'secondary'    // 继室
  | 'concubine';   // 侧室

数据存储格式

{
  "family": {
    "id": "family-001",
    "name": "张氏家族",
    "mainPersonId": "person-001",
    "settings": {
      "orientation": "vertical",
      "showImages": true,
      "cardWidth": 200,
      "cardHeight": 85
    }
  },
  "persons": [
    {
      "id": "person-001",
      "name": "张三",
      "gender": "male",
      "birthDate": "1980-01-01",
      "rels": {
        "fathers": [],
        "mothers": [],
        "spouses": [{ "spouseId": "person-002", "spouseType": "primary" }],
        "children": ["person-003"]
      }
    }
  ]
}

快速开始

环境要求

  • Node.js >= 18.0.0
  • pnpm >= 8.0.0
  • Rust >= 1.70 (桌面端)
  • Android Studio (Android 开发)
  • Xcode >= 15 (iOS 开发,需要 Mac)

安装依赖

# 安装 pnpm
npm install -g pnpm

# 进入项目目录
cd KindredTree

# 安装依赖
pnpm install

开发模式

# 开发桌面端
pnpm tauri:dev

# 开发移动端
pnpm dev:mobile

构建部署

桌面端构建

# 构建所有平台
pnpm tauri:build

# 仅构建当前平台
cd apps/desktop
pnpm tauri:build

# 输出位置
# Windows: src-tauri/target/release/kindred-tree-desktop.exe
# macOS: src-tauri/target/release/bundle/dmg/
# Linux: src-tauri/target/release/bundle/deb/

移动端构建

# Android
cd apps/mobile
pnpm build
npx cap sync android
npx cap open android
# 在 Android Studio 中构建 APK

# iOS
pnpm build
npx cap sync ios
npx cap open ios
# 在 Xcode 中构建 IPA

API 参考

核心库 API

import {
  // 类型
  Person, Family, TreeNode, KinshipResult,
  
  // 工厂函数
  createPerson, createFamily,
  
  // 布局计算
  calculateTreeLayout, getLinks, getAllNodes,
  
  // 亲属关系
  calculateKinship, getAllKinships,
  
  // 工具函数
  getParents, getChildren, getSpouses, getSiblings,
  getGenerationTitle, exportToJson, importFromJson,
} from '@kindred-tree/core';

桌面端 Tauri API

import { invoke } from '@tauri-apps/api/core';

// 保存族谱
await invoke('save_family', { family, persons });

// 加载族谱
const data = await invoke('load_family', { id });

// 列出所有族谱
const families = await invoke('list_families');

// 删除族谱
await invoke('delete_family', { id });

移动端存储 API

import { storage } from './utils/storage';

// 保存族谱
await storage.saveFamily(family, persons);

// 加载族谱
const data = await storage.loadFamily(id);

// 列出所有族谱
const families = await storage.listFamilies();

// 删除族谱
await storage.deleteFamily(id);

// 导出/导入
const json = await storage.exportFamily(id);
const family = await storage.importFamily(json);

许可证

MIT License


更新日志

v1.0.0 (2024-04)

  • 初始版本发布
  • 支持桌面端 (Windows/macOS/Linux)
  • 支持移动端 (Android/iOS)
  • 核心功能:家族树可视化、人员管理、关系计算

About

跨平台家族谱系管理应用

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors