Browse Source

commit 优化人员选择器

Hai Lin 1 week ago
parent
commit
cd65495b09
5 changed files with 199 additions and 17 deletions
  1. 26 7
      app.py
  2. 25 0
      check_db.py
  3. 31 8
      templates/add_member.html
  4. 102 2
      templates/tree.html
  5. 15 0
      test_api.py

+ 26 - 7
app.py

@@ -1126,13 +1126,28 @@ def get_members():
             total_result = cursor.fetchone()
             total_result = cursor.fetchone()
             total = total_result['total'] if total_result else 0
             total = total_result['total'] if total_result else 0
             
             
-            # Get members for current page
+            # Get members for current page with father information
             if search:
             if search:
-                cursor.execute("SELECT id, name, simplified_name, sex FROM family_member_info WHERE name LIKE %s OR simplified_name LIKE %s LIMIT %s OFFSET %s", 
-                              (f'%{search}%', f'%{search}%', per_page, offset))
+                cursor.execute("""
+                    SELECT 
+                        fmi.id, fmi.name, fmi.simplified_name, fmi.sex, fmi.name_word_generation,
+                        father.name as father_name, father.simplified_name as father_simplified_name, father.name_word_generation as father_generation
+                    FROM family_member_info fmi
+                    LEFT JOIN family_relation_info fri ON fmi.id = fri.child_mid AND fri.relation_type IN (1, 2)
+                    LEFT JOIN family_member_info father ON fri.parent_mid = father.id
+                    WHERE fmi.name LIKE %s OR fmi.simplified_name LIKE %s
+                    LIMIT %s OFFSET %s
+                """, (f'%{search}%', f'%{search}%', per_page, offset))
             else:
             else:
-                cursor.execute("SELECT id, name, simplified_name, sex FROM family_member_info LIMIT %s OFFSET %s", 
-                              (per_page, offset))
+                cursor.execute("""
+                    SELECT 
+                        fmi.id, fmi.name, fmi.simplified_name, fmi.sex, fmi.name_word_generation,
+                        father.name as father_name, father.simplified_name as father_simplified_name, father.name_word_generation as father_generation
+                    FROM family_member_info fmi
+                    LEFT JOIN family_relation_info fri ON fmi.id = fri.child_mid AND fri.relation_type IN (1, 2)
+                    LEFT JOIN family_member_info father ON fri.parent_mid = father.id
+                    LIMIT %s OFFSET %s
+                """, (per_page, offset))
             members = cursor.fetchall()
             members = cursor.fetchall()
             
             
             # Convert to list of dictionaries if needed
             # Convert to list of dictionaries if needed
@@ -1142,10 +1157,14 @@ def get_members():
                     'id': member['id'],
                     'id': member['id'],
                     'name': member['name'],
                     'name': member['name'],
                     'simplified_name': member['simplified_name'],
                     'simplified_name': member['simplified_name'],
-                    'sex': member['sex']
+                    'sex': member['sex'],
+                    'name_word_generation': member.get('name_word_generation'),
+                    'father_name': member.get('father_name'),
+                    'father_simplified_name': member.get('father_simplified_name'),
+                    'father_generation': member.get('father_generation')
                 })
                 })
             
             
-            return jsonify({"members": members_list, "total": total})
+            return jsonify({"success": True, "members": members_list, "total": total})
     except Exception as e:
     except Exception as e:
         return jsonify({"success": False, "message": f"获取成员失败: {e}"}), 500
         return jsonify({"success": False, "message": f"获取成员失败: {e}"}), 500
     finally:
     finally:

+ 25 - 0
check_db.py

@@ -0,0 +1,25 @@
+import pymysql
+
+print('测试数据库连接...')
+try:
+    conn = pymysql.connect(
+        host='rm-f8ze60yirdj8786u2wo.mysql.rds.aliyuncs.com',
+        port=3306,
+        user='root',
+        password='csqz@20255',
+        db='csqz-client',
+        charset='utf8mb4',
+        cursorclass=pymysql.cursors.DictCursor
+    )
+    print('数据库连接成功')
+    
+    cursor = conn.cursor()
+    cursor.execute('SHOW COLUMNS FROM family_member_info')
+    columns = cursor.fetchall()
+    print('\n表结构:')
+    for col in columns:
+        print(col['Field'])
+    
+    conn.close()
+except Exception as e:
+    print(f'Error: {e}')

+ 31 - 8
templates/add_member.html

@@ -1352,7 +1352,9 @@
                     // Trigger the lineage generation reference display
                     // Trigger the lineage generation reference display
                     const relatedMid = document.getElementById('related_mid').value;
                     const relatedMid = document.getElementById('related_mid').value;
                     if (relTypeSelect.value == 1 && relatedMid) {
                     if (relTypeSelect.value == 1 && relatedMid) {
-                        fetch(`/manager/api/member/${relatedMid}`)
+                        fetch(`/manager/api/member/${relatedMid}`, {
+                credentials: 'include'
+            })
                             .then(response => response.json())
                             .then(response => response.json())
                             .then(data => {
                             .then(data => {
                                 if (data.member && data.member.name_word_generation) {
                                 if (data.member && data.member.name_word_generation) {
@@ -2005,7 +2007,9 @@
             
             
             if (relationTypeSelect) {
             if (relationTypeSelect) {
                 // Find the father in the members list
                 // Find the father in the members list
-                fetch(`/manager/api/members?search=${encodeURIComponent(fatherName)}`) 
+                fetch(`/manager/api/members?search=${encodeURIComponent(fatherName)}`, {
+                credentials: 'include'
+            }) 
                     .then(response => response.json())
                     .then(response => response.json())
                     .then(data => {
                     .then(data => {
                         if (data.members && data.members.length > 0) {
                         if (data.members && data.members.length > 0) {
@@ -2034,7 +2038,9 @@
                                 // Trigger the lineage generation reference display
                                 // Trigger the lineage generation reference display
                                 const relatedMid = document.getElementById('related_mid').value;
                                 const relatedMid = document.getElementById('related_mid').value;
                                 if (relationTypeSelect.value == 1 && relatedMid) {
                                 if (relationTypeSelect.value == 1 && relatedMid) {
-                                    fetch(`/manager/api/member/${relatedMid}`)
+                                    fetch(`/manager/api/member/${relatedMid}`, {
+                credentials: 'include'
+            })
                                         .then(response => response.json())
                                         .then(response => response.json())
                                         .then(data => {
                                         .then(data => {
                                             if (data.member && data.member.name_word_generation) {
                                             if (data.member && data.member.name_word_generation) {
@@ -2096,7 +2102,9 @@
                 
                 
                 if (relationTypeSelect) {
                 if (relationTypeSelect) {
                     // Find the spouse in the members list
                     // Find the spouse in the members list
-                    fetch(`/manager/api/members?search=${encodeURIComponent(spouseName)}`) 
+                    fetch(`/manager/api/members?search=${encodeURIComponent(spouseName)}`, {
+                        credentials: 'include'
+                    }) 
                         .then(response => response.json())
                         .then(response => response.json())
                         .then(data => {
                         .then(data => {
                             if (data.members && data.members.length > 0) {
                             if (data.members && data.members.length > 0) {
@@ -2214,7 +2222,9 @@
     // 加载成员数据
     // 加载成员数据
     function loadMembers(page = 1, search = '') {
     function loadMembers(page = 1, search = '') {
         console.log('Loading members...', { page, search });
         console.log('Loading members...', { page, search });
-        fetch(`/manager/api/members?page=${page}&search=${encodeURIComponent(search)}`)
+        fetch(`/manager/api/members?page=${page}&search=${encodeURIComponent(search)}`, {
+            credentials: 'include'
+        })
             .then(response => {
             .then(response => {
                 console.log('Response status:', response.status);
                 console.log('Response status:', response.status);
                 return response.json();
                 return response.json();
@@ -2222,7 +2232,7 @@
             .then(data => {
             .then(data => {
                 console.log('Response data:', data);
                 console.log('Response data:', data);
                 // Check if it's an error response
                 // Check if it's an error response
-                if (data.message && data.success === false) {
+                if (data.success === false) {
                     console.error('API error:', data.message);
                     console.error('API error:', data.message);
                     // If unauthorized, redirect to login
                     // If unauthorized, redirect to login
                     if (data.message === 'Unauthorized') {
                     if (data.message === 'Unauthorized') {
@@ -2280,8 +2290,19 @@
             item.innerHTML = `
             item.innerHTML = `
                 <div class="d-flex justify-content-between align-items-center">
                 <div class="d-flex justify-content-between align-items-center">
                     <div>
                     <div>
-                        <h6 class="mb-0">${member.name}</h6>
+                        <h6 class="mb-0">
+                            <a href="/manager/member_detail/${member.id}" target="_blank" class="text-primary text-decoration-none" onclick="event.stopPropagation();">
+                                ${member.name} ${member.simplified_name && member.simplified_name !== member.name ? `(${member.simplified_name})` : ''}
+                            </a>
+                        </h6>
                         <small class="text-muted">ID: ${member.id} | ${member.sex === 1 ? '男' : '女'}</small>
                         <small class="text-muted">ID: ${member.id} | ${member.sex === 1 ? '男' : '女'}</small>
+                        ${member.name_word_generation ? `<small class="d-block text-muted">世系世代: ${member.name_word_generation}</small>` : ''}
+                        ${member.father_name ? `
+                            <small class="d-block text-muted">
+                                父亲: ${member.father_name} ${member.father_simplified_name && member.father_simplified_name !== member.father_name ? `(${member.father_simplified_name})` : ''}
+                                ${member.father_generation ? ` | 世系世代: ${member.father_generation}` : ''}
+                            </small>
+                        ` : ''}
                     </div>
                     </div>
                     <button type="button" class="btn btn-sm btn-primary" onclick="event.stopPropagation(); selectMemberById(${member.id});">
                     <button type="button" class="btn btn-sm btn-primary" onclick="event.stopPropagation(); selectMemberById(${member.id});">
                         选择
                         选择
@@ -2419,7 +2440,9 @@
                 const relatedMid = document.getElementById('related_mid').value;
                 const relatedMid = document.getElementById('related_mid').value;
                 if (this.value == 1 && relatedMid) {
                 if (this.value == 1 && relatedMid) {
                     // 如果选择了父子关系且已选择关联成员,显示父亲的世系世代
                     // 如果选择了父子关系且已选择关联成员,显示父亲的世系世代
-                    fetch(`/manager/api/member/${relatedMid}`)
+                    fetch(`/manager/api/member/${relatedMid}`, {
+                credentials: 'include'
+            })
                         .then(response => response.json())
                         .then(response => response.json())
                         .then(data => {
                         .then(data => {
                             if (data.member && data.member.name_word_generation) {
                             if (data.member && data.member.name_word_generation) {

+ 102 - 2
templates/tree.html

@@ -129,8 +129,14 @@
 {% block content %}
 {% block content %}
 <div class="d-flex justify-content-between align-items-center mb-3">
 <div class="d-flex justify-content-between align-items-center mb-3">
     <h2><i class="bi bi-diagram-3"></i> 家谱关系树状图</h2>
     <h2><i class="bi bi-diagram-3"></i> 家谱关系树状图</h2>
-    <div>
-        <a href="{{ url_for('tree_classic') }}" class="btn btn-outline-primary" target="_blank">
+    <div class="d-flex gap-2">
+        <div class="input-group" style="width: 300px;">
+            <input type="text" id="memberSearch" class="form-control form-control-sm" placeholder="输入成员名字搜索">
+            <button class="btn btn-sm btn-primary" onclick="searchMember()">
+                <i class="bi bi-search"></i> 搜索
+            </button>
+        </div>
+        <a href="{{ url_for('tree_classic') }}" class="btn btn-outline-primary btn-sm">
             <i class="bi bi-printer"></i> 导出传统吊线图
             <i class="bi bi-printer"></i> 导出传统吊线图
         </a>
         </a>
     </div>
     </div>
@@ -655,5 +661,99 @@
         );
         );
         zoomScale = 1;
         zoomScale = 1;
     }
     }
+
+    // 搜索成员并定位
+    function searchMember() {
+        const searchTerm = document.getElementById('memberSearch').value.trim();
+        if (!searchTerm) {
+            alert('请输入成员名字');
+            return;
+        }
+
+        if (!currentData || !currentData.members) {
+            alert('数据未加载完成,请稍后再试');
+            return;
+        }
+
+        // 搜索匹配的成员
+        const matchedMembers = currentData.members.filter(member => {
+            if (!member) return false;
+            const name = (member.name || '').toLowerCase();
+            const simplifiedName = (member.simplified_name || '').toLowerCase();
+            const searchLower = searchTerm.toLowerCase();
+            return name.includes(searchLower) || simplifiedName.includes(searchLower);
+        });
+
+        if (matchedMembers.length === 0) {
+            alert('未找到匹配的成员');
+            return;
+        }
+
+        // 如果找到多个匹配项,让用户选择
+        let targetMember;
+        if (matchedMembers.length === 1) {
+            targetMember = matchedMembers[0];
+        } else {
+            const memberNames = matchedMembers.map(m => `${m.name} (${m.simplified_name || '无简化名'})`).join('\n');
+            const selectedIndex = prompt(`找到多个匹配成员,请输入编号选择:\n${matchedMembers.map((m, i) => `${i + 1}. ${m.name} (${m.simplified_name || '无简化名'})`).join('\n')}`);
+            const index = parseInt(selectedIndex) - 1;
+            if (isNaN(index) || index < 0 || index >= matchedMembers.length) {
+                return;
+            }
+            targetMember = matchedMembers[index];
+        }
+
+        // 定位到成员
+        locateMember(targetMember.id);
+    }
+
+    // 定位到指定成员
+    function locateMember(memberId) {
+        const svg = d3.select("#tree-container svg");
+        if (!svg.empty()) {
+            // 查找对应的节点
+            const node = d3.selectAll(".node").filter(function(d) {
+                return d.data && d.data.id === memberId;
+            });
+
+            if (node.size() > 0) {
+                // 获取节点位置
+                const nodeData = node.datum();
+                const container = document.getElementById('tree-container');
+                const containerWidth = container.clientWidth;
+                const containerHeight = container.clientHeight;
+
+                // 计算缩放和平移,使节点位于中心
+                const scale = 1.5; // 放大一点以突出显示
+                const translateX = containerWidth / 2 - nodeData.x * scale;
+                const translateY = containerHeight / 2 - nodeData.y * scale;
+
+                // 应用变换
+                svg.transition().duration(1000).call(
+                    zoomBehavior.transform, 
+                    d3.zoomIdentity.translate(translateX, translateY).scale(scale)
+                );
+
+                // 高亮显示节点
+                node.select("rect, circle").transition().duration(500)
+                    .attr("stroke", "#ff0000")
+                    .attr("stroke-width", 3)
+                    .transition().duration(1000).attr("stroke", function(d) {
+                        return d.data.sex === 1 ? "#3B82F6" : "#EC4899";
+                    }).attr("stroke-width", 2);
+            } else {
+                alert('未在树中找到该成员');
+            }
+        } else {
+            alert('树图未加载完成,请稍后再试');
+        }
+    }
+
+    // 支持回车键搜索
+    document.getElementById('memberSearch').addEventListener('keypress', function(e) {
+        if (e.key === 'Enter') {
+            searchMember();
+        }
+    });
 </script>
 </script>
 {% endblock %}
 {% endblock %}

+ 15 - 0
test_api.py

@@ -0,0 +1,15 @@
+import requests
+import json
+
+# 测试API接口
+url = 'http://localhost:5001/manager/api/members'
+params = {'page': 1, 'search': ''}
+
+print('Testing API...')
+try:
+    response = requests.get(url, params=params)
+    print(f'Response status code: {response.status_code}')
+    print(f'Response content: {response.text}')
+    
+except Exception as e:
+    print(f'Error: {e}')