import relationship from 'relationship.js' import type { FamilyMember } from '@/types/family' /** * 家族关系计算器 * 使用 relationship.js 库计算中文亲属关系 */ export class RelationshipCalculator { private members: Map constructor(members: FamilyMember[]) { this.members = new Map(members.map(m => [m.id, m])) } /** * 计算两个成员之间的关系 * @param fromId 起始成员ID(成员A) * @param toId 目标成员ID(成员B) * @returns 关系描述(toId 对 fromId 的称谓,即B是A的XXX) */ getRelationship(fromId: string, toId: string): RelationshipResult { if (fromId === toId) { return { term: '本人', path: '', success: true } } const fromMember = this.members.get(fromId) const toMember = this.members.get(toId) if (!fromMember || !toMember) { return { term: '未知', path: '', success: false, error: '成员不存在' } } // 构建关系路径(从 fromId 到 toId) const path = this.buildRelationPath(fromId, toId) if (!path) { return { term: '无血缘关系', path: '', success: false } } // 将路径转换为中文 const chinesePath = this.pathToChineseText(path) // 使用 relationship.js 计算关系 try { // 获取fromId成员的性别(从谁的角度看关系) const fromMemberGender = fromMember.gender === 'FEMALE' ? 0 : 1 // 计算关系(toId 对 fromId 的称谓) const result = relationship({ text: chinesePath, target: '', sex: fromMemberGender, // 传入fromId的性别(从谁的角度看) reverse: true // 使用反向,因为路径是从A到B,但我们要的是B对A的称谓 }) const term = Array.isArray(result) && result.length > 0 ? result[0] : '亲戚' return { term, path: chinesePath, success: true } } catch (error) { return { term: '亲戚', path: chinesePath, success: true } } } /** * 构建关系路径字符串 * 例如:'f' = 父亲, 'm' = 母亲, 'h' = 丈夫, 'w' = 妻子 * 'ob' = 哥哥, 'lb' = 弟弟, 'os' = 姐姐, 'ls' = 妹妹 */ private buildRelationPath(fromId: string, toId: string): string | null { // 使用BFS查找最短路径 const queue: Array<{ id: string; path: string }> = [{ id: fromId, path: '' }] const visited = new Set() while (queue.length > 0) { const { id, path } = queue.shift()! if (id === toId) { return path } if (visited.has(id)) continue visited.add(id) const member = this.members.get(id) if (!member) continue // 向上:父亲 if (member.fatherId && !visited.has(member.fatherId)) { queue.push({ id: member.fatherId, path: path + 'f' }) } // 向上:母亲 if (member.motherId && !visited.has(member.motherId)) { queue.push({ id: member.motherId, path: path + 'm' }) } // 配偶关系 if (member.spouseIds) { for (const spouseId of member.spouseIds) { if (!visited.has(spouseId)) { const spouse = this.members.get(spouseId) if (spouse) { const spousePath = member.gender === 'MALE' ? 'w' : 'h' queue.push({ id: spouseId, path: path + spousePath }) } } } } // 向下:子女 if (member.childrenIds) { for (const childId of member.childrenIds) { if (!visited.has(childId)) { const child = this.members.get(childId) if (child) { const childPath = child.gender === 'MALE' ? 's' : 'd' queue.push({ id: childId, path: path + childPath }) } } } } // 兄弟姐妹(通过父母查找) if (member.fatherId || member.motherId) { const siblings = this.getSiblings(id) for (const siblingId of siblings) { if (!visited.has(siblingId)) { const sibling = this.members.get(siblingId) if (sibling) { // 需要比较年龄确定是哥哥/弟弟/姐姐/妹妹 const siblingPath = this.getSiblingPath(member, sibling) queue.push({ id: siblingId, path: path + siblingPath }) } } } } } return null } /** * 获取兄弟姐妹 */ private getSiblings(memberId: string): string[] { const member = this.members.get(memberId) if (!member) return [] const siblings: string[] = [] // 通过父亲查找 if (member.fatherId) { const father = this.members.get(member.fatherId) if (father?.childrenIds) { siblings.push(...father.childrenIds.filter(id => id !== memberId)) } } // 通过母亲查找 if (member.motherId) { const mother = this.members.get(member.motherId) if (mother?.childrenIds) { for (const childId of mother.childrenIds) { if (childId !== memberId && !siblings.includes(childId)) { siblings.push(childId) } } } } return siblings } /** * 获取兄弟姐妹关系路径 */ private getSiblingPath(from: FamilyMember, to: FamilyMember): string { const fromAge = this.getAge(from) const toAge = this.getAge(to) if (to.gender === 'MALE') { // 男性:哥哥(ob) 或 弟弟(lb) return toAge > fromAge ? 'ob' : 'lb' } else { // 女性:姐姐(os) 或 妹妹(ls) return toAge > fromAge ? 'os' : 'ls' } } /** * 获取年龄(用于比较) */ private getAge(member: FamilyMember): number { if (!member.birthDate) return 0 const birthYear = new Date(member.birthDate).getFullYear() return new Date().getFullYear() - birthYear } /** * 获取关系的详细说明 */ getRelationshipDetail(fromId: string, toId: string): RelationshipDetail { const result = this.getRelationship(fromId, toId) if (!result.success) { return { ...result, description: result.error || '无法计算关系' } } // 构建带人名的路径(从 fromId 到 toId) const pathWithNames = this.buildPathWithNames(fromId, toId) // 只返回关系称谓,不包含完整句子 return { ...result, path: pathWithNames || result.path, description: result.term } } /** * 构建带人名的路径描述 * @param fromId 起始ID(查询的起点) * @param toId 目标ID(查询的终点) */ private buildPathWithNames(fromId: string, toId: string): string | null { const queue: Array<{ id: string; path: Array<{name: string, relation: string}> }> = [ { id: fromId, path: [] } ] const visited = new Set() // 获取起始成员名字 const startMember = this.members.get(fromId) const startName = startMember?.fullName || '未知' while (queue.length > 0) { const { id, path } = queue.shift()! if (id === toId) { // 构建路径字符串:从起点到终点 if (path.length === 0) return '本人' // 格式:起点 → 关系1(人名1) → 关系2(人名2) → ... const pathStr = path.map(p => `${p.relation}(${p.name})`).join(' → ') return `${startName} → ${pathStr}` } if (visited.has(id)) continue visited.add(id) const member = this.members.get(id) if (!member) continue // 向上:父亲 if (member.fatherId && !visited.has(member.fatherId)) { const father = this.members.get(member.fatherId) if (father) { queue.push({ id: member.fatherId, path: [...path, { name: father.fullName, relation: '父亲' }] }) } } // 向上:母亲 if (member.motherId && !visited.has(member.motherId)) { const mother = this.members.get(member.motherId) if (mother) { queue.push({ id: member.motherId, path: [...path, { name: mother.fullName, relation: '母亲' }] }) } } // 配偶关系 if (member.spouseIds) { for (const spouseId of member.spouseIds) { if (!visited.has(spouseId)) { const spouse = this.members.get(spouseId) if (spouse) { const relation = member.gender === 'MALE' ? '妻子' : '丈夫' queue.push({ id: spouseId, path: [...path, { name: spouse.fullName, relation }] }) } } } } // 向下:子女 if (member.childrenIds) { for (const childId of member.childrenIds) { if (!visited.has(childId)) { const child = this.members.get(childId) if (child) { const relation = child.gender === 'MALE' ? '儿子' : '女儿' queue.push({ id: childId, path: [...path, { name: child.fullName, relation }] }) } } } } // 兄弟姐妹 if (member.fatherId || member.motherId) { const siblings = this.getSiblings(id) for (const siblingId of siblings) { if (!visited.has(siblingId)) { const sibling = this.members.get(siblingId) if (sibling) { const siblingPath = this.getSiblingRelationName(member, sibling) queue.push({ id: siblingId, path: [...path, { name: sibling.fullName, relation: siblingPath }] }) } } } } } return null } /** * 获取兄弟姐妹关系名称 */ private getSiblingRelationName(from: FamilyMember, to: FamilyMember): string { const fromAge = this.getAge(from) const toAge = this.getAge(to) if (to.gender === 'MALE') { return toAge > fromAge ? '哥哥' : '弟弟' } else { return toAge > fromAge ? '姐姐' : '妹妹' } } /** * 计算路径长度(关系代数) */ private calculatePathLength(path: string): number { let length = 0 let i = 0 while (i < path.length) { // 两字符的关系 if (i + 1 < path.length) { const twoChar = path.substring(i, i + 2) if (['ob', 'lb', 'os', 'ls'].includes(twoChar)) { length++ i += 2 continue } } // 单字符的关系 length++ i++ } return length } /** * 将符号路径转换为中文文本 * 例如:'fm' -> '父亲的母亲' */ private pathToChineseText(path: string): string { const pathMap: Record = { 'f': '父亲', 'm': '母亲', 'h': '丈夫', 'w': '妻子', 's': '儿子', 'd': '女儿', 'ob': '哥哥', 'lb': '弟弟', 'os': '姐姐', 'ls': '妹妹' } const parts: string[] = [] let i = 0 while (i < path.length) { // 处理两字符的关系(如 ob, lb, os, ls) if (i + 1 < path.length) { const twoChar = path.substring(i, i + 2) if (pathMap[twoChar]) { parts.push(pathMap[twoChar]) i += 2 continue } } // 处理单字符的关系 const oneChar = path[i] if (pathMap[oneChar]) { parts.push(pathMap[oneChar]) } i++ } return parts.join('的') } /** * 将路径转换为中文描述(带"的"字) */ private getPathDescription(path: string): string { const pathMap: Record = { 'f': '的父亲', 'm': '的母亲', 'h': '的丈夫', 'w': '的妻子', 's': '的儿子', 'd': '的女儿', 'ob': '的哥哥', 'lb': '的弟弟', 'os': '的姐姐', 'ls': '的妹妹' } let description = '' for (const char of path) { description += pathMap[char] || '' } return description || '本人' } } export interface RelationshipResult { term: string // 关系称谓(如:父亲、堂兄弟) path: string // 关系路径(如:f, fbs) success: boolean // 是否成功计算 error?: string // 错误信息 } export interface RelationshipDetail extends RelationshipResult { description: string // 关系描述(如:父亲的哥哥的儿子) }