一、为什么要做族谱小程序?
清明节回家祭祖,父亲突然问我:"咱们家的族谱你能帮忙整理一下吗?老的那本已经破旧不堪了。"
我翻出那本泛黄的族谱,上面密密麻麻记录着家族几代人的信息,但很多字迹已经模糊,有些页面还被虫蛀了。作为一个程序员,我第一反应就是:能不能做一个数字化的族谱系统?
需求很简单:
能可视化展示家族树,像思维导图那样 支持多人协作,家族成员可以自己补充信息 最好有个小程序,方便长辈们使用 除了基本的族谱,还能记录家族大事、人物传记、成就榜
但现实是:我平时工作很忙,这个项目只能用业余时间来做。如果按传统方式开发,光是写前端页面、搭后端、设计数据库,没有两三个月根本搞不定。
这时候我想到了 AI,于是就两天整理spec和技术方案,两天做出来。下面就是我的思路和经验。
二、技术选型:为什么选这些技术?
前端框架对比
最终选择 UniApp + Vue 3 + TypeScript,原因:
一套代码可以编译到微信小程序、H5、App 多端 Vue 3 的 Composition API 写起来很舒服 TypeScript 提供类型安全,AI 生成代码时不容易出错 UniApp 生态成熟,遇到问题容易找到解决方案
后端方案对比
对于族谱这种低频访问的应用,微信云开发是最优解:
免费额度足够(每天 5 万次数据库读取) 不用操心服务器运维 云函数 + 云数据库 + 云存储一站式搞定 天然支持微信登录
网页版技术栈
为了让家族成员在电脑上也能查看,我还做了一个网页版:
- 前端
:React 18 + TypeScript + ReactFlow(家族树可视化)+ Ant Design - 后端
:Node.js + Express + SQLite - 部署
:Docker 一键部署
三、架构设计:一个云函数搞定一切
传统方案 vs 我的方案
传统做法是每个功能模块一个云函数:getMembers、createPost、getComments... 这样会导致:
云函数数量爆炸,管理困难 每个函数都有冷启动时间 代码重复度高
我的方案是:单一云函数 + Action 路由代码// family-tree-db/index.js - 唯一的云函数入口exports.main = async (event, context) => {const action = event.actionconst data = event.dataswitch (action) {case'getMembers':returnawaitgetMembers(data)case'createPost':returnawaitcreatePost(data)case'getChronicleTimeline':returnawaitgetChronicleTimeline(data)// ... 50+ 个 action }}
前端调用时只需要:const result = awaitcallCloud('getMembers', { clan_id: 'xxx' })
这样做的好处:
- 减少冷启动
:只有一个云函数需要预热 - 统一权限校验
:所有操作都走同一个鉴权逻辑 - 便于维护
:所有后端代码在一个文件里,AI 修改起来也方便
分层架构┌─────────────────────────────────────┐│ 小程序页面层 (20+ 页面) │├─────────────────────────────────────┤│ 组件层 (11 个公共组件) │├─────────────────────────────────────┤│ API 层 (统一接口封装) │├─────────────────────────────────────┤│ 云函数层 (单一入口) │├─────────────────────────────────────┤│ 云数据库 (10+ 集合) │└─────────────────────────────────────┘
多租户设计
一个小程序要服务多个家族,怎么隔离数据?
答案是:每个集合都有 clan_id 字段。// API 层自动注入 clan_idasyncgetMembers(params) {const clan = getCurrentClan()const clan_id = clan?.id || user?.clan_idreturnawait memberDB.getAll({ ...params, clan_id })}
用户登录后选择自己的族群,之后所有操作都会自动带上 clan_id,确保数据隔离。
四、核心功能实现
1. 家族树可视化(最难的部分)
在小程序中实现树形图,最大的挑战是没有 DOM,不能像网页那样用 SVG 或 HTML 布局。
我的方案:绝对定位 + scroll-view// treeLayout.ts - 树布局算法interfaceLayoutBox {node: FamilyTreeNodeDatacenterX: number// 节点中心 X 坐标offsetLeft: number// 左偏移量children: LayoutBox[] // 子节点}// 递归计算布局functionlayoutTree(node: FamilyTreeNodeData): LayoutBox {// 1. 先递归计算所有子树的布局const childBoxes = node.children.map(child =>layoutTree(child))// 2. 计算当前节点需要的宽度const totalWidth = childBoxes.reduce((sum, box) => sum + box.width, 0)// 3. 当前节点居中于子节点之上const centerX = totalWidth / 2return { node, centerX, children: childBoxes }}
渲染时:<scroll-view scroll-x scroll-y> <view :style="{ width: canvasWidth + 'rpx', height: canvasHeight + 'rpx' }"> <!-- 节点卡片 --> <view v-for="couple in layout.couples" :style="{ left: couple.x + 'rpx', top: couple.y + 'rpx' }" > <FamilyTreeCoupleCard :node="couple.node" /> </view> <!-- 连接线 --> <view v-for="seg in layout.segments" :style="{ left: seg.left + 'rpx', top: seg.top + 'rpx', width: seg.width + 'px' }" /> </view></scroll-view>
还支持:
展开/折叠子树 导出为图片(Canvas 2D) 左右滑动查看
2. 亲缘关系计算
"我和堂哥的曾孙是什么关系?" 这种问题,用算法就能回答。
实现方案:BFS 图搜索码// relationship.tsfunctionbuildRelationGraph(members: RelationMember[]) {const graph = newMap<string, RelationEdge[]>()for (const member of members) {// 父子关系if (member.father_id) addEdge(member.father_id, member.id, 'father')if (member.mother_id) addEdge(member.mother_id, member.id, 'mother')// 配偶关系if (member.spouse_id) addEdge(member.id, member.spouse_id, 'spouse')// 兄弟姐妹关系(同一父母)// ... }return graph}functionfindRelationship(fromId: string, toId: string, graph) {// BFS 搜索最短路径const queue = [{ id: fromId, path: [] }]const visited = newSet()while (queue.length > 0) {const { id, path } = queue.shift()if (id === toId) return pathfor (const edge of graph.get(id)) {if (!visited.has(edge.to)) { visited.add(edge.to) queue.push({ id: edge.to, path: [...path, edge] }) } } }}
最后把路径翻译成中文称谓:// 路径:A → 父亲 → 父亲的兄弟 → 兄弟的儿子// 翻译:堂兄弟
3. 大事记时间线
为了让家族历史更有仪式感,我做了两种视图:
列表视图:卡片式展示,适合快速浏览 时间线视图:按年月分组,带连接线,适合沉浸式阅读<!-- 时间线视图 --><view class="timeline"> <view v-for="group in timeline" class="timeline-group"> <view class="timeline-year-header"> <text>{{ group.year }}年{{ group.month }}月</text> <text>{{ group.events.length }}件</text> </view> <view class="timeline-events"> <view v-for="event in group.events" class="timeline-event"> <view class="timeline-dot" :style="{ background: getTypeColor(event.event_type) }" /> <view class="timeline-content"> <text class="timeline-title">{{ event.title }}</text> <text class="timeline-date">{{ formatDate(event.event_date) }}</text> <text v-if="event.summary" class="timeline-summary">{{ event.summary }}</text> </view> </view> </view> </view></view>
五、AI 辅助开发:真实体验分享
1. 需求分析阶段
我的做法:把需求描述丢给 AI,让它帮我梳理技术方案。我:我想做一个族谱小程序,需要哪些功能模块?数据库怎么设计?AI:核心模块:1. 成员管理 - 存储家族成员信息2. 关系管理 - 父母、配偶、子女关系3. 族谱可视化 - 树形图展示4. 权限系统 - 多角色控制数据库设计:- family_members 集合:存储成员基本信息- users 集合:存储用户账号和角色- clans 集合:存储族群信息(支持多租户)
AI 帮我快速理清了思路,省去了大量调研时间。
2. 组件开发阶段
案例:FamilyTreeGraph 组件
这个组件有 300+ 行代码,如果手写至少需要半天。我用 AI 的方式是:
先描述需求:"我要在小程序里实现一个可滚动的家族树,支持夫妻卡片、父子连线、展开折叠" AI 生成基础代码 我调整细节,比如尺寸、颜色、交互逻辑 遇到问题再问 AI
整个过程大概 2 小时就完成了。
3. 算法实现阶段
案例:亲缘关系计算
BFS 算法我本来就会,但要处理各种边界情况(多重婚姻、过继、养子女等)很麻烦。我让 AI 帮我:
生成基础的 BFS 框架 处理各种关系类型的边构建 翻译路径为中文称谓
AI 生成的代码质量很高,我只需要微调就能用。
4. Bug 修复阶段
这是 AI 帮助最大的地方。
真实案例:头像不显示问题
前几天我遇到一个问题:成员详情页面的头像不显示了。
传统排查方式:
检查图片 URL 是否正确 → 正确 检查网络请求 → 正常 检查 CSS 样式 → 发现问题了!
问题根因:Vue 的 scoped 样式无法穿透到子组件内部的 <image> 标签。因为微信小程序的 <image> 是组件而不是 HTML 元素。
AI 帮我想到的解决方案:<!-- 方案 1:使用 :deep() 穿透 --><style scoped>.avatar-large :deep(image) { width: 100%; height: 100%;}</style><!-- 方案 2:使用全局样式(不推荐,会污染其他组件) --><style>.member-avatar-root image { width: 100%; height: 100%;}</style>
这种问题如果不是 AI 提醒,我可能要调试很久。
六、踩坑记录
坑 1:小程序的 image 是组件不是标签/* 错误写法 - 无法生效 */.avatar image { width: 100%; }/* 正确写法 - 需要 :deep() */.avatar :deep(image) { width: 100%; }
坑 2:云函数冷启动
第一次调用云函数会很慢(3-5 秒),解决方案:
使用定时触发器预热 前端加 loading 状态 关键接口做缓存
坑 3:rpx 单位换算
小程序的 rpx 是响应式单位,但 Canvas 绘图时需要换算:const dpr = wx.getSystemInfoSync().pixelRatiocanvas.width = width * dprcanvas.height = height * dprctx.scale(dpr, dpr)
坑 4:隐私 API 合规
微信要求调用相册、定位等隐私 API 前必须先声明并获得用户授权:functionrunWithPrivacyAuthorize(action: () => void) { wx.requirePrivacyAuthorize({success: action,fail: () => { uni.showToast({ title: '需要授权才能使用此功能' }) } })}
七、最终效果
功能清单
开发时间对比
| 总计 | 25 天 | 7 天 | 3.5x |
八、总结与建议
AI 辅助开发的正确姿势
- 不要完全依赖 AI
:AI 生成的代码需要你理解并验证,不能直接 copy-paste - 先想清楚再问 AI
:需求描述越清晰,AI 生成的代码质量越高 - 分步骤迭代
:不要一次性让 AI 生成整个项目,分模块、分功能来 - 保持学习
:AI 是工具,核心能力还是你自己的技术功底
这个项目适合哪些场景
家族族谱数字化 家庭相册管理 家族通讯录 宗亲会管理
作者简介:后端开发工程师,8 年经验。欢迎关注我的公众号,分享更多 AI 辅助开发的实战经验。






夜雨聆风