Kaynağa Gözat

commit 世代优化

Hai Lin 2 gün önce
ebeveyn
işleme
1223eab591
3 değiştirilmiş dosya ile 150 ekleme ve 12 silme
  1. 1 1
      app.py
  2. 43 1
      templates/members.html
  3. 106 10
      templates/tree_classic.html

+ 1 - 1
app.py

@@ -571,7 +571,7 @@ def tree_data():
     try:
         with conn.cursor() as cursor:
             # 获取所有成员
-            cursor.execute("SELECT id, name, simplified_name, sex, family_rank FROM family_member_info")
+            cursor.execute("SELECT id, name, simplified_name, sex, family_rank, name_word_generation FROM family_member_info")
             members = cursor.fetchall()
             # 获取所有关系 (1:父子 2:母子 10:夫妻 11:兄弟 12:姐妹)
             cursor.execute("SELECT parent_mid, child_mid, relation_type FROM family_relation_info")

+ 43 - 1
templates/members.html

@@ -2,6 +2,31 @@
 
 {% block title %}成员列表 - 家谱管理系统{% endblock %}
 
+{% block extra_css %}
+<style>
+    .lineage-badge {
+        display: inline-flex;
+        align-items: center;
+        gap: 5px;
+        padding: 4px 14px 4px 10px;
+        border: 1.5px solid #b8c9e8;
+        border-radius: 20px;
+        background: #f0f4fb;
+        color: #3b5998;
+        font-size: 13px;
+        font-weight: 500;
+        white-space: nowrap;
+        line-height: 1.6;
+    }
+    .lineage-icon {
+        width: 16px;
+        height: 16px;
+        opacity: 0.6;
+        flex-shrink: 0;
+    }
+</style>
+{% endblock %}
+
 {% block content %}
 <div class="d-flex justify-content-between align-items-center mb-4">
     <h2><i class="bi bi-people"></i> 家谱成员管理</h2>
@@ -27,6 +52,7 @@
                     <tr>
                         <th class="px-4">序号</th>
                         <th>基本信息</th>
+                        <th>世系世代</th>
                         <th>分房 / 堂号</th>
                         <th>居住地</th>
                         <th>状态</th>
@@ -45,6 +71,22 @@
                                 {{ '男' if member.sex == 1 else '女' }} | {{ member.birthday_str or '未知' }}
                             </div>
                         </td>
+                        <td>
+                            {% if member.name_word_generation %}
+                            <div class="d-flex flex-column gap-1">
+                                {% for gen in member.name_word_generation.split(';') %}
+                                    {% if gen.strip() %}
+                                    <span class="lineage-badge">
+                                        <svg class="lineage-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>
+                                        {{ gen.strip() }}
+                                    </span>
+                                    {% endif %}
+                                {% endfor %}
+                            </div>
+                            {% else %}
+                            <span class="text-muted small">-</span>
+                            {% endif %}
+                        </td>
                         <td>
                             <div class="small">
                                 {{ member.family_branch or member.hall_name or '-' }}
@@ -84,7 +126,7 @@
                     {% endfor %}
                     {% if not members %}
                     <tr>
-                        <td colspan="6" class="text-center py-5 text-muted">
+                        <td colspan="7" class="text-center py-5 text-muted">
                             <i class="bi bi-person-x fs-1 d-block mb-2"></i>
                             暂无成员数据
                         </td>

+ 106 - 10
templates/tree_classic.html

@@ -233,7 +233,7 @@
             Y_STEP: 160,          // 行高 (代与代之间的距离)
             NODE_WIDTH: 24,       // 名字竖排的估计宽度
             NODE_HEIGHT: 80,      // 名字竖排的估计高度
-            MARGIN_LEFT: 60,      // 左侧世代标记留白
+            MARGIN_LEFT: 120,     // 左侧世系世代标记留白
             MARGIN_TOP: 20,       // 顶部留白
             MARGIN_BOTTOM: 40,    // 底部留白
             LINE_MID_OFFSET: 40   // 横线距离父节点底部的距离
@@ -411,6 +411,76 @@
             return match ? parseInt(match[0]) : null;
         }
 
+        // ===== 世系世代:中文数字转换 =====
+        const CN_DIGITS = ['零','一','二','三','四','五','六','七','八','九'];
+        const CN_UNITS  = ['','十','百','千','万'];
+
+        function numToChinese(n) {
+            if (n <= 0) return '零';
+            if (n <= 10) return n === 10 ? '十' : CN_DIGITS[n];
+            if (n < 20) return '十' + (n % 10 === 0 ? '' : CN_DIGITS[n % 10]);
+            let result = '';
+            const str = String(n);
+            const len = str.length;
+            for (let i = 0; i < len; i++) {
+                const d = parseInt(str[i]);
+                const unitIdx = len - 1 - i;
+                if (d === 0) {
+                    if (result && !result.endsWith('零') && i < len - 1) result += '零';
+                } else {
+                    result += CN_DIGITS[d] + CN_UNITS[unitIdx];
+                }
+            }
+            return result.replace(/零+$/, '');
+        }
+
+        function chineseToNum(s) {
+            if (!s) return NaN;
+            s = s.trim();
+            const digitMap = {'零':0,'一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9};
+            const unitMap = {'十':10,'百':100,'千':1000,'万':10000};
+            let result = 0, temp = 0;
+            for (let i = 0; i < s.length; i++) {
+                const ch = s[i];
+                if (digitMap[ch] !== undefined) {
+                    temp = digitMap[ch];
+                } else if (unitMap[ch] !== undefined) {
+                    if (temp === 0 && unitMap[ch] === 10) temp = 1;
+                    result += temp * unitMap[ch];
+                    temp = 0;
+                }
+            }
+            result += temp;
+            return result || NaN;
+        }
+
+        function parseLineageGenerations(str) {
+            if (!str) return [];
+            return str.split(';').filter(s => s.trim()).map(item => {
+                item = item.trim();
+                const m = item.match(/^(.+?)第(.+?)代$/);
+                if (m) {
+                    return { place: m[1], num: chineseToNum(m[2]), raw: item };
+                }
+                return { place: '', num: NaN, raw: item };
+            });
+        }
+
+        function formatLineageGeneration(place, num) {
+            if (isNaN(num) || num <= 0) return '';
+            return place + '第' + numToChinese(num) + '代';
+        }
+
+        function computeLineageForGen(refLineage, refGen, targetGen) {
+            if (!refLineage || refLineage.length === 0) return [];
+            const diff = targetGen - refGen;
+            return refLineage.map(lg => ({
+                place: lg.place,
+                num: lg.num + diff,
+                formatted: formatLineageGeneration(lg.place, lg.num + diff)
+            })).filter(lg => lg.num > 0);
+        }
+
         function buildAndRenderPages(members, relations, selectedRootIds) {
             // 1. 构建树结构
             const memberMap = {};
@@ -458,6 +528,22 @@
             // 按照世代对roots进行排序,优先显示最老的祖先
             roots.sort((a, b) => a.gen - b.gen);
 
+            // 查找世系世代参考点:找到任意一个有 name_word_generation 的人
+            let lineageRef = null;
+            function findLineageRef(node) {
+                if (lineageRef) return;
+                if (node.name_word_generation) {
+                    const parsed = parseLineageGenerations(node.name_word_generation);
+                    if (parsed.length > 0 && !isNaN(parsed[0].num)) {
+                        lineageRef = { gen: node.gen, lineage: parsed };
+                    }
+                }
+                if (!lineageRef && node.children) {
+                    node.children.forEach(c => findLineageRef(c));
+                }
+            }
+            roots.forEach(r => findLineageRef(r));
+
             document.getElementById('loading').style.display = 'none';
             const container = document.getElementById('exportArea');
             
@@ -494,7 +580,8 @@
                     nodes: blockNodes,
                     startGen: currentJob.startGen,
                     endGen: maxGenForThisPage,
-                    leftTitle: currentJob.leftTitle
+                    leftTitle: currentJob.leftTitle,
+                    lineageRef: lineageRef
                 });
             }
 
@@ -654,20 +741,29 @@
                 return html;
             }).join('');
 
-            // 4. 构建左侧世代标记
+            // 4. 构建左侧世系世代标记
             let genLabels = '';
+            const hasLineageRef = !!page.lineageRef;
+            const lineageColCount = hasLineageRef ? page.lineageRef.lineage.length : 0;
+            
             for(let i=0; i<=maxDepth; i++) {
                 const actualGen = page.startGen + i;
                 const y = i * CONFIG.Y_STEP + CONFIG.MARGIN_TOP + 15;
-                const x = 30; // 左侧留白
                 
-                // 将代数竖排
-                let genStr = actualGen + '世';
-                // 支持如"30世"
-                genLabels += `<text class="gen-text" x="${x}" y="${y}" text-anchor="middle">${actualGen}</text>`;
-                genLabels += `<text class="gen-text" x="${x}" y="${y + 18}" text-anchor="middle">世</text>`;
+                if (hasLineageRef) {
+                    const lineageItems = computeLineageForGen(page.lineageRef.lineage, page.lineageRef.gen, actualGen);
+                    lineageItems.forEach((item, colIdx) => {
+                        const x = 20 + colIdx * 22;
+                        const chars = Array.from(item.formatted);
+                        chars.forEach((ch, ci) => {
+                            genLabels += `<text class="gen-text" x="${x}" y="${y + ci * 15}" text-anchor="middle" style="font-size:13px;">${ch}</text>`;
+                        });
+                    });
+                } else {
+                    genLabels += `<text class="gen-text" x="30" y="${y}" text-anchor="middle">${actualGen}</text>`;
+                    genLabels += `<text class="gen-text" x="30" y="${y + 18}" text-anchor="middle">世</text>`;
+                }
                 
-                // 画一条灰色的分隔虚线(非必须,但好看)
                 genLabels += `<line x1="10" y1="${y-20}" x2="${svgWidth}" y2="${y-20}" stroke="#eee" stroke-dasharray="4" />`;
             }