Переглянути джерело

commit 优化人员搜索框

林海 6 днів тому
батько
коміт
be3a22ace1
3 змінених файлів з 157 додано та 63 видалено
  1. 10 7
      app.py
  2. 46 45
      templates/lineage_query.html
  3. 101 11
      templates/tree.html

+ 10 - 7
app.py

@@ -1474,14 +1474,17 @@ def search_member():
     try:
         with conn.cursor() as cursor:
             cursor.execute("""
-                SELECT id, name, simplified_name 
-                FROM family_member_info 
-                WHERE name LIKE %s OR simplified_name LIKE %s OR former_name LIKE %s
+                SELECT fmi.id, fmi.name, fmi.simplified_name, fmi.name_word_generation,
+                       p.name AS father_name, p.simplified_name AS father_simplified_name
+                FROM family_member_info fmi
+                LEFT JOIN family_relation_info r ON r.child_mid = fmi.id AND r.relation_type = 1
+                LEFT JOIN family_member_info p ON r.parent_mid = p.id
+                WHERE fmi.name LIKE %s OR fmi.simplified_name LIKE %s OR fmi.former_name LIKE %s
                 ORDER BY 
-                    CASE WHEN name = %s THEN 1 
-                         WHEN simplified_name = %s THEN 2 
-                         WHEN name LIKE %s THEN 3 
-                         WHEN simplified_name LIKE %s THEN 4 
+                    CASE WHEN fmi.name = %s THEN 1 
+                         WHEN fmi.simplified_name = %s THEN 2 
+                         WHEN fmi.name LIKE %s THEN 3 
+                         WHEN fmi.simplified_name LIKE %s THEN 4 
                          ELSE 5 END
             """, (f'%{keyword}%', f'%{keyword}%', f'%{keyword}%', keyword, keyword, f'{keyword}%', f'{keyword}%'))
             members = cursor.fetchall()

+ 46 - 45
templates/lineage_query.html

@@ -565,6 +565,40 @@ function showMemberSelection(members) {
     // Store members in sessionStorage
     sessionStorage.setItem('searchMembers', JSON.stringify(members));
     
+    // Build member list rows
+    const memberRows = members.map((member, index) => {
+        const simplifiedPart = member.simplified_name && member.simplified_name !== member.name
+            ? `<span style="color: rgba(255,255,255,0.6); font-size: 13px;">(${member.simplified_name})</span>` : '';
+        const genPart = member.name_word_generation
+            ? `<span style="background: rgba(74,105,189,0.3); color: #8ab4f8; border-radius: 4px; padding: 1px 6px; font-size: 12px; margin-left: 6px;">第${member.name_word_generation}世</span>` : '';
+        const fatherPart = member.father_name
+            ? `<span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 6px;">父: ${member.father_name}${member.father_simplified_name && member.father_simplified_name !== member.father_name ? '(' + member.father_simplified_name + ')' : ''}</span>` : '';
+        const idPart = `<span style="color: rgba(255,200,100,0.6); font-size: 11px; margin-left: 6px;">ID:${member.id}</span>`;
+        return `
+            <div class="member-select-row" 
+                 onclick="selectMemberByIndex(${index})"
+                 style="padding: 10px 12px; border-bottom: 1px solid rgba(255,255,255,0.08);
+                        cursor: pointer; display: flex; justify-content: space-between;
+                        align-items: center; transition: background 0.15s; border-radius: 6px;"
+                 onmouseover="this.style.background='rgba(74,105,189,0.2)'"
+                 onmouseout="this.style.background=''">
+                <div style="flex: 1; min-width: 0;">
+                    <span style="color: #ffd700; font-size: 13px; margin-right: 8px; flex-shrink: 0;">${index + 1}.</span>
+                    <span style="color: #fff; font-weight: 600;">${member.name}</span>
+                    ${simplifiedPart}${genPart}${fatherPart}${idPart}
+                </div>
+                <a href="/manager/member_detail/${member.id}" target="_blank"
+                   onclick="event.stopPropagation()"
+                   title="新标签页查看详情"
+                   style="flex-shrink: 0; margin-left: 12px; padding: 4px 10px;
+                          background: rgba(74,105,189,0.4); border: 1px solid #4a69bd;
+                          border-radius: 6px; color: #8ab4f8; font-size: 12px;
+                          text-decoration: none; white-space: nowrap;">
+                    查看详情 ↗
+                </a>
+            </div>`;
+    }).join('');
+    
     // Create dialog
     const dialog = document.createElement('div');
     dialog.id = 'memberSelectionDialog';
@@ -578,36 +612,21 @@ function showMemberSelection(members) {
         border-radius: 12px;
         padding: 24px;
         z-index: 10000;
-        min-width: 400px;
-        box-shadow: 0 10px 40px rgba(0,0,0,0.5);
+        min-width: 480px;
+        max-width: 680px;
+        width: 90vw;
+        box-shadow: 0 10px 40px rgba(0,0,0,0.6);
     `;
     
-    // Dialog content
     dialog.innerHTML = `
-        <h3 style="color: #ffd700; margin-bottom: 16px; text-align: center;">找到多个匹配成员,请输入编号选择:</h3>
-        <div style="margin-bottom: 16px; max-height: 200px; overflow-y: auto;">
-            ${members.map((member, index) => `
-                <div style="padding: 10px; border-bottom: 1px solid rgba(255,255,255,0.1);">
-                    <span style="color: #ffd700; margin-right: 10px;">${index + 1}.</span>
-                    <span style="color: #fff; font-weight: 600;">${member.name}</span>
-                    ${member.simplified_name && member.simplified_name !== member.name ? 
-                        `<span style="color: rgba(255,255,255,0.7);">(${member.simplified_name})</span>` : ''}
-                </div>
-            `).join('')}
+        <h3 style="color: #ffd700; margin-bottom: 4px; text-align: center; font-size: 16px;">找到多个匹配成员</h3>
+        <p style="color: rgba(255,255,255,0.5); text-align: center; font-size: 12px; margin-bottom: 14px;">点击条目加载世系;点击「查看详情」在新标签页中查看</p>
+        <div style="margin-bottom: 16px; max-height: 320px; overflow-y: auto; padding-right: 2px;">
+            ${memberRows}
         </div>
-        <input type="text" id="selectionInput" 
-               placeholder="请输入编号选择(1-${members.length})"
-               style="width: 100%; padding: 10px; margin-bottom: 16px; 
-                      background: #2d3436; border: 1px solid #4a69bd; 
-                      border-radius: 8px; color: #fff; text-align: center;
-                      font-size: 16px;" />
-        <div style="display: flex; justify-content: center; gap: 16px;">
-            <button onclick="selectMember()" 
-                    style="padding: 10px 30px; background: linear-gradient(135deg, #4a69bd, #2d3436); 
-                           border: 2px solid #4a69bd; border-radius: 8px; color: #fff;
-                           cursor: pointer; font-weight: 600;">确定</button>
+        <div style="display: flex; justify-content: center;">
             <button onclick="closeSelectionDialog()" 
-                    style="padding: 10px 30px; background: #2d3436; 
+                    style="padding: 8px 30px; background: #2d3436; 
                            border: 2px solid #666; border-radius: 8px; color: #aaa;
                            cursor: pointer;">取消</button>
         </div>
@@ -629,16 +648,6 @@ function showMemberSelection(members) {
     
     document.body.appendChild(overlay);
     document.body.appendChild(dialog);
-    
-    // Focus input
-    document.getElementById('selectionInput').focus();
-    
-    // Enter key
-    document.getElementById('selectionInput').addEventListener('keypress', function(e) {
-        if (e.key === 'Enter') {
-            selectMember();
-        }
-    });
 }
 
 // Close selection dialog
@@ -647,11 +656,8 @@ function closeSelectionDialog() {
     document.getElementById('dialogOverlay')?.remove();
 }
 
-// Select member
-function selectMember() {
-    const input = document.getElementById('selectionInput');
-    const index = parseInt(input.value) - 1;
-    
+// Select member by row click
+function selectMemberByIndex(index) {
     const membersStr = sessionStorage.getItem('searchMembers');
     if (!membersStr) {
         alert('数据错误,请重新搜索');
@@ -662,15 +668,10 @@ function selectMember() {
     const members = JSON.parse(membersStr);
     
     if (index >= 0 && index < members.length) {
-        // Load lineage for selected member
         document.getElementById('emptyState').style.display = 'none';
         document.getElementById('treeView').style.display = 'block';
         loadLineage(members[index].id);
-        
-        // Close dialog
         closeSelectionDialog();
-    } else {
-        alert(`请输入有效的编号(1-${members.length})`);
     }
 }
 

+ 101 - 11
templates/tree.html

@@ -935,21 +935,111 @@
         }
 
         // 如果找到多个匹配项,让用户选择
-        let targetMember;
         if (matchedMembers.length === 1) {
-            targetMember = matchedMembers[0];
+            locateMember(matchedMembers[0].id);
         } 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];
+            showTreeMemberSelectionDialog(matchedMembers);
         }
+    }
 
-        // 定位到成员
-        locateMember(targetMember.id);
+    // 显示多成员选择弹窗(树图搜索)
+    function showTreeMemberSelectionDialog(members) {
+        // Remove existing dialog
+        document.getElementById('treeMemberSelectionDialog')?.remove();
+        document.getElementById('treeDialogOverlay')?.remove();
+
+        // Build member index for quick name lookup
+        const memberMap = {};
+        if (currentData && currentData.members) {
+            currentData.members.forEach(m => { memberMap[m.id] = m; });
+        }
+
+        // Build rows
+        const rows = members.map((member, index) => {
+            const simplifiedPart = member.simplified_name && member.simplified_name !== member.name
+                ? `<span style="color:rgba(255,255,255,0.6);font-size:13px;">(${member.simplified_name})</span>` : '';
+            const genPart = member.name_word_generation
+                ? `<span style="background:rgba(74,105,189,0.3);color:#8ab4f8;border-radius:4px;padding:1px 6px;font-size:12px;margin-left:6px;">第${member.name_word_generation}世</span>` : '';
+
+            // Look up father via relations
+            let fatherName = '';
+            if (currentData && currentData.relations) {
+                const fatherRel = currentData.relations.find(r => r.child_mid === member.id && r.relation_type === 1);
+                if (fatherRel && memberMap[fatherRel.parent_mid]) {
+                    const fm = memberMap[fatherRel.parent_mid];
+                    fatherName = fm.name;
+                    if (fm.simplified_name && fm.simplified_name !== fm.name) fatherName += `(${fm.simplified_name})`;
+                }
+            }
+            const fatherPart = fatherName
+                ? `<span style="color:rgba(255,255,255,0.5);font-size:12px;margin-left:6px;">父: ${fatherName}</span>` : '';
+            const idPart = `<span style="color:rgba(255,200,100,0.6);font-size:11px;margin-left:6px;">ID:${member.id}</span>`;
+
+            return `
+                <div onclick="selectTreeMember(${index})"
+                     style="padding:10px 12px;border-bottom:1px solid rgba(255,255,255,0.08);
+                            cursor:pointer;display:flex;justify-content:space-between;
+                            align-items:center;border-radius:6px;transition:background 0.15s;"
+                     onmouseover="this.style.background='rgba(74,105,189,0.2)'"
+                     onmouseout="this.style.background=''">
+                    <div style="flex:1;min-width:0;">
+                        <span style="color:#ffd700;font-size:13px;margin-right:8px;">${index + 1}.</span>
+                        <span style="color:#fff;font-weight:600;">${member.name}</span>
+                        ${simplifiedPart}${genPart}${fatherPart}${idPart}
+                    </div>
+                    <a href="/manager/member_detail/${member.id}" target="_blank"
+                       onclick="event.stopPropagation()"
+                       style="flex-shrink:0;margin-left:12px;padding:4px 10px;
+                              background:rgba(74,105,189,0.4);border:1px solid #4a69bd;
+                              border-radius:6px;color:#8ab4f8;font-size:12px;
+                              text-decoration:none;white-space:nowrap;">
+                        查看详情 ↗
+                    </a>
+                </div>`;
+        }).join('');
+
+        // Overlay
+        const overlay = document.createElement('div');
+        overlay.id = 'treeDialogOverlay';
+        overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:9999;';
+        overlay.onclick = () => {
+            document.getElementById('treeMemberSelectionDialog')?.remove();
+            document.getElementById('treeDialogOverlay')?.remove();
+        };
+
+        // Dialog
+        const dialog = document.createElement('div');
+        dialog.id = 'treeMemberSelectionDialog';
+        dialog.style.cssText = `
+            position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
+            background:#1a1a2e;border:2px solid #4a69bd;border-radius:12px;
+            padding:24px;z-index:10000;min-width:480px;max-width:680px;
+            width:90vw;box-shadow:0 10px 40px rgba(0,0,0,0.6);
+        `;
+        dialog.innerHTML = `
+            <h3 style="color:#ffd700;margin-bottom:4px;text-align:center;font-size:16px;">找到多个匹配成员</h3>
+            <p style="color:rgba(255,255,255,0.5);text-align:center;font-size:12px;margin-bottom:14px;">点击条目在树图中定位;点击「查看详情」在新标签页中查看</p>
+            <div id="treeSelectionList" style="margin-bottom:16px;max-height:320px;overflow-y:auto;">${rows}</div>
+            <div style="display:flex;justify-content:center;">
+                <button onclick="document.getElementById('treeMemberSelectionDialog').remove();document.getElementById('treeDialogOverlay').remove();"
+                        style="padding:8px 30px;background:#2d3436;border:2px solid #666;border-radius:8px;color:#aaa;cursor:pointer;">取消</button>
+            </div>
+        `;
+
+        // Store matched members for click handler
+        window._treeDialogMembers = members;
+
+        document.body.appendChild(overlay);
+        document.body.appendChild(dialog);
+    }
+
+    function selectTreeMember(index) {
+        const members = window._treeDialogMembers;
+        if (members && members[index]) {
+            locateMember(members[index].id);
+        }
+        document.getElementById('treeMemberSelectionDialog')?.remove();
+        document.getElementById('treeDialogOverlay')?.remove();
     }
 
     // 定位到指定成员